Stack overflow when defining subscript on CKRecord in Swift

ckrecord, cloudkit, subscript-operator, swift

Solution

After some testing and debugging (via a subclass), I discovered that, for `CKRecord`, `objectForKey:` does indeed call `objectForKeyedSubscript:`. Also, implementing `subscript` in a Swift class that is marked `@objc` implicitly (by descending from `NSObject`) or explicitly means that `subscript` is implemented as `objectForKeyedSubscript:`.

This means that implementing `subscript` on `CKRecord` in an extension hides the default implementation, which causes the stack overflow.

Problem

This question asks whether one can use subscripting with `CKRecord` in Swift. While I already knew how to do what the questioner wanted, every permutation of it gives me a stack overflow: ``` subscript(key: String) -> CKRecordValue? { get { return objectForKey(key) as CKRecordValue? } set { setObject(newValue, forKey: key) } } ``` The stack overflow occurs in the getter. (I've never tried the setter, so it may occur there, too.) I've tried implementing with `objectForKey:`, `objectForKeyedSubscript:`, and `valueForKey:`. All produce the same result: a stack overflow. This is very strange, since `CKRecord` is certainly written in Objective-C. Why would it recursively call Swift's `subscript` method? It makes no sense. Nate Cook, in his answer to the questioner, wonders why Swift doesn't bridge `objectForKeyedSubscript:` automatically. Well, maybe the code to do that is not fully baked, but is causing this problem. I will have to try it with another class that has `objectForKeyedSubscript:`. UPDATE It appears that `objectForKeyedSubscript:` is ordinarily bridged. I created a class in Objective-C with the appropriate methods, added it to the bridging header, and the indexers were there and compiled without issue. Even better, it worked without a stack overflow. This means that something very unusual is going on with `CKRecord`. A THEORY If you create a class in Swift that descends from `NSObject` and implements the `subscript` method on it with a `String` as the key, this becomes `objectForKeyedSubscript:`. (For "pure Swift" classes, I suspect this is not the case.) You can verify this by importing your Swift class into Objective-C and verifying that `objectForKeyedSubscript:` is there. Since `CKRecord` descends from `NSObject`, implementing `subscript` overrides the default implementation. Further, it seems that `objectForKey:` and `valueForKey:` all ultimately called `objectForKeyedSubscript:`, which results in (read: "is the same as") a call to `subscript`, which causes the stack overflow. That may explain why the stack overflow occurs. It still does not explain why `objectForKeyedSubscript:` was not automatically bridged, but perhaps it's because the definition of `setObject:forKeyedSubscript:` has a slightly different type signature from the canonical one: `- (void)setObject:(id <CKRecordValue>)object forKeyedSubscript:(NSString *)key;`. This makes no difference to Objective-C, but might trip up the "bridging code". Swift is pretty new, after all.

Original source