swift 基础:关联引用讲解

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

前言

最近在 review 一些代码时,发现了objc_setAssociatedObjectobjc_getAssociatedObject 方法,突然忘了这两个方法的机制和作用。

于是决定深入探究一番。

关联引用的定义

objc_setAssociatedObjectobjc_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_setAssociatedObjectobjc_getAssociatedObject 有一个了解,能够在实际开发中得心应手地使用这两个方法。

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

本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
2501_916013749 分钟前
iOS 加固工具使用经验与 App 安全交付流程的实战分享
android·ios·小程序·https·uni-app·iphone·webview
大熊猫侯佩27 分钟前
探秘 WWDC 25 全新 #Playground 宏:提升 Swift 开发效率的超级神器
xcode·swift·wwdc
万少43 分钟前
云测试提前定位和解决问题 萤火故事屋 上架流程
前端·harmonyos·客户端
2501_915106324 小时前
Fiddler 中文版抓包实战 构建标准化调试流程提升团队协作效率
android·ios·小程序·https·uni-app·iphone·webview
iReaShare5 小时前
iPhone 数据擦除软件评测(最新且全面)
ios
iReaShare6 小时前
轻松将文件从 iPhone 传输到 Mac
ios
9144062326 小时前
IOS 18下openURL 失效问题
ios
移动端小伙伴9 小时前
10.推送的扩展能力 — 打造安全的通知体验
swift
移动端小伙伴9 小时前
推送的扩展能力 — 打造个性化的通知体验
swift
移动端小伙伴9 小时前
远程推送(Remote Push Notification)
swift