How to deal with an empty object {} in RestKit - objective-c

The response I sometimes get from the server is a JSON representing an empty object like this {}
I've read this question/answer over here already, which states I should implement the willMapData delegate function and point the *mappableData somewhere else. The thing is I can't figure out what should I assign to *mappableData so that my app won't crash.
I've tried this
- (void)objectLoader:(RKObjectLoader *)loader willMapData:(inout id *)mappableData
{
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:RKMIMETypeJSON];
*mappableData = [parser objectFromString:#"{\"unknownObject\":\"\"}" error:nil];
}
But nevertheless my app crashes with a rather pissing
'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
Can you help me out?
UPDATE
Turning on RKDebug messages gives me this in the console:
Performing object mapping sourceObject: {
}
and targetObject: (null)
then the code reaches RKObjectMapper.m:
if (mappableData) {
id mappingResult = [self performMappingForObject:mappableData atKeyPath:#"" usingMapping:mappingsForContext];
foundMappable = YES;
results = [NSDictionary dictionaryWithObject:mappingResult forKey:#""];
}
but mappingResult over there comes back nil... so the app crashes when it tries to create an NSDictionary with a nil object.

Break up the assignment into two lines.
SomeDataType *object = [parser objectFromString:#"{\"unknownObject\":\"\"}" error:nil];
if(object){
*mappableData = object;
}else{
// You've got nil, do something with it
}
Now you can check for nil values and take the appropriate action. What that action is depends on the context of the crash.

Related

Passing NSArray of custom objects as NSData via WatchConnectivity's sendMessageData

Once a WKInterfaceController's didAppear function is fired, I send an empty NSData to WCSession's default session with the sendMessageData callback function:
// WKInterfaceController
NSData *emptyData = [[NSData alloc] init];
[[WCSession defaultSession] sendMessageData:emptyData replyHandler:^(NSData *replyMessageData) {
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:replyMessageData];
} errorHandler:^(NSError *error) {
NSLog(#"WATCH: Error from replyData %#", error);
}];
The emptyData NSData object is sent because sendMessageData: is a non-null argument. I only use it to be able to fire WCSession's Delegate method, didReceiveMessageData on the iOS app. Then the replyHandler in that very function sends the appropriate data back to the replyHandler to the WKInterfaceController.
// UITableViewController
- (void)session:(WCSession *)session didReceiveMessageData:(NSData *)messageData replyHandler:(void (^)(NSData * _Nonnull))replyHandler
{
[self loadData:nil onSuccess:^(NSArray *tips) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:tips];
replyHandler(data);
}];
}
The problem I'm having is that I get a crash on the following line in the WKInterfaceController
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:replyMessageData];
Here's the error I get:
* Terminating app due to uncaught exception
'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (Tip) for key (NS.objects); the class may be defined in source
code or a library that is not linked'
What I've found so far:
The NSArray I'm trying to pass is made of custom objects (Tip.m). I know that all of the objects within the NSArray must conform to the NSCoding protocol (How to convert NSArray to NSData?), which I have done properly in my opinion. I've encoded and decoded every variable and object within the object with initWithCoder and encodeWithCoder.
My Tip.m object should be added to my WatchKit Extension (NSInvalidUnarchiveOperationException cannot decode object error in Apple Watch extension). Adding the Tip.m file only gives me: "Undefined symbols for architecture i386" from other objects.
Sorry for the long post but I've tried everything to find a solution to this problem, without success. Hope this helps more people that are having issues with WatchConnectivity Framework.
I solved this temporarily by using didReceiveMessage (the NSDictionary version instead of the NSData).
I sent a manually created NSDictionary of a single NSArray that held regular NSStrings of my previous custom objects.
I have the same scenario and reached the same problem. After some searching (without any luck) and experimenting, I've solved it by adding the -all_load flag to the linker flags in the extension target.

Having trouble adding objects to NSMutableArray

