Bill
Bill

Reputation: 45426

How do I do weak linking in Swift?

In Objective-C, if I wanted to use a specific class that's only present in a new version of iOS, I would do something like this:

if( [UIBlurEffect class] ) {
  // do something with UIBlurEffect
}
else {
  // gracefully fallback to old behavior
}

However, the equivalent Swift:

if UIBlurEffect.self != nil {
  let blur: UIBlurEffect = UIBlurEffect(...)
  // ...
else {
  // ...
}

// also occurs with NSClassFromString("UIBlurEffect")

doesn't have the same functionality.

If run on an environment where NSNewFeature is available, everything is fine. But if the class isn't defined, I get a link error when starting the application:

dyld: Symbol not found: _OBJC_CLASS_$_UIBlurEffect

So how do I do weak linking in Swift?

Edit Added UIBlurEffect as specific example.

Upvotes: 19

Views: 4966

Answers (2)

Daniel Galasko
Daniel Galasko

Reputation: 24247

Just to add my two cents since I ended up using this to my immediate dismay upon distributing the app to our testers. There is a bug in the Swift compiler (Xcode <= 6.1.1) whereby when building in Release mode the compiler actually doesn't return nil when calling NSClassFromString.

To check me simply change your configuration to Release and see how she crashes.

I ended up having to explicitly check for iOS versions or a simple respondsToSelector call in other place.

So where my code looked like this:

    if NSClassFromString("UIVisualEffectView") != nil {
        //use the blur safely
    } else {
        //abandon blur! try something safer
    }

I ended up having to use this instead

switch UIDevice.currentDevice().systemVersion.compare("8.0.0", options: NSStringCompareOptions.NumericSearch) {
        case .OrderedSame, .OrderedDescending:
            //go wild with blur
        case .OrderedAscending:
            //blur shall not pass here!
        }

This question dealt with the same issue - UIAlertView is crashing app on iOS 7

Upvotes: 2

user207128
user207128

Reputation:

Seems like I've figured out what you can do

  1. I used NSClassFromString() to check if class is available on device, i.e.

    if NSClassFromString("UIBlurEffect") {
        let blur = UIBlurEffect(...)
        //...
    }
    else {
        //...
    }
    
  2. It's needed to make UIKit.framework (or another corresponding framework) optional. If you create Swift-based application in XCode6-BetaX, all the frameworks wouldn't be explicitly added to the link build phase so you need to go to your target settings, add UIKit.framework as a linked framework (in 'Link Binary With Libraries' section) and to change its status to Optional. This step does the trick and I've managed to run version specific code without a problem.

Update: You don't need to make it optional anymore, since Xcode 6 beta 6 (via @user102008)

Update 2: You can't actually perform implicit if statement checks for nil (since Xcode 6 Beta 5). You need to assert it like that:

    if NSClassFromString("UIBlurEffect") != nil {
        let blur = UIBlurEffect(...)
        //...
    }
    else {
        //...
    }

(via @daniel-galasko)

Upvotes: 7

Related Questions