【问题标题】:Is there a way to set associated objects in Swift?有没有办法在 Swift 中设置关联对象?
【发布时间】:2023-03-24 23:15:01
【问题描述】:

来自 Objective-C,您可以在 2 个对象之间调用函数 objc_setAssociatedObject 以让它们维护一个引用,如果在运行时您不希望一个对象被销毁,直到它的引用也被删除,这会很方便。 Swift 有没有类似的东西?

【问题讨论】:

标签: swift objective-c associated-object


【解决方案1】:

这是一个源自jckarter's answer的简单但完整的示例。

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

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

【讨论】:

  • 您是否能够以这种方式设置符合 typealias 的函数?
  • @PhamHoan Int 是 Swift 原生类型。尝试改用 NSNumber。
  • @PhamHoan 我添加了一个获取未初始化对象的解决方案。
  • @Jawwad 我猜,这并没有什么区别。 UInt8 来自链接的原始线程。
  • 嘿@Jacky - 是什么让你说它应该是 COPY .. 请告诉我们!
【解决方案2】:

该解决方案还支持所有值类型,而不仅仅是那些自动桥接的,例如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
    }
}

一个可能的 类扩展(使用示例)

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 存储在扩展中。我应该使用哪些标志?
  • 另外,我正在尝试使用此方法存储 [AnyObject] 数组,但 getter 总是返回 nil。不知何故, get 的 else if 没有正确返回值。
  • 您决定使用哪种关联策略取决于您,这取决于您的程序。欲了解更多信息,我建议您查看这篇文章,nshipster.com/associated-objects,out。我试图存储数组,它似乎工作得很好。顺便问一下,您使用的是哪个 Swift 版本?
  • 这太棒了。对于 Swift 2.3,我最终将泛型从 Lift 中移出并改用 Any。我想我会写一篇关于这个的博客文章
  • 这在 Swift 3 中变得容易多了。但无论如何,这里是我的文章,基于你在这里的回答:yar2050.com/2016/11/associated-object-support-for-swift-23.html
【解决方案3】:

我写了一个现代包装器,可在https://github.com/b9swift/AssociatedObject找到

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

【讨论】:

    【解决方案4】:

    显然,这只适用于 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)
    }
    

    【讨论】:

    • 我已经尝试过这种方法,但是当 swift 代码中没有其他对它的引用时,我的值对象似乎立即被释放。我的目标对象是一个 SKNode,我的值对象是一个扩展 NSObject 的 swift 类。这应该有效吗?
    • 实际上在 objc_setAssociatedObject 期间似乎调用了 deinit,即使该对象刚刚创建并在方法中进一步使用。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      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
      

      【讨论】:

        【解决方案7】:

        只需在 brinding 头文件中添加 #import &lt;objc/runtime.h&gt; 即可在 swift 代码下访问 objc_setAssociatedObject

        【讨论】:

        • 或者你可以在 Swift 中 import ObjectiveC
        【解决方案8】:

        上面的朋友已经回答了你的问题,但是如果和闭包属性有关,请注意:

        ```

        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
        }
        

        ```

        【讨论】:

          猜你喜欢
          • 2017-11-04
          • 1970-01-01
          • 2015-03-08
          • 1970-01-01
          • 2015-02-22
          • 1970-01-01
          • 1970-01-01
          • 2011-05-05
          相关资源
          最近更新 更多