magicalpanda / MagicalRecord

Super Awesome Easy Fetching for Core Data!
Other
10.8k stars 1.79k forks source link

Importing data using Swift 3 #1294

Open ro22e0 opened 7 years ago

ro22e0 commented 7 years ago

Hello,

I try to import data from JSON array like this :

let json = JSON(value)
let data: [[String: Any]] = self.transformJson(json)
MagicalRecord.saveInBackground({ (localContext) in
     User.mr_import(from: data, in: localContext)
})

Using Swift 3

The problem is mr_import call MR_importFromObject:inContext: instead of MR_importFromArray:inContext:

Here's the exception :

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<_SwiftValue 0x6080000c9e60> valueForUndefinedKey:]: this class is not key value coding-compliant for the key id.' First throw call stack: ( 0 CoreFoundation 0x0000000109ae4d4b exceptionPreprocess + 171 1 libobjc.A.dylib 0x000000010cf4a21e objc_exception_throw + 48 2 CoreFoundation 0x0000000109ae4c99 -[NSException raise] + 9 3 Foundation 0x000000010cb247c5 -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 226 4 Foundation 0x000000010ca5060b -[NSObject(NSKeyValueCoding) valueForKey:] + 283 5 Foundation 0x000000010ca53eec -[NSArray(NSKeyValueCoding) valueForKey:] + 467 6 Foundation 0x000000010ca503e8 -[NSArray(NSKeyValueCoding) valueForKeyPath:] + 448 7 MagicalRecord 0x000000010a968ce8 -[NSObject(MagicalRecord_DataImport) MR_lookupKeyForAttribute:] + 232 8 MagicalRecord 0x000000010a968efe -[NSObject(MagicalRecord_DataImport) MR_valueForAttribute:] + 62 9 MagicalRecord 0x000000010a95b7c7 75+[NSManagedObject(MagicalRecord_DataImport) MR_importFromObject:inContext:]_block_invoke + 135 10 CoreData 0x000000010d4519a7 developerSubmittedBlockToNSManagedObjectContextPerform + 199 11 CoreData 0x000000010d45185f -[NSManagedObjectContext performBlockAndWait:] + 255 12 MagicalRecord 0x000000010a95b5db +[NSManagedObject(MagicalRecord_DataImport) MR_importFromObject:inContext:] + 331 13 Kairos 0x0000000108e057ce _TFFZFV6Kairos8DataSync10fetchUsersFFOS_13StatusRequestT_T_U_FGV9Alamofire12DataResponseP__T_UFCSo22NSManagedObjectContextT + 158 14 Kairos 0x0000000108dc9ecc _TTRXFo_oCSo22NSManagedObjectContextXFdCbdS + 60 15 MagicalRecord 0x000000010a953671 __73+[MagicalRecord(ActionsDeprecated) saveInBackgroundWithBlock:completion:]_block_invoke + 129 16 CoreData 0x000000010d4519a7 developerSubmittedBlockToNSManagedObjectContextPerform + 199 17 libdispatch.dylib 0x000000010e9740cd _dispatch_client_callout + 8 18 libdispatch.dylib 0x000000010e951e17 _dispatch_queue_serial_drain + 236 19 libdispatch.dylib 0x000000010e952b4b _dispatch_queue_invoke + 1073 20 libdispatch.dylib 0x000000010e95302b _dispatch_queue_override_invoke + 683 21 libdispatch.dylib 0x000000010e955385 _dispatch_root_queue_drain + 720 22 libdispatch.dylib 0x000000010e955059 _dispatch_worker_thread3 + 123 23 libsystem_pthread.dylib 0x000000010ed23712 _pthread_wqthread + 1299 24 libsystem_pthread.dylib 0x000000010ed231ed start_wqthread + 13 ) libc++abi.dylib: terminating with uncaught exception of type NSException

rugheid commented 7 years ago

If you are still interested in what the problem is, than here is the answer: The problem is that your export of the JSON object uses Swift representations internally. When you use the processed SwiftyJSON values, this will almost always be the case, unless you transform them yourself to the Objective-C equivalents. If you are using the dictionaryValue-like methods of SwiftyJSON, then there are definitely JSON objects inside and they use Swift values. You could modify your transformJson to do this transformation. For me however, since the JSON data was already in the correct format, the easiest way was using the raw values inside the JSON object:

guard let array = json.rawValue as? [[AnyHashable: Any]] else { return nil }
// mr_import here using array

I use [[AnyHashable: Any] here, since that is what the Objective-C bridged method expects.

ro22e0 commented 7 years ago

Wow thanks !

Coeur commented 5 years ago

You can disambiguate which obj-c selector is called by casting your parameters:

// will call MR_importFromArray
User.mr_import(from: data as [[AnyHashable : Any]], in: localContext)
// will call MR_importFromObject
User.mr_import(from: data as Any, in: localContext)
EzDream commented 5 years ago

You can disambiguate which obj-c selector is called by casting your parameters:

// will call MR_importFromArray
User.mr_import(from: data as [[AnyHashable : Any]], in: localContext)
// will call MR_importFromObject
User.mr_import(from: data as Any, in: localContext)

Code in Swift didn’t distingush the function name between MR_importFromArray and MR_importFromObject. Your code still cannot work because it’s always recognize as the MR_importFromObject function. This problem is depressing.

Coeur commented 5 years ago

@EzDream I tested the code myself, with NSLog and breakpoints: it does call MR_importFromArray when the type is explicitly [[AnyHashable : Any]]. Maybe you didn't clean the DerivedData or something.

Coeur commented 5 years ago

We could eventually add an explicit distinct naming with NS_SWIFT_NAME, as described in https://developer.apple.com/documentation/swift/objective-c_and_c_code_customization/renaming_objective-c_apis_for_swift.

Coeur commented 4 years ago

Sorry, I have no more plans to perform a renaming for now, so I'll unassign myself. Pull request welcome.