重命名并改写为Swift后解码对象时崩溃

7

由于我们将类名从Bestemming更改为Place,并将其从Objective-C重写为Swift,一些用户在使用它时会遇到崩溃。我们试图使用NSCoding原则从NSUserDefaults中加载对象。

崩溃信息:

Thread : Crashed: com.apple.main-thread
0  Flitsmeister                   0x10018b720 specialized Place.init(coder : NSCoder) -> Place? (Place.swift)
1  Flitsmeister                   0x10018a6f4 @objc Place.init(coder : NSCoder) -> Place? (Place.swift)
2  Foundation                     0x1839ab92c _decodeObjectBinary + 2276
3  Foundation                     0x1839aaf90 _decodeObject + 304
4  Foundation                     0x1839aa124 +[NSKeyedUnarchiver unarchiveObjectWithData:] + 92
5  Flitsmeister                   0x100103fa0 +[SharedUserDefaultsManager WorkPlace] (SharedUserDefaultsManager.m:72)
6  Flitsmeister                   0x100090830 -[InvoerBestemmingTableViewController viewWillAppear:] (InvoerBestemmingTableViewController.m:106)
7  UIKit                          0x187d8074c -[UIViewController _setViewAppearState:isAnimating:] + 628
8  UIKit                          0x187d804c0 -[UIViewController __viewWillAppear:] + 156
9  UIKit                          0x187e27130 -[UINavigationController _startTransition:fromViewController:toViewController:] + 760
10 UIKit                          0x187e26a6c -[UINavigationController _startDeferredTransitionIfNeeded:] + 868
11 UIKit                          0x187e26694 -[UINavigationController __viewWillLayoutSubviews] + 60
12 UIKit                          0x187e265fc -[UILayoutContainerView layoutSubviews] + 208
13 UIKit                          0x187d63778 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 656
14 QuartzCore                     0x185772b2c -[CALayer layoutSublayers] + 148
15 QuartzCore                     0x18576d738 CA::Layer::layout_if_needed(CA::Transaction*) + 292
16 QuartzCore                     0x18576d5f8 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32
17 QuartzCore                     0x18576cc94 CA::Context::commit_transaction(CA::Transaction*) + 252
18 QuartzCore                     0x18576c9dc CA::Transaction::commit() + 512
19 UIKit                          0x187d59c78 _afterCACommitHandler + 180
20 CoreFoundation                 0x18302c588 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
21 CoreFoundation                 0x18302a32c __CFRunLoopDoObservers + 372

这个类:

@objc(Place)
class Place : NSObject, NSCoding, CustomDebugStringConvertible
{
    let name: String

    let location: CLLocation
    var lastUsed: NSDate?

    var type: PlaceType

    var address: String?
    //MARK: - NSCoding protocol

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(name, forKey: "name")
        aCoder.encodeObject(address, forKey: "address")
        aCoder.encodeInt(type.rawValue, forKey: "type")
        aCoder.encodeObject(location, forKey: "location")
        aCoder.encodeObject(lastUsed, forKey: "lastUsed")
    }

    required init?(coder aDecoder: NSCoder) {


        if let locatieNaam : String = aDecoder.decodeObjectForKey("locatieNaam") as? String {
            //This is the OLD object
            name = locatieNaam

            let nullableLocation : CLLocation? = aDecoder.decodeObjectForKey("locatie") as? CLLocation
            if let notnulllablelocation : CLLocation = nullableLocation {
                location = notnulllablelocation
            } else {
                location = CLLocation.init(latitude: 0, longitude: 0) //Not possible
            }
            lastUsed = aDecoder.decodeObjectForKey("lastUsed") as? NSDate

            if aDecoder.decodeBoolForKey("isThuis") {
                type = .Home
            } else if aDecoder.decodeBoolForKey("isWerk") {
                type = .Work
            } else if aDecoder.decodeBoolForKey("isFavoriet") {
                type = .Favoriet
            } else {
                type = .Other
            }

            address = nil
        }
        else {
            name = aDecoder.decodeObjectForKey("name") as! String
            let nullableLocation : CLLocation? = aDecoder.decodeObjectForKey("location") as? CLLocation

            if let notnullableLocation : CLLocation = nullableLocation {
                location = notnullableLocation
            } else {
                location = CLLocation.init(latitude: 0, longitude: 0) //Not possible
            }

            lastUsed = aDecoder.decodeObjectForKey("lastUsed") as? NSDate
            type = PlaceType.init(rawValue: aDecoder.decodeInt32ForKey("type"))!
            address = aDecoder.decodeObjectForKey("address") as? String
        }
    }
}

从NSUserDefaults中读取数据:

+ (Place*)WorkPlace;
{
    @try {
        NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:kSharedUserDefaults];

        NSData *result = [mySharedDefaults objectForKey:kWerkBestemming];
        if(result == NULL)
            return nil;

        [NSKeyedUnarchiver setClass:[Place class] forClassName:@"Bestemming"];
        [NSKeyedUnarchiver setClass:[Place class] forClassName:@"BestemmingBase"];

        Place *place = [NSKeyedUnarchiver unarchiveObjectWithData:result];
        if(place != nil) {
            place.type = PlaceTypeWork; //Needed because the old Bestemming class didnt saved the boolean isWerk
        }
        return place;
    }
    @catch (NSException *exception) {
        return nil;
    }
}

崩溃日志显示它在第0行崩溃,这是注释,所以我认为它在init方法中崩溃,我认为它与一个对象有关,该对象为空但不能为空。

我尝试过:

  • 在SharedUserDefaultsManager中使用try catch
  • 对非空项进行额外检查

对于那些应用程序崩溃的用户,我可以从NSUserDefaults中删除该对象。只有当我知道发生这种情况时才能实现。


尝试将 if (result == nil) 与 NULL 进行比较,这只是一条注释而已。您是否尝试过调试 aDecoder.decodeObjectForKey("locatieNaam") 返回的内容?如果我正在查看错误的行,请纠正我。 - Josip B.
很抱歉,我无法重现它 :( 而且我不知道崩溃是哪一行,因为崩溃日志显示行号为零。 - Sjoerd Perfors
你能在Swift类之前检出你的代码到最新提交吗?然后将ObjC对象保存到磁盘,再检出最新的Swift代码并重新创建崩溃吗? - Josip B.
是的,我尝试过了,但没有崩溃。我们从 Fabric 获取崩溃报告,但我无论如何都无法复现它。 - Sjoerd Perfors
那真的很难调试。最后你可以做的是检查NSKeyedArchiver如何序列化对象。我确定它是某种XML/Plist格式,然后不要使用NSKeyedUnarchiver进行反序列化,自己实现一个。 - Josip B.
1个回答

0

我认为这是一种更好的处理NSCoding init方法并在变量不符合预期时返回nil的方法:

required convenience init?(coder decoder: NSCoder) {
    guard let title = decoder.decodeObjectForKey("title") as? String,
        let author = decoder.decodeObjectForKey("author") as? String,
        let categories = decoder.decodeObjectForKey("categories") as? [String]
        else { return nil }

    self.init(
        title: title,
        author: author,
        pageCount: decoder.decodeIntegerForKey("pageCount"),
        categories: categories,
        available: decoder.decodeBoolForKey("available")
    )
}

来自 NSHipster: http://nshipster.com/nscoding/

现在让我们看看新版本的效果如何。

编辑:它起作用了!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接