Im having truble adding objects to my 2 NSMutableArrays. The data comes from a database and I know that my parsing is correct because I get valid outputs when I use the NSLog. However I can't figur out how to add the 2 different objects to my 2 different NSMutableArrays. Here is my code
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
allDataDictionary = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
feed = [allDataDictionary objectForKey:#"feed"];
arrayOfEntry = [feed objectForKey:#"entry"];
for (NSDictionary *dictionary in arrayOfEntry) {
NSDictionary *title = [dictionary objectForKey:#"title"];
NSString *labelTitle = [title objectForKey:#"label"];
[arrayLabel addObject:labelTitle];
NSDictionary *summary = [dictionary objectForKey:#"summary"];
NSString *labelSummary = [summary objectForKey:#"label"];
[arraySummary addObject:labelSummary]; //This line makes the application crash
}
}
for some reason when I want to add labelSummary to arraySummary I get this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Any help is appreciated.
Your parsing is indeed correct. However, when the parser comes across an empty field it returns nil. The problem is the that NSArrays cannot accept nils, because nil is not an object, it's equivalent to 0. Therefore, you most add an object. This the role of NSNull.
Must test to see if the parser returns nil, and if so add [NSNull null].
NSString* labelSummary = [summary objectForKey:#"label"];
[arraySummary addObject:(labelSummary!=nil)?labelSummary:[NSNull null];
The error message tells you that one of the objects you are trying to add to the array is nil.
You have to replace
[arrayLabel addObject:labelTitle];
with
if (labelTitle != nil) {
[arrayLabel addObject:labelTitle];
}
and
[arraySummary addObject:labelSummary];
with
if (labelSummary != nil) {
[arraySummary addObject:labelSummary];
}
If you really need to include a nil object, then use NSNull.

Objective-C errors after getting the IAP product response

This code is from the Phonegap Code: IAP Plugin. The error happens on the line of the code right after the "sent js". All the elements sent to the function are non-nil except for the last one 'nil'. I even logged them out to make sure they were sent. This code is right out of the plugin (https://github.com/usmart/InAppPurchaseManager-EXAMPLE) and has not been modified except for the logging. In the debugger i saw that none of the objects were nil, so i don't understand why the error is happening.
Here is the error:
[__NSArrayI JSONRepresentation]: unrecognized selector sent to
instance 0xdc542d0
2013-02-13 23:26:17.209 GoblinSlots[4519:707] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'-[__NSArrayI JSONRepresentation]: unrecognized selector sent to
instance 0xdc542d0'
here is the code:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response
{
NSLog(#"got iap product response");
for (SKProduct *product in response.products) {
NSLog(#"sending js for %#", product.productIdentifier);
NSLog(#" title %#", product.localizedTitle );
NSLog(#" desc%# - %#", product.localizedDescription, product.localizedPrice );
NSArray *callbackArgs = [NSArray arrayWithObjects:
NILABLE(product.productIdentifier),
NILABLE(product.localizedTitle),
NILABLE(product.localizedDescription),
NILABLE(product.localizedPrice),
nil ];
NSLog(#"sent js");
NSString *js = [NSString stringWithFormat:#"%#.apply(plugins.inAppPurchaseManager, %#)", successCallback, [callbackArgs JSONSerialize]];
NSLog(#"js: %#", js);
[command writeJavascript: js];
}
All the stuff to do JSON serialization seems to be already included with the Cordova plugins.
There's no need to download and install yet another JSON library.(a)
It appears that PhoneGap is in the process of switching from SBJson to JSONKit.(b)
PhoneGap is also in the process of changing all the JSON methods to use a "cdvjk_" prefix. (c)
As far as I can tell, something didn't quite go right during those changes.
What I did was edit the file Plugins/InAppPurchaseManager.m , where I made these changes:
Add the line
#import <Cordova/CDVJSON.h>
Replace the line
return [self respondsToSelector:#selector(cdvjk_JSONString)] ? [self cdvjk_JSONString] : [self cdvjk_JSONRepresentation];
with
return [self JSONString];
. (What's the right way to push this or a better bugfix back to the nice PhoneGap people?)
JSONRepresentation is a category that SBJson adds so you have to include SBJson.h in the class that uses it.

How to initialize, pass argument, and check error condition using NSError**

Xcode 4.3
I've read the SO questions on NSError**, so I wrote a simple test program that uses a slightly different syntax recommended by Xcode 4.3 (see __autoreleasing below), so I'm not 100% sure if this is correct, although the code does appear to function properly. Anyway, just a simple file reader, prints an error if the file can't be found.
Questions
Would like to know if the NSError initialization, argument passing using &, and error condition checking are correct.
Also, in the readFileAndSplit.. method, I noticed a big difference between if(!*error) and if(!error), in fact, if(!error) does not work when no error condition is raised.
File Reading Method w/Possible Error Condition
-(NSArray*) readFileAndSplitLinesIntoArray:(NSError *__autoreleasing *) error {
NSString* rawFileContents =
[NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error
NSArray* fileContentsAsArray = nil;
if(!*error)
fileContentsAsArray =
[rawFileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
return fileContentsAsArray;
Caller
SimpleFileReader* reader = ...
NSError* fileError = nil;
NSArray* array = [reader readFileAndSplitLinesIntoArray: &fileError];
if(fileError){
NSLog(#"Error was : %#, with code: %li",
[fileError localizedDescription],(long)[fileError code]);
}
There are a couple of issues.
First, As per Apple's Error Handling Programming Guide, you should be checking a method's return value to determine whether a method failed or not, and not NSError. You only use NSError to get additional error information in the event that the method failed.
E.g.:
NSArray* fileContentsAsArray = nil;
NSString* rawFileContents = [NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error];
if (rawFileContents)
{
// Method succeeded
fileContentsAsArray = [rawFileContents ...];
}
return fileContentsAsArray; // may be nil
Second, NSError out parameters are typically optional and may be NULL. But if you pass a NULL error variable into your method it will crash on this line:
if (!*error) {
because you're dereferencing a NULL pointer. Instead, you must always check for NULL before referencing a pointer, like so:
if (error && *error)
{
// Do something with the error info
}
However, if you rewrite the method as indicated above then you won't be accessing the error variable at all.

Using blocks within blocks in Objective-C: EXC_BAD_ACCESS

Using iOS 5's new TWRequest API, I've ran into a brick wall related with block usage.
What I need to do is upon receiving a successful response to a first request, immediately fire another one. On the completion block of the second request, I then notify success or failure of the multi-step operation.
Here's roughly what I'm doing:
- (void)doRequests
{
TWRequest* firstRequest = [self createFirstRequest];
[firstRequest performRequestWithHandler:^(NSData* responseData,
NSHTTPURLResponse* response,
NSError* error) {
// Error handling hidden for the sake of brevity...
TWRequest* secondRequest = [self createSecondRequest];
[secondRequest performRequestWithHandler:^(NSData* a,
NSHTTPURLResponse* b,
NSError* c) {
// Notify of success or failure - never reaches this far
}];
}];
}
I am not retaining either of the requests or keeping a reference to them anywhere; it's just fire-and-forget.
However, when I run the app, it crashes with EXC_BAD_ACCESS on:
[secondRequest performRequestWithHandler:...];
It executes the first request just fine, but when I try to launch a second one with a handler, it crashes. What's wrong with that code?
The methods to create the requests are as simple as:
- (TWRequest*)createFirstRequest
{
NSString* target = #"https://api.twitter.com/1/statuses/home_timeline.json";
NSURL* url = [NSURL URLWithString:target];
TWRequest* request = [[TWRequest alloc]
initWithURL:url parameters:params
requestMethod:TWRequestMethodGET];
// _twitterAccount is the backing ivar for property 'twitterAccount',
// a strong & nonatomic property of type ACAccount*
request.account = _twitterAccount;
return request;
}
Make sure you're keeping a reference/retaining the ACAccountStore that owns the ACAccount you are using to sign the TWRequests.
If you don't, the ACAccount will become invalid and then you'll get EXC_BAD_ACCESS when trying to fire a TWRequest signed with it.
I'm not familiar with TW*, so consider this a wild guess ... try sending a heap-allocated block:
[firstRequest performRequestWithHandler:[^ (NSData *responseData, ...) {
...
} copy]];
To clarify, I think the block you're sending is heap-allocated, so while TW* might be retaining it, it won't make any difference if it has already gone out of scope.