【问题标题】:How to have stored properties in Swift, the same way I had on Objective-C?如何像在 Objective-C 上一样在 Swift 中存储属性?
【发布时间】:2014-10-15 02:43:06
【问题描述】:

我正在将一个应用程序从 Objective-C 切换到 Swift,我有几个具有存储属性的类别,例如:

@interface UIView (MyCategory)

- (void)alignToView:(UIView *)view
          alignment:(UIViewRelativeAlignment)alignment;
- (UIView *)clone;

@property (strong) PFObject *xo;
@property (nonatomic) BOOL isAnimating;

@end

由于 Swift 扩展不接受这样的存储属性,我不知道如何保持与 Objc 代码相同的结构。存储属性对我的应用程序非常重要,我相信 Apple 一定已经为在 Swift 中创建了一些解决方案。

正如jou所说,我正在寻找的实际上是使用关联对象,所以我做了(在另一个上下文中):

import Foundation
import QuartzCore
import ObjectiveC

extension CALayer {
    var shapeLayer: CAShapeLayer? {
        get {
            return objc_getAssociatedObject(self, "shapeLayer") as? CAShapeLayer
        }
        set(newValue) {
            objc_setAssociatedObject(self, "shapeLayer", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    var initialPath: CGPathRef! {
        get {
            return objc_getAssociatedObject(self, "initialPath") as CGPathRef
        }
        set {
            objc_setAssociatedObject(self, "initialPath", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }
}

但是我在做的时候得到一个 EXC_BAD_ACCESS:

class UIBubble : UIView {
    required init(coder aDecoder: NSCoder) {
        ...
        self.layer.shapeLayer = CAShapeLayer()
        ...
    }
}

有什么想法吗?

【问题讨论】:

  • Objective-C类类别也不能定义实例变量,你是怎么实现这些属性的?
  • 不知道为什么您的访问权限不正确,但您的代码不应该工作。您将不同的值传递给 setAssociateObject 和 getAssociatedObject。使用字符串“shapeLayer”作为键是错误的,指针(实际上是地址)才是键,而不是它指向的内容。位于不同地址的两个相同字符串是两个不同的键。查看 Jou 的回答并注意他如何将 xoAssociationKey 定义为全局变量,因此在设置/获取时它是相同的键/指针。

标签: ios swift associated-object


【解决方案1】:

注意:经过进一步分析,下面的代码可以正常工作,但不会释放视图对象,所以如果我能找到解决方法,我会编辑答案。同时,阅读 cmets。

如何将静态映射存储到像这样扩展的类:

extension UIView {
    
    struct Holder {
        static var _padding:[UIView:UIEdgeInsets] = [:]
    }
   
    var padding : UIEdgeInsets {
        get{ return UIView.Holder._padding[self] ?? .zero}
        set { UIView.Holder._padding[self] = newValue }
    }

}

【讨论】:

  • 为什么这个答案没有投票。毕竟是智能解决方案。
  • 这行得通,但我认为这种方法有缺陷,正如此处所述medium.com/@valv0/…
  • 投反对票! _padding在其key中保留传入的view对象后,该view什么时候释放?
【解决方案2】:

PURE SWIFT 中使用 WEAK 参考处理

import Foundation
import UIKit

extension CustomView {
    
    // can make private
    static let storedProperties = WeakDictionary<UIView, Properties>()
    
    struct Properties {
        var url: String = ""
        var status = false
        var desc: String { "url: \(url), status: \(status)" }
    }
    
    var properties: Properties {
        get {
            return CustomView.storedProperties.get(forKey: self) ?? Properties()
        }
        set {
            CustomView.storedProperties.set(forKey: self, object: newValue)
        }
    }
}

var view: CustomView? = CustomView()
print("1 print", view?.properties.desc ?? "nil")
view?.properties.url = "abc"
view?.properties.status = true
print("2 print", view?.properties.desc ?? "nil")
view = nil

WeakDictionary.swift

import Foundation

private class WeakHolder<T: AnyObject>: Hashable {
    weak var object: T?
    let hash: Int

    init(object: T) {
        self.object = object
        hash = ObjectIdentifier(object).hashValue
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(hash)
    }

    static func ==(lhs: WeakHolder, rhs: WeakHolder) -> Bool {
        return lhs.hash == rhs.hash
    }
}

class WeakDictionary<T1: AnyObject, T2> {
    private var dictionary = [WeakHolder<T1>: T2]()

    func set(forKey: T1, object: T2?) {
        dictionary[WeakHolder(object: forKey)] = object
    }

    func get(forKey: T1) -> T2? {
        let obj = dictionary[WeakHolder(object: forKey)]
        return obj
    }

    func forEach(_ handler: ((key: T1, value: T2)) -> Void) {
        dictionary.forEach {
            if let object = $0.key.object, let value = dictionary[$0.key] {
                handler((object, value))
            }
        }
    }
    
    func clean() {
        var removeList = [WeakHolder<T1>]()
        dictionary.forEach {
            if $0.key.object == nil {
                removeList.append($0.key)
            }
        }
        removeList.forEach {
            dictionary[$0] = nil
        }
    }
}

【讨论】:

【解决方案3】:

关联对象 API 使用起来有点麻烦。您可以使用帮助类删除大部分样板。

public final class ObjectAssociation<T: AnyObject> {

    private let policy: objc_AssociationPolicy

    /// - Parameter policy: An association policy that will be used when linking objects.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {

        self.policy = policy
    }

    /// Accesses associated object.
    /// - Parameter index: An object whose associated object is to be accessed.
    public subscript(index: AnyObject) -> T? {

        get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}

前提是您可以以更易读的方式将属性“添加”到objective-c 类:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

        get { return SomeType.association[self] }
        set { SomeType.association[self] = newValue }
    }
}

至于解决方法:

extension CALayer {

    private static let initialPathAssociation = ObjectAssociation<CGPath>()
    private static let shapeLayerAssociation = ObjectAssociation<CAShapeLayer>()

    var initialPath: CGPath! {
        get { return CALayer.initialPathAssociation[self] }
        set { CALayer.initialPathAssociation[self] = newValue }
    }

    var shapeLayer: CAShapeLayer? {
        get { return CALayer.shapeLayerAssociation[self] }
        set { CALayer.shapeLayerAssociation[self] = newValue }
    }
}

【讨论】:

  • 好的,如果我想存储 Int、Bool 等数据怎么办?
  • 无法通过对象关联直接存储 Swift 类型。你可以存储例如NSNumberNSValue 并编写额外的访问器对,它们将是您想要的类型(Int、Bool 等)。
  • WARNING 这对结构不起作用,因为objective-c运行时库只支持符合NSObjectProtocol的类
  • @CharltonProvatas 这个 API 不能使用结构体,它们不符合 AnyObject 协议。
  • @VyachaslavGerchicov [String]? 是一个结构体,这与 Int, Bool 的情况相同。您可以使用NSArray 来保存NSString 实例的集合。
【解决方案4】:

如果您希望为 UIView 设置自定义字符串属性,这就是我在 Swift 4 上的做法

创建一个 UIView 扩展

extension UIView {

    func setStringValue(value: String, key: String) {
        layer.setValue(value, forKey: key)
    }

    func stringValueFor(key: String) -> String? {
        return layer.value(forKey: key) as? String
    }
}

使用此扩展程序

let key = "COLOR"

let redView = UIView() 

// To set
redView.setStringAttribute(value: "Red", key: key)

// To read
print(redView.stringValueFor(key: key)) // Optional("Red")

【讨论】:

    【解决方案5】:

    与在 Objective-C 中一样,您不能将存储属性添加到现有类。如果你正在扩展一个 Objective-C 类(UIView 肯定是其中之一),你仍然可以使用 Associated Objects 来模拟存储的属性:

    适用于 Swift 1

    import ObjectiveC
    
    private var xoAssociationKey: UInt8 = 0
    
    extension UIView {
        var xo: PFObject! {
            get {
                return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject
            }
            set(newValue) {
                objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN))
            }
        }
    }
    

