Ios 如何在Swift中存储属性,与我在Objective-C中的存储方式相同?

Ios 如何在Swift中存储属性,与我在Objective-C中的存储方式相同?,ios,swift,associated-object,Ios,Swift,Associated Object,我正在将一个应用程序从Objective-C切换到Swift,我有几个类别,其中包含存储属性,例如: @interface UIView (MyCategory) - (void)alignToView:(UIView *)view alignment:(UIViewRelativeAlignment)alignment; - (UIView *)clone; @property (strong) PFObject *xo; @property (nonatomic) B

我正在将一个应用程序从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实现这一点创造了一些解决方案

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

但在执行以下操作时,我会获得EXC\u BAD\u访问权限:

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

有什么想法吗?

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

对于Obj-c类别,您只能添加方法,不能添加实例变量

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


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

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

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

}
    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
适用于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))
        }
    }
}
关联键是一个指针,它对于每个关联都应该是唯一的。为此,我们创建一个私有全局变量,并使用
&
运算符将其内存地址用作键。有关如何在Swift中处理指针的更多详细信息,请参见

为Swift 2和Swift 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更新

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

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


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
    }
}
这确实是一个非常好的解决方案,我想添加另一个使用示例,其中包括非可选的结构和值。此外,还可以简化关联的键值

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传统。因此,我编写了具有两个优点和两个缺点的纯Swift解决方案

优势:

  • 纯Swift码

  • 用于类和补全,或更具体地用于
    任何
    对象

  • 缺点:

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

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

  • 编辑:
    因此,我认为我找到了一种比上述方法更简洁的方法,因为它不需要任何全局变量。我从这里得到的:

    要点是使用如下结构:

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

    我的薪水是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)
                }
            }
        }
    }
    

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

    我还遇到了EXC\u BAD\u访问问题。
    objc\u getAssociatedObject()
    objc\u setAssociatedObject()
    中的值应该是一个对象。
    objc\u AssociationPolicy
    应该与对象匹配。

    我尝试使用这里的一些答案中提到的objc\u setassociated对象,但在失败了几次之后,我退了一步,意识到我没有理由需要它。借用这里的一些想法,我提出了这段代码,它简单地存储了一个数组,其中包含我的额外数据(在本例中为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创建扩展,以验证文本输入字符的长度。 下面的代码对我来说很好。希望这能帮助别人

    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)
        }
    }
    
    private var\u min:Int?
    私有变量max:Int?
    扩展UITextField{
    @IBInspectable变量minLength:Int{
    得到{
    返回最小值??0
    }
    设置{
    _最小值=新值
    }
    }
    @IBInspectable变量maxLength:Int{
    得到{
    返回最大值为1000
    }
    设置{
    _最大值=新值
    }
    }
    func validation()->(有效:Bool,错误:String){
    var-valid:Bool=true
    变量错误:String=“”
    guard let text=self.text else{return(true,“”)}
    如果text.characters.count<最小长度{
    有效=错误
    error=“Textfield应至少包含\(minLength)个字符”
    }
    如果text.characters.count>maxLength{
    有效=错误
    error=“Textfield不应包含超过\(maxLength)个字符”
    }
    if(text.characters.countmaxLength){
    有效=错误
    error=“Textfield应至少包含\(minLength)个字符\n”
    error=“Textfield不应包含超过\(maxLength)个字符”
    }
    返回(有效,错误)
    }
    }
    
    我发现这个解决方案更实用

    更新为Swift 3

    …然后在你的代码中

    class gameController: UIViewController {
    
        @IBOutlet var game: gameClass!
    
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.graySpace
    
        }
    }
    
    这是simplif
    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
    
        }
    }
    
    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)
        }
    }
    
    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)
            }
        }
    
    }
    
        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
    
    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) }
        }
    }
    
    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 }
        }
    }
    
    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 )
    
    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
                    )
                }
            }
        }
    
    }
    
    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
            }
        }
    }
    
    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")
    
    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 }
        }
    
    }
    
    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
    
    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
            }
        }
    }