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

Mar*_*rte 129 ios associated-object swift

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

@interface UIView (MyCategory)

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

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

@end
Run Code Online (Sandbox Code Playgroud)

由于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))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是在执行以下操作时我得到了EXC_BAD_ACCESS:

class UIBubble : UIView {
    required init(coder aDecoder: NSCoder) {
        ...
        self.layer.shapeLayer = CAShapeLayer()
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

jou*_*jou 186

与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))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

关联键是一个指针,应该是每个关联的唯一指针.为此,我们创建一个私有全局变量,并使用它的内存地址作为&运算符的键.有关如何在Swift中处理指针的详细信息,请参阅使用Swift with Cocoa和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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新为Swift 4

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

资源

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

  • 如果您想要有超过1个UIViewController实例使用扩展属性,Swift4解决方案不起作用.请检查源代码中的更新. (7认同)
  • @Yar不知道'objc_AssociationPolicy`,谢谢!我已经更新了答案 (2认同)
  • 在Swift2中你必须使用objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN (2认同)
  • Swift 4示例不适用于多个实例,因此可能不应该在此处。 (2认同)

Woj*_*zki 47

关联对象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) }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

        get { return SomeType.association[self] }
        set { SomeType.association[self] = newValue }
    }
}
Run Code Online (Sandbox Code Playgroud)

至于解决方案:

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 }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @CharltonProvatas不能使用带有此API的结构,它们不符合AnyObject协议. (2认同)

Ale*_*exK 35

所以我认为我找到了一种比上面的方法更干净的方法,因为它不需要任何全局变量.我从这里得到它: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)
                )
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)


Hep*_*Kes 14

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这真是一个非常好的解决方案,我想添加另一个使用示例,其中包含非选项的结构和值.此外,可以简化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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这当然是最好的解决方案,可惜它没有得到很好的解释。它适用于类、结构和其他值类型。这些属性也不必是可选的。干得好。 (2认同)

Ada*_*ght 13

您无法使用新存储定义类别(Swift扩展); 必须计算任何其他属性而不是存储.该语法适用于Objective C,因为@property在一个类别中,本质上意味着"我将提供getter和setter".在Swift中,您需要自己定义这些以获取计算属性; 就像是:

extension String {
    public var Foo : String {
        get
        {
            return "Foo"
        }

        set
        {
            // What do you want to do here?
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Amr*_*fie 7

我的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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


ved*_*ano 5

我更喜欢在纯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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这不起作用,因为extensionPropertyStorage在所有实例之间共享.如果为一个实例设置值,则为所有实例设置值. (4认同)

val*_*ine 5

为什么要依赖objc运行时?我不明白这一点.通过使用类似下面的内容,您将通过仅使用纯Swift方法实现几乎相同的存储属性行为:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 聪明的!这种方法的一个可能的缺点是内存管理。主机对象被释放后,它的属性仍将存在于字典中,如果与大量对象一起使用,则可能会导致内存使用成本高昂。 (2认同)