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

相关推荐
库奇噜啦呼1 天前
【iOS】NSOperation与NSOperationQueue
macos·ios·cocoa
二流小码农1 天前
鸿蒙开发:实现文本渐变效果
android·ios·harmonyos
大龄秃头程序员1 天前
Sendable 不是“能跑就行”:Swift Concurrency 下的实战踩坑与架构落地指南
swift
WiresharkClub1 天前
抓包遇到私有协议全是乱码?抓包鹰自定义协议解码教程
ios
Lvan的前端笔记2 天前
ios:版本号和Build构建号
ios
大熊猫侯佩2 天前
WWDC26 全新 SwiftData 的 .codable 宏选项:它不是泛型专用胶水
数据库·swift·apple
GuWenyue2 天前
等AI回复卡顿到劝退?Vue3+DeepSeek流式输出实战,70行代码实现打字机效果
前端·人工智能·客户端
大龄秃头程序员2 天前
Swift 并发实战:从 Task 到 Actor 重入性,我掉进了“逻辑不一致”的坑
swift
鹤卿1232 天前
「iOS」3GShare总结
macos·ios·objective-c·cocoa