Stack overflow on index definition on CKRecord in Swift

This question asks if signatures can be used with CKRecord in Swift. Although I already knew how to do what the request asked, each permutation of it gives me a stack overflow:

 subscript(key: String) -> CKRecordValue? { get { return objectForKey(key) as CKRecordValue? } set { setObject(newValue, forKey: key) } } 

Stack overflow occurs in the receiver. (I never tried the installer, so this can happen there as well.) I tried to implement using objectForKey: objectForKeyedSubscript: and valueForKey: All produce the same result: stack overflow.

This is very strange, since CKRecord is certainly written in Objective-C. Why does it call the Swift subscript method recursively? It makes no sense. Nate Cook in his answer to the questionnaire asks why Swift does not connect objectForKeyedSubscript: automatically. Well, maybe the code for this is not completely baked, but it causes this problem. I need to try it with another class that has objectForKeyedSubscript:

UPDATE

It looks like objectForKeyedSubscript: usually connects. I created a class in Objective-C with the appropriate methods, added it to the header of the bridge, and the indexes were there and compiled without problems. Even better, it worked without.

This means that something very unusual is happening with CKRecord .

THEORY

If you create a class in Swift that descends from NSObject and implements the subscript method on it with 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 checking that objectForKeyedSubscript: .

Since CKRecord derived from NSObject , the subscript implementation overrides the default implementation. Furthermore, it seems that objectForKey: and valueForKey: all ultimately called objectForKeyedSubscript: which results in a (read: "is same as") call to subscript , which causes a stack overflow.

This may explain why stack overflow occurs. It still doesn't explain why objectForKeyedSubscript: not automatically overridden, but perhaps because the definition of setObject:forKeyedSubscript: has a slightly different type signature from the canonical: - (void)setObject:(id <CKRecordValue>)object forKeyedSubscript:(NSString *)key; . This does not matter for Objective-C, but can lead to disabling "bridge code". After all, Swift is pretty new.

+6
source share
3 answers

After some testing and debugging (via a subclass), I found that for CKRecord , objectForKey: does indeed call objectForKeyedSubscript: In addition, the subscript implementation in the Swift class labeled @objc implicit (descending from NSObject ) or explicitly means that subscript implemented as objectForKeyedSubscript:

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

+2
source

This is a simple extension for CKRecord to simplify indexing with.

 extension CKRecord { struct Sub { let record: CKRecord subscript(key: String) -> CKRecordValue? { get { return record.objectForKey(key) as? CKRecordValue } set { record.setObject(newValue, forKey: key) } } } var sub: Sub { return Sub(record: self) } var πŸ‘Œ: Sub { return sub } } 

Using:

 var sub = record.sub sub["name"] = name /* Or */ // One does not simply subscript CKRecord record.πŸ‘Œ["name"] = name 

(by the way, joking about πŸ‘Œ)

+1
source

I was able to successfully index by copying some code written by an Apple engineer in the developer forums.

 import CloudKit protocol MyCKRecordValueType { var asObject: CKRecordValue { get } } extension CKRecord { func set<ValueType>(value: ValueType, forKey key: String) where ValueType : MyCKRecordValueType { let object = value.asObject self.setObject(object, forKey: key) } subscript(key : String) -> MyCKRecordValueType? { set { self.setObject(newValue?.asObject, forKey: key) } get { return object(forKey: key) as? MyCKRecordValueType } } } extension String : MyCKRecordValueType { var asObject: CKRecordValue { return self as NSString } } extension Bool : MyCKRecordValueType { var asObject: CKRecordValue { return self as NSNumber } } extension Int : MyCKRecordValueType { var asObject: CKRecordValue { return self as NSNumber } } extension Data : MyCKRecordValueType { var asObject: CKRecordValue { return self as NSData } } 

you can call the index as you expected:

 let firstRecordID = CKRecordID(recordName: "0") let record = CKRecord(recordType: "Foo", recordID: firstRecordID) record["title"] = "Hello World" record["year_established"] = 2000 
+1
source

Source: https://habr.com/ru/post/978762/


All Articles