Objective-C data type issue - ios

I'm not that strong with Objective-C, so this is probably a simple issue. I cannot understand why the the last line in the error completion block is causing an exception:
- (void)sendInappropriateNewsfeedComment:(NSString *)comment newsfeedEventId:(NSString *)newsfeedEventId completion:(void (^)(NSString *, NSInteger))completion error:(void (^)(NSString *, NSInteger))error {
PAInappropriateNewsFeedRequest *inappropriateNewsfeedRequest = [[PAInappropriateNewsFeedRequest alloc] initWithComment:comment newsfeedEventId:newsfeedEventId];
[inappropriateNewsfeedRequest executeWithCompletionBlock:^(id obj) {
completion(#"SUCCESS", (NSInteger)1);
} error:^(NSError *e, id obj) {
NSString * message = [obj objectForKey:#"message"];
error(message, [obj integerForKey:#"code"]);
}];
}
I've also attached a screenshot showing that the "obj" object has a key called "code" that is of type "(long)-1".
What is the proper way to declare the error block and pass the "-1" value back to the call site?

Simply because NSDictionary has no method called integerForKey. That's what "unrecognized selector" means. Selector is basically a method name.
The fact that this can be even compiled is caused by using id for the parameter type. You can call anything on id but it will crash your app if the method does not exist. You should cast obj to a proper type as soon as possible.
NSDictionary *dictionary = (NSDictionary *) obj;
NSString *message = dictionary[#"message"];
NSNumber *code = dictionary[#"code"];
If obj can be a different type, you should make sure to check [obj isKindOfClass:NSDictionary.self] before casting.

The whole solution taking Sulthan's suggestions into account might looks something like this
typedef void (^NewFeedCompletion)(NSString *, NSInteger);
typedef void (^NewsFeedError)(NSString *, NSInteger);
- (void) sendInappropriateNewsfeedComment: (NSString *)comment
newsfeedEventId: (NSString *)newsfeedEventId
completion: (NewFeedCompletion) completion
error: (NewsFeedError) error
{
PAInappropriateNewsFeedRequest *inappropriateNewsfeedRequest = [[PAInappropriateNewsFeedRequest alloc] initWithComment:comment newsfeedEventId:newsfeedEventId];
[inappropriateNewsfeedRequest executeWithCompletionBlock: ^(id obj) {
completion(#"SUCCESS", (NSInteger)1);
} error:^(NSError *e, id obj) {
assert([obj isKindOfClass: NSDictionary.class])
NSDictionary *errorDictionary = (NSDictionary *) obj;
NSString *message = [errorDictionary objectForKey: #"message"];
NSNumber *code = [errorDictionary objectForKey: #"code"]
error(message, [code integerValue]);
}];
}

Related

Return type 'NSDictionary *' must match previous return type 'void' when block literal has unspecified explicit return type

Sir I am using google place Autocomplete and I am finding coordinate of a place I am using below code but i found an error in returning statement-
there are 5 places name and placeID in finalarray
-(NSDictionary * ) checkArr
{
NSLog(#"%#",finalarray);
NSDictionary *dict = [finalarray objectAtIndex:0];
NSString *placeID = [dict objectForKey:#"place_ID"];
NSDictionary * dict1 = [self getquardinate:placeID];
return dict1;
}
I am finding above error in return statement while returning dictionary
- (NSDictionary * )getquardinate:(NSString*)placeID{
[_placesClient lookUpPlaceID:placeID callback:^(GMSPlace *place, NSError *error) {
if (error != nil) {
NSLog(#"Place Details error %#", [error localizedDescription]);
return ;
}
if (place != nil) {
NSNumber *lat = [NSNumber numberWithDouble:place.coordinate.latitude];
NSNumber *lon = [NSNumber numberWithDouble:place.coordinate.longitude];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:lat, #"latitude",lon, #"longitude", nil];
return dict; // above error in this line
}
}];
}
Your block has one plain return statement (returns void) and one statement return dict (returns an NSDictionary*). That's not allowed.
If you had defined the return type of the block as void, the second would give an error. If you had defined the return type of the block as NSDictionary*, the first would give an error. If you had defined the return type of the block as NSInteger, both would give an error.
You didn't specify the return type, leaving it to the compiler. After the first "return" the compiler thinks "well, return type must be void" so you get an error message on the second return.
PS. As trojanfoe remarked, you need to figure out how asynchronous calls work. What you are trying to do cannot possibly work that way. Another PS: What is a quardinate? Are you from Texas where "coordinate" sounds like "quardinate"?

NSMutableDictionary inside JSONModel - EXC_BAD_ACCESS KERN_INVALID_ADDRESS

Crashlytics reported this crash in one of my apps and I am not able to reproduce it at all, no matter what I do.
This happens to about 5% of the users, so it's a pretty big deal.
I'm posting screenshots with the crash report and also the methods that are mentioned in the crash report.
Any idea how to solve this?
This is where the app crashed:
#pragma mark - custom transformations
-(BOOL)__customSetValue:(id<NSObject>)value forProperty:(JSONModelClassProperty*)property
{
if (!property.customSetters)
property.customSetters = [NSMutableDictionary new];
NSString *className = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:[value class]]);
if (!property.customSetters[className]) {
//check for a custom property setter method
NSString* ucfirstName = [property.name stringByReplacingCharactersInRange:NSMakeRange(0,1)
withString:[[property.name substringToIndex:1] uppercaseString]];
NSString* selectorName = [NSString stringWithFormat:#"set%#With%#:", ucfirstName, className];
SEL customPropertySetter = NSSelectorFromString(selectorName);
//check if there's a custom selector like this
if (![self respondsToSelector: customPropertySetter]) {
property.customSetters[className] = [NSNull null]; // this is line 855
return NO;
}
//cache the custom setter selector
property.customSetters[className] = selectorName;
}
if (property.customSetters[className] != [NSNull null]) {
//call the custom setter
//https://github.com/steipete
SEL selector = NSSelectorFromString(property.customSetters[className]);
((void (*) (id, SEL, id))objc_msgSend)(self, selector, value);
return YES;
}
return NO;
}
This is the originating method:
-(void)reloadUserInfoWithCompletion:(void (^) (LoginObject *response))handler andFailure:(void (^)(NSError *err))failureHandler {
NSString *lat;
NSString *lon;
lat = [NSString stringWithFormat:#"%.6f",[[LocationManager sharedInstance] getPosition].coordinate.latitude];
lon = [NSString stringWithFormat:#"%.6f",[[LocationManager sharedInstance] getPosition].coordinate.longitude];
NSMutableDictionary *params = [NSMutableDictionary new];
[params setObject:lat forKey:#"latitude"];
[params setObject:lon forKey:#"longitude"];
[[LoginHandler sharedInstance] getLoginToken:^(NSString *response) {
NSDictionary *headers;
if (response) {
headers = #{#"Login-Token":response};
}
GETRequest *req = [GETRequest new];
[req setCompletionHandler:^(NSString *response) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"response: %#",response);
NSError *err = nil;
self.loginObject.userDetails = [[User alloc] initWithString:response error:&err]; // <- this is the line reported in the crash
[self storeLoginObject];
NSLog(#"%#",self.loginObject.userDetails);
// [Utils updateFiltersFullAccessIfAll];
dispatch_async(dispatch_get_main_queue(), ^{
if (handler) {
handler(self.loginObject);
}
});
});
}];
[req setFailedHandler:^(NSError *err) {
if (failureHandler) {
failureHandler(err);
}
}];
NSLog(#"%#",params);
[req requestWithLinkString:USER_DETAILS parameters:nil andHeaders:headers];
}];
}
So setObject:forKey: can cause problems in two ways. 1. If object is nil or 2. the key is nil. Both could cause the crash you are seeing. Given that you are setting the object to [NSNull null] it is probably safe to assume that it is the key giving you problems (on line 855).
Walking back from there that would reveal that className is nil. If you look, your code does not protect against this. You make an assumption here that NSStringFromClass (a couple lines before) is giving you back a valid string, which assumes that the value originally passed into the method is non-nil. If it is nil it would make it past all of your checks, including !property.customSetters[className], since this would be !nil allowing it to enter the if.
If I am reading your code right (a bit hard since I cannot test any of my assumptions) NSLog(#"response: %#",response); would print out a nil response.
Try seeing how your code handles these unexpected nils and let me know in the comments how things go.
If you don't use model custom setters you can replace JSONModel __customSetValue:forProperty: with swizzling or Aspects library
#import "JSONModel+Aspects.h"
#import "JSONModel.h"
#import "Aspects.h"
#implementation JSONModel (Aspects)
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[JSONModel aspect_hookSelector:#selector(__customSetValue:forProperty:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo) {
return NO;
} error:NULL];
});
}
#end

Exception isn't thrown when nil inserted in NSDictionary

So I was working on pulling photos from user's Facebook album and for some reason NSDictionary doesn't throw an exception when nil is inserted.
I've tested it on a real device and on iOS simulator (both iOS 7).
Maybe debugger crashes, maybe it's Facebook SDK bug.
Anyway, I'm ready to listen to your opinion.
UPDATE: One more thing I forgot to mention. In debugger, after inserting nil object in dictionary, debugger itself continues program execution. And I'm not even pressing the buttong "Continue program execution".
Here's the snippet
- (void)downloadAlbumsWithCompletionBlock:(void (^)(NSArray *albums, NSError *error))completionBlock;
{
[FBRequestConnection startWithGraphPath:#"/me/albums"
parameters:nil
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if (error)
{
NSLog(#"%#", error.localizedDescription);
completionBlock(nil, error);
}
else
{
NSMutableArray *data = [result objectForKey:#"data"];
NSMutableArray *albums = [NSMutableArray new];
for (NSMutableDictionary *dict in data)
{
NSString *albumID = dict[#"id"];
NSString *coverPhotoID = dict[#"cover_photo"];
// coverPhotoID gets nil and then successfully gets put in NSDictionary
// You can just write "NSString *coverPhotoID = nil;".
NSString *description = dict[#"description"];
NSString *name = dict[#"name"];
NSDictionary *album = #{#"album_id": albumID,
#"album_cover_photo_id": coverPhotoID,
#"album_description": description,
#"album_name": name };
// Here an exception should be thrown but for some reason
// we just quit out of the method. Result would be the same if we put
// empty "return;" statement.
// If we explicitly put nil, like
// #"album_cover_photo_id": nil
// we get compiler error.
[albums addObject:album];
}
completionBlock(albums, nil);
}
}];
}
You wrote:
// Here an exception should be thrown but for some reason
// we just quit out of the method. Result would be the same if we put empty "return;" statement.
Most likely the exception is caught somewhere. Try putting a breakpoint on Objective-C exception throws (In Xcode, go to the Breakpoint tab, click + in the bottom left corner and select Add Exception Breakpoint. Make sure the parameters are set to Objective-C exception and break On Throw).
You claim that
NSString *albumID = dict[#"id"];
NSString *coverPhotoID = nil;
NSString *description = dict[#"description"];
NSString *name = dict[#"name"];
NSDictionary *album = #{#"album_id": albumID,
#"album_cover_photo_id": coverPhotoID,
#"album_description": description,
#"album_name": name };
doesn't crash your app.
Honestly, I don't believe it. Either you're trolling or you're misinterpreting whats happening.
As the first line of your for cycle log the dict dictionary:
NSLog(#"dict var: %#", dict);
Does dict contains the key album_cover_photo_id?
If you get something like
"album_cover_photo_id" : null
then NSString *coverPhotoID = dict[#"cover_photo"] is assigning an NSNull instance to coverPhotoID. In that case the app doesn't crash because an NSNull instance is not nil.
NSString *coverPhotoID = nil;
is different then
NSString *coverPhotoID = [NSNull null];
It's very common for servers to return nulls in JSON instead of omitting the key (server people are weird).
You can also log album after you create it
NSLog(#"album var: %#", album);
And if you are 100 % sure you're adding a nil to a dictionary, take it to the Apple developer forum. I bet they'd love to know about that bug.
I hope this helps you in some way.

Bridge casts for collection elements

I'm using a vendor API that returns a CFDictionaryRef that can contain a variety of CF object types and which the caller needs to CFRelease. I'd like to cast it to an NSDictionary to more easily work with it, and want to make sure I understand I'm handling the elements correctly in terms of casting.
It looks to me like toll-free bridged types (e.g. CFString, CFNumber) are just handled by NSDictionary and I can just get the NS types as I would if they'd been Obj-C types all along (I'm guessing there's a bridge cast going on under the covers).
For a non-toll-free bridged type (e.g. CFHost) it looks like I can bridge cast the result from -valueForKey: into the CF type and go from there, though I'm not positive if I need to release that value or not.
Here's some sample code that illustrates the problem. Is this the right way to handle things?
// Caller is responsible for releasing returned value
//
+ (CFDictionaryRef)someCFCreateFunctionFromVendor
{
CFMutableDictionaryRef cfdict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(cfdict, CFSTR("string1"), CFSTR("value"));
int i = 42;
CFDictionarySetValue(cfdict, CFSTR("int1"), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i));
CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault, CFSTR("myhost"));
CFDictionarySetValue(cfdict, CFSTR("host1"), host);
return cfdict;
}
+ (void)myMethod
{
NSDictionary *dict = CFBridgingRelease([self someCFCreateFunctionFromVendor]);
for (NSString *key in [dict allKeys]) {
id value = [dict valueForKey:key];
NSLog(#"%# class: %#", key, [value class]);
if ([value isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)value;
NSLog(#"%# is an NSString with value %#", key, str);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *num = (NSNumber *)value;
NSLog(#"%# is an NSNumber with value %#", key, num);
} else if ([value isKindOfClass:[NSHost class]]) {
NSLog(#"%# is an NSHost", key); // never hit because no toll-free bridge to NSHost
} else {
NSLog(#"%# is an unexpected class", key);
}
// Sample handling of non-toll-free bridged type
if ([key isEqualToString:#"host1"]) {
CFHostRef host = (__bridge CFHostRef)value;
NSArray *names = (__bridge NSArray *)(CFHostGetNames(host, false));
NSLog(#"host1 names: %#", names);
// I don't think I need to CFRelease(host) because ARC is still handling value
}
}
}
Output...
string1 class: __NSCFConstantString
string1 is an NSString with value strvalue
int1 class: __NSCFNumber
int1 is an NSNumber with value 42
host1 class: __NSCFType
host1 is an unexpected class
host1 names: ( myhost )
Ironically, the only error in your code is in the Core Foundation, non-ARC stuff. Your +someCFCreateFunctionFromVendor method calls CFNumberCreate() and CFHostCreateWithName(), both of which are Create functions, but doesn't CFRelease() the objects after adding them to the dictionary. It should. Otherwise, it's a leak. The static analyzer would catch that, by the way. (For the CFNumber, that means you have to split creation into a separate line so you can reference the object after adding it to the dictionary.)
You must not release the host object in +myMethod because that code doesn't receive ownership of it. It has nothing to do with ARC. You wouldn't release it there in MRR (manual retain/release) code, either.

SoapRequest to NSString on iOS?

I'm using an API generated by Sudzc.com from a WDSL. I have this method:
- (SoapRequest*) getToolListAsXML: (id <SoapDelegate>) handler getEmptyFC: (BOOL) getEmptyFC repoid: (NSString* ) repoid
And I thought that It calls the webservice and receive an XML as string as the documentation generated by sudzc.com told me:
But I really don't know how the SoapDelegate works, if I want the response (the list as string) what I'm supposed to do? The examples are more confusing, it says:
But, obviously,
NSString resp = [service getToolListAsXML:self action:#selector(getToolListAsXMLHandler:) getEmptyFC: NO repoid: #""];
doesn't work because of 'incompatible pointer types..."
I'm very new on this so sorry if what I'm saying has nonsense.
Thanks.
Try this
[service getToolListAsXML:self action:#selector(getToolListAsXMLHandler:) getEmptyFC: NO repoid: #""]
- (void) getToolListAsXMLHandler: (id) value {
if([value isKindOfClass:[NSError class]]) {
//NSLog(#"%#", value);
return;
}
// Handle faults
if([value isKindOfClass:[SoapFault class]]) {
//NSLog(#"%#", value);
return;
}
NSString * resp =(NSString *)value;
}
you will get soap request in string

Resources