这里每天分享一个 iOS 的新知识,快来关注我吧
前言

最近在 review 一些代码时,发现了objc_setAssociatedObject
和objc_getAssociatedObject
方法,突然忘了这两个方法的机制和作用。
于是决定深入探究一番。
关联引用的定义
objc_setAssociatedObject
和objc_getAssociatedObject
被称为关联引用(Associative References)。根据苹果的官方文档:
-
objc_setAssociatedObject
用于通过给定的 key 和 value 关联策略为指定对象设置关联值。 -
objc_getAssociatedObject
用于返回与指定对象和 key 关联的 value。
简而言之,关联引用使我们能够通过键将一个对象链接或附加到另一个对象上。我们可以将第一个对象称为负载(payload),而第二个对象称为目标(target)。这种链接确保了当目标对象被释放时,负载也可以被释放。
创建对象与字符串的关联
我们可以利用objc_setAssociatedObject
方法将负载(例如 "Example message")附加到 UIViewController 上,注意,这里的 self 就是控制器。
php
enum AssociatedKey {
staticvar key: Int = 0
}
func onButtonTapped() {
// 创建 Alert和负载
let alert = UIAlertController(title: "Title", message: "Example message", preferredStyle: .alert)
let payload = "Example message"
// 使用 objc_setAssociatedObject 函数将负载与 Alert 关联
objc_setAssociatedObject(self, &AssociatedKey.key, payload, .OBJC_ASSOCIATION_RETAIN)
// 为 Alert 添加一个 OK 按钮
let okAction = UIAlertAction(title: "OK", style: .default) { [weakself] _in
self?.handleAlertDismissed()
}
alert.addAction(okAction)
// 显示 Alert
present(alert, animated: true, completion: nil)
}
代码解析
在上面的代码中,我们首先创建一个唯一的键,用于每个关联。使用静态变量作为 key 是比较推荐的做法。
接下来,我们创建一个 Alert,并将负载存储在一个变量中。这个 Alert 稍后会被调用以检索关联对象。
objc_setAssociatedObject
函数将负载与视图控制器关联。这个函数需要四个参数:源对象或目标对象、键、值或负载,以及关联策略常量。
第四个参数指定关联策略。此策略决定了在关联中,源对象(目标对象)与值(负载)之间的引用类型。它可以是"弱引用"(OBJC_ASSOCIATION_ASSIGN
)、"强引用"(OBJC_ASSOCIATION_RETAIN
)或复制(OBJC_ASSOCIATION_COPY
)。此外,它还指定关联是以原子方式还是非原子方式进行的。
检索关联对象
我们使用objc_getAssociatedObject
函数来检索关联对象。
swift
func handleAlertDismissed() {
// 使用 objc_getAssociatedObject 检索关联负载
if let payload = objc_getAssociatedObject(self, &AssociatedKey.key) {
// 打印负载
print("Payload is: \(payload)") // 输出 "Payload is: Example message"
}
}
释放关键对象
如果你想解除关联并释放负载而不分配新的对象,你可以调用objc_setAssociatedObject
,将值设为 nil。
lua
objc_setAssociatedObject(self, &AssociatedKey.key, nil, .OBJC_ASSOCIATION_RETAIN)
完整代码示例
swift
class ViewController: UIViewController {
// MARK: - Enums
// 用于关联的静态变量键
enum AssociatedKey {
staticvar key: Int = 0
}
// MARK: - Properties
privatevar button: UIButton = {
let button = UIButton()
button.setTitle("Tap me", for: .normal)
button.backgroundColor = .blue
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
// MARK: - View Lifecycles
overridefunc viewDidLoad() {
super.viewDidLoad()
setupUI()
button.addTarget(self, action: #selector(onButtonTapped), for: .touchUpInside)
}
}
// MARK: - Private Methods
extension ViewController {
func setupUI() {
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
@objcfunc onButtonTapped() {
// 创建 Alert 和负载
let alert = UIAlertController(title: "Title", message: "Example message", preferredStyle: .alert)
let payload = "Example message"
// 使用 objc_setAssociatedObject 函数将负载与 Alert 关联
objc_setAssociatedObject(self, &AssociatedKey.key, payload, .OBJC_ASSOCIATION_RETAIN)
// 为 Alert 添加一个 OK 按钮
let okAction = UIAlertAction(title: "OK", style: .default) { [weakself] _in
self?.handleAlertDismissed()
}
alert.addAction(okAction)
// 显示 Alert
present(alert, animated: true, completion: nil)
}
func handleAlertDismissed() {
// 使用 objc_getAssociatedObject 检索关联负载
iflet payload = objc_getAssociatedObject(self, &AssociatedKey.key) {
// 打印负载
print("Payload is: \(payload)") // 输出 "Payload is: Example message"
}
}
}
总结
通过关联引用,我们可以灵活地将对象之间进行关联,而不必直接修改类的结构,增加了代码的灵活性和扩展性。
通过本文的介绍,希望大家对objc_setAssociatedObject
和objc_getAssociatedObject
有一个了解,能够在实际开发中得心应手地使用这两个方法。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!