How do I do weak linking in Swift?
Asked Answered
C

2

19

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.

Carcinogen answered 6/7, 2014 at 1:32 Comment(9)
possible duplicate of What is the Swift preprocessor equivalent to iOS version check comparison?Merriweather
I guess you should use optional, something like if let featureAvailable = NSNewFeature? {} else {} . But I'm pretty new to swiftGreatuncle
Sorry NSNewFeature.selfGreatuncle
@Merriweather Not a dupe - I want to conditionally use individual classesCarcinogen
Should it not be [NSNewFeature class] rather than [NSNewFeatureClass class] ? or Just try the other syntax: Class cls = NSClassFromString (@"NSRegularExpression"); if (cls) { // Create an instance of the class and use it. } else { // Alternate code path to follow when the // class is not available. }Mendiola
It's a made-up name. Either way.Carcinogen
@Bill: But it's the same problem. In both questions, the issue is that having the class in the code causes an exception when the app starts, even when you don't use it at runtime.Merriweather
This seems to be fixed in Xcode 6 beta 6Merriweather
I don't think Beta6 is available yet, right?Carcinogen
C
7

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)

Chemarin answered 25/7, 2014 at 12:48 Comment(4)
but the whole point of the import statement is to obviate the need for adding frameworks manuallyMerriweather
I think it might be just an implementation bug. Or feature.Chemarin
update: you don't need to make it optional anymore, in Xcode 6 beta 6Merriweather
you can't actually perform implicit if statement checks for nil. You need to assert if NSClass... != nil {use that class} else {use something else}Raffle
R
2

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

Raffle answered 10/2, 2015 at 10:4 Comment(2)
Interesting - Does this still happen in the 6.3 beta?Carcinogen
@Carcinogen will confirm later whether this is the case, I'm hoping not since they mentioned that they fixed several issues with the Release optimisationsRaffle

© 2022 - 2024 — McMap. All rights reserved.