在iOS 11.0中,TouchID锁定已被弃用。

21

当我使用Xcode 9编译我的应用程序以适用于IOS11时,我会收到以下警告:


(The translated text is:

When I compile my application with Xcode 9 for iOS 11, I get the following warnings:

)
warning: 'touchIDLockout' was deprecated in iOS 11.0: use LAErrorBiometryLockout

warning: 'touchIDNotEnrolled' was deprecated in iOS 11.0: use LAErrorBiometryNotEnrolled

warning: 'touchIDNotAvailable' was deprecated in iOS 11.0: use LAErrorBiometryNotAvailable

我正在使用TouchID,但我没有使用TouchIdLockout……Cste,TouchID正常工作。

如何消除这些警告?


编辑(不是原作者):

我查出了唯一的原因。在我的代码中引用LocalAuthentication框架中的LAError就足以使这些警告出现。

重现步骤(在Xcode 9.2中尝试):

  1. 创建一个新的iOS应用程序(单视图模板)。请注意,iOS部署目标设置为iOS 11.2。
  2. 将这些行添加到AppDelegate.swift

import LocalAuthentication

appDidFinishLaunching方法中只需要加入一行代码:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let _: LAError? = nil
    return true
}
  • 构建应用程序。

  • let _: LAError? = nil 这一行足以使这三个警告出现。然而这些警告并不与任何特定的代码行相关,它们在构建日志中出现,没有任何文件/行引用:

    <unknown>:0: warning: 'touchIDLockout' was deprecated in iOS 11.0: use LAErrorBiometryLockout
    <unknown>:0: warning: 'touchIDNotEnrolled' was deprecated in iOS 11.0: use LAErrorBiometryNotEnrolled
    <unknown>:0: warning: 'touchIDNotAvailable' was deprecated in iOS 11.0: use LAErrorBiometryNotAvailable
    

    这是屏幕截图: Xcode中的警告截图

    还有一个示例项目: 用于下载的示例项目(Xcode 9.2)

    供参考,我已将此报告给了苹果公司。 Radar #36028653。


    @Ole:你能重现“即使你不使用touchID...标识符也会出现弃用警告”的问题吗? - Martin R
    1
    @MartinR 我在问题中添加了更多信息(对于劫持这个问题的sebastien感到抱歉)。 - Ole Begemann
    1
    仍然在Xcode 10 GM中出现。 - Mark Thormann
    4个回答

    25

    简短回答: 在我看来,这似乎是编译器的一个错误,由于导入定义了多个具有相同值的常量的 C 枚举而引起。

    详细回答:很遗憾,我没有解决如何避免这个废弃警告的方法,只能提供一个可能导致它的解释。

    LAError 代码在 LocalAuthentication 框架中的 <LAError.h> 中作为 C 枚举定义。以下是该定义的一部分:

    // Error codes
    #define kLAErrorAuthenticationFailed                       -1
    #define kLAErrorUserCancel                                 -2
    // ...
    #define kLAErrorTouchIDNotAvailable                        -6
    #define kLAErrorTouchIDNotEnrolled                         -7
    #define kLAErrorTouchIDLockout                             -8
    // ...
    #define kLAErrorBiometryNotAvailable                        kLAErrorTouchIDNotAvailable
    #define kLAErrorBiometryNotEnrolled                         kLAErrorTouchIDNotEnrolled
    #define kLAErrorBiometryLockout                             kLAErrorTouchIDLockout
    
    typedef NS_ENUM(NSInteger, LAError)
    {
        LAErrorAuthenticationFailed = kLAErrorAuthenticationFailed,
        LAErrorUserCancel = kLAErrorUserCancel,
        // ...
        LAErrorTouchIDNotAvailable NS_ENUM_DEPRECATED(10_10, 10_13, 8_0, 11_0, "use LAErrorBiometryNotAvailable") = kLAErrorTouchIDNotAvailable,
        LAErrorTouchIDNotEnrolled NS_ENUM_DEPRECATED(10_10, 10_13, 8_0, 11_0, "use LAErrorBiometryNotEnrolled") = kLAErrorTouchIDNotEnrolled,
        LAErrorTouchIDLockout NS_ENUM_DEPRECATED(10_11, 10_13, 9_0, 11_0, "use LAErrorBiometryLockout")
        __WATCHOS_DEPRECATED(3.0, 4.0, "use LAErrorBiometryLockout") __TVOS_DEPRECATED(10.0, 11.0, "use LAErrorBiometryLockout") = kLAErrorTouchIDLockout,
        // ...
        LAErrorBiometryNotAvailable NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryNotAvailable,
        LAErrorBiometryNotEnrolled NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryNotEnrolled,
        LAErrorBiometryLockout NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryLockout,
        // ...
    } NS_ENUM_AVAILABLE(10_10, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);
    

    可以看到,“旧”(已弃用)和“新”的错误代码使用相同的值。例如,LAErrorTouchIDNotAvailableLAErrorBiometryNotAvailable都定义为-6

    这在C语言中是完全有效的,但Swift enum的原始值必须相互独立。显然,Swift导入器通过将新/重复的情况映射到静态变量来解决此问题。

    以下是Swift映射的摘录:

    public struct LAError {
    
        public init(_nsError: NSError)
        public static var _nsErrorDomain: String { get }
    
    
        public enum Code : Int {
            case authenticationFailed
            case userCancel
            // ...
            @available(iOS, introduced: 8.0, deprecated: 11.0, message: "use LAErrorBiometryNotAvailable")
            case touchIDNotAvailable
            @available(iOS, introduced: 8.0, deprecated: 11.0, message: "use LAErrorBiometryNotEnrolled")
            case touchIDNotEnrolled
            @available(iOS, introduced: 9.0, deprecated: 11.0, message: "use LAErrorBiometryLockout")
            case touchIDLockout
            // ...
            @available(iOS 11.0, *)
            public static var biometryNotAvailable: LAError.Code { get }
            @available(iOS 11.0, *)
            public static var biometryNotEnrolled: LAError.Code { get }
            @available(iOS 11.0, *)
            public static var biometryLockout: LAError.Code { get }
            // ...
        }
    
        // ...
    }
    

    这似乎是产生弃用警告的原因,也是swift-users邮件列表中报告的问题的原因。

    这导致无法编写详尽且无警告的LAError语句。


    为了验证我的猜想,我使用自定义枚举重现了此问题:将以下定义添加到macOS 10.13或iOS 11项目的桥接标头文件中:

    #import <Foundation/Foundation.h>
    
    typedef NS_ENUM(NSInteger, MyEnum)
    {
        MyEnumA = 1,
        MyEnumB = 2,
        MyEnumC NS_ENUM_DEPRECATED(10_10, 10_13, 8_0, 11_0, "use MyEnumNewC") = 3,
    
        MyEnumNewC NS_ENUM_AVAILABLE(10_13, 11_0) = 3,
    };
    

    这将作为 Swift 的导入内容

     public enum MyEnum : Int {
        case A
        case B
        @available(OSX, introduced: 10_10, deprecated: 10_13, message: "use MyEnumNewC")
        case C
    
        @available(OSX 10_13, *)
        public static var newC: MyEnum { get }
     }
    

    第一个(不同的)枚举值有3种情况,并且对于重复的值有一个静态属性。

    事实上,任何使用MyEnum都会触发不建议使用的警告:

    // main.swift:
    print(MyEnum.A) // Or: let _: MyEnum? = nil
    
    // Build log:
    // <unknown>:0: warning: 'C' was deprecated in OS X 10.13: use MyEnumNewC
    

    另外,在 switch 语句中无法使用新的枚举值:

    func foo(err: MyEnum) {
        switch err {
        case .A:
            print("A")
        case .B:
            print("B")
        case .newC:
            print("C")
        }
    }
    
    // Build log:
    // main.swift:12:9: error: switch must be exhaustive
    // <unknown>:0: warning: 'C' was deprecated in OS X 10.13: use MyEnumNewC
    

    即使编译器(表面上)知道这些情况是穷尽的:

    func foo(err: MyEnum) {
        switch err { // Switch must be exhaustive
        case .A:
            print("A")
        case .B:
            print("B")
        case .newC:
            print("C")
        default:
            print("default")
        }
    }
    
    // Build log:
    // <unknown>:0: warning: 'C' was deprecated in OS X 10.13: use MyEnumNewC
    // main.swift:19:9: warning: default will never be executed
    

    我认为这看起来像是编译器的错误。


    1
    感谢您提供如此全面的答案!我相信您是正确的。 - Ole Begemann
    谢谢您提供这份全面的报告。您能否追踪一下是否存在clang/llvm问题? - skagedal
    @skagedal:我不知道有没有这样的问题。在Swift论坛上已经讨论过(https://forums.swift.org/t/handle-deprecated-enum-cases-from-a-library/6952),但我在https://bugs.swift.org上找不到相应的问题,所以显然还没有人报告过。 - Martin R
    @MartinR 谢谢。我在论坛上发表了评论,至少让它浮现出来。 - skagedal
    1
    啊,Ole 提交了一个 Swift Bug。https://bugs.swift.org/browse/SR-6637 - skagedal

    7

    是的,随着苹果转向iOS 11和FaceID,这些都是新的警告。最可能的是,您正在检查生物识别硬件是否未被锁定,已注册指纹以及设备是否具有支持的硬件。

    这是一个设置示例:

    import LocalAuthentication
    
    ...
    
    var authContext = LAContext()
    var biometricsError: NSError?
    authContext?.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &biometricsError)
    

    在iOS 10之前,人们会运行如下检查:

    if biometricsError?.code == LAError.touchIDNotAvailable.rawValue {
      // No hardware
    }
    
    if biometricsError?.code == LAError.touchIDNotEnrolled.rawValue {
      // No fingerprints
    }
    
    if biometricsError?.code == LAError.touchIDLockout.rawValue {
      // Locked out
    }
    

    注意:iOS 11引入了以上代码的一个微小变体。而不是为每个错误属性使用LAError.touchID,他们引入了LAError.biometry。因此,你需要: biometryNotAvailablebiometryNotEnrolledbiometryLockout

    苹果似乎更喜欢这种方法:

    if biometricsError?.code == Int(kLAErrorBiometryNotAvailable) {
      // No hardware
    }
    
    if biometricsError?.code == Int(kLAErrorBiometryNotEnrolled) {
      // No fingerprints
    }
    
    if biometricsError?.code == Int(kLAErrorBiometryLockout) {
      // Locked out
    }
    

    这种方法可以消除Xcode的警告。


    3
    谢谢回复,但不幸的是它并没有消除警告,因为我没有使用 LAError.touchIDNotAvailable、LAError.touchIDNotEnrolled 或者 LAError.touchIDLockout 这些常量。 我同意这些常量在IOS11中已经过时,但我没有在使用它们! - sebastien
    需要注意的是,这不是苹果公司偏爱的方法,而是已确认的编译器错误 - 请参见上面的答案。使用kLAError常量的解决方法确实有效,但有一个警告。我会添加自己的答案。 - skagedal

    5
    正如注意到的那样,这是编译器中的一个错误。Swift团队已经知道了这个问题,你可能想去投票支持这个错误。同时,在它被修复之前,请添加一个监视器,以便在移除以下解决方法时使用。
    为了避免收到警告,你需要做的是:不要提到LAError。把LAError看作是伏地魔。
    相反,使用Objective-C风格的错误检查。所有的Error枚举都映射到一个NSError,这些错误由域和代码构成。这些比较常量也导出到了Swift中。它们可以被命名而不会出现警告。所以你的代码可能看起来有点(希望非常少)像这样。
    let context = LAContext()
    let text = "Authenticate, please!"
    context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: text) { (success, error) in
        if success {
            print("")
        } else {
            guard let error = error else {
                return print("Should not happen according to the docs!")
            }
            let nsError = error as NSError
            switch nsError.domain {
            case kLAErrorDomain:
                switch nsError.code {
                case Int(kLAErrorUserCancel):
                    print("User cancelled.")
                case Int(kLAErrorBiometryLockout):
                    print("Biometry lockout.")
                default:
                    print("Unhandled error.")
                }
            default:
                print("Unhandled error domain. Probably will not happen.")
            }
        }
    }
    

    是的,其他现有的答案展示了相同的技术,但没有真正将所有内容整合在一起,并且没有提到检查域名,所以我想写下这篇文章。 - skagedal

    0
    我也曾经为此苦苦挣扎了很长时间。Oliver的答案让我找到了一个适合我的解决方案,但是以防还有其他人仍然遇到这个问题 - 似乎任何对旧的LAError值的引用都会产生上述警告。
    例如,这段简单的代码会产生警告。删除所有对旧代码的引用并使用Oliver的方法。
    func evaluateAuthenticationPolicyMessageForLA(errorCode: Int) -> String {
        var message = ""
    
        switch errorCode {
        case LAError.authenticationFailed.rawValue:
            message = "The user failed to provide valid credentials"
        default:
            message = "Can't use any version of old LAError"
        }
    
        return message
    }//evaluateAuthenticationPolicyMessageForLA
    

    实际上,它甚至可以更简单。试试这个:
    func evaluateAuthenticationPolicyMessageForBiometricsError(biometricsError: Int) -> String {
    
        var message = "I would normally leave this blank"
    
        if biometricsError == kLAErrorBiometryNotAvailable {
            message = "Biometrics are not available on this device"
        }//if not avail
    
        if biometricsError == kLAErrorBiometryNotEnrolled {
            message = "Biometrics are not enrolled on this device"
        }//if not enrolled
    
        if biometricsError == kLAErrorBiometryLockout {
            message = "Biometrics are locked out on this device"
        }//if locked out
    
        return message
    
    }//evaluateAuthenticationPolicyMessageForBiometricsError
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接