在 Swift 中有设置关联对象的方法吗?

来自 Objective-C 的函数可以在两个对象之间调用函数 objc_setAssociatedObject,让它们维护一个引用,如果在运行时您不希望一个对象在其引用也被删除之前被销毁,这种方法会很方便。斯威夫特有类似的东西吗?

40139 次浏览

Just add #import <objc/runtime.h> on your brindging header file to access objc_setAssociatedObject under swift code

Obviously, this only works with Objective-C objects. After fiddling around with this a bit, here's how to make the calls in Swift:

import ObjectiveC


// Define a variable whose address we'll use as key.
// "let" doesn't work here.
var kSomeKey = "s"


…


func someFunc() {
objc_setAssociatedObject(target, &kSomeKey, value, UInt(OBJC_ASSOCIATION_RETAIN))


let value : AnyObject! = objc_getAssociatedObject(target, &kSomeKey)
}

Here is a simple but complete example derived from jckarter's answer.

It shows how to add a new property to an existing class. It does it by defining a computed property in an extension block. The computed property is stored as an associated object:

import ObjectiveC


// Declare a global var to produce a unique address as the assoc object handle
private var AssociatedObjectHandle: UInt8 = 0


extension MyClass {
var stringProperty:String {
get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! String
}
set {
objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

EDIT:

If you need to support getting the value of an uninitialized property and to avoid getting the error unexpectedly found nil while unwrapping an Optional value, you can modify the getter like this:

    get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as? String ?? ""
}

The solution supports all the value types as well, and not only those that are automagically bridged, such as String, Int, Double, etc.

Wrappers

import ObjectiveC


final class Lifted<T> {
let value: T
init(_ x: T) {
value = x
}
}


private func lift<T>(x: T) -> Lifted<T>  {
return Lifted(x)
}


func setAssociatedObject<T>(object: AnyObject, value: T, associativeKey: UnsafePointer<Void>, policy: objc_AssociationPolicy) {
if let v: AnyObject = value as? AnyObject {
objc_setAssociatedObject(object, associativeKey, v,  policy)
}
else {
objc_setAssociatedObject(object, associativeKey, lift(value),  policy)
}
}


func getAssociatedObject<T>(object: AnyObject, associativeKey: UnsafePointer<Void>) -> T? {
if let v = objc_getAssociatedObject(object, associativeKey) as? T {
return v
}
else if let v = objc_getAssociatedObject(object, associativeKey) as? Lifted<T> {
return v.value
}
else {
return nil
}
}

A possible Class extension (Example of usage)

extension UIView {


private struct AssociatedKey {
static var viewExtension = "viewExtension"
}


var referenceTransform: CGAffineTransform? {
get {
return getAssociatedObject(self, associativeKey: &AssociatedKey.viewExtension)
}


set {
if let value = newValue {
setAssociatedObject(self, value: value, associativeKey: &AssociatedKey.viewExtension, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}

Klaas answer just for Swift 2.1:

import ObjectiveC


let value = NSUUID().UUIDString
var associationKey: UInt8 = 0


objc_setAssociatedObject(parentObject, &associationKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)


let fetchedValue = objc_getAssociatedObject(parentObject, &associationKey) as! String

The above friend has answered your question, but if it is related to closure properties, please note:

```

import UIKit
public extension UICollectionView {


typealias XYRearrangeNewDataBlock = (_ newData: [Any]) -> Void
typealias XYRearrangeOriginaDataBlock = () -> [Any]


// MARK:- associat key
private struct xy_associatedKeys {
static var originalDataBlockKey = "xy_originalDataBlockKey"
static var newDataBlockKey = "xy_newDataBlockKey"
}




private class BlockContainer {
var rearrangeNewDataBlock: XYRearrangeNewDataBlock?
var rearrangeOriginaDataBlock: XYRearrangeOriginaDataBlock?
}




private var newDataBlock: BlockContainer? {
get {
if let newDataBlock = objc_getAssociatedObject(self, &xy_associatedKeys.newDataBlockKey) as? BlockContainer {
return newDataBlock
}
return nil
}


set(newValue) {
objc_setAssociatedObject(self, xy_associatedKeys.newDataBlockKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
convenience init(collectionVewFlowLayout : UICollectionViewFlowLayout, originalDataBlock: @escaping XYRearrangeOriginaDataBlock, newDataBlock:  @escaping XYRearrangeNewDataBlock) {
self.init()




let blockContainer: BlockContainer = BlockContainer()
blockContainer.rearrangeNewDataBlock = newDataBlock
blockContainer.rearrangeOriginaDataBlock = originalDataBlock
self.newDataBlock = blockContainer
}

```

Update in Swift 3.0 For example this is a UITextField

import Foundation
import UIKit
import ObjectiveC


// Declare a global var to produce a unique address as the assoc object handle
var AssociatedObjectHandle: UInt8 = 0


extension UITextField
{
var nextTextField:UITextField {
get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! UITextField
}
set {
objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

I wrote a modern wrapper available at https://github.com/b9swift/AssociatedObject

You may be surprised that it even supports Swift structures for free.

Swift struct association

For 2022, now very simple:

//  Utils-tags.swift


// Just a "dumb Swift trick" to add a string tag to a view controller.
// For example, with UIDocumentPickerViewController you need to know
// "which button was clicked to launch a picker"


import UIKit
private var _docPicAssociationKey: UInt8 = 0
extension UIDocumentPickerViewController {
public var tag: String {
get {
return objc_getAssociatedObject(self, &_docPicAssociationKey)
as? String ?? ""
}
set(newValue) {
objc_setAssociatedObject(self, &_docPicAssociationKey,
newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}