Is it safe to cast a nonnull objc type to a Swift optional?

Hi,

since the advertisingIdentifier of the ASIdentifierManager seems to be incorrectly annotated and therefore causes a crash when accessing the uuidString on Swift (if advertisingIdentifier is nil -
SR-6143), my question is whether it's safe to do the following for now and future versions of Swift:

guard let advertisingIdentifier = ASIdentifierManager.shared().advertisingIdentifier as UUID? else {
    return "00000000-0000-0000-0000-000000000000"
}

or is it safer to make an objective c wrapper with a nullable annotation?

4 Likes

You should make the Objective-C wrapper (and sorry on behalf of the AS framework team).

The compiler had a workaround for incorrect nonnulls, but it only works with reference types (i.e. methods where the return type is the same as it is in Objective-C). So you'll need to use the wrapper function this time.

1 Like

Thanks Jordan. I have created a Radar, maybe you could push this a little? It's just a single word after all, and would perfectly fit into the iOS 12 strategy of stability as a main focus. Feedback Assistant

Thanks!

We’ve seen a similar but different problem in Alamofire where the Data passed by the URLSession delegate method is occasionally nil from the Objective-C side but is being bridged as a non-optional and so crashing in the bridge code. Making the delegate method take a Data? works but causes a permanent compiler warning. I imaging dropping to Objective-C could work but causes issues for a potential Linux port. Any better ideas for a workaround or something?

@jrose
You know whether this is a valid work-around for getting the uuidString?

let optionalIdfv: UUID! = ASIdentiferManager.shared().advertisingIdentifier
if let idfv = optionalIdfv {
   return idfv.uuidString
}

I can't remember if that's the syntactic form we treated specially (as opposed to the one whcemyesil posted originally), but that trick is only for reference types. Since UUID is a value type bridged to NSUUID, it won't work, sorry. The workaround has to be on the Objective-C side.

Thanks!

If you want to avoid the crash issue, I don't think the code will work.
Actually the crash happens in Object-C side, we can write a OC wrapper to the crash like this:

Create a Header file, and add OCCatch.h in your Bridging-Header.h.

//
//  OCCatch.h
//
//

#ifndef OCCatch_h
#define OCCatch_h

// add the code below to your -Bridging-Header.h

/**
 #import "OCCatch.h"
 */

//   How to use it in Swift?
/**
 let exception = tryBlock {
        let statusBar = UIApplication.shared.value(forKey: "statusBar") as? UIView
        //......
    }
  if let exception = exception {  
    print("exception: \(exception)")
  }  
*/

#import <Foundation/Foundation.h>

NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        return exception;
    }
    return nil;
}

#endif /* OCCatch_h */

You can find the file here:How to properly catch NSExceptions in Swift? · GitHub

The get the advertisingIdentifier by

let exception = tryBlock {
    let advertisingIdentifier = ASIdentifierManager.shared().advertisingIdentifier
}
if let exception = exception {
    print("advertisingIdentifier exception: \(exception)")
}

I haven't tested it, please try and let me know if there is any problem

Another workaround for this specific issue is to use the Objective-C runtime from Swift, which doesn't require any Objective-C code which you may prefer if you have an all Swift codebase

import AdSupport

extension ASIdentifierManager {
    public var safeAdvertisingIdentifier: UUID? {
        return self.perform(#selector(getter: ASIdentifierManager.advertisingIdentifier))?.takeUnretainedValue() as? UUID
    }
}
5 Likes

Nitpick: that should be as! UUID? rather than as? UUID.

1 Like

Hi, still seeing the crash regarding advertisingIdentifier on an iOS 13 device running an app built with Xcode 10.3.

@jrose would you know if building with Xcode 11 would resolve the issue, or would I still need one of the workarounds described above? Thx

I'm not at Apple anymore, sorry. But it looks like the headers haven't changed, and if you're still seeing the crash I guess the implementation hasn't changed either. So yes, the workaround is still necessary. :-(

1 Like

can it be considered as a "working" workaround?