文章

在 Swift 中使用 Objective-C 运行时特性

使用选择器和键路径与动态 Objective-C API 进行交互。

概览

一些 Objective-C API (如 target-action) 接受将方法或属性名称作为参数,然后使用这些名称来动态调用或访问相应的方法或属性。在 Swift 中,你可以分别使用 #selector#keyPath 表达式,将这些方法或属性名称表示为选择器或键路径。

使用选择器安排对 Objective-C 方法的调用

在 Objective-C 中,选择器是一种引用 Objective-C 方法名称的类型。在 Swift 中,Objective-C 选择器通过 Selector (英文) 结构来表示,你可以使用 #selector 表达式来创建它们。

在 Swift 中,你可以通过将方法名称放在 #selector 表达式中来为 Objective-C 方法创建选择器:#selector(MyViewController.tappedButton(_:))。要为属性的 Objective-C getter 或 setter 方法构建选择器,应使用 getter:setter: 标签为属性名称添加前缀,例如:#selector(getter: MyViewController.myButton)。以下示例演示了将一个选择器用作 target-action 模式的一部分来调用一个用于响应 touchUpInside (英文) 事件的方法。

import UIKit
class MyViewController: UIViewController {
    let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
    
    override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        let action = #selector(MyViewController.tappedButton)
        myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
    }
    
    @objc func tappedButton(_ sender: UIButton?) {
        print("tapped button")
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}

如果你需要分清不同的重载函数,应使用带括号的表达式及 as 运算符,使 #selector 表达式明确地引用特定的重载。

使用键路径动态访问 Objective-C 属性

在 Objective-C 中,键是一个用于标识对象的特定属性的字符串。键路径是一串用句点分隔的键,用于指定要遍历的一系列对象属性。键和键路径常常用于键值编码 (KVC),这是一种利用字符串标识符间接访问对象的属性和关系的机制。

你可以使用 #keyPath 字符串表达式来创建由编译器检查的键和键路径,供 value(forKey:) (英文)value(forKeyPath:) (英文) 等 KVC 方法使用。#keyPath 字符串表达式接受方法或属性的链式引用;同时亦支持链中的可选值链式调用,如 #keyPath(Person.bestFriend.name)。利用 #keyPath 字符串表达式创建的键路径不会向接受键路径的 API 传递与所引用的属性或方法相关的类型信息。

以下示例定义了一个 Person 类,创建了该类的两个实例,然后使用多个 #keyPath 字符串表达式来访问各个属性以及这些属性的属性:

class Person: NSObject {
    @objc var name: String
    @objc var friends: [Person] = []
    @objc var bestFriend: Person? = nil
    
    init(name: String) {
        self.name = name
    }
}
 
let gabrielle = Person(name: "Gabrielle")
let jim = Person(name: "Jim")
let yuanyuan = Person(name: "Yuanyuan")
gabrielle.friends = [jim, yuanyuan]
gabrielle.bestFriend = yuanyuan
 
#keyPath(Person.name)
// "name"
gabrielle.value(forKey: #keyPath(Person.name))
// "Gabrielle"
#keyPath(Person.bestFriend.name)
// "bestFriend.name"
gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
// "Yuanyuan"
#keyPath(Person.friends.name)
// "friends.name"
gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
// ["Yuanyuan", "Jim"]

另请参阅

语言互操作性

Objective-C 和 C 代码自定 (英文)

在 Objective-C API 中使用宏来自定将它们导入到 Swift 的方式。

将 Objective-C 代码迁移到 Swift

学习迁移代码的推荐步骤。

Cocoa 设计模式 (英文)

在 App 中采用 Cocoa 设计模式并与其进行互操作。

在 Swift 中处理动态类型的方法和对象

将 Objective-C id 类型的实例转换为特定的 Swift 类型。

导入的 C 和 Objective-C API (英文)

使用原生 Swift 语法与 C 和 Objective-C 中的类型和函数进行互操作。