1

Well, this possibly a duplicated question.
I've found some questions like this one: Is there a way to set associated objects in Swift?

However, I want to add an Int property into swift's extension and these answers in the link above doesn't work.

Here's my code:

import ObjectiveC
var xoAssociationKey: UInt8 = 0

extension NSData {

    var position: Int {
        get {
            return objc_getAssociatedObject(self, &xoAssociationKey) as Int
        }
        set {
            objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
        }
    }

    override convenience init() {
        self.init()
        position = 0
    }
}

And I get fatal error: unexpectedly found nil while unwrapping an Optional value everytime I access position

FYI, I did find a solution for this error in Objective C and I'm looking for a swift solution. Here's my code in objective C if you interested:

static char PROPERTY_KEY;

@implementation NSData (Extension)
@dynamic position;
- (NSInteger)position {
    return  [objc_getAssociatedObject(self, &PROPERTY_KEY) integerValue];
}
- (void)setPosition:(NSInteger)position {
    // Must convert to an object for this trick to work
    objc_setAssociatedObject(self, &PROPERTY_KEY, @(position), OBJC_ASSOCIATION_COPY);
}

- (instancetype)init {
    self = [super init];
    if (self) {
        self.position = 0;
    }
    return self;
}
Community
  • 1
  • 1
Pham Hoan
  • 2,107
  • 2
  • 20
  • 34

1 Answers1

5

NSData is part of a class cluster, so your custom init method is not necessarily called, e.g.

let d = NSMutableData()

does not use your init method. The next problem is that your init method calls itself recursively, therefore

let d = NSData()

crashes with a stack overflow. Note also that the Objective-C code relies on undefined behaviour, because it replaces a method in a class extension.

So better remove your custom initialization, and change the getter to return a default value if the associated object has not been set. This can easily be achieved with an optional cast (as? Int) and the nil-coalescing operator (??):

extension NSData {

    var position: Int {
        get {
            return objc_getAssociatedObject(self, &xoAssociationKey) as? Int ?? 0
        }
        set {
            objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
        }
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382