Blocks in Objective-C and Multithreds - asynchronous

Intro:
On my Xcode project i’ve 3 datastore :SFGroupsItemStore, SFFeedsItemStore, SFQuestsItemStore. All of this class are subclass of 1 my custom class and all are singleton.
Each of this class i use at next controllers : SFGroupsListViewController SFFeedsListViewController, SFQuestsListViewController. All of this class has method: -downloadItems
Problem:
I want to create one controller SFHomeViewController, that will present table with all 3 datastore mixed.
For resolve this problem i want to create aggregator SFHomeItemStore, also singleton, and i want that this class has method -downloadItems.
So, i’v done it, there are the implemaemtation of -downloadIItems in SF HomeItemStore
1 step:
- (NSArray *)downloadItemsFrom:(int32_t)from withLimit:(int)limit andDirection:(NSString *)direction{
NSArray *groupsItems = [[SFGroupsItemStore shared] downloadItems];
NSArray *feedsItems = [[SFFeedsItemStore shared] downloadItems];
NSArray *questsItems = [[SFQuestsItemStore shared] downloadItems];
NSMutableArray *mixedArray = [NSMutableArray array];
[mixedArray addObjectsFromArray:groupsItems];
[mixedArray addObjectsFromArray:feedsItems];
[mixedArray addObjectsFromArray:questsItems];
NSArray *sortedArray = [mixedArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:NO]]];
return sortedArray;
}
That work’s fine for me.
2 step:
But i do not like that call of method -downloadItems goes sequentaly, that why for call this method i need to spend more time
But if i add GCD i’ve a problem,
Now implementation method -downloadItems is
- (NSArray *)downloadItemsFrom:(int32_t)from withLimit:(int)limit andDirection:(NSString *)direction{
__block NSArray *sortedArray = nil;
__block NSArray *groupsItems;
__block NSArray *feedsItems;
__block NSArray *questsItems;
dispatch_group_t groupedTask = dispatch_group_create();
dispatch_queue_t globalQueue = dispatch_get_global_queue( 0, 0 );
dispatch_group_async( groupedTask, globalQueue, ^{
groupsItems = [[SFGroupsItemStore shared] downloadItems];
} );
dispatch_group_async( groupedTask, globalQueue, ^{
feedsItems = [[SFFeedsItemStore shared] downloadItems];
} );
dispatch_group_async( groupedTask, globalQueue, ^{
questsItems = [[SFQuestsItemStore shared] downloadItems];
} );
dispatch_group_notify( groupedTask, globalQueue, ^{
NSMutableArray *mixedArray = [NSMutableArray array];
[mixedArray addObjectsFromArray:groupsItems];
[mixedArray addObjectsFromArray:feedsItems];
[mixedArray addObjectsFromArray:questsItems];
sortedArray = [mixedArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:NO]]];
} );
return sortedArray;
}
Problem is that method return result regardless of dispatch_group_notify and that why array sortedArray is empty
So, my question is:
How i can do that method will return result only after end of dispatch_group_notify function?

You have two options:
If it's ok for your method to block the current thread, then you can use dispatch_group_wait.
Use a completion handler to return your NSArray.
dispatch_group_wait
From the docs:
Waits synchronously for the previously submitted block objects to
complete; returns if the blocks do not complete before the specified
timeout period has elapsed.
Your method would look something like this:
- (NSArray *)downloadItemsFrom:(int32_t)from withLimit:(int)limit andDirection:(NSString *)direction{
// ...
dispatch_group_async( groupedTask, globalQueue, ^{
groupsItems = [[SFGroupsItemStore shared] downloadItems];
} );
// ...
dispatch_group_wait(groupedTask, DISPATCH_TIME_FOREVER);
NSMutableArray *mixedArray = [NSMutableArray array];
[mixedArray addObjectsFromArray:groupsItems];
[mixedArray addObjectsFromArray:feedsItems];
[mixedArray addObjectsFromArray:questsItems];
sortedArray = [mixedArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:NO]]];
return sortedArray;
}
Using a completion handler
This is the method I prefer.
You'd need to change your method signature so that it takes a completion handler as a new parameter, and returns void instead of NSArray. With this method, you'd still be using dispatch_group_notify.
It would look something like this:
- (void)downloadItemsFrom:(int32_t)from
withLimit:(int)limit
direction:(NSString *)direction
andCompletionHandler:(void (^)(NSArray*))completionHandler
{
// ...
dispatch_group_async( groupedTask, globalQueue, ^{
groupsItems = [[SFGroupsItemStore shared] downloadItems];
} );
// ...
dispatch_group_notify( groupedTask, globalQueue, ^{
NSMutableArray *mixedArray = [NSMutableArray array];
[mixedArray addObjectsFromArray:groupsItems];
[mixedArray addObjectsFromArray:feedsItems];
[mixedArray addObjectsFromArray:questsItems];
sortedArray = [mixedArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:NO]]];
completionHandler(sortedArray);
} );
}
Calling it would now look like:
[object downloadItemsFrom:...
withLimit:...
diretion:...
andCompletionHandler:^(NSArray *items)
{
}];

Related

Repeating Audio in WatchKit (AVAudioPlayer?)?

I am wanting to loop a local audio file in my Apple Watch App. Currently I am using AVAudioPlayerNode and AVAudioEngine which works well but I cannot figure out how to loop the sound.
I noticed that I can use AVAudioPlayer, which has the handy "numberOfLoops" but, for some reason AVAudioPlayer is not working on the watch. I have no idea why.
Here is my current code to play a sound:
_audioPlayer = [[AVAudioPlayerNode alloc] init];
_audioEngine = [[AVAudioEngine alloc] init];
[_audioEngine attachNode:_audioPlayer];
AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
[_audioEngine connect:_audioPlayer to:_audioEngine.mainMixerNode format:stereoFormat];
if (!_audioEngine.isRunning) {
NSError* error;
[_audioEngine startAndReturnError:&error];
}
NSError *error;
NSBundle* appBundle = [NSBundle mainBundle];
NSURL *url = [NSURL fileURLWithPath:[appBundle pathForResource:#"FILE_NAME" ofType:#"mp3"]];
AVAudioFile *asset = [[AVAudioFile alloc] initForReading:url error:&error];
[_audioPlayer scheduleFile:asset atTime:nil completionHandler:nil];
[_audioPlayer play];
Here is the code i've tried to use for AVAudioPlayer, but does not work:
NSError *audioError;
AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FILE_NAME" ofType:#"mp3"]] error:&audioError];
player.numberOfLoops = MAXFLOAT;
player.delegate = self;
[player play];
I am using WatchKit 5.0(+).
You can loop your AVAudioFile by recursively scheduling it:
__block __weak void (^weakSheduleFile)(void);
void (^scheduleFile)(void);
weakSheduleFile = scheduleFile = ^{ [self->_audioPlayer scheduleFile:asset atTime:nil completionHandler:weakSheduleFile]; };
scheduleFile();
I'm not sure if this will be a seamless loop. If it's not, you can try always having two files scheduled:
scheduleFile();
scheduleFile();
If your audio file fits into memory, you could schedule playback as an AVAudioBuffer with the AVAudioPlayerNodeBufferLoops option (N.B. only tested on simulator!):
AVAudioFormat *outputFormat = [_audioPlayer outputFormatForBus:0];
__block AVAudioPCMBuffer *srcBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:asset.processingFormat frameCapacity:(AVAudioFrameCount)asset.length];
if (![asset readIntoBuffer:srcBuffer error:&error]) {
NSLog(#"Read error: %#", error);
abort();
}
AVAudioPCMBuffer *dstBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)asset.length];
AVAudioConverter *converter = [[AVAudioConverter alloc] initFromFormat:srcBuffer.format toFormat:dstBuffer.format];
AVAudioConverterOutputStatus status = [converter convertToBuffer:dstBuffer error:&error withInputFromBlock:^AVAudioBuffer * _Nullable(AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus * _Nonnull outStatus) {
if (srcBuffer) {
AVAudioBuffer *result = srcBuffer;
srcBuffer = NULL;
*outStatus = AVAudioConverterInputStatus_HaveData;
return result;
} else {
*outStatus = AVAudioConverterInputStatus_EndOfStream;
return NULL;
}
}];
assert(status != AVAudioConverterOutputStatus_Error);
[_audioPlayer scheduleBuffer:dstBuffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[_audioPlayer play];

Resolving <NSManagedObject: 0x155ec810> (entity: EventDetails; id:some id /EventDetails/p1> ; data: <fault>)"

How to resolve this issue.
" (entity: EventDetails; id: 0x155ebe90 ; data: )".
I am using below piece of code to fetch events from my entity.
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] parentContext];
writerObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = managedObjectContext;
[temporaryContext performBlockAndWait:^{
if (temporaryContext == nil) {
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] parentContext];
writerObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = managedObjectContext;
}
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"EventDetails"];
NSArray *arr = [temporaryContext executeFetchRequest:request error:&error];
NSLog(#" %#", arr);
if (![temporaryContext save:&error]) {
NSLog(#"Error in getsize - error:%#",[error userInfo]);
}
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if(![managedObjectContext save:&error])
{
NSLog(#"error");
}
[writerObjectContext performBlockAndWait:^{
NSError *error = nil;
if(![writerObjectContext save:&error])
{
NSLog(#"error");
}
}]; // writer
}]; // main
}]; // parent
I runned above code both on my iphone and simulator.But getting same result. How to get rid of this?
data: <fault> (presumably from a log statement) simply means that Core Data did not fetch all the associated data from the database but will make future trips to the store automatically, should the information be needed.
In other words, this is normal behavior. You do not need to worry about it.

Playing 2 sounds with AVAudioPlayer without sleep();

I have this 2 sounds playing in a scrollview using AVAudioPlayer.
The problem is that the sleep() is creating lags and I need an alternative to playing two sounds one after the other more easily without the sleep function.
PS: the name of the file is given by the page in the scrollView in which the user is.
if( nextPage!=6 ) {
[scrMain scrollRectToVisible:CGRectMake(0, nextPage*250, scrMain.frame.size.width, scrMain.frame.size.height) animated:YES];
pgCtr.currentPage=nextPage;
NSString *path;
NSError *error;
path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"pas%02i",nextPage+1] ofType:#"m4a"];
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
{
player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
player.volume = 0.5f;
[player prepareToPlay];
[player setNumberOfLoops:0];
[player play];
}
sleep(1);
NSString *path2;
NSError *error2;
path2 = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"passun%02i",nextPage+1] ofType:#"m4a"];
if ([[NSFileManager defaultManager] fileExistsAtPath:path2])
{
player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path2] error:&error2];
player.volume = 0.5f;
[player prepareToPlay];
[player setNumberOfLoops:0];
[player play];
}
How can I do this?

Core Data Fetch is Returning Nothing

In this app, I am using an sqlite database that was created by another app. I can query the database using the Firefox SQLite Manager and see that what I am searching for does exist in the database. I have reduced my query to something very simple, but still get nothing returned in my NSFetchedResultsController.
Here is my code:
- (NSFetchedResultsController*) frc {
if (!frc_) {
#autoreleasepool {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription * entity = [NSEntityDescription entityForName:#"INDEX" inManagedObjectContext:[ManagedObjectContext moc]];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:15];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"lemma = 'dog'"];
[NSFetchedResultsController deleteCacheWithName:nil];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"lemma" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:[ManagedObjectContext moc]
sectionNameKeyPath:#"lemma"
cacheName:nil];
aFetchedResultsController.delegate = (id<NSFetchedResultsControllerDelegate>)self;
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved Error %#, %#", error, [error userInfo]);
abort();
}
self.frc = aFetchedResultsController;
}
}
return frc_;
}
There is an entity called "INDEX" in the data model. To confirm that the entity has the lemma property, I show the content of its lemma property, getting this:
po [[entity propertiesByName] objectForKey:#"lemma"]
(id) $3 = 0x1104f740 (<NSAttributeDescription: 0x1104f740>), name lemma, isOptional 1, isTransient 0, entity INDEX, renamingIdentifier lemma, validation predicates (
), warnings (
), versionHashModifier (null)
userInfo {
}, attributeType 700 , attributeValueClassName NSString, defaultValue (null)
Examining the contents of the aFetchedResultsController immediately after the fetch (where it is assigned to self.frc) gives this:
po aFetchedResultsController
(NSFetchedResultsController *) $4 = 0x1105c5c0 <NSFetchedResultsController: 0x1105c5c0>
po [[aFetchedResultsController fetchedObjects] count]
(id) $1 = 0x00000000 <nil>
I suppose the problem here is something very basic, but I don't know what I am overlooking.
I found the answer at the Ray Wenderlich site. The problem is that the sqlite file must be moved from the application bundle to the application documents directory.
This is done by copying it form the bundle into the documents directory if it is not already there.
Here is the code for creating the persistent store coordinator:
- (NSPersistentStoreCoordinator *)pstore {
if (pstore_ != nil) {
return pstore_;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"words.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath: storePath];
// THIS IS THE KEY PIECE TO IMPORTING AN EXISTING SQLITE FILE:
// Put down default db if it doesn't already exist
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:#"words" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
pstore_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.mom];
if(![pstore_ addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:options error:&error]) {
// Error for store creation should be handled in here
}
return pstore_;
}

UILocalNotification nil userInfo

I try to cancel a Local notification. I attach a dict with a Id for locate it later:
+ (void) send:(NSString*)title actionText:(NSString *)actionText when:(NSDate *)when count:(NSInteger)count option:(NSDictionary *)options
{
UILocalNotification *notif = [[UILocalNotification alloc] init];
//Setup code here...
notif.userInfo = options;
ALog(#"Sending notification %# %#", notif.alertBody, notif.userInfo);
//Print: Sending notification Task Col 0 0 {id = "1-1"};
[[UIApplication sharedApplication] scheduleLocalNotification:notif];
}
Then when I try to locate it:
+ (void) cancelNotification:(NSString *)theId {
for(UILocalNotification *aNotif in [[UIApplication sharedApplication] scheduledLocalNotifications])
{
if([[aNotif.userInfo objectForKey:#"id"] isEqualToString:theId])
{
// Never come here: userInfo is nil!!!
}
}
}
Always the userInfo is nil. I send the dict:
NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
[info setObject:theId forKey:#"id"];
or
[NSMutableDictionary dictionaryWithObject:theId forKey:#"id"]
with the same result. (theId is NSString)
The local notification valid only, it has valid timestamp. So while you are creating make sure that, it has valid timestamp. So that, it will be present in scheduledNotifications of UIApplicaiton.
Please let me know your comments.
eg: localNotification.fireDate = [NSDate date];

Resources