1

I'm trying to add properties to a extension to Timer using associated objects, but the function objc_setAssociatedObject always returns nil.

Timer is a NSObject so this should work.

Code:

extension Timer {
    private struct AssociatedKeys {
        static var counterAddress = "counter_address"
    }

    public var repeatCounter: Int {
        get {
            return objc_getAssociatedObject(self, AssociatedKeys.counterAddress) as? Int ?? 0
        }
        set {
            objc_setAssociatedObject(self,
                                     AssociatedKeys.counterAddress,
                                     newValue,
                                     .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

Any ideas why is this not working?


more code:

@nonobjc public class func new(every interval: TimeInterval, _ block: @escaping (Timer) -> Void) -> Timer {
    var timer: Timer!
    timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0) { _ in
        print("timer: \(timer.numberOfRepeats), : \(timer.repeatCounter), : \(timer)")
        if timer.numberOfRepeats > 0 {

            if timer.repeatCounter > timer.numberOfRepeats {

                timer.invalidate()
                return
            } else {

                timer.repeatCounter += 1
            }
        }

        block(timer)
    }
    return timer
}

So some of the problem was that I wasn't using the same timer object. There are still some issues:

  1. static let repeatCounterAddress = "repeat_counter_address" sometimes is not initialised and has an empty string value.

  2. Sometimes I get the same associated value and sometime not.

clemens
  • 16,716
  • 11
  • 50
  • 65
ilan
  • 4,402
  • 6
  • 40
  • 76
  • I think i am, im using the timer in a CFRunLoopTimerCreateWithHandler closure, updated question with more code – ilan Feb 11 '18 at 11:24
  • This is a fork of https://github.com/radex/SwiftyTimer – ilan Feb 11 '18 at 11:27
  • the short answer is there is no guarantee that it will work. Better to use `-[NSTimer userInfo]` property. – Bogdan Aug 30 '21 at 08:46

1 Answers1

10

Try the following (note the ampersands):

extension Timer {
    private struct AssociatedKeys {
        static var counterAddress = "counter_address"
    }

    public var repeatCounter: Int {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.counterAddress) as? Int ?? 0
        }
        set {
            objc_setAssociatedObject(self,
                                     &AssociatedKeys.counterAddress,
                                     newValue,
                                     .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

which always ensures that the same address is given as value for the key parameter. If you use just a string value, its raw value may be different for each call of objc_getAssociatedObject or objc_setAssociatedObject.

You can check this with the following code:

func test(_ a: UnsafeRawPointer) {
    print("\(a)")
}

let a = "abc"

test(a)
test(a)
test(a)
test(a)

This prints

0x00006040004427e0
0x00006040004427e0
0x0000608000052980
0x0000600000055c50

for instance, and different raw pointers are interpreted as different associated objects.

clemens
  • 16,716
  • 11
  • 50
  • 65