UUID._unconditionallyBridgeFromObjectiveC(_ *:) crash

1,714 阅读3分钟

UUID.unconditionallyBridgeFromObjectiveC( :)

崩溃日志信息

最近项目收到一个类型的crash日志,感觉挺有意思。

Thread 0 Crashed:

0   libswiftFoundation.dylib            0x00000001a6161350 static UUID._unconditionallyBridgeFromObjectiveC(_:)

1   --                           0x000000010463f35c --.-.action(pageId:model:actionName:params:)

2   --                           0x000000010463db28 -.-.action(pageId:model:actionName:params:)

3   --                               0x0000000101b0c53c specialized static --.action(pageId:model:actionName:params:) + [<compiler-generated> : 0

由于一些原因,IDFA的获取相关由OC代码改成了Swift代码实现,新版本上线,收集到一些崩溃信息。 看完崩溃日志,有这样几个问题:

  • 崩溃信息集中在 iOS 14~iOS14.5之间,只发现一个 iOS14.8系统的崩溃。

  • 新写的Swift代码肯定有问题!毕竟是新出现的。

  • 明明Debug下,测试过很多次,怎么可能会有问题? 

  • 看到报错信息:UUID._unconditionallyBridgeFromObjectiveC(_:)   桥接问题,都是Swift新写的,哪有桥接OC的? 

开始搜索网上信息

搜索出来许多类似的信息: 

大概意思是: 使用了OC的对象,传给Swift,没有判断为nil,导致的崩溃。 例如:

// OC 注意这里返回的URL是nullable)
NSURL* url = [NSURL URLWithString:@"一个链接"];

// Swift
func handle(url: URL)

可是跟我的实现没什么关系? 我这边的实现是纯Swift的!

静下心来思考

点进去 ASIdentifierManager.shared().advertisingIdentifier.uuidString  查看系统方法:

// 节选部分
public struct UUID : ReferenceConvertible, Hashable, Equatable, CustomStringConvertible {
   
   public typealias ReferenceType = NSUUID
    /// Create a UUID from a string such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F".

    /// Returns nil for invalid strings.

    public init?(uuidString string: String)

    /// Returns a string created from the UUID, such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F"
    
    public var uuidString: String { get }
}

看到几个关键信息:

public var uuidString: String { get } 返回由UUID创建的字符串(String类型,不是可选的)。 就是通过这个方法获取的UUID。

public init?(uuidString string: String)  通过无效的字符串初始化返回一个nil。

  • public typealias ReferenceType = NSUUID   UUID的实现桥接的OC的NSUUID。

swift的代码仓库 看到这样的实现: 

UUID的实现.png

    public static func _unconditionallyBridgeFromObjectiveC(_ source: NSUUID?) -> UUID {
        var result: UUID? = nil
        _forceBridgeFromObjectiveC(source!, result: &result)
        return result!
    }

大胆的得出结论: Swift的UUID的实现是桥接OC的NSUUID,由于未知原因,从OC拿来的NSUUID是一个nil,但是Swift转成UUID 把nil当非可选使用了。能解释这个报错信息 UUID._unconditionallyBridgeFromObjectiveC(_:)

毕竟没DEBUG复现过,只能说有可能是这个问题。不确定苹果是否修复了。

确定问题:

bugs.swift.org 网站上的[issues](Crash from static UUID.unconditionallyBridgeFromObjectiveC(:)) 有讨论。  看里面的描述,大概率可以确定是苹果系统内部的Bug。

问题修复

extension ASIdentifierManager {
    static let zeroUUID: String = "00000000-0000-0000-0000-000000000000"
    /// https://bugs.swift.org/browse/SR-6143
    var safeAdvertisingIdentifier: UUID? {
        return self.perform(#selector(getter: ASIdentifierManager.advertisingIdentifier))?.takeUnretainedValue() as? UUID
    }
}

func getIdfa() -> String {
    guard let advertisingIdentifier = ASIdentifierManager.shared().safeAdvertisingIdentifier else {
        return ASIdentifierManager.zeroUUID
    }
    let identifier = advertisingIdentifier.uuidString
    return identifier
}

修复方案的合理性

有一个做虚拟货币的三方库mopub (fork 410,Star 376),在2021年用这个方案修复过,间接的验证了方案的可行性。

UUID方案合理性.png

简单的做个总结

  • 应该是Swift和OC混编下,出现的系统bug。

  • 我这边的crash,集中在iOS14.0~iOS14.5,之后一个iOS14.8的。 不知道该怎么解释。

  • 这个bug,应该在Debug下没法复现(不敢保证),不记得哪个博客上看到过。