Objective-C block doesn't return and call it self in the block? - objective-c

I'm learning to develop iOS applications and now I'm reading some Objective-C source code.
This is a method to get user profile.
+ (void)getProfile:(void (^)(NSString *message))completion {
NSDictionary *dic = #{#"module":#"profile"};
[[self defaultManager] POST:KBaseUrl parameters:dic success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([self jsonOKForResponseObject:responseObject] && [self checkLogin:responseObject]) {
[ProfileManager sharedInstance].rank = responseObject[#"Variables"][#"space"][#"group"][#"grouptitle"];
[ProfileManager sharedInstance].credit = responseObject[#"Variables"][#"space"][#"credits"];
[ProfileManager sharedInstance].gender = responseObject[#"Variables"][#"space"][#"gender"];
completion(nil);
} else {
completion(#"fail");
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completion(#"fail");
}];
}
My question is about the completion block.
I suppose that the completion block returns void and receives an NSString parameter.
In the block, what does completion(nil) mean?
Does that mean the block completion calls it self and send nil as parameter?
Doesn't that conflict with the parameter's type NSString*?
I'm not quite familiar with block in ObjC. Can anyone give a hint?

Yes you are right. It calls itself and sends nil as a parameter and it doesn't conflict with the NSString parameter. You are just passing nil to the NSString param.
You can call the above method like:
[YourClass getProfile:^(NSString *message) {
//message will be nill if you pass completion(nil);
}];
So when you pass the nill in the completion block, the message in the above method call will be nil!
The completion block is to notify you that your method call is complete, and at this point you can let that method pass certain paramteres , and if we consider your method:
+ (void)getProfile:(void (^)(NSString *message))completion {
NSDictionary *dic = #{#"module":#"profile"};
[[self defaultManager] POST:KBaseUrl parameters:dic success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([self jsonOKForResponseObject:responseObject] && [self checkLogin:responseObject]) {
.....
completion(#"hey sucess");
} else {
completion(#"if loop failed");
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completion(#"some error occured");
}];
}
and when you call the method getProfile:
[YourClass getProfile:^(NSString *message) {
//Execution will reach here when the method call for getPRofile is complete and you have a result which you just sent through the completion block as a parameter which is a string and is waiting for you to process.
//you can do more from here
if([message isEqualToString:#"hey success"]){
//do something
}
if([message isEqualToString:#"if loop failed"]){
//do something
}
if([message isEqualToString:#"some error occured"]){
//do something
}
}];
As per #rmaddy comment, iis always a good practice to use BOOL to indicate the status success or fail rather than depending on a string as string can get localized/changed. We shold use the string to get more description of the error.
So your block should be:
+ (void)getProfile:(void (^)(BOOL status,NSString *message))completion {
.....
completion(YES,#"hey success");
}
and you can call it like":
[YourClass getProfile:^(BOOL status, NSString *message) {
if(status){
//get the success message
}else{
//get the fail message
}
}];

Blocks had a lot of type like :-
As a local variable:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
As a property:
#property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
As a method parameter:
- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
As an argument to a method call:
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
As a typedef:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};
read more about it from here

Completion block does not return anything. It is just a piece of code to be executed, thats all. Though, you can give it some input where you call it so you can use the result elsewhere.
NSString *message is the input for your block so when you call your function getProfile as:
[MyClass getProfile:^(NSString *message) {
// write code to be executed when getProfile function finishes its job and sends message here.
}];
[MyClass getProfile:nil];
When used like this you're preferring not to do anything when getProfile finishes its job.
You are probably mixing your network manager's function's completion block with the one you wrote.

Related

How to understand the type of a objective-c block?

- (void)writeImageDataToSavedPhotosAlbum:(NSData *)imageData metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
In this function signature, the completionBlock has type ALAssetsLibraryWriteImageCompletionBlock. However, since when we are creating an anonymous function we use
^(int a){
//code here
}
It seems we've never specified anything to be the type of a block. So how to understand this unusual type here?
If you look at the docs for ALAssetsLibraryWriteImageCompletionBlock it is defined as:
typedef void (^ALAssetsLibraryWriteImageCompletionBlock)(NSURL *assetURL, NSError *error);
This is a block with no return value that has two parameters.
Your code needs to be something like:
[library writeImageDataToSavedPhotosAlbum:someData metadata:someMetaData completionBlock:^(NSURL *assetURL, NSError *error) {
// completion handler code here with access to assetURL and error
}];

dispatch_async in return method Objective-C

I have been trying to use dispatch_async in a method that returns a result. However, I observed that the method returns before executing the dispatch_async block. Due to this I'm not getting the results I expect. Here is some code that demonstrates my problem.
-(BOOL) isContactExists {
BOOL isContactExistsInXYZ = YES;
UserId *userId = contact.userId;
dispatch_async(dispatch_get_main_queue(), ^
{
iOSContact *contact = [iOSContact contactForUserId:userId];
if (nil == contact)
{
isContactExistsInXYZ = NO;
}
});
return isContactExistsInXYZ;
}
This method isContactExists is called somewhere else and based on the response from that method I have to do some stuff. But every time, the value of isContactExistsInXYZ is not what I expect. How do I handle dispatch_async in this situation?
If your going the block route your method needs to look something like this.
- (void)isContactExistsWithCompletionHandler:(void(^)(BOOL exists)) completion
{
dispatch_async(dispatch_get_main_queue(), ^
{
BOOL isContactExistsInXYZ = YES;
UserId *userId = contact.userId;
iOSContact *contact = [iOSContact contactForUserId:userId];
if (nil == contact)
{
isContactExistsInXYZ = NO;
}
completion(isContactExistsInXYZ);
});
}
And where you are calling it something like this.
[someObject isContactExistsWithCompletionHandler:^(BOOL exists) {
// do something with your BOOL
}];
You should also consider placing your heavy operations in a other que than main. Like this.
- (void)isContactExistsWithCompletionHandler:(void(^)(BOOL exists)) completion
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);
dispatch_async(queue, ^
{
BOOL isContactExistsInXYZ = YES;
UserId *userId = contact.userId;
iOSContact *contact = [iOSContact contactForUserId:userId];
if (nil == contact)
{
isContactExistsInXYZ = NO;
}
dispatch_async(dispatch_get_main_queue(), ^
{
completion(isContactExistsInXYZ);
});
});
}
You need to respect that what you are trying to do is asynchronous and embrace that. This means not using a return value. Instead you can write your method to take a callback block as a parameter. Then, when your asynchronous check is complete you can call the block with the result.
So your method signature would become:
- (void)checkIfContactExistsWithCompletion:(ContactExistsBlock)completion {
Where ContactExistsBlock is a block definition with no return and probably a single BOOL parameter.
typedef void (^ContactExistsBlock) (BOOL exists);
The reason is dispatch_async(dispatch_get_main_queue(), ^does not wait until execution is done. You are probably messing up stuff there. Normally, this is used to update UI asynchronously along with other server content getting downloaded in some other thread. Try using dispatch_sync instead.

Return User's State as an NSString with reverseGeocodeLocation

I am trying to simply return a user's state. I understand that I need to use reverseGeocodeLocation. I would like to return the state as an NSString in the same way that I am returning the user latitude below:
- (NSString *)getUserLatitude
{
NSString *userLatitude = [NSString stringWithFormat:#"%f",
locationManager.location.coordinate.latitude];
return userLatitude;
}
I currently have this code, but I cannot get it to work. It may be because I am using (void). I am just a bit lost.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation
*)newLocation fromLocation:(CLLocation *)oldLocation {
CLGeocoder * geoCoder = [[CLGeocoder alloc] init];
[geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks,
NSError *error) {
for (CLPlacemark * placemark in placemarks) {
NSString *userState = [placemark locality];
return userState;
}
}];
}
Anyone have any ideas? Thank you!
You have to do something with the retrieved locality in the completion block. This code is executed asynchronously long after the method (with void return) has returned itself.
Usually you would call some sort of method on your own view controller or model class that passes the retrieved information.
Replace the return userState, it does not match the return type of the block.
Instead put something like:
[myViewController didFinishGettingState:userState];
You should look into block and GCD basics so that you can appreciate how this asynchnonicity works.
You are probably not understanding the way your completionHandler is working. The reverseGeocodeLocation:completionHandler: takes an handler, which is a function that will be executed when the lookup is completed and invoked with the placemarks and error as parameters.
What you have to do is to perform something meaningful in that block.
I would start checking whether any error occurred, then I would call a method for failing or success as follows
[geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) {
if (error != nil) {
// Something bad happened...
[self didFailRetrievingUserState:error];
} else {
// Check whether the placemark retrieved is unique
if (placemarks.count > 1) {
NSMutableArray * states = [NSMutableArray array];
for (CLPlacemark * placemark in placemarks) {
NSString * userState = [placemark locality];
[states addObject:userState];
}
[self didFinishRetrievingUserStates:states];
} else {
[self didFinishRetrievingUserState:[placemarks[0] locality]];
}
}
}];
Then of course you need to implement the three methods we are calling in the block above
- (void)didFailRetrievingUserState:(NSError *)error {
// Show error
}
- (void)didFinishRetrievingUserStates:(NSArray *)userStates {
// Do something reasonable with the multiple possible values
}
- (void)didFinishRetrievingUserState:(NSString *)userState {
// Do something reasonable with the only result you have
}
Clearly the above code is meant as a suggestion. You can make different decisions, like for instance handling all the logic inside the handler block, or not discriminating between the unique/not unique cases.
In general it's just important that you understand that the handler block is not supposed to return anything since it's a void function. It's just supposed to do something, and this something may be invoking your "delegate" methods as defined in the example.

Why use Try/Catch instead of testing for an NSError

Sometimes you see a piece of iOS - Objective-C code use a Try/Catch structure.
For example this example from: http://docs.xrtml.org/2-1-0/pubsub/ios/ortcclient.html
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate OrtcClient
ortcClient = [OrtcClient OrtcClientWithConfig:self];
// Post permissions
#try {
NSMutableDictionary* myPermissions = [[NSMutableDictionary alloc] init];
[myPermissions setObject:#"w" forKey:#"channel1"];
[myPermissions setObject:#"w" forKey:#"channel2"];
[myPermissions setObject:#"r" forKey:#"channelread"];
BOOL result = [ortcClient saveAuthentication:#"http://ortc-developers.realtime.co/server/2.1/" isCLuster:YES authenticationToken:#"myAuthenticationToken" authenticationTokenIsPrivate:NO applicationKey:#"myApplicationKey" timeToLive:1800 privateKey:#"myPrivateKey" permissions:myPermissions];
if (result) {
// Permissions correctly posted
}
else {
// Unable to post permissions
}
}
#catch (NSException* exception) {
// Exception posting permissions
}
// Set connection properties
[ortcClient setConnectionMetadata:#"clientConnMeta"];
[ortcClient setClusterUrl:#"http://ortc-developers.realtime.co/server/2.1/"];
// Connect
[ortcClient connect:#"myApplicationKey" authenticationToken:#"myAuthenticationToken"];
}
Why use such a structure, couldn't you just check for an NSError (indirect) return from the saveAuthentication:isCLuster:authenticationToken:... method like 'regular' Cocoa-Touch code does? For example when reading JSON:
NSError *error = nil;
id result = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if (error == nil){
NSLog(#"%#", result);
}else{
NSLog(#"%#", [error localizedDescription]);
}
Use try catch where you expect a condition that cannot be recovered from or which may lead to an undefined behaviour such as crash, use NSError where recovereable errors are expected like wrong values from a json object or xml.
You can go throughApple documentation about exception programming.
In general, try-catch is more robust, does not require you to define an exact position of where to test (could be a block) and provides info about the exception.

EXC_BAD_ACCESS during NSFileVersion call to removeOtherVersionsOfItemAtURL: inside coordinated write block

I'm using what seems to be a simple invocation of the NSFileVersion class method removeOtherVersionsOfItemAtURL: inside a coordinated writing block for some iCloud conflict resolution.
When my devices go into 'spaz mode', which is a technical term for repeatedly opening and closing the application on a few devices, an EXC_BAD_ACCESS exception is thrown internally. Code snippet:
- (void)compareVersionChanges:(NSFileVersion *)version {
if (![DataLoader iCloudPreferenceEnabled]) {
NSLog(#"Ignoring iCloud changes (version comparison) based on user preference");
return;
}
NSLog(#"compareVersionChanges");
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^(void) {
NSError *readError = nil;
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:(id)self];
[coordinator coordinateReadingItemAtURL:[version URL] options:0 error:&readError byAccessor:^(NSURL *newURL) {
DataContext *loadedContext = nil;
NSData *data = [NSData dataWithContentsOfURL:newURL];
NSError *e = nil;
loadedContext = [self convertXmlDataToContext:data error:&e];
if (e) {
NSLog(#"Done loading, error: %#", e);
[[DataLoader applicationDelegate] displayError:e];
loadedContext = nil;
}
if (!loadedContext) {
return;
}
id appDelegate = [DataLoader applicationDelegate];
DataContext *inMemoryContext = nil;
if (appDelegate != nil && [appDelegate respondsToSelector:#selector(context)]) {
inMemoryContext = [appDelegate performSelector:#selector(context)];
}
if (inMemoryContext) {
NSLog(#"Performing iCloud context synchronizating...");
DataContextSynchronizer *synchronizer = [[DataContextSynchronizer alloc] init];
ChangeSet *changes = [synchronizer compareLocalContext:inMemoryContext andRemoteContext:loadedContext];
if ([[changes changes] count] > 0) {
[SelectionManager disable];
#synchronized(appDelegate) {
NSLog(#"Applying synchronization changes...");
[synchronizer applyChangeSet:changes toDataContext:inMemoryContext];
NSLog(#"Synchronization changes applied");
}
[SelectionManager enable];
if ([appDelegate respondsToSelector:#selector(setSkipRefreshSave:)]) {
[appDelegate performSelector:#selector(setSkipRefreshSave:) withObject:[NSNumber numberWithBool:YES]];
}
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^(void) {
[SelectionManager notifyListeners];
});
if ([appDelegate respondsToSelector:#selector(setSkipRefreshSave:)]) {
[appDelegate performSelector:#selector(setSkipRefreshSave:) withObject:[NSNumber numberWithBool:NO]];
}
[self save:[[DataLoader applicationDelegate] context]];
} else {
NSLog(#"No sync changes applicable.");
}
NSError *coordinateWriteRemoveError = nil;
[coordinator coordinateWritingItemAtURL:newURL options:NSFileCoordinatorWritingForDeleting error:&coordinateWriteRemoveError byAccessor:^(NSURL *theURL) {
theURL = [theURL copy];
NSError *removeOtherVersionsError = nil;
[NSFileVersion removeOtherVersionsOfItemAtURL:theURL error:&removeOtherVersionsError];
if (removeOtherVersionsError) {
NSLog(#"Error removing other versions: %#", removeOtherVersionsError);
}
}];
if (coordinateWriteRemoveError) {
NSLog(#"Error occurred coordinating write for deletion of other file versions: %#", coordinateWriteRemoveError);
}
}
}];
if (readError) {
NSLog(#"Done loading (outside block) error: %#", readError);
}
});
}
I thought a little syntax highlighting might make this easier to examine:
Link to image of code snippet and failure stack in Xcode
The error actually occurs on line 1404, and as you can see from the below screenshot, it's deep in Apple code territory.
Link to image of debugger
Before submitting a radar, I thought I'd check here to see if there's something I'm doing wrong? The extra [... copy] on line 1402 was just a quick check to make sure I'm not losing the reference to the block-provided argument, and will be removed.
Edit: An important note! I'm using ARC.
Edit 2: I've noticed that when calling:
[NSFileVersion otherVersionsOfItemAtURL:theURL]
The return value is nil, which indicates (via the documentation):
...or nil if there is no such file. The array does not contain the version object returned by the currentVersionOfItemAtURL: method.
So by checking the return value of this method before I make the call to removeOtherVersionsOfItemAtURL:, it has alleviated the issue. But I still find it strange that an EXC_BAD_ACCESS is thrown, rather than that method handling it properly.
I've noticed that when calling:
[NSFileVersion otherVersionsOfItemAtURL:theURL]
immediately prior to the call to removeOtherVersionsOfItemAtURL:, the return value is nil, which indicates (via the documentation):
Returns: An array of file version objects or nil if there is no such
file. The array does not contain the version object returned by the
currentVersionOfItemAtURL: method.
So by checking the return value of this method before I make the call to removeOtherVersionsOfItemAtURL:, it has alleviated the issue. But I still find it strange that an EXC_BAD_ACCESS is thrown by removeOtherVersionsOfItemAtURL:, rather than that method simply returning NO, or simply populating the provided NSError object.
I'll be filing a Radar and will update here when I hear back.