This is my nested block, please take have a look :
- (void)getVideoList:(NSDictionary*)videoData
completionBlock:(void (^)(NSMutableArray *))
completionBlock {
NSArray *videos = (NSArray*)[videoData objectForKey:#"items"];
NSMutableArray* videoList = [[NSMutableArray alloc] init];
for (NSDictionary *videoDetail in videos) {
if (videoDetail[#"id"][#"videoId"]){
[self initializeDictionary:videoDetail completionBlock:^(YoutubeVideo * utubeVideo) {
[videoList addObject:utubeVideo];
// NSLog(#"zuuudo %#", utubeVideo.channelProfileImageURL);
}];
}
}
completionBlock(videoList);
}
- (void)initializeDictionary:(NSDictionary *)dictionary completionBlock:(void (^)(YoutubeVideo *))
completionBlock {
YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
youtubeVideo.videoTitle = dictionary[#"snippet"][#"title"];
youtubeVideo.videoID = dictionary[#"id"][#"videoId"];
youtubeVideo.channelID = dictionary[#"snippet"][#"channelId"];
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID completionBlock:^(NSMutableArray *channelList) {
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
}];
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
completionBlock(youtubeVideo);
}
- (void)getChannelProfilePictureForChannelID:(NSString*)channelID completionBlock:(void (^)(NSMutableArray *))completionBlock
{
NSString *URL = [NSString stringWithFormat:#"https://www.googleapis.com/youtube/v3/channels?part=snippet&fields=items/snippet/thumbnails/default&id=%#&key=%#", channelID, apiKey];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:[URL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (!error){
[self getChannelProfileImageList:[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] completionBlock:
^(NSMutableArray * channelList) {
// return the final list
completionBlock(channelList);
}];
}
else {
// TODO: better error handling
NSLog(#"error = %#", error);
}
}] resume];
}
- (void)getChannelProfileImageList:(NSDictionary*)channelData
completionBlock:(void (^)(NSMutableArray *))
completionBlock {
NSArray *channels = (NSArray*)[channelData objectForKey:#"items"];
NSMutableArray *channelList = [[NSMutableArray alloc] init];
for (NSDictionary *channelDetail in channels) {
[self initializeDictionaryForChannelProfileImage:channelDetail completionBlock:^(NSString *chnlProfileImageURL) {
[channelList addObject:chnlProfileImageURL];
}];
//[channelList addObject:[self initializeDictionaryForChannelProfileImage:channelDetail]];
//[channelList addObject:[[YoutubeVideo alloc] initWithDictionaryForChannelProfileImage:channelDetail]];
}
completionBlock(channelList);
}
- (void)initializeDictionaryForChannelProfileImage:(NSDictionary *)dictionary completionBlock:(void (^)(NSString *))
completionBlock
{
_channelProfileImageURL = dictionary[#"snippet"][#"thumbnails"]
[#"default"][#"url"];
completionBlock(_channelProfileImageURL);
}
Problem is in this - (void)initializeDictionary:(NSDictionary *)dictionary completionBlock:(void (^)(YoutubeVideo *))
completionBlock {
} block, has the below block
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID completionBlock:^(NSMutableArray *channelList) {
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
}];
Where these line of code is not executing when the block return value NSSting value.
youtubeVideo.channelProfileImageURL = _channelProfileImageURL;
NSLog(#"youtubeVideo.channelProfileImageURL %#", youtubeVideo.channelProfileImageURL);
It is getting called after executing rest of the code:
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
So the value is not inserting in my object model.
Please give me a suggestion. Thanks in advance.
Have a good day.
It is getting called after executing rest of the code
You are mixing up asynchronous execution with an expectation that code will be executed synchronously:
- (void)initializeDictionary:(NSDictionary *)dictionary
completionBlock:(void (^)(YoutubeVideo *))completionBlock
{
This is a typical declaration for an asynchronous method where the completionBlock argument should be called asynchronously after the all the work of initializeDictionary has been completed.
YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
youtubeVideo.videoTitle = dictionary[#"snippet"][#"title"];
youtubeVideo.videoID = dictionary[#"id"][#"videoId"];
youtubeVideo.channelID = dictionary[#"snippet"][#"channelId"];
Three synchronous assignments.
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID
completionBlock:^(NSMutableArray *channelList)
{
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
}
];
This is a nested call to another asynchronous method, which will call its completion block after it has finished. At the point it returns it probably has not yet called its competition block.
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
Four more synchronous assignments...
completionBlock(youtubeVideo);
And then you call the completion block of initializeDictionary: before you know that getChannelProfilePictureForChannelID: has completed and called its completion block.
}
If you are writing an asynchronous method which itself needs to call an asynchronous method then you have to complete your method in the nested asynchronous method's completion...
Yes that's a bit confusing in words! Let's rearrange your method:
- (void)initializeDictionary:(NSDictionary *)dictionary
completionBlock:(void (^)(YoutubeVideo *))completionBlock
{
YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
youtubeVideo.videoTitle = dictionary[#"snippet"][#"title"];
youtubeVideo.videoID = dictionary[#"id"][#"videoId"];
youtubeVideo.channelID = dictionary[#"snippet"][#"channelId"];
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
Do all the synchronous assignments first, then do the nested asynchronous call:
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID
completionBlock:^(NSMutableArray *channelList)
{
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
At this point the completion block of getChannelProfilePictureForChannelID has done what you want it to, now do any remaining work that initializeDictionary: needs to do after getChannelProfilePictureForChannelID completes. This is not much in this case, just call initializeDictionary: competition:
completionBlock(youtubeVideo);
}
];
}
HTH
Addendum
From your comments I think you are misunderstanding how asynchronous chaining needs to work. Let's see if the following helps.
The method you wrote has for format:
A - block of work to do before async nested call
B - async call
nested async completion block
C - block of work to do after nested call completes
D - second block of work
E - call our async completion block
When you call this method A, B, D & E will execute in order and then the method will return. You've no idea when C will execute and there is no guarantee it will execute before E, indeed with async network calls in all probability it will not (so you're unlikely to even get accidental correctness).
To do a sequence of async operations you need to chain them via the continuation blocks. So you can change your method to:
A - block of work to do before async nested call
B - async call
nested async completion block
C - block of work to do after nested call completes
D - second block of work
E - call our async completion block
Putting D & E into the nested completion block. Now when you call your method only A & B execute before it returns. At some later point the nested completion block is executed asynchronously and C and D are executed. Finally E is executed, the completion block of the original call, thus completing the work. You've now guaranteed correctness, E will only be executed after the nested async call has completed.
Note: What I noticed when reading your code was that block D (the set of four assignments in your code) did not seem to be required to be executed after the nested call so I rearranged your code as:
A & D - block of work to do before async nested call
B - async call
nested async completion block
C - block of work to do after nested call completes
E - call our async completion block
hoisting D to the top.
This chaining of asynchronous calls is fundamental when you have an async method which itself relies on another async method – at every stage you must use the completion block chain to execute code it the correct order.
HTH
Related
I'm trying to make an equivalent to the .NET recognize() call, which is synchronous, for ios in objective-c. I found code to recognize speech but the string that was recognized is only inside a block.
I've tried making the block not a block (it seems to be part of the API that it be a block), making __block variables and returning their values, also out parameters in the caller/declarer of the block; finally I wrote a file while in the block and read the file outside. It still didn't work like I want because of being asynchronous although I at least got some data out. I also tried writing to a global variable from inside the block and reading it outside.
I'm using code from here: How to implement speech-to-text via Speech framework, which is (before I mangled it):
/*!
* #brief Starts listening and recognizing user input through the
* phone's microphone
*/
- (void)startListening {
// Initialize the AVAudioEngine
audioEngine = [[AVAudioEngine alloc] init];
// Make sure there's not a recognition task already running
if (recognitionTask) {
[recognitionTask cancel];
recognitionTask = nil;
}
// Starts an AVAudio Session
NSError *error;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryRecord error:&error];
[audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
// Starts a recognition process, in the block it logs the input or stops the audio
// process if there's an error.
recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
AVAudioInputNode *inputNode = audioEngine.inputNode;
recognitionRequest.shouldReportPartialResults = YES;
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
BOOL isFinal = NO;
if (result) {
// Whatever you say in the microphone after pressing the button should be being logged
// in the console.
NSLog(#"RESULT:%#",result.bestTranscription.formattedString);
isFinal = !result.isFinal;
}
if (error) {
[audioEngine stop];
[inputNode removeTapOnBus:0];
recognitionRequest = nil;
recognitionTask = nil;
}
}];
// Sets the recording format
AVAudioFormat *recordingFormat = [inputNode outputFormatForBus:0];
[inputNode installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[recognitionRequest appendAudioPCMBuffer:buffer];
}];
// Starts the audio engine, i.e. it starts listening.
[audioEngine prepare];
[audioEngine startAndReturnError:&error];
NSLog(#"Say Something, I'm listening");
}
I want to call Listen(), (like startListening() above), have it block execution until done, and have it return the string that was said. But actually I would be thrilled just to get result.bestTranscription.formattedString somehow to the caller of startListening().
I'd recommend you to take another approach. In Objective-C having a function that blocks for a long period of time is an anti-pattern.
In this language there's no async/await, nor cooperative multitasking, so blocking for long-ish periods of time might lead to resource leaks and deadlocks. Moreover if done on the main thread (where the app UI runs), the app might be forcefully killed by the system due to being non-responsive.
You should use some asynchronous patterns such as delegates or callbacks.
You might also try using some promises library to linearize your code a bit, and make it look "sequential".
The easiest approach with callbacks would be to pass a completion block to your "recognize" function and call it with the result string when it finishes:
- (void)recognizeWithCompletion:(void (^)(NSString *resultString, NSError *error))completion {
...
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest
resultHandler:^(SFSpeechRecognitionResult *result, NSError *error)
{
...
dispatch_async(dispatch_get_main_queue(), ^{
completion(result.bestTranscription.formattedString, error);
});
...
}];
...
}
Note that the 2nd parameter (NSError) - is an error in case the caller wants to react on that too.
Caller side of this:
// client side - add this to your UI code somewhere
__weak typeof(self) weakSelf = self;
[self recognizeWithCompletion:^(NSString *resultString, NSError *error) {
if (!error) {
[weakSelf processCommand:resultString];
}
}];
// separate method
- (void)processCommand:(NSString *command) {
// can do your processing here based on the text
...
}
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.
In my app I open the camera by a picker and after the photo has been taken I'd like to safe it by the following method the assets library. The method freezes after the call of the writeImageToSavedPhotosAlbum.
Without the semaphores the methods work perfectly. But than I miss to receive the assetURL.
+ (NSURL*)safeImageToAssetsLibrary:(UIImage *)image metadata:(NSDictionary *)metadata
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
__block NSURL *retAssestURL = nil;
dispatch_semaphore_t semaWaitingForSafeImage = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// safe the image to the assests library
NSLog(#"Safe image to asssets library...");
dispatch_async(queue, ^{
[library writeImageToSavedPhotosAlbum:image.CGImage metadata:metadata completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(#"Image could not be safed to the assets library: %#", error);
retAssestURL = nil;
}
else {
NSLog( #"Image safed successfully to assetURL: %#", assetURL);
retAssestURL = assetURL;
}
dispatch_semaphore_signal(semaWaitingForSafeImage);
}];
});
dispatch_semaphore_wait(semaWaitingForSafeImage, DISPATCH_TIME_FOREVER);
return retAssestURL;
}
And this is method where I call the safeImageToAssetsLibrary method:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[picker dismissViewControllerAnimated:YES completion:NULL];
// get chosen image and add thumbnail to collection view
NSURL *imageUrl = info[UIImagePickerControllerReferenceURL];
UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
// safe image to photo library if the camera has been used
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
imageUrl = [BaseImageHandler safeImageToAssetsLibrary:chosenImage metadata:info[UIImagePickerControllerMediaMetadata]];
}
// UPDATE View and Core Data here...
}
Do not wait. Never, never do what you are doing. You are failing to understand what "asynchronous" is about. It means that you are called back when it's all over in the completion block. So that is where you perform the next step.
Do NOT try to return a value from a method that obtains that value in an asynchronous completion block.
So, here, in writeImageToSavedPhotosAlbum:'s completion block, that is where you receive retAssestURL. So if there is a further step, now do it, there, in the completion block. This could involve calling another method or whatever you like, but the point is, things will now happen in the correct order.
And above all, Do NOT use semaphores (or other trickery) to try to turn asynchronous into synchronous. Asynchronous things are asynchronous for a reason. Use the framework, don't fight it. (Actually, what you are doing with semaphores here is not just fighting the framework but spitting in its eye.)
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.
So,here a simple method with a block
-(void)getPointsInRange:(double)radius nearByPoint:(SGPoint *)nearByPoint
{
SGStorageQuery *query = [SGStorageQuery queryWithPoint:nearByPoint layer:SimpleGeoMainLayerName];
[query setRadius:radius];
[mainClient retain];
[mainClient getRecordsForQuery:query
callback:[SGCallback callbackWithSuccessBlock:
^(id response) {
// you've got records!
// to create an array of SGStoredRecord objects...
NSArray *records = [NSArray arrayWithSGCollection:response type:SGCollectionTypeRecords];
NSLog(#"records received:%i",[records count]);
[self arrayOfPointsReceived:records];
} failureBlock:^(NSError *error) {
// handle failure
NSLog(#"getPointsInRange error:%#",[error description]);
}]];
}
the method connects to some SDK and returns an NSArray with results.
i want to find a way that the getPointsInRange method will return the NSArray.
so its signature will be -(NSArray*)getPointsInRange...
I can do it simply with delegate, but i'd like to do it all within one function.
It seems to me like you want to keep your cake and eat it, too. Or have a method that calls asynchronous code and at the same time returns the results synchronously. You can turn the method into a synchronous one, if that’s what you want:
- (void) computeSomethingAndReturnSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self doSomeAsynchronousOperationWithCompletion:^{
// take the call results here
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
This will run the asynchronous code and then block the execution until the results from the async call are available. Does that help? (I should add that I would much rather keep the code asynchronous and return the NSArray in another completion block.)