问题
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.
回答1:
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.
回答2:
Here’s a simple extension to CKRecord to make it easier to subscript 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
}
}
Usage:
var sub = record.sub
sub["name"] = name
/* Or */
// One does not simply subscript CKRecord
record.👌["name"] = name
(I’m kidding about the 👌 by the way)
回答3:
I was able to successfully subscript by piggy-backing off of some code written by an Apple engineer on dev 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 then call the subscript as you would expect:
let firstRecordID = CKRecordID(recordName: "0")
let record = CKRecord(recordType: "Foo", recordID: firstRecordID)
record["title"] = "Hello World"
record["year_established"] = 2000
来源:https://stackoverflow.com/questions/27159950/stack-overflow-when-defining-subscript-on-ckrecord-in-swift