Undefined selector with Objective-C runtime - blocks - ios

I'm trying to create a function where multiple times I do the same thing. I've deceided to go with a block this time. However after writing following code:
- (BOOL)readyForProcessing {
void (^notDeclaredError)(id) = ^(id missingObject) {
NSString *missingObjectName = NSStringFromSelector(#selector(missingObject));
NSString *errorDescription = [NSString stringWithFormat:#"You need to provide %# property", missingObjectName];
[self configureErrorWithLocalizedDescription:errorDescription];
};
if (!self.delegate) notDeclaredError(self.delegate);
return (self.error == nil);
}
I get a warning in the line, where I declare missingObjectName.
Undeclared selector 'missingObject'
I know it will probably try to make a NSString from missingObject instead of delegate. How to pass it this way, that the output will be delegate and the code will be inside the block?

Here you are:
- (BOOL)readyForProcessing {
void (^notDeclaredError)(SEL) = ^(SEL missingObject) {
NSString *missingObjectName = NSStringFromSelector(missingObject);
NSString *errorDescription = [NSString stringWithFormat:#"You need to provide %# property", missingObjectName];
[self configureErrorWithLocalizedDescription:errorDescription];
};
if (!self.delegate) notDeclaredError(#selector(delegate));
return self.error ? NO : YES;
}

Related

Should I use function parameters in block, or keep them as __block variables

When I use a instance variable inside a block, the best practice is to add __block before it, so the scope is async, and I will be able to use it after.
Example:
-(void) someFunction {
__block NSString *nameIWillUseInBlock = #"Some name"; //Here ill use the __block
[self functionWithCallback:^(NSString *string) {
NSString *stringWithString = [NSString stringWithFormat:#"%#%#", nameIWillUseInBlock, #"with bla"];
//Here Ill do somthing with this string
}];
}
So my question is, what about params passed to the function, can I use them like so:
-(void) someFunctionWithParam:(NSString *) nameIWillUseInBlock {
[self functionWithCallback:^(NSString *string) {
NSString *stringWithString = [NSString stringWithFormat:#"%#%#", nameIWillUseInBlock, #"with bla"];
//Here Ill do somthing with this string
}];
}
Or should I keep the param as a __block instance like so:
-(void) someFunctionWithParam:(NSString *) nameIWillUseInBlock {
__block NSString *asyncNameIWillUseInBlock = nameIWillUseInBlock;
[self functionWithCallback:^(NSString *string) {
NSString *stringWithString = [NSString stringWithFormat:#"%#%#", asyncNameIWillUseInBlock, #"with bla"];
//Here Ill do somthing with this string
}];
}
And if I should use the params, and not as a __block instance, how does the scope know witch params did pass before the current scope?
Thanks.
Here is a really good apple doc on the topic Working With Blocks
I think the section "Blocks Can Capture Values from the Enclosing Scope" is what you are looking for specifically.
"In this example, anInteger is declared outside of the block, but the value is captured when the block is defined.
Only the value is captured, unless you specify otherwise. This means that if you change the external value of the variable between the time you define the block and the time it’s invoked"
So if you look at this:
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(#"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
The log will show
Integer is: 42
"Use __block Variables to Share Storage"
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(#"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
The log will show
Integer is: 84
You can youse parameter directly in block you can use this code.
-(void) someFunctionWithParam:(NSString *) nameIWillUseInBlock {
[self functionWithCallback:^(NSString *string) {
NSString *stringWithString = [NSString stringWithFormat:#"%#%#", nameIWillUseInBlock, #"with bla"];
//Here Ill do somthing with this string
}];
}

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

nil object in NSDictionary

I have a NSDictionary from which I read some values in two different methods in my code, however only in one of the methods the value of the key is retrieved correctly whereas in the other method I always get nil.
This is the method that works:
-(void)drawDriver:(NSString*)driverId withPosition:(CLLocationCoordinate2D)position withRotation:(float)bearing isAvailable:(BOOL)isDriverAvailable{
GMSMarker *driverMarker = [driversList objectForKey:driverId];
if (driverMarker != nil) {
// do some work
}
}
and it's called from this other method:
- (void) socketIO:(SocketIO *)socket didReceiveMessage:(SocketIOPacket *)packet
{
NSMutableDictionary *datos = (NSMutableDictionary*)packet.data;
NSDictionary *driverId = datos[#"from"];
NSString *id = [driverId objectForKey:#"id"];
//gets other values
NSLog(#"Socket message: driverId: %#, position %f,%f, isAvailable:%d", id, latitude, longitude, isDriverAvailable);
//Call the first method
[self drawDriver:id withPosition:position withRotation:bearing isAvailable:isDriverAvailable];
}
And this is the method that doesn't work (returns nil for driverMarker):
-(void)updateMapForTripWithDriver:(NSString *)driverId {
GMSMarker *driverMarker = [driversList objectForKey:driverId];
if (driverMarker != nil) {
//do some work
}
}
and this is called from here:
- (void)updateTripWithData:(NSDictionary *)data {
//This NSDictionary comes from a notification
NSString *newStatus = [[data objectForKey:#"aps"] objectForKey:#"alert"];
if ([newStatus isEqualToString:NO_TRIP]) {
} else if ([newStatus isEqualToString:WAITING_CONFIRMATION]) {
} else if ([newStatus isEqualToString:DRIVER_ON_THE_WAY]) {
//Get the driverId and call the other method
NSString *driverId = [data objectForKey:#"driver_id"];
[self updateMapForTripWithDriver:driverId];
}
}
As you can see both methods have the exact same code to retrieve the object but still I get different results.
The only difference I could find is when I put some breakpoints on the methods and this is what I found:
On the first method, although I pass the driverId as a NSString, somehow it's read as NSFCNumber and that seems to work because I get the value with no problems.
But on the second method the key is read as an actual NSString which for some reason makes the method to return nil for an object that does exists on the NSDictionary.
How can I get the second method to work and return the right value?

autoreleasepool for nested methods

Say I have following code:
- (void) abc
{
#autoreleasepool
{
NSString *str = [NSString stringWithFormat:#"ABC %d", 12];
[self pqr];
}
}
- (void) pqr
{
NSString *str2 = [NSString stringWithFormat:#"PQR %d", 14];
}
For the above code, after the execution of method abc, will only str be released or str2 will also be released?
Both. If you are not sure, create your custom class and override dealloc implementation and add there some log message to not only believe me but experienced it on your own.

Objective-C exercises undeclared identifier error

I'm currently teaching myself Objective-C as a first language. I understand the difficulty involved, but I'm quiet a persevering individual. I've began to do the exercises on the Apple Objective-C documentation. My goal is to have my program log out my first and last name instead of a generic Hello World greeting.
I keep receiving a Use of Undeclared identifier error. I'm trying to figure out what is causing the error.
Here is the introClass.h
#import <UIKit/UIKit.h>
#interface XYZperson : NSObject
#property NSString *firstName;
#property NSString *lastName;
#property NSDate *dateOfBirth;
- (void)sayHello;
- (void)saySomething:(NSString *)greeting;
+ (instancetype)person;
-(int)xYZPointer;
-(NSString *)fullName;
#end
Here is IntroClass.m
#import "IntroClass.h"
#implementation XYZperson
-(NSString *)fullName
{
return[NSString stringWithFormat:#" %# %#", self.firstName, self.lastName];
}
-(void)sayHello
{
[self saySomething:#"Hello %#", fullName]; //use of undeclared identifier "fullName"
};
-(void)saySomething:(NSString *)greeting
{
NSLog(#"%#", greeting);
}
+(instancetype)person{
return [[self alloc] init];
};
- (int)xYZPointer {
int someInteger;
if (someInteger != nil){
NSLog(#"its alive");
}
return someInteger;
};
#end
The problem is that fullName is the name of a method. It should be invoked on self with square brackets.
Since saySomething: expects a single parameter, you need to either (1) remove the #"Hello %#" portion of the call, like this:
-(void)sayHello {
[self saySomething:[self fullName]];
};
or to make a single string from #"Hello %#" and [self fullName], like this:
-(void)sayHello {
[self saySomething:[NSString stringWithFormat:#"Hello %#", [self fullName]]];
};
You are passing back a string of first and last name but I don't see anywhere that you had set a value for them. As others noted try
-(void)sayHello
{
_firstName = [NSString stringWithFormat:#"John"];
_lastName = [NSString stringWithFormat:#"Doe"];
//if you want to see what's happening through out your code, NSLog it like
NSLog(#"_firstName: %# ...", _firstName);
NSLog(#"_lastName: %# ...", _lastName);
NSString *strReturned = [self fullName];
NSString *concatStr = [NSString stringWithFormat:#"Hello %#", strReturned];
NSLog(#"strReturned: %# ...", strReturned);
NSLog(#"concatStr: %# ...", concatStr);
[self saySomething:concatStr];
};
-(NSString *)fullName
{
return[NSString stringWithFormat:#" %# %#", self.firstName, self.lastName];
}
use
[self saySomething:#"Hello %#", self.fullName]];
or
[self saySomething:#"Hello %#", [self fullName]];

Resources