Search code examples
iosswiftassociated-object

How to have stored properties in Swift, the same way I had on Objective-C?


I am switching an application from Objective-C to Swift, which I have a couple of categories with stored properties, for example:

@interface UIView (MyCategory)

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

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

@end

As Swift extensions don't accept stored properties like these, I don't know how to maintain the same structure as the Objc code. Stored properties are really important for my app and I believe Apple must have created some solution for doing it in Swift.

As said by jou, what I was looking for was actually using associated objects, so I did (in another context):

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

But I get an EXC_BAD_ACCESS when doing:

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

Any ideas?


Solution

  • Associated objects API is a bit cumbersome to use. You can remove most of the boilerplate with a helper class.

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

    Provided that you can "add" a property to objective-c class in a more readable manner:

    extension SomeType {
    
        private static let association = ObjectAssociation<NSObject>()
    
        var simulatedProperty: NSObject? {
    
            get { return SomeType.association[self] }
            set { SomeType.association[self] = newValue }
        }
    }
    

    As for the solution:

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