RestKit crashing swift app for random users when dealing with error response

346 views
Skip to first unread message

Mark Coburn

unread,
Nov 20, 2014, 9:49:56 AM11/20/14
to res...@googlegroups.com
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
X-Powered-By: ASP.NET
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)

Mark Coburn

unread,
Nov 20, 2014, 7:52:41 PM11/20/14
to res...@googlegroups.com
I figured this out.  The crash only happens for release builds.  It seems that if you assign a response descriptor a mapping that is coming from a class-level function from the base class and that base class function is getting the mapping from another class-level method of another class, then responseDescriptor.mapping will be nil at least by the time the RKResponseMapperOperation buildResponseMappingsDictionary method is called.  So the response decriptor exists as far as RestKit is concerned but its mapping is nil.  Most likely the compiler is doing something to cause this for just release builds.  I was able to workaround the problem by getting rid of the call to the base class and just use the class-level method the base class was calling.

Here is an example of what can cause the crash:

public class AbstractDao {

public class func errorResponseMapping() -> RKObjectMapping? {
        return ApiResultDao.responseMapping()
    }
}

public class ApiResultDao: AbstractDao {
    
    public override class func propertyMapping() -> Dictionary<String, String> {
        return ["ErrorMessage": "errorMessage"]
    }
    
    public override class func responseMapping() -> RKObjectMapping {
        let mapping = RKObjectMapping(forClass: ApiResult.self)
        mapping.addAttributeMappingsFromDictionary(propertyMapping())
        return mapping
    }
    
}

public class AuthenticationDao: AbstractDao {

func authenticate() {
-- omitted for brevity --
let failureResponseDescriptor = RKResponseDescriptor(mapping: AbstractDao.errorResponseMapping(), method: RKRequestMethod.POST,
pathPattern: relativeUrl, keyPath: nil, statusCodes: failureResponseStatusCodes)
-- omitted for brevity --
}

}
Reply all
Reply to author
Forward
0 new messages