    关联键是一个指针,对于每个关联应该是唯一的。为此,我们创建了一个私有全局变量,并使用它的内存地址作为 &amp; 运算符的键。有关如何在 Swift 中处理指针的更多详细信息,请参阅 Using Swift with Cocoa and Objective-C

    已针对 Swift 2 和 3 更新

    import ObjectiveC
    
    private var xoAssociationKey: UInt8 = 0
    
    extension UIView {
        var xo: PFObject! {
            get {
                return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject
            }
            set(newValue) {
                objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            }
        }
    }
    

    为 Swift 4 更新

    在 Swift 4 中,它要简单得多。 Holder 结构将包含我们的计算属性将向世界公开的私有值,从而产生存储属性行为的错觉。

    Source

    extension UIViewController {
        struct Holder {
            static var _myComputedProperty:Bool = false
        }
        var myComputedProperty:Bool {
            get {
                return Holder._myComputedProperty
            }
            set(newValue) {
                Holder._myComputedProperty = newValue
            }
        }
    }
    

    【讨论】:

    • @Yar 不知道objc_AssociationPolicy,谢谢!我已经更新了答案
    • 在 Swift2 中你必须使用 objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
    • @SimplGy 请将您的编辑添加为附加答案,而不是将您的代码编辑到其他人的答案中。
    • Swift4 解决方案如果您想拥有超过 1 个使用扩展中的属性的 UIViewController 实例,则该解决方案不起作用。请检查源中的更新。
    • Swift 4 示例不适用于多个实例,可能不应该在这里。
    【解决方案6】:

