make 3 "for" loops wait for async answer. obj-c ios - objective-c

I'm working on an app for iPad iOS 8, and I need to make my app wait for an answer from:
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {}]
This method is inside 3 loops. I tried dispatch_semaphore_t, but the app can't continue after this line:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
and gets stuck. I tried with dispatch_group_t and got the same result.
I guess that I'm doing something wrong, but I don't know what. I tried to search SO for similar problems and but found nothing. Can someone explain how I could accomplish this?
-(void)setTimesMissions {
for (Driver *d in self.dataList) {
for (Period *p in d.periods) {
for (Mission *m in p.missions) {
MKDirections *directions = ....
// HERE i want the for loop stop until this completionHandler finish
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
//and when he finish here continue
}];
}
}
}
}

Call your method in dispatch_async block.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[youClassInstance setTimesMissions];
});
And use dispatch_semaphore_wait in your loop
- (void)setTimesMissions {
Mission *home = [[Mission alloc]init];
Mission *next = [[Mission alloc]init];
for (Driver *d in self.dataList) {
home.clientLat = d.centralPointLat;
home.clientLon = d.centralPointLon;
home.clientPaddres = d.centralAddress;
for (Period *p in d.periods) {
home.times = [[NSMutableArray alloc]init];
if ([p.periodIx isEqualToString:self.thisPeriodIX]) {
for (Mission *m in p.missions) {
Mission *source = home;
Mission *destination = m ;
MKPlacemark *placemarkSource = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake([source.clientLat doubleValue], [source.clientLon doubleValue]) addressDictionary:nil] ;
MKMapItem *mapItemSource = [[MKMapItem alloc] initWithPlacemark:placemarkSource];
MKPlacemark *placemarkDestination = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake([destination.clientLat doubleValue], [destination.clientLon doubleValue])addressDictionary:nil] ;
MKMapItem *mapItemDestination = [[MKMapItem alloc] initWithPlacemark:placemarkDestination];
MKDirectionsRequest *directionsRequest = [[MKDirectionsRequest alloc] init];
[directionsRequest setSource:mapItemSource];
[directionsRequest setDestination:mapItemDestination];
directionsRequest.transportType = MKDirectionsTransportTypeAutomobile;
[directionsRequest setRequestsAlternateRoutes:NO];
MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block double timeTo;
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
if ( response.expectedTravelTime) {
timeTo = response.expectedTravelTime;
double ans = timeTo;
Time *t = [[Time alloc]init];
t.ix = m.serviceIX;
t.time = ans;
[home.times addObject:t];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
// code that should be executed on main queue
});
if (next.clientPaddres) {
home = next;
}
}
}
}
}

Related

OBJC AVSpeechUtterance writeUtterance how?

