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


(Whcemyesil) #1

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?


(Jordan Rose) #2

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.


(Fabian) #3

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. https://bugreport.apple.com/web/?problemID=38213021


(Whcemyesil) #4

Thanks!


(Jon Shier) #5

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?


(Ole-Magnus) #6

@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
}

(Jordan Rose) #7

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.


(Ole-Magnus) #8

Thanks!


(Zhihui Tang) #9

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:https://gist.github.com/zhihuitang/6d3de0963d96a552d47721a598ca79c8

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


(Keith Smiley) #10

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
    }
}

(Jordan Rose) #11

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