    为什么要依赖 objc 运行时?我不明白这一点。通过使用类似以下的内容,您将实现与存储属性几乎相同的行为,仅使用pure Swift approach

    extension UIViewController {
        private static var _myComputedProperty = [String:Bool]()
    
        var myComputedProperty:Bool {
            get {
                let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
                return UIViewController._myComputedProperty[tmpAddress] ?? false
            }
            set(newValue) {
                let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
                UIViewController._myComputedProperty[tmpAddress] = newValue
            }
        }
    }
    

    【讨论】:

    • 聪明!这种方法的一个可能缺点是内存管理。宿主对象被释放后,它的属性仍将存在于字典中,如果与大量对象一起使用,可能会在内存使用方面变得昂贵。
    • 可能我们可以保存一个静态的包装器列表变量,其中包含对对象(自身)的弱引用。并从 _myComputedProperty 字典中删除条目以及 Wrapper 的 didSet 方法中的静态列表变量(当对象被重新分配时,我们的 Wrapper 类中的 didSet 被调用,因为我们的弱引用被设置为 nil 的新值)。
    【解决方案7】:

    另一个为 Swift 3Swift 4

    使用 Objective-C 关联对象和计算属性的示例
    import CoreLocation
    
    extension CLLocation {
    
        private struct AssociatedKeys {
            static var originAddress = "originAddress"
            static var destinationAddress = "destinationAddress"
        }
    
        var originAddress: String? {
            get {
                return objc_getAssociatedObject(self, &AssociatedKeys.originAddress) as? String
            }
            set {
                if let newValue = newValue {
                    objc_setAssociatedObject(
                        self,
                        &AssociatedKeys.originAddress,
                        newValue as NSString?,
                        .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                    )
                }
            }
        }
    
        var destinationAddress: String? {
            get {
                return objc_getAssociatedObject(self, &AssociatedKeys.destinationAddress) as? String
            }
            set {
                if let newValue = newValue {
                    objc_setAssociatedObject(
                        self,
                        &AssociatedKeys.destinationAddress,
                        newValue as NSString?,
                        .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                    )
                }
            }
        }
    
    }
    

    【讨论】:

      【解决方案8】:

      这是一个同样有效的替代方法

      public final class Storage : AnyObject {
      
          var object:Any?
      
          public init(_ object:Any) {
              self.object = object
          }
      }
      
      extension Date {
      
