Request to access calendar hangs on splash screen - objective-c

In my app, I am reading the calendar as soon as the app launches. So for the first time as per the guidelines my app asks for calendar access but what's happening is it never shows up and all i can see is the splash screen.
When i close the app i see the popup for granting access to calendar on my phone. I grant access to it and after that point everything works fine.
Here's my code :
-(NSArray*) listOfEventsInCalendar
{
_eventStore = [[EKEventStore alloc]init];
__block NSArray * events;
if ([_eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
/* iOS Settings > Privacy > Calendars > MY APP > ENABLE | DISABLE */
[_eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
if ( granted )
{
NSDate * startDate = [NSDate date];
NSDate * endDate = [NSDate distantFuture];
NSPredicate * predicate = [_eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil];
events = [_eventStore eventsMatchingPredicate:predicate];
if (events) {
insideArray = [self castEvents:events];
}
else{
NSLog(#"No Events found in the calendar");
}
}
else
{
NSLog(#"User has not granted permission!");
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
return insideArray;
}
What am i doing wrong??
Thanks,

The problem is simple. You are blocking the main thread with your semaphore. This is a bad idea.
Don't have your listOfEventsInCalendar block. Let it return immediately. Let the completion block handle the result. One option would be to add a block parameter to your listOfEventsInCalendar method. Then call the block from the completion block passing the obtained array.

Related

How to stop NSOperationQueue during dispatch_async

I'm adding many block-operations to an operationsqueue in a for loop. In each operation I need to check on another thread if a condition is fulfilled. If the condition is fulfilled, all operations should be cancelled.
I made a sample code to show you my problem:
__block BOOL queueDidCancel = NO;
NSArray *array = [NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
if (queueDidCancel) {return;}
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([string isEqualToString:#"1"]) {
queueDidCancel = YES;
[myQueue cancelAllOperations];
}
});
}];
}
Expected output from NSLog:
run: 1
Output I got (it varies between 7 and 9):
run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8
I googled for hours, but I could not find a solution.
I think I found a solution. Here's the updated code:
NSArray *array = [NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
[myQueue setSuspended:YES];
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_main_queue(), ^{
if (![string isEqualToString:#"1"]) {
[myQueue setSuspended:NO];
}
});
}];
}
Let me use more space. You need to sync access to your variable. It is the correct idea but you are using it incorrectly. You need a lock or an atomic ivar or something like that to sync access to it.
Then if you cancel in the dispatch_async bit it happens looooong after all the blocks executed. That is what your output shows. As mentioned in the comment, if you add a NSLog e.g.
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:#"1"]) {
queueDidCancel = YES;
// Add here
NSLog(#"Going to cancel now");
[myQueue cancelAllOperations];
}
you will see what I mean. I expect that to typically execute deep into your array or even after all of the array finished executing.
But the biggest problem is your logic. You need some logic to cancel those blocks. Just messaging cancelAllOperations or setSuspended is not enough and blocks that are already running will keep on running.
Here is a quick example.
NSObject * lock = NSObject.new; // Use this to sync access
__block BOOL queueDidCancel = NO;
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
// Here you also need to add some logic, e.g. as below
// Note the sync access
#synchronized ( lock ) {
if (queueDidCancel) { break; }
}
[myQueue addOperationWithBlock:^{
// You need to sync access to queueDidCancel especially if
// you access it from main and the queue or if you increase
// the concurrent count
// This lock is one way of doing it, there are others
#synchronized ( lock ) {
// Here is your cancel logic! This is fine here
if (queueDidCancel) {return;}
}
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:#"1"]) {
// Again you need to sync this
#synchronized ( lock ) {
queueDidCancel = YES;
}
// This is not needed your logic should take care of it ...
// The problem is that running threads will probably
// keep on running and you need logic to stop them
// [myQueue cancelAllOperations];
}
});
}];
}
Now this example does what yours does but with a bit more locking and a bit more logic and NO cancelAllOperations nor suspended = YESs. This will not do what you want as even with this running threads tend to run to completion and you need logic to stop it.
Also, in this example, I left the exit or cancel condition as is in the main thread. Again here this will probably mean nothing gets cancelled, but in real life you'd typically cancel from some UI e.g. a button click and then you'd do it as here. But you could cancel anywhere using the lock.
EDIT
Based on lots of comments here is another possible way.
Here you check inside the block and based on the check add another block or not.
NSOperationQueue * queue = NSOperationQueue.new;
// Important
queue.maxConcurrentOperationCount = 1;
void ( ^ block ) ( void ) = ^ {
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means continue adding blocks
if ( cond )
{
[queue addOperationWithBlock:block];
}
// else done
} );
};
// Start it all
[queue addOperationWithBlock:block];
Above I use the same block every time which is also quite an assumption but you can change it easily to add different blocks. However, if the blocks are all the same you will only need one and do not need to keep on scheduling new blocks and then can do it as below.
void ( ^ block1 ) ( void ) = ^ {
// Some logic
__block BOOL done = NO;
while ( ! done )
{
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means stop! here
done = cond;
} );
}
};

How to get data out of a block?

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
...
}

Need clarification on dispatch_group_wait() behavior when dispatch_group_create() and dispatch_group_enter() are called from different queues

I am looking at the Ray Wenderlich tutorial on using dispatch queues to get notified when a group of tasks complete. http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2
The first code shown under "Code that works" is straight from the tutorial. The Alert view(final completion block) get executed after all 3 downloads complete.
I tried to play around with it and moved the dispatch async down in the "Code that does not work" to see what will happen if dispatch_group_create() and dispatch_group_enter() happen on different queues. In this case, the dispatch_group_enter() does not seem to register because the dispatch_group_wait() immediately completes and alert view(final completion block) is executed even before all the downloads have completed.
Can someone explain whats happening in the second case? (This is just for my understanding of how dispatch group works and I realize thats its better to put the entire function in the global concurrent queue to avoid blocking the main thread).
Code that works
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++)
{
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup);
__block Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
NSLog(#"Finished completion block for photo alloc for URL %# and photo is %#",url,photo) ;
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
NSLog(#"Finished adding photo to shared manager for URL %# and photo is %#",url,photo) ;
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
NSLog(#"Executing completion block after download group complete") ;
completionBlock(error);
}
}) ;
}) ;
}
EDITED Code that does not work with extra NSLog statements
Code that does not work
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++)
{
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{
dispatch_group_enter(downloadGroup);
NSLog(#"Enetered group for URL %#",url) ;
__block Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
NSLog(#"Finished completion block for photo alloc for URL %# and photo is %#",url,photo) ;
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
NSLog(#"Finished adding photo to shared manager for URL %# and photo is %#",url,photo) ;
}) ;
}
NSLog(#"Executing wait statement") ;
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
NSLog(#"Executing completion block after download group complete") ;
completionBlock(error);
}
}) ;
}
The "dispatch_group_enter() does not seem to register" because it hasn't actually been called yet by the time that dispatch_group_wait() is called. Or, rather, it's not guaranteed to have been called. There's a race condition.
This isn't specifically about different queues. It's about concurrency and asynchronicity.
dispatch_async() just means "add a task to a list" with an implicit understanding that something, somewhere, somewhen will take tasks off of that list and execute them. It returns to its caller immediately after the task has been put on the list. It does not wait for the task to start running, let alone complete running.
So, your for loop runs very quickly and by the time it exits, it may be that none of the tasks that it has queued have started. Or, if any have started, it may be that they haven't finished entering the group.
Your code may complete its call to dispatch_group_wait() before anything has entered the group.
Usually, you want to be sure that all relevant calls to dispatch_group_enter() have completed before the call to dispatch_group_wait() is made. The easiest way to do that is to have them all happen synchronously in one execution context. That is, don't put calls to dispatch_group_enter() inside blocks that are dispatched asynchronously.

How to get the name/id of default calendar in iOS 6

I have two (2) calendars (iCal) on my iPad (one personal, one for the app). They are sync'd to my iMac for testing only. (Saves me time making entries to the specific app calendar).
I am currently writing an app that needs to access the app's calendar. It is the primary calendar on the iPad. I am trying to get Apple's SimpleEKDemo (unmodified) to work with the app's calendar, but so far I can't even get it not crash, much less to return anything. I have been looking at Google and SO questions for hours now, and decided it's time to call in the big guns.
This is the code where it's crashing:
- (void)viewDidLoad
{
self.title = #"Events List";
// Initialize an event store object with the init method. Initilize the array for events.
self.eventStore = [[EKEventStore alloc] init];
self.eventsList = [[NSMutableArray alloc] initWithArray:0];
// Get the default calendar from store.
self.defaultCalendar = [self.eventStore defaultCalendarForNewEvents]; // <---- crashes here --------
// Create an Add button
UIBarButtonItem *addButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:
UIBarButtonSystemItemAdd target:self action:#selector(addEvent:)];
self.navigationItem.rightBarButtonItem = addButtonItem;
[addButtonItem release];
self.navigationController.delegate = self;
// Fetch today's event on selected calendar and put them into the eventsList array
[self.eventsList addObjectsFromArray:[self fetchEventsForToday]];
[self.tableView reloadData];
}
This is the output from the "crash":
2012-10-05 14:33:12.555 SimpleEKDemo[874:907] defaultCalendarForNewEvents failed: Error Domain=EKCADErrorDomain Code=1013 "The operation couldn’t be completed. (EKCADErrorDomain error 1013.)"
I need to make sure I'm on the correct calendar... how do I do that?
You need to ensure you ask permission before trying to access the Event Store. Note that you need to only call this once. If the user denies access, they need to go to iOS Settings (see comment in code) to enable permissions for your app.
/* iOS 6 requires the user grant your application access to the Event Stores */
if ([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
/* iOS Settings > Privacy > Calendars > MY APP > ENABLE | DISABLE */
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
if ( granted )
{
NSLog(#"User has granted permission!");
}
else
{
NSLog(#"User has not granted permission!");
}
}];
}
In iOS 5, you are only allowed to access Events (EKEntityTypeEvent) in the Event Store, unlike in iOS 6, where you can access Reminders (EKEntityTypeReminder). But you need the above code to at least get granted 1 time.
I should also mention that you need to be granted permission BEFORE you access the EventStore, in your case: [self.eventStore defaultCalendarForNewEvents];.
Also, defaultCalendarForNewEvents would be the correct way to access the users Default Calendar. If you wish to access a calendar with another name, then you need to iterate through the calendars and choose the appropriate one based on the results returned.
//Check if iOS6 or later is installed on user's device *********
if([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)]) {
//Request the access to the Calendar
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted,NSError* error){
//Access not granted-------------
if(!granted){
NSString *message = #"Hey! I Can't access your Calendar... check your privacy settings to let me in!";
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Warning"
message:message
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil,nil];
//Show an alert message!
//UIKit needs every change to be done in the main queue
dispatch_async(dispatch_get_main_queue(), ^{[alertView show];});
//Access granted------------------
}
else
{
self.defaultCalendar=[self.eventStore defaultCalendarForNewEvents];
}
}];
}
//Device prior to iOS 6.0 *********************************************
else{
self.defaultCalendar=[self.eventStore defaultCalendarForNewEvents];
}

How can I cause test method to wait till delegate has finished processing?

I have the following Objective C test code in a SenTestCase class. I get no errors, but the httpReceiveDataFinished method never gets called. Is this because the test is ended before the delegate has a chance to process the http method?
If that is the case how can I spin off a thread (or something similar) to make the test wait for a few seconds?
Thanks a million for any help. I have programmed Java for years, but Objective-C only a few days.
- (void)testExample
{
HttpClient *client = [[HttpClient alloc] init];
client.method = METHOD_GET;
client.followRedirects = YES;
[client processRequest:#"http://google.com" delegate:self];
NSLog(#"Test Over");
}
-(void) httpReceiveError:(NSError*)error {
NSLog(#"***\n%#\n***",[error description]);
}
- (void) httpReceiveDataChunk:(NSData *)data {
[self.httpResponseData appendData:data];
}
-(void) httpReceiveDataFinished {
NSString *result = [[NSString alloc]
initWithData:self.httpResponseData
encoding:NSUTF8StringEncoding];
NSLog(#"***\nRESULT: %# \n***",result);
}
First: Stanislav's link is excellent.
For myself, I needed something that was more flexible (run for long durations) but would also pass the test immediately on success. (Large file downloads and the like.)
Here's my utility function:
-(BOOL)runLooperDooper:(NSTimeInterval)timeoutInSeconds
optionalTestName:(NSString *)testName
{
NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
// loop until the operation completes and sets stopRunLoop = TRUE
// or until the timeout has expired
while (!stopRunLoop && [giveUpDate timeIntervalSinceNow] > 0)
{
// run the current run loop for 1.0 second(s) to give the operation code a chance to work
NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
[[NSRunLoop currentRunLoop] runUntilDate:stopDate];
}
STAssertTrue(stopRunLoop,
#"%# failed to finish before runloop expired after %f seconds", testName, timeoutInSeconds);
return stopRunLoop;
}
Declare stopRunLoop as an ivar in your tester-class. Make sure to reset stopRunLoop to FALSE in your setUp function, and call this function prior to calling your [client processRequest:#"http://google.com" delegate:self]; Then, in your event handlers, set stopRunLoop to TRUE.
By passing it a testName, you can reuse it for multiple tests and get some meaningful error messages.
Edit: 99% sure I based the above code off of another StackOverflow post that I can't find at the moment, or I'd link it.