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新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
@大迁世界14 小时前
14个你现在必须关闭的 iOS 26 设置,不然手机很快被它榨干
macos·ios·智能手机·objective-c·cocoa
四眼蒙面侠21 小时前
深入 SwiftWork(第 0 篇):用 SwiftUI 构建一个 Agent 可视化工作台
swift·openagentsdk
YJlio1 天前
10.2.8 以其他账户运行服务(Running services in alternate accounts):为什么“把服务切到某个用户账号下运行”,本质上是在改变服务的整个安全上下文?
python·安全·ios·机器人·django·iphone·7-zip
pop_xiaoli1 天前
【iOS】KVC与KVO
笔记·macos·ios·objective-c·cocoa
90后的晨仔1 天前
《swiftUI进阶 第10章:现代状态管理(iOS 17+)》
ios
sakiko_1 天前
UIKit学习笔记4-使用UITableView制作滚动视图
笔记·学习·ios·swift·uikit
小锋学长生活大爆炸2 天前
【开源软件】这次iPhone也是用上Claw了 | PhoneClaw
ios·开源软件·iphone·claw
SameX2 天前
独立开发一个把走过的路变成 km² 的 App,聊聊 25m 网格和后台 GPS 的坑
ios
XD7429716362 天前
科技早报晚报|2026年4月30日:Agent 安全壳、浏览器 iOS 测试台与可穿戴数据 API,今天更值得看的 3 个技术机会
科技·ios·开源项目·科技新闻·开发者工具
北京自在科技2 天前
Find Hub App 小更新
android·ios·安卓·findmy·airtag