          private static let associationMap = NSMapTable<NSString, AnyObject>()
          private struct Keys {
              static var Locale:NSString = "locale"
          }
      
          public var locale:Locale? {
              get {
      
                  if let storage = Date.associationMap.object(forKey: Keys.Locale) {
                      return (storage as! Storage).object as? Locale
                  }
                  return nil
              }
              set {
                  if newValue != nil {
                      Date.associationMap.setObject(Storage(newValue), forKey: Keys.Locale)
                  }
              }
          }
      }
      
      
      
      var date = Date()
      date.locale = Locale(identifier: "pt_BR")
      print( date.locale )
      

      【讨论】:

        【解决方案9】:

        这里是简化且更具表现力的解决方案。它适用于值和引用类型。提升的方法取自@HepaKKes的回答。

        关联代码:

        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 associated<T>(to base: AnyObject,
                        key: UnsafePointer<UInt8>,
                        policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN,
                        initialiser: () -> T) -> T {
            if let v = objc_getAssociatedObject(base, key) as? T {
                return v
            }
        
            if let v = objc_getAssociatedObject(base, key) as? Lifted<T> {
                return v.value
            }
        
            let lifted = Lifted(initialiser())
            objc_setAssociatedObject(base, key, lifted, policy)
            return lifted.value
        }
        
        func associate<T>(to base: AnyObject, key: UnsafePointer<UInt8>, value: T, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN) {
            if let v: AnyObject = value as AnyObject? {
                objc_setAssociatedObject(base, key, v, policy)
            }
            else {
                objc_setAssociatedObject(base, key, lift(value), policy)
            }
        }
        

        使用示例:

        1) 创建扩展并将属性与其关联。让我们同时使用值和引用类型属性。

        extension UIButton {
        
            struct Keys {
                static fileprivate var color: UInt8 = 0
                static fileprivate var index: UInt8 = 0
            }
        
            var color: UIColor {
                get {
                    return associated(to: self, key: &Keys.color) { .green }
                }
                set {
                    associate(to: self, key: &Keys.color, value: newValue)
                }
            }
        
            var index: Int {
                get {
                    return associated(to: self, key: &Keys.index) { -1 }
                }
                set {
                    associate(to: self, key: &Keys.index, value: newValue)
                }
            }
        
        }
        

        2) 现在您可以像常规属性一样使用:

            let button = UIButton()
            print(button.color) // UIExtendedSRGBColorSpace 0 1 0 1 == green
            button.color = .black
            print(button.color) // UIExtendedGrayColorSpace 0 1 == black
        
            print(button.index) // -1
            button.index = 3
            print(button.index) // 3
        

        更多细节:

        1. 包装值类型需要提升。
        2. 默认的关联对象行为是保留。如果您想了解有关关联对象的更多信息,我建议您查看this article

        【讨论】:

          【解决方案10】:

          我发现这个解决方案更实用

          为 Swift 3 更新

          extension UIColor {
          
              static let graySpace = UIColor.init(red: 50/255, green: 50/255, blue: 50/255, alpha: 1.0)
              static let redBlood = UIColor.init(red: 102/255, green: 0/255, blue: 0/255, alpha: 1.0)
              static let redOrange = UIColor.init(red: 204/255, green: 17/255, blue: 0/255, alpha: 1.0)
          
              func alpha(value : CGFloat) -> UIColor {
                  var r = CGFloat(0), g = CGFloat(0), b = CGFloat(0), a = CGFloat(0)
                  self.getRed(&r, green: &g, blue: &b, alpha: &a)
                  return UIColor(red: r, green: g, blue: b, alpha: value)
              }
          
          }
          

          ...然后在你的代码中

          class gameController: UIViewController {
          
              @IBOutlet var game: gameClass!
          
              override func viewDidLoad() {
                  self.view.backgroundColor = UIColor.graySpace
          
              }
          }
          

          【讨论】:

          • 这是正确的做法 - 但不是真正的扩展中的存储属性,就像问题所问的那样。
          【解决方案11】:

          我更喜欢用纯 Swift 编写代码,而不是依赖于 Objective-C 的传统。因此,我编写了具有两个优点和两个缺点的纯 Swift 解决方案。

          优点:

          1. 纯 Swift 代码

          2. 适用于类和完成,或更具体地适用于 Any 对象

          缺点:

          1. 代码应该调用方法willDeinit()来释放链接到特定类实例的对象以避免内存泄漏

          2. 对于这个确切的示例,您不能直接扩展 UIView,因为 var frame 是 UIView 的扩展,而不是类的一部分。

          编辑:

          import UIKit
          
          var extensionPropertyStorage: [NSObject: [String: Any]] = [:]
          
          var didSetFrame_ = "didSetFrame"
          
          extension UILabel {
          
              override public var frame: CGRect {
          
                  get {
                      return didSetFrame ?? CGRectNull
                  }
          
                  set {
                      didSetFrame = newValue
                  }
              }
          
              var didSetFrame: CGRect? {
          
                  get {
                      return extensionPropertyStorage[self]?[didSetFrame_] as? CGRect
                  }
          
                  set {
                      var selfDictionary = extensionPropertyStorage[self] ?? [String: Any]()
          
                      selfDictionary[didSetFrame_] = newValue
          
                      extensionPropertyStorage[self] = selfDictionary
                  }
              }
          
              func willDeinit() {
                  extensionPropertyStorage[self] = nil
              }
          }
          

          【讨论】:

          • 这不起作用,因为 extensionPropertyStorage 在所有实例之间共享。如果您为一个实例设置一个值,那么您就是在为所有实例设置该值。
          • 这不是好事,因为搞砸了功能。现在它更好了,可以按预期工作。
          • @picciano extensionPropertyStorage 按设计与所有实例共享。全局变量(字典)首先取消引用 UILabel 实例(NSObject),然后是其属性([String: Any])。
          • @picciano 我猜这适用于class 属性;)
          • frame 是一个实例属性。类属性从何而来?
          【解决方案12】:

          我尝试使用 objc_getAssociatedObject、objc_setAssociatedObject 来存储属性,但没有任何运气。我的目标是为 UITextField 创建扩展,以验证文本输入字符的长度。 以下代码对我来说很好。希望这会对某人有所帮助。

          private var _min: Int?
          private var _max: Int?
          
          extension UITextField {    
              @IBInspectable var minLength: Int {
                  get {
                      return _min ?? 0
                  }
                  set {
                      _min = newValue
                  }
              }
          
              @IBInspectable var maxLength: Int {
                  get {
                      return _max ?? 1000
                  }
                  set {
                      _max = newValue
                  }
              }
          
              func validation() -> (valid: Bool, error: String) {
                  var valid: Bool = true
                  var error: String = ""
                  guard let text = self.text else { return (true, "") }
          
                  if text.characters.count < minLength {
                      valid = false
                      error = "Textfield should contain at least \(minLength) characters"
                  }
          
                  if text.characters.count > maxLength {
                      valid = false
                      error = "Textfield should not contain more then \(maxLength) characters"
                  }
          
                  if (text.characters.count < minLength) && (text.characters.count > maxLength) {
                      valid = false
                      error = "Textfield should contain at least \(minLength) characters\n"
                      error = "Textfield should not contain more then \(maxLength) characters"
                  }
          
                  return (valid, error)
              }
          }
          

          【讨论】:

          • 全局私有变量?您是否意识到_min_max 是全局的并且在UITextField 的所有实例中都相同?即使它对你有用,这个答案也无关紧要,因为 Marcos 询问 instance 变量。
          • 是的,你说得对,这不适用于所有实例。我通过在结构中存储最小值和最大值来修复。抱歉跑题了。
          【解决方案13】:

          我尝试使用这里的一些答案中提到的 objc_setAssociatedObject,但是在几次失败后我退后一步,意识到我没有理由需要它。借用这里的一些想法,我想出了这段代码,它简单地存储了一个数组,其中包含我想要关联的对象索引的任何我的额外数据(在本例中为 MyClass):

          class MyClass {
              var a = 1
              init(a: Int)
              {
                  self.a = a
              }
          }
          
          extension UIView
          {
              static var extraData = [UIView: MyClass]()
          
              var myClassData: MyClass? {
                  get {
                      return UIView.extraData[self]
                  }
                  set(value) {
                      UIView.extraData[self] = value
                  }
              }
          }
          
          // Test Code: (Ran in a Swift Playground)
          var view1 = UIView()
          var view2 = UIView()
          
          view1.myClassData = MyClass(a: 1)
          view2.myClassData = MyClass(a: 2)
          print(view1.myClassData?.a)
          print(view2.myClassData?.a)
          

          【讨论】:

          • 您知道如何自动清除 extraData 以免导致内存泄漏吗?
          • 我实际上并没有在视图中使用它,在我的情况下,我只在我使用的类中实例化有限数量的对象,所以我没有真正考虑过内存泄漏。我想不出一种自动的方法来做到这一点,但我想在上面的设置器中,如果 value == nil,您可以添加逻辑来删除元素,然后在您不再需要该对象时将值设置为 nil,或者也许在 didReceiveMemoryWarning 之类的。
          • Tnx @Dan,我使用 viewWillDisapper 将值设置为 nil 并且工作正常,现在调用 deinit 并且一切正常。 Tnx 用于发布此解决方案
          • 不要尝试覆盖extension中的父方法。
          【解决方案14】:

          您不能使用新存储定义类别(Swift 扩展);任何附加属性都必须计算而不是存储。该语法适用于 Objective C,因为类别中的 @property 本质上意味着“我将提供 getter 和 setter”。在 Swift 中,您需要自己定义这些以获得计算属性;类似:

          extension String {
              public var Foo : String {
                  get
                  {
                      return "Foo"
                  }
          
                  set
                  {
                      // What do you want to do here?
                  }
              }
          }
          

          应该可以正常工作。请记住,您不能在 setter 中存储新值,只能使用现有的可用类状态。

          【讨论】:

            【解决方案15】:

            我也遇到了 EXC_BAD_ACCESS 问题。objc_getAssociatedObject()objc_setAssociatedObject() 中的值应该是一个对象。 objc_AssociationPolicy 应该与 Object 匹配。

            【讨论】:

            • 这并不能真正回答问题。如果您有其他问题,可以点击 进行提问。一旦你有足够的reputation,你也可以add a bounty 来引起对这个问题的更多关注。 - From Review
            • @AtheistP3ace 对此我感到非常抱歉。我尝试对问题添加评论。但我没有足够的声誉。所以我试着回答这个问题。
            【解决方案16】:

            jou指出的解决方案不支持值类型, 这也适用于他们

            包装器

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

            这真是一个很棒的解决方案,我想添加另一个使用示例,其中包括非可选的结构和值。此外,还可以简化 AssociatedKey 值。

            struct Crate {
                var name: String
            }
            
            class Box {
                var name: String
            
                init(name: String) {
                    self.name = name
                }
            }
            
            extension UIViewController {
            
                private struct AssociatedKey {
                    static var displayed:   UInt8 = 0
                    static var box:         UInt8 = 0
                    static var crate:       UInt8 = 0
                }
            
                var displayed: Bool? {
                    get {
                        return getAssociatedObject(self, associativeKey: &AssociatedKey.displayed)
                    }
            
                    set {
                        if let value = newValue {
                            setAssociatedObject(self, value: value, associativeKey: &AssociatedKey.displayed, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                        }
                    }
                }
            
                var box: Box {
                    get {
                        if let result:Box = getAssociatedObject(self, associativeKey: &AssociatedKey.box) {
                            return result
                        } else {
                            let result = Box(name: "")
                            self.box = result
                            return result
                        }
                    }
            
                    set {
                        setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.box, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                    }
                }
            
                var crate: Crate {
                    get {
                        if let result:Crate = getAssociatedObject(self, associativeKey: &AssociatedKey.crate) {
                            return result
                        } else {
                            let result = Crate(name: "")
                            self.crate = result
                            return result
                        }
                    }
            
                    set {
                        setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.crate, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                    }
                }
            }
            

            【讨论】:

            • 我该如何使用 Lifted 类?
            • 你为什么需要它?干什么用的?
            • 我的意思是你一般如何使用它,谢谢你的帮助:) 你能添加一个“如何使用”的例子吗?
            • “如何使用”示例将在“可能的类扩展”标题下方开始。我认为用户不需要知道如何使用lift 方法和Lifted 类,但他们应该使用getAssociatedObjectsetAssociatedObject 函数。为了清楚起见,我将在标题旁边的括号之间添加“使用示例”。
            • 这当然是最好的解决方案,可惜没有很好地解释。它适用于类、结构和其他值类型。这些属性也不必是可选的。干得好。
            【解决方案17】:

            所以我认为我找到了一种比上述方法更干净的方法,因为它不需要任何全局变量。我从这里得到它: http://nshipster.com/swift-objc-runtime/

            要点是你使用这样的结构:

            extension UIViewController {
                private struct AssociatedKeys {
                    static var DescriptiveName = "nsh_DescriptiveName"
                }
            
                var descriptiveName: String? {
                    get {
                        return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
                    }
                    set {
                        if let newValue = newValue {
                            objc_setAssociatedObject(
                                self,
                                &AssociatedKeys.DescriptiveName,
                                newValue as NSString?,
                                UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                            )
                        }
                    }
                }
            }
            

            Swift 2 更新

            private struct AssociatedKeys {
                static var displayed = "displayed"
            }
            
            //this lets us check to see if the item is supposed to be displayed or not
            var displayed : Bool {
                get {
                    guard let number = objc_getAssociatedObject(self, &AssociatedKeys.displayed) as? NSNumber else {
                        return true
                    }
                    return number.boolValue
                }
            
                set(value) {
                    objc_setAssociatedObject(self,&AssociatedKeys.displayed,NSNumber(bool: value),objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                }
            }
            

            【讨论】:

            • @AKWeblS 我已经实现了像你这样的代码,但是当更新到 swift 2.0 时,我收到错误无法使用类型为 'objc_AssociationPolicy)' 的参数列表调用类型为 'UInt' 的初始化程序。下一条评论中的代码
            • 如何更新 swift 2.0? var postDescription: 字符串? { 获取 { 返回 objc_getAssociatedObject(self, &AssociatedKeys.postDescription) 作为? String } set { if let newValue = newValue { objc_setAssociatedObject( self, &AssociatedKeys.postDescription, newValue as NSString?, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) ) } } }
            • 不起作用:static var 表示所有实例的属性值都相同
            • @fry - 它继续为我工作。是的,键是静态的,但它与特定对象相关联,对象本身不是全局的。
            • 这太性感了!非常感谢!
            【解决方案18】:

            我的 0.02 美元。这段代码是用 Swift 2.0

            编写的
            extension CALayer {
                private struct AssociatedKeys {
                    static var shapeLayer:CAShapeLayer?
                }
            
                var shapeLayer: CAShapeLayer? {
                    get {
                        return objc_getAssociatedObject(self, &AssociatedKeys.shapeLayer) as? CAShapeLayer
                    }
                    set {
                        if let newValue = newValue {
                            objc_setAssociatedObject(self, &AssociatedKeys.shapeLayer, newValue as CAShapeLayer?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                        }
                    }
                }
            }
            

            我尝试了很多解决方案,发现这是使用额外变量参数实际扩展类的唯一方法。

            【讨论】:

            • 看起来很有趣,但拒绝不适用于协议,type 'AssociatedKeys' cannot be defined within a protocol extension...
            【解决方案19】:

            使用 Obj-c 类别,您只能添加方法,不能添加实例变量。

            在您的示例中,您使用 @property 作为添加 getter 和 setter 方法声明的快捷方式。您仍然需要实现这些方法。

            与 Swift 类似,您可以添加使用扩展来添加实例方法、计算属性等,但不能添加存储属性。

            【讨论】:

              猜你喜欢
              • 2011-05-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多