I am trying to create a TTS to file in Objc.
Since iOS13 can write it to a file.
But I'm stuck with writeUtterance:toBufferCallback.
Do someone has an exemple with this function in objc?
[synth speakUtterance:utterance];
Referring to the potential answer in Swift, this would be the Objective-C implementation
AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init];
AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:#"test 123"];
AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:#"en-US"];
[utterance setVoice:voice];
__block AVAudioFile *output = nil;
[synthesizer writeUtterance:utterance
toBufferCallback:^(AVAudioBuffer * _Nonnull buffer) {
AVAudioPCMBuffer *pcmBuffer = (AVAudioPCMBuffer*)buffer;
if (!pcmBuffer) {
NSLog(#"Error");
return;
}
if (pcmBuffer.frameLength != 0) {
//append buffer to file
if (output == nil) {
output = [[AVAudioFile alloc] initForWriting:[NSURL fileURLWithPath:#"test.caf"]
settings:pcmBuffer.format.settings
commonFormat:AVAudioPCMFormatInt16
interleaved:NO error:nil];
}
[output writeFromBuffer:pcmBuffer error:nil];
}
}];

Memory leak when adding custom NSOperations to NSOperationQueue

I'm profiling (Leaks) my app (ARC) and it's showing several memory leaks which I can't figure out.
One object (ArchivingTasksManager) kicks off a method that creates many NSOperations within a for in loop, and adds them to a queue. Each NSOperation is a custom subclass (DownloadImageOperation) that I instantiate with an NSURL.
In ArchivingTasksManager:
- (void)archiveAllImages
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundTask];
[self archiveImages];
});
}
- (void)archiveImages
{
self.queue.suspended = true;
self.queue.maxConcurrentOperationCount = 1;
for (Highlight *highlight in self.list.highlights) {
for (Experience *experience in highlight.experiences) {
for (NSURL *imageURL in [experience allImageURLsOfType:ExperienceImageType640x640]) {
DownloadImageOperation *experienceImageDownload = [[DownloadImageOperation alloc] initWithImageURL:imageURL];
[self.queue addOperation:experienceImageDownload];
}
NSURL *authorURL = [NSURL URLWithString:experience.author.image_250x250Url];
if (authorURL) {
DownloadImageOperation *authorImageDownload = [[DownloadImageOperation alloc] initWithImageURL:authorURL];
[self.queue addOperation:authorImageDownload];
}
}
MapSnapshotOperation *mapSnapshot = [[MapSnapshotOperation alloc] initWithHighlight:highlight];
[self.queue addOperation:mapSnapshot];
}
[self.queue addObserver:self
forKeyPath:NSStringFromSelector(#selector(operationCount))
options:NSKeyValueObservingOptionInitial context:nil];
self.totalOperations = self.queue.operationCount;
self.queue.suspended = false;
}
In DownloadImageOperation:
- (instancetype)initWithImageURL:(NSURL *)imageURL;
{
self = [super init];
if (!self) {
return nil;
}
_imageURL = imageURL;
_targetURL = [[ArchivingOperation class] localImageURLFromRemoteImagePath:imageURL.absoluteString];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
_downloadTask = [_session downloadTaskWithURL:imageURL];
return self;
}
Here's what Instruments shows me in ArchivingTasksManager:
And here's what Instruments shows me in DownloadImageOperation:

code within continueWithBlock executed but result is not immediate [duplicate]

When running this code...
-(IBAction)loginWithTwitter:(id)sender {
NSLog(#"Logging in with twitter");
ACAccountStore *accountStore = [[ACAccountStore alloc]init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (error) {
[self showError:error];
return;
}
if (granted) {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] > 1) {
NSLog(#"Multiple twitter accounts");
}
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
NSLog(#"%#", twitterAccount);
[self pushMainController];
}
}];
}
There is a 5-10 second delay before the pushMainControlleris actually called, even though the account information is logged almost immediately (after pre-authorization). If I move the pushMainController call after the block however, it happens immediately, the only problem being that a user isn't necessarily logged in at that point. I understand that it can take a second for a block to have a response due to variables such as network connectivity, but can someone help me understand this?
The completion block is not being done on the main queue. You need to ensure your UI code gets done on the main thread:
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (error) {
[self showError:error];
return;
}
if (granted) {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] > 1) {
NSLog(#"Multiple twitter accounts");
}
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
NSLog(#"%#", twitterAccount);
dispatch_async(dispatch_get_main_queue(), ^{
[self pushMainController];
});
}
}];
You may have to wrap the showError call as well.

which dispatch_async method is advisable to load the json data to table view controller?

I have json data and now I want to push into table view? But initially, I have to get the json data so which dispatch method or child thread is recommend so that i can load the data in background and then push it.
Best practice is load data in background thread using dispatch_async method and passing a queue other then main so it don't block UI, and when u have data ready just call reload table view on main thread...
Below is a class an example from real project...
Class level method load transcations
+ (void)getAllTransactionsWithHandler:(void(^)(NSArray *transactions, NSError *error))handler{
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(q, ^{
NSURL *url = [[NSBundle mainBundle] URLForResource:trnasactionFileName withExtension:#"json"];//[NSData dataWithContentsOfFile:trnasactionFileName];
NSData *data = [NSData dataWithContentsOfURL:url];
if (!data) {
if (handler) {
handler(nil, [NSError errorWithDomain:#"bank" code:900 userInfo:nil]);
}
return ;
}
NSError *error = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error) {
if (handler) {
handler(nil, error);
}
}
else{
Transaction *transaction;
NSString *dateString;
NSMutableArray *objects = [NSMutableArray new];
for (NSDictionary *dic in jsonArray) {
transaction = [Transaction new];
dateString = dic[kOccured];
NSDateFormatter *dateFormater = [NSDateFormatter new];
[dateFormater setDateFormat:#"yyyy-MM-dd"];
transaction.occured = [dateFormater dateFromString:dateString];
transaction.proecessed = [dateFormater dateFromString:dic[kProcessed]];
transaction.desc = dic[kDescription];
transaction.amount = dic[kAmount];
[objects addObject:transaction];
}
if (handler) {
handler([NSArray arrayWithArray:objects], nil);
}
}
});
}
You can use this as
[Transaction getAllTransactionsWithHandler:^(NSArray *transactions, NSError *error) {
if (error) {
}else{
if ([transactions count] > 0) {
weakSelf.objects = transactions;
runOnMainThread(^{
[weakSelf.tableView reloadData];
});
}
}
}];
Where as runOnMainthread is a utility method which will run provided block of code on main thread
void runOnMainThread(void(^block)(void)){
if ([[NSThread currentThread] isMainThread])
block();
else{
dispatch_sync(dispatch_get_main_queue(), ^{
block();
});
}
}

How to load several photos with assetForURL with a list of URLs

For a list of URLs I need to load the photos with ALAssetsLibrary:assetForURL, and this within one method.
Since this method works async but it is not iterating over the passed list of URLs, as we all know.
I found this snippet (which should work):
- (void)loadImages:(NSArray *)imageUrls loadedImages:(NSArray *)loadedImages callback: (void(^)(NSArray *))callback
{
if (imageUrls == nil || [imageUrls count] == 0) {
callback(loadedImages);
}
else {
NSURL *head = [imageUrls head];
__unsafe_unretained id unretained_self = self;
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library assetForURL:head resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *assetRepresentation = asset.defaultRepresentation;
UIImage *image = [UIImage imageWithCGImage:assetRepresentation.fullResolutionImage scale:assetRepresentation.scale orientation:(UIImageOrientation)assetRepresentation.orientation];
[unretained_self loadImages:[imageUrls tail] loadedImages:[loadedImages arrayByAddingObject:image] callback:callback];
} failureBlock:^(NSError *error) {
[unretained_self loadImages:[imageUrls tail] loadedImages:loadedImages callback:callback];
}];
}
}
How do I write the method definition in the form (above all the callback)
void loadImages(NSArray *imageUrls, NSArray *loadedImages, ...) ?
How do I call this method from another method (again mainly the callback part) ?
Can the callback be in the calling method or a 3rd method needed for this? and how does this method need to be written?
I have found the snippet here: http://www.calebmadrigal.com/functional-programming-deal-asynchronicity-objective-c/
Use NSThread to call the loadImages method.
NSMutableArray *imageCollection = [NSThread detachNewThreadSelector:#selector (loadImages:)
toTarget:self
withObject:imageUrlsCollection];
- (NSMutableArray *)loadImages:(NSArray *)imageUrls
{
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
NSMutableArray *loadedImages = [[NSMutableArray alloc] init];
#try
{
for(int index = 0; index < [imageUrls count]; index++)
{
NSURL *url = [imageUrls objectAtIndex:index];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *assetRepresentation = asset.defaultRepresentation;
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [UIImage imageWithCGImage:assetRepresentation.fullResolutionImage scale:assetRepresentation.scale orientation:(UIImageOrientation)assetRepresentation.orientation];
[loadedImages addObject:image];
});
} failureBlock:^(NSError *error) {
NSLog(#"Failed to get Image");
}];
}
}
#catch (NSException *exception)
{
NSLog(#"%s\n exception: Name- %# Reason->%#", __PRETTY_FUNCTION__,[exception name],[exception reason]);
}
#finally
{
return loadedImages;
}
}
Note: With ARC,take care about invalid attempt to access ALAssetPrivate past the lifetime of its owning ALAssetsLibrary issue
Here is the fix :)