Skip to content

Swift 入门(12):协议

协议(protocol)就像“契约”,规定了 必须具备的属性和方法
类、结构体、枚举都可以遵循协议。

1. 定义协议

swift
protocol Drawable {
    func draw()
}

2. 遵循协议

swift
struct Circle: Drawable {
    func draw() {
        print("画一个圆")
    }
}

let c = Circle()
c.draw()  // 画一个圆

3. 属性要求

协议可以要求属性,但只规定 读/写权限,不实现。

swift
protocol Named {
    var name: String { get set }
    var description: String { get }
}

struct Person: Named {
    var name: String
    var description: String {
        "我的名字是 \(name)"
    }
}

let p = Person(name: "Alice")
print(p.description) // 我的名字是 Alice

4. 多协议

一个类型可以同时遵循多个协议。

swift
protocol Flyable { func fly() }
protocol Swimmable { func swim() }

struct Duck: Flyable, Swimmable {
    func fly() { print("鸭子会飞") }
    func swim() { print("鸭子会游泳") }
}

5. 协议作为类型

协议不仅是规范,也能作为类型使用。

swift
func test(d: Drawable) {
    d.draw()
}

test(d: Circle()) // 画一个圆

6. 协议继承

协议可以继承其它协议。

swift
protocol Animal {
    func eat()
}

protocol Pet: Animal {
    func play()
}

struct Dog: Pet {
    func eat() { print("狗在吃") }
    func play() { print("狗在玩") }
}

7. 实战:系统协议实现

AuthenticationServices 协议示例

swift
import AuthenticationServices

class PasskeysManager: NSObject {
    // 私有属性存储回调
    private var callback: ((String) -> Void)?
}

// 使用 extension 分组实现协议
extension PasskeysManager: ASAuthorizationControllerDelegate {

    // 认证成功回调
    func authorizationController(controller: ASAuthorizationController,
                               didCompleteWithAuthorization authorization: ASAuthorization) {
        callback?("认证成功")
        callback = nil
    }

    // 认证失败回调
    func authorizationController(controller: ASAuthorizationController,
                               didCompleteWithError error: Error) {
        callback?("认证失败: \(error.localizedDescription)")
        callback = nil
    }
}

extension PasskeysManager: ASAuthorizationControllerPresentationContextProviding {

    // 提供展示认证界面的窗口
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return UIApplication.shared.windows.first { $0.isKeyWindow } ?? UIWindow()
    }
}

#### 协议方法详解:presentationAnchor

```swift
public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return UIApplication.shared.windows.first {
        $0.isKeyWindow
    } ?? ASPresentationAnchor()
}

这个方法在做什么?

  • 为 AuthenticationServices 提供一个"锚点窗口"
  • 系统会在这个窗口上显示 Face ID/Touch ID 认证界面

语法分解

1. 方法签名

swift
// 协议要求的方法签名
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor

2. 核心逻辑:寻找活跃窗口

swift
UIApplication.shared.windows.first {
    $0.isKeyWindow  // 尾随闭包语法
}

等价于:

swift
UIApplication.shared.windows.first(where: { window in
    return window.isKeyWindow
})

3. 类型别名

swift
// ASPresentationAnchor 实际上就是 UIWindow
typealias ASPresentationAnchor = UIWindow

不同的实现方式对比

swift
// ✅ 你的实现:安全的空窗口降级
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return UIApplication.shared.windows.first { $0.isKeyWindow } ?? ASPresentationAnchor()
}

// ✅ 常见实现:返回空 UIWindow
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return UIApplication.shared.windows.first { $0.isKeyWindow } ?? UIWindow()
}

// ✅ iOS 13+ 现代写法
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return view.window ?? UIWindow()
}

// ❌ 不安全的写法(可能崩溃)
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return UIApplication.shared.windows.first { $0.isKeyWindow }!  // 强制解包危险
}

实际应用场景

swift
class PasskeysViewController: UIViewController {
    private let passkeysManager = PasskeysManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        // 当用户点击认证按钮时
        let authController = ASAuthorizationController(authorizationRequests: [/* ... */])
        authController.delegate = passkeysManager
        authController.presentationContextProvider = passkeysManager  // 👈 会调用 presentationAnchor
        authController.performRequests()
    }
}

为什么需要这个方法?

  1. 系统需要知道在哪个窗口显示认证界面
  2. 确保认证界面显示在正确的屏幕上(多屏设备)
  3. 处理应用的窗口层级关系

常见问题和解决方案

swift
// 问题:iOS 13+ 中 windows 数组可能为空
// 解决:使用 scene-based 方法
@available(iOS 13.0, *)
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
        return windowScene.windows.first { $0.isKeyWindow } ?? UIWindow()
    }
    return UIWindow()
}

// 问题:SwiftUI 环境中的处理
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return UIApplication.shared.windows.first { $0.isKeyWindow } ??
           UIApplication.shared.windows.first ??
           UIWindow()
}

### @objc 协议兼容

```swift
// 跨语言调用的协议设计
@objc public protocol AuthenticationAPI {
    func register(_ options: String, callback: @escaping (String) -> Void)
    func authenticate(_ options: String, callback: @escaping (String) -> Void)
}

@objc public class AuthManager: NSObject, AuthenticationAPI {
    @objc public func register(_ options: String, callback: @escaping (String) -> Void) {
        // 实现注册逻辑
        callback("注册成功")
    }

    @objc public func authenticate(_ options: String, callback: @escaping (String) -> Void) {
        // 实现认证逻辑
        callback("认证成功")
    }
}

协议实现的最佳实践

swift
// ✅ 使用 extension 分组组织协议实现
extension MyClass: ProtocolA {
    // ProtocolA 的方法
}

extension MyClass: ProtocolB {
    // ProtocolB 的方法
}

// ✅ 使用 MARK 注释分组
class MyClass {
    // MARK: - ProtocolA Implementation

    // MARK: - ProtocolB Implementation
}

总结

  • protocol 用来定义 方法和属性的规范
  • 类/结构体/枚举都可以遵循协议。
  • 协议也可以 继承组合
  • 使用 extension 分组实现协议方法,代码更清晰。
  • @objc 协议支持跨语言调用。
  • 协议能作为类型使用,是 Swift 面向协议编程的基础。

本站总访问