Swift & the Objective-C Runtime

Swift & the Objective-C Runtime

Written byNate Cook—January 26th,2015

Even when written without a single line of Objective-C code,every Swift app executes inside the Objective-C runtime,opening up a world of dynamic dispatch and associated runtime manipulation. To be sure,this may not always be the case—Swift-only frameworks,whenever they come,may lead to a Swift-only runtime. But as long as the Objective-C runtime is with us,let’s use it to its fullest potential.

This week we take a new,Swift-focused look at two runtime techniques covered on NSHipster back when Objective-C was the only game in town:associated objectsandmethod swizzling.

Note:This post primarily covers the use of these techniques in Swift—for the full run-down,please refer to the original articles.

Associated Objects

Swift extensions allow for great flexibility in adding to the functionality of existing Cocoa classes,but they’re limited in the same way as their Objective-C brethren,categories. Namely,you can’t add a property to an existing class via an extension.

Happily,Objective-Cassociated objectscome to the rescue. For example,to add adescriptiveNameproperty to all the view controllers in a project,we simply add a computed property usingobjc_get/setAssociatedObject()in the backinggetandsetblocks:

extension UIViewController {
    private struct AssociatedKeys {
        static var DescriptiveName = "nsh_DescriptiveName"
    }

    descriptiveName: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
        }

        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    &DescriptiveName,
                    newValue as NSString?,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )
            }
        }
    }
}

Note the use ofstatic varin a private nestedstruct—this pattern creates the static associated object key we need but doesn’t muck up the global namespace.

Method Swizzling

Sometimes for convenience,sometimes to work around a bug in a framework,or sometimes because there’s just no other way,you need to modify the behavior of an existing class’s methods. Method swizzling lets you swap the implementations of two methods,essentially overriding an existing method with your own while keeping the original around.

In this example,we swizzleUIViewController’sviewWillAppearmethod to print a message any time a view is about to appear on screen. The swizzling happens in the special class methodinitialize(see note below); the replacement implementation is in thensh_viewWillAppearmethod:

public override class func initialize() { Static { token: dispatch_once_t = 0 } // make sure this isn't a subclass if self !== UIViewController.self { return } dispatch_once(&Static.token) { originalSelector = Selector("viewWillAppear:") swizzledSelector = "nsh_viewWillAppear:") originalMethod = class_getInstanceMethod(originalSelector) swizzledMethod = swizzledSelector) didAddMethod = class_addMethod(originalSelector,210)">method_getImplementation(swizzledMethod),210)">method_getTypeEncoding(swizzledMethod)) if didAddMethod { class_replaceMethod(swizzledSelector,210)">originalMethod),210)">originalMethod)) } else { method_exchangeImplementations(originalMethod,210)">swizzledMethod); } } } // MARK: - Method Swizzling nsh_viewWillAppear(animated: Bool) { self.animated) name = descriptiveName { print("viewWillAppear: \(name)") } else { "viewWillAppear: \(self)") } } }

load vs. initialize (Swift Edition)

The Objective-C runtime typically calls two class methods automatically when loading and initializing classes in your app’s process:loadandinitialize. In the full article onmethod swizzling,Mattt writes that swizzling shouldalwaysbe done inload(),for safety and consistency.loadis called only once per class and is called on each class that is loaded. On the other hand,a singleinitializemethod can be called on a class and all its subclasses,which are likely to exist forUIViewController,or not called at all if that particular class isn’t ever messaged.

Unfortunately,aloadclass method implemented in Swift isnevercalled by the runtime,rendering that recommendation an impossibility. Instead,we’re left to pick among second-choice options:

  • Implement method swizzling ininitialize
    This can be done safely,so long as you check the type at execution time and wrap the swizzling indispatch_once(which you should be doing anyway).

  • Implement method swizzling in the app delegate
    Instead of adding method swizzling via a class extension,simply add a method to the app delegate to be executed whenapplication(_:didFinishLaunchingWithOptions:)is called. Depending on the classes you’re modifying,this may be sufficient and should guarantee your code is executed every time.


In closing,remember that tinkering with the Objective-C runtime should be much more of a last resort than a place to start. Modifying the frameworks that your code is based upon,as well as any third-party code you run,is a quick way to destabilize the whole stack. Tread softly!

相关文章

Swift 正式开源!Swift 团队很高兴宣布 Swift 开始开源新篇章。自从苹果发布 Swfit 编程语言,就成为了...
快,快,快!动动您的小手,分享给更多朋友! 苹果去年推出了全新的编程语言Swift,试图让iOS开发更简单...
开发者(KaiFaX) 面向开发者、程序员的专业平台! 和今年年初承诺的一样,苹果贴出了Swift语言的源码,...
本文由@Chun发表于Chun Tips :http://chun.tips/blog/2014/12/11/shi-yong-swift-gou-jian-zi-ding-yi...
本文由CocoaChina译者leon(社区ID)翻译 原文:THE RIGHT WAY TO WRITE A SINGLETON 在之前的帖子里聊过...
本文由CocoaChina译者leon(社区ID)翻译 原文:THE RIGHT WAY TO WRITE A SINGLETON 在之前的帖子里聊过...