如何在Swift中存储属性,就像Objective-C一样?

我将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代码相同的结构。 存储的属性对我的应用程序非常重要,我相信苹果公司必须在Swift中创build一些解决scheme。

正如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() ... } } 

有任何想法吗?

关联的对象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 } } } 

和Objective-C一样,您不能将存储的属性添加到现有的类中。 如果你扩展一个Objective-C类( UIView肯定是一个),你仍然可以使用关联对象来模拟存储的属性:

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

关联关键字是一个应该是每个关联唯一的指针。 为此,我们创build一个私有的全局variables,并用&运算符的内存地址作为关键字。 有关如何在Swift中处理指针的更多细节,请参阅使用Cocoa和Objective-C的 Swift。

更新了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) } } } 

所以我想我find了一个比上面的更干净的方法,因为它不需要任何全局variables。 我从这里得到它: 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) } } 

jou指出的解决scheme不支持值types ,这也适用于他们

包装

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

这真是一个很好的解决scheme,我想添加另一个使用示例,其中包含非可选的结构和值。 而且,关联密钥值可以被简化。

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

你不能用新的存储来定义类别(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中存储新的值,只能使用现有的可用类的状态。

我的$ 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) } } } } 

我已经尝试了许多解决scheme,并发现这是实际扩展一个具有额外variables参数的类的唯一方法。

我更喜欢在纯Swift中执行代码,而不依赖于Objective-C的遗产。 正因为如此,我写了纯粹的Swift解决scheme,有两个优点和两个缺点。

优点:

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

使用Obj-c类别,您只能添加方法,而不是实例variables。

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

同样在Swift中,您可以添加使用扩展来添加实例方法,计算属性等,但不能存储属性。

我也得到一个EXC_BAD_ACCESS问题objc_getAssociatedObject()objc_setAssociatedObject()应该是一个Object。 而objc_AssociationPolicy应该匹配对象。

这里是简化和更具performance力的解决scheme。 它适用于值types和引用types。 解除的方法取自@ 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)创build扩展和关联属性。 让我们使用值和引用types属性。

 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. 包装值types需要提升。
  2. 默认的关联对象行为是保留。 如果你想了解更多的关联对象,我build议检查这篇文章 。

我尝试使用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) 

我试图通过使用objc_getAssociatedObject,objc_setAssociatedObject存储属性,没有任何运气。 我的目标是为UITextField创build扩展,以validation文本input字符的长度。 以下代码适合我。 希望这会帮助别人。

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

这也是一个可行的scheme

 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 ) 

另一个使用Objective-C关联对象和Swift 3Swift 4的计算属性的例子

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

我发现这个解决scheme更实用

更新了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 } } 
Interesting Posts