I have an iOS app writen in Swift that is crashing a lot now that I have made it available to about 100 beta testers. Crashlytics shows that it is the same fatal exception that is causing the problem and says it is happening in RKResponseMapperOperation.m at line 233.
Fatal Exception: NSInvalidArgumentException
*** setObjectForKey: object cannot be nil (key: null)
Thread : Fatal Exception: NSInvalidArgumentException
0 CoreFoundation 0x2c95549f __exceptionPreprocess + 126
1 libobjc.A.dylib 0x3a10bc8b objc_exception_throw + 38
2 CoreFoundation 0x2c872c63 -[__NSDictionaryM setObject:forKey:] + 850
3 TestApp 0x0026e6a7 -[RKResponseMapperOperation buildResponseMappingsDictionary] (RKResponseMapperOperation.m:233)
4 TestApp 0x0026de97 -[RKResponseMapperOperation initWithRequest:response:data:responseDescriptors:] (RKResponseMapperOperation.m:190)
5 TestApp 0x00262303 -[RKObjectRequestOperation performMappingOnResponseWithCompletionBlock:] (RKObjectRequestOperation.m:482)
6 TestApp 0x002628ff __35-[RKObjectRequestOperation execute]_block_invoke (RKObjectRequestOperation.m:508)
7 TestApp 0x0020043d __64-[AFHTTPRequestOperation setCompletionBlockWithSuccess:failure:]_block_invoke143 (AFHTTPRequestOperation.m:283)
8 libdispatch.dylib 0x3a66b7bb _dispatch_call_block_and_release + 10
9 libdispatch.dylib 0x3a672577 _dispatch_async_redirect_invoke + 550
10 libdispatch.dylib 0x3a674dab _dispatch_root_queue_drain + 866
11 libdispatch.dylib 0x3a675cd7 _dispatch_worker_thread3 + 94
12 libsystem_pthread.dylib 0x3a7cce31 _pthread_wqthread + 668
13 libsystem_pthread.dylib 0x3a7ccb84 start_wqthread + 8
I have been unable to reproduce the problem myself but caught the HTTP request and response when having someone else reproduce the crash. Note that they can reproduce the crash by entering bad credentials which should trigger RestKit to use the failureObjectMapping I have defined.
In any case, the HTTP request and response appears to be good and here they are for completeness:
REQUEST:
Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 121
Content-Type: application/json; charset=utf-8
Accept-Language: en;q=1
Connection: keep-alive
User-Agent: TestApp/1.0.9 (iPhone; iOS 8.1.1; Scale/2.00)
[
{
"Credential" : {
"Password" : "ddxdxd",
"UserName" : "ffffff"
},
"CredentialType" : 0
}
]
RESPONSE:
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
Date: Thu, 20 Nov 2014 01:36:59 GMT
Content-Length: 33
{"ErrorMessage":"","Response":""}
Note that the app works just fine for me using the simulator, an iPod with iOS 8.1.1, or my iPhone 5 with iOS 7.1.2 installed. I am not sure exactly what is occuring but looking at the RestKit source code it seems that when it is building the response mappings dictionary, at times responseDescriptor.mapping becomes null and causes the crash when performing this line:
[dictionary setObject:responseDescriptor.mapping forKey:(responseDescriptor.keyPath ?: [NSNull null])];
Here is the code that I am using. I am currently stumped as to why this is happening. I am using RestKit version 0.23 (pod 'RestKit', '~> 0.23'). Thanks in advance for any assistance. Note that I modified the code slightly to make it easier to post here in this forum. In my actual code the property mapping dictionaries and response mappings are defined in class-level functions so that they can be reused if needed. Also, this is just one example of how to reproduce the crash. The same crash can occur when users perform other types of operations in our app that uses RestKit and the same error in Crashlytics is reported.
let relativeUrl = "/authentication/v1/authenticate"
let jsonData = NSJSONSerialization.dataWithJSONObject(credentials, options: NSJSONWritingOptions.PrettyPrinted, error: nil)
let objectManager = RKObjectManager.sharedManager()
let successPropertyMapping = [
"ApiToken": "apiToken",
"ErrorMessage": "errorMessage",
"IsAuthenticated": "isAuthenticated"
]
let successObjectMapping = RKObjectMapping(forClass: AuthenticationResult.self)
successObjectMapping.addAttributeMappingsFromDictionary(successPropertyMapping)
let failurePropertyMapping = [
"ErrorMessage": "errorMessage"
]
let failureObjectMapping = RKObjectMapping(forClass: ApiResult.self)
failureObjectMapping.addAttributeMappingsFromDictionary(failurePropertyMapping)
var failureResponseStatusCodes = NSMutableIndexSet()
failureResponseStatusCodes.addIndexes(RKStatusCodeIndexSetForClass(UInt(RKStatusCodeClassClientError)))
failureResponseStatusCodes.addIndexes(RKStatusCodeIndexSetForClass(UInt(RKStatusCodeClassServerError)))
let successResponseDescriptor = RKResponseDescriptor(mapping: successObjectMapping, method: RKRequestMethod.POST,
pathPattern: relativeUrl, keyPath: "Response", statusCodes: RKStatusCodeIndexSetForClass(UInt(RKStatusCodeClassSuccessful)))
let failureResponseDescriptor = RKResponseDescriptor(mapping: failureObjectMapping, method: RKRequestMethod.POST,
pathPattern: relativeUrl, keyPath: nil, statusCodes: failureResponseStatusCodes)
let request = objectManager.HTTPClient.requestWithMethod("POST", path: relativeUrl, parameters: nil)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.HTTPBody = jsonData
let objectRequestOperation = RKObjectRequestOperation(request: request, responseDescriptors:
[successResponseDescriptor, failureResponseDescriptor])
objectRequestOperation.setCompletionBlockWithSuccess(
{(requestOperation, mappingResult) in
var result = mappingResult.firstObject() as AuthenticationResult?
objectManager.HTTPClient.setDefaultHeader(HeaderNameApiToken, value: result?.apiToken)
self.delegate?.authenticationDaoDidGetAuthResult(result)
},
failure: {(requestOperation, error) in
let (message, statusCode) =
self.getServerErrorAndStatusCode(requestOperation, error: error)
self.delegate?.authenticationDaoDidGetAuthResultFail(error, statusCode: statusCode)
})
operationQueue.addOperation(objectRequestOperation)