SkygearIO / skygear-SDK-iOS

Skygear SDK for iOS
https://skygear.io
Other
10 stars 28 forks source link

Crash when calling SKYRecord:setNilValueForKey: #244

Closed johncpang closed 5 years ago

johncpang commented 5 years ago

The problem happened when our app is written in Swift. In Swift, following function of SKYRecord.h will translate into non-null object (Any, rather than Any?):

- (void)setObject:(id)object forKey:(id<NSCopying> _Nullable)key;

Thus, the following will throw Expression implicitly coerced from 'String?' to 'Any'

let name: String? = nil
record.setObject(name, forKey: "name" as NSCopying)

Although inside SKYRecord.m it does handle the situation when object is nil (it create an NSNull object on fly). Since Swift doesn't want us to pass an optional, we are tamped to wrap it with an if-case:

if name != nil, !name.isEmpty {
    record.setObject(name!, forKey: "name" as NSCopying)
} else {
    record.setNilValueForKey("name")
}

Although we can provide an empty string like this: name ?? "", there are cases we cannot do so. For example, to unset an SKYReference, or unset an int field. However, this crash because SKYRecord.m does not provide implementation of setNilValueForKey:. It's better to provide the implementation and it would be as similar as calling

- (void)setNilValueForKey:(id<NSCopying>)key
{
    [self setObject:nil forKey:key];
}

Meanwhile, we can create an extension to overcome the problem:

import SKYKit

extension SKYRecord {
    func setObject(_ object: Any?, forKey: String) {
        setObject(object ?? NSNull(), forKey: forKey as NSCopying)
    }
}

Then we can also save the need of as NSCopying in Swift code:

let team: SKYReference? = nil
record.setObject(team, forKey: "team") // this employee leave a team