有没有办法在Swift中设置关联对象?


82

来自目标C的您可以objc_setAssociatedObject在2个对象之间调用函数以使它们维护一个引用,如果在运行时您不希望在删除该对象的引用之前也不要破坏该对象的话,这会很方便。迅捷有与此类似的东西吗?


3
您可以使用objc_setAssociatedObject:从斯威夫特developer.apple.com/library/prerelease/ios/documentation/Swift/...
jtbandes

带有完整代码以剪切和粘贴的大量示例:stackoverflow.com/documentation/swift/1085/associated-objects/…–
Fattie

Answers:


130

这是一个从jckarter的答案获得的简单但完整的示例。

它显示了如何向现有类添加新属性。它通过在扩展块中定义计算属性来实现。计算的属性存储为关联的对象:

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)
        }
    }
}

编辑:

如果需要支持获取未初始化属性的值并避免发生错误unexpectedly found nil while unwrapping an Optional value,则可以按以下方式修改getter:

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

您是否能够以这种方式设置符合类型别名的功能?
Morkrom 2014年

1
@PhamHoan Int是Swift本机类型。尝试改用NSNumber。
克拉斯2014年

1
@PhamHoan我添加了一个获取未初始化对象的解决方案。
克拉斯2014年

1
@Jawwad我猜,这没什么关系。在UInt8来自原挂线。
克拉斯

1
嘿@Jacky-是什么让您说应该是COPY ..请让我们知道!
Fattie

31

该解决方案还支持所有值类型,不仅支持自动桥接的值类型,例如String,Int,Double等。

包装纸

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
    }
}

可能的 Class扩展(用法示例)

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)
            }
        }
    }
}

您能否解释一下这些关联标志是什么意思?实际上,我实际上是在尝试使滚动条与scrollView一起滚动,因此我将scrollView存储在扩展名中。我应该使用哪些标志?
2015年

1
另外,我试图使用此方法存储[AnyObject]的数组,但getter总是返回nil。不知何故,如果没有得到正确的返回值。
2015年

您决定使用哪种关联性策略取决于您,取决于您的程序。有关更多信息,建议您查阅nshipster.com/associated-objects这篇文章。我试图存储数组,它似乎工作得很好。您正在使用哪个Swift版本?
HepaKKes 2015年

太好了 对于Swift 2.3,我最终从“提升”中移除了“泛型”并Any改为使用。我
想写

5
在Swift 3中,这变得容易得多。但是无论如何,这是基于您的回答的秘诀:yar2050.com/2016/11/associated-object-support-for-swift-23.html
Dan Rosenstark

5

显然,这仅适用于Objective-C对象。经过一些摆弄之后,以下是在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)
}

我已经尝试过这种方法,但是当没有其他快速代码对它的引用时,我的值对象似乎会立即被释放。我的目标对象是SKNode,我的值对象是扩展NSObject的swift类。应该行吗?
2014年

实际上,即使刚创建对象并在方法中进一步使用该对象,实际上在objc_setAssociatedObject期间仍会调用deinit。
2014年

4

在Swift 3.0中进行更新例如,这是一个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)
        }
    }
}

2

Klaas仅为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

2

我写了一个需要Swift 5.1的现代助手。

/*
 AssociatedObject.swift

 Copyright © 2020 RFUI.
 https://github.com/BB9z/iOS-Project-Template

 The MIT License
 https://opensource.org/licenses/MIT
 */

import Foundation

/**
 Objective-C associated value wrapper.

 Usage

 ```
 private let fooAssociation = AssociatedObject<String>()
 extension SomeObject {
     var foo: String? {
         get { fooAssociation[self] }
         set { fooAssociation[self] = newValue }
     }
 }
 ```
 */
public final class AssociatedObject<T> {
    private let policy: objc_AssociationPolicy

    /// Creates an associated value wrapper.
    /// - Parameter policy: The policy for the association.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
        self.policy = policy
    }

    /// Accesses the associated value.
    /// - Parameter index: The source object for the association.
    public subscript(index: AnyObject) -> T? {
        get { objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as? T }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}

您可能会惊讶于它甚至免费支持Swift结构。

Swift结构关联


0

只需添加#import <objc/runtime.h>您的桥接头文件,以快速代码访问objc_setAssociatedObject


18
或者您也可以import ObjectiveC使用Swift
newacct 2014年

0

上面的朋友回答了您的问题,但是如果它与闭包属性有关,请注意:

```

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
}

```

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.