简体   繁体   中英

Swift 4: Save and retrieve an array of custom objects (with nested custom objects) to UserDefaults

I am trying to save an array of custom objects to UserDefaults. I believe I should, at this point, be saving correctly, but I continue to get the "unrecognized selector" error. I think this may be because two parts of my custom object are arrays of other custom objects. What am I doing wrong here?

The Object:

class SaleObject: NSObject, NSCoding {

//MARK: Properties

var ranges: [RangeObject]
var timberSaleName: String
var lbOperatorName: String
var lbOperatorID: String
var timberSaleID: Int
var ID: Int
var contracts: [ContractObject]

//MARK: Initialization
init(ranges: [RangeObject], timberSaleName: String, lbOperatorName: String, lbOperatorID: String, timberSaleID: Int, ID: Int ,contracts: [ContractObject]) {

    //Initialize stored properties
    self.ranges = ranges
    self.timberSaleName = timberSaleName
    self.lbOperatorName = lbOperatorName
    self.lbOperatorID = lbOperatorID
    self.timberSaleID = timberSaleID
    self.ID = ID
    self.contracts = contracts

}

required init?(coder aDecoder: NSCoder) {
    self.ranges = (aDecoder.decodeObject(forKey: "ranges") as? [RangeObject])!
    self.timberSaleName = (aDecoder.decodeObject(forKey: "timber_sale_name") as? String)!
    self.lbOperatorName = (aDecoder.decodeObject(forKey: "lb_operator_name") as? String)!
    self.lbOperatorID = (aDecoder.decodeObject(forKey: "lb_operator_id") as? String)!
    self.timberSaleID = (aDecoder.decodeObject(forKey: "timber_sale_id") as? Int)!
    self.ID = (aDecoder.decodeObject(forKey: "id") as? Int)!
    self.contracts = (aDecoder.decodeObject(forKey: "contracts") as? [ContractObject])!
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(self.ranges, forKey: "ranges")
    aCoder.encode(self.timberSaleName, forKey: "timber_sale_name")
    aCoder.encode(self.lbOperatorName, forKey: "lb_operator_name")
    aCoder.encode(self.lbOperatorID, forKey: "lb_operator_id")
    aCoder.encode(self.timberSaleID, forKey: "timber_sale_id")
    aCoder.encode(self.ID, forKey: "id")
    aCoder.encode(self.contracts, forKey: "contracts")
}

}

The save attempt:

let userDefaults = UserDefaults.standard
        let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.saleArray)
        userDefaults.set(encodedData, forKey: "sales")

The error:

2018-11-28 14:28:30.024768-0600 Load BOSS[14991:282335] 
libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not 
supported on this platform.
2018-11-28 14:28:30.362954-0600 Load BOSS[14991:282335] [MC] System 
group container for systemgroup.com.apple.configurationprofiles path 
is /Users/beauburchfield/Library/Developer/
CoreSimulator/Devices/4ED03814-DD8C-43F3-ABA7- 
B5D0751161A0/data/Containers/Shared/SystemGroup/
systemgroup.com.apple.configurationprofiles
2018-11-28 14:28:30.364591-0600 Load BOSS[14991:282335] [MC] Reading 
from private effective user settings.
2018-11-28 14:28:31.034136-0600 Load BOSS[14991:282394] - 
[Load_BOSS.RangeObject encodeWithCoder:]: unrecognized selector sent 
 to 
instance 0x600001bcc680
2018-11-28 14:28:31.037429-0600 Load BOSS[14991:282394] *** 
Terminating app due to uncaught exception 
'NSInvalidArgumentException', reason: '-[Load_BOSS.RangeObject 
encodeWithCoder:]: unrecognized selector sent to instance 
0x600001bcc680'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010cbc51bb _ . 
_exceptionPreprocess + 331
1   libobjc.A.dylib                     0x000000010b73b735 
objc_exception_throw + 48
2   CoreFoundation                      0x000000010cbe3f44 - 
[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3   CoreFoundation                      0x000000010cbc9ed6 ___forwarding___ + 1446
4   CoreFoundation                      0x000000010cbcbda8 _CF_forwarding_prep_0 + 120
5   Foundation                          0x000000010b182175 _encodeObject + 1230
6   Foundation                          0x000000010b182aee -[NSKeyedArchiver _encodeArrayOfObjects:forKey:] + 439
7   Foundation                          0x000000010b182175 _encodeObject + 1230
8   Load BOSS                           0x000000010add0e2f $S9Load_BOSS10SaleObjectC6encode4withySo7NSCoderC_tF + 207
9   Load BOSS                           0x000000010add128c $S9Load_BOSS10SaleObjectC6encode4withySo7NSCoderC_tFTo + 60
10  Foundation                          0x000000010b182175 _encodeObject + 1230
11  Foundation                          0x000000010b182aee -[NSKeyedArchiver _encodeArrayOfObjects:forKey:] + 439
12  Foundation                          0x000000010b182175 _encodeObject + 1230
13  Foundation                          0x000000010b18122e +[NSKeyedArchiver archivedDataWithRootObject:] + 156
14  Load BOSS                           0x000000010adbf15a $S9Load_BOSS18MainViewControllerC14getJsonFromUrlyyFy10Foundation4DataVSg_So13NSURLResponseCSgs5Error_pSgtcfU_ + 15706
15  Load BOSS                           0x000000010adbf43d $S9Load_BOSS18MainViewControllerC14getJsonFromUrlyyFy10Foundation4DataVSg_So13NSURLResponseCSgs5Error_pSgtcfU_TA + 13
16  Load BOSS                           0x000000010adbf590 $S10Foundation4DataVSgSo13NSURLResponseCSgs5Error_pSgIegggg_So6NSDataCSgAGSo7NSErrorCSgIeyByyy_TR + 336
17  CFNetwork                           0x000000010e0d9940 __75-[__NSURLSessionLocal taskForClass:request:uploadFile:bodyData:completion:]_block_invoke + 19
18  CFNetwork                           0x000000010e0efb0c __49-[__NSCFLocalSessionTask _task_onqueue_didFinish]_block_invoke + 172
19  Foundation                          0x000000010b1a8f9e __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
20  Foundation                          0x000000010b1a8ea5 -[NSBlockOperation main] + 68
21  Foundation                          0x000000010b1a5c14 -[__NSOperationInternal _start:] + 689
22  Foundation                          0x000000010b1abc4b __NSOQSchedule_f + 227
23  libdispatch.dylib                   0x000000010f0ae595 _dispatch_call_block_and_release + 12
24  libdispatch.dylib                   0x000000010f0af602 _dispatch_client_callout + 8
25  libdispatch.dylib                   0x000000010f0b254d _dispatch_continuation_pop + 565
26  libdispatch.dylib                   0x000000010f0b1927 _dispatch_async_redirect_invoke + 859
27  libdispatch.dylib                   0x000000010f0c000a _dispatch_root_queue_drain + 351
28  libdispatch.dylib                   0x000000010f0c09af _dispatch_worker_thread2 + 130
29  libsystem_pthread.dylib             0x000000010f49e70e _pthread_wqthread + 619
30  libsystem_pthread.dylib             0x000000010f49e435 start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException

NSCoding must be implemented for your two custom objects as well. Everything else is fine.

There is one thing I want to mention, though.

(aDecoder.decodeObject(forKey: "timber_sale_name") as? String)!

can be written as

aDecoder.decodeObject(forKey: "timber_sale_name") as! String

It basically means the same but is easier to read.

The crash here

[Load_BOSS.RangeObject encodeWithCoder:]: unrecognized selector sent

gives the reason which is making the root class conform to

class SaleObject: NSObject, NSCoding {

doesn't mean that you can use custom object ( RangeObject in your case ) without making it also to conform like

class RangeObject : NSObject, NSCoding {

Highly recommend using Codable

class SaleObject: Codable { 

    let ranges: [RangeObject]
    let timberSaleName: String
    let lbOperatorName: String
    let lbOperatorID: String
    let timberSaleID: Int
    let ID: Int
    let contracts: [ContractObject]
}

class RangeObject : Codable { 
  // add it's properties 
}

How to use:

let data = try? JSONEncoder().encode(obj) // obj may be single SaleObject or array

let obj = try? JSONDecoder().decode(SaleObject.self,from:data) // for data being array make it [SaleObject].self

Go ahead and implement NSCoding for the other two custom objects. Also, change your decodeObject to decodeInteger on all Integers of your custom objects, and remove the "as! Int" from them. Then, do this:

let userDefaults = UserDefaults.standard
let encodedData = NSKeyedArchiver.archivedData(withRootObject: self.saleArray)
userDefaults.set(encodedData, forKey: "sales")

To retrieve the data, do this:

let newArray = NSKeyedUnarchiver.unarchiveObject(with: data as! Data) as! [SaleObject]

After you have it working, go back and research Codable. Enjoy!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM