NSOperationQueue is crashing in IOS - nsoperationqueue

I have a project which downloads images in background using NSOperationQueue. It was working until now on devices with IOS 4.3. However if I build the app with base sdk 4.3 or with 5 and run the app on device with IOS5, the app crashes. When app is launched, it adds NSOperation objects into queue for downloading the images. If in between I press back button, I cancel the NSOperation and it crashes and displays following trace on console:
#0 0x004727b7 in ____NSOQSchedule_block_invoke_0 ()
#1 0x026a5618 in _dispatch_call_block_and_release ()
#2 0x026a7a10 in _dispatch_worker_thread2 ()
#3 0x974bb781 in _pthread_wqthread ()
#4 0x974bb5c6 in start_wqthread ()
and prints "ResourceLoadOperation isFinished = YES without being started by the queue it is in"
If I comment the cancel method call, app doesnot crash.
Is there any updates on the NSOperation changes for IOS5?

I had this same problem when building against iOS 5. I ended up creating a flag named operationStarted that was NO by default and I toggled to YES when the start method was called. Then in my finish method (where I generate the KVO notifications), I checked the value of the flag before firing the notifications.
The flag definition looks like this:
#property (nonatomic, assign, getter=isOperationStarted) BOOL operationStarted;
The start method:
- (void)start {
[self setOperationStarted:YES];
...
}
My finish method which is called when the operation is complete or cancelled:
- (void)finish {
if (![self isOperationStarted]) return;
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
}
That ended up resolving the issue for me. Hope it helps someone else.

Related

Update NSWindow content while asynchronously loading a file

In my document-based MacOS application, I have some big files that are loaded (especially recent files opened at application launch). I created ProgressController (a NSWindowController subclass) to inform user in a window that file loading is in progress. It is allocated by the makeWindowControllers method of the NSDocument subclass I use to manage documents. These are created when the user opens a file (and notably at startup when documents were displayed as the user did quit the application) and they load their content asynchronously in a background queue which in principle should not affect the main thread performance:
-(instancetype) initForURL:(NSURL *)urlOrNil withContentsOfURL:(NSURL *)contentsURL ofType:(NSString *)typeName error:(NSError *
{
if (self = [super init]){
... assign some variables before loading content...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^{
[[[NSURLSession sharedSession] dataTaskWithURL:urlOrNil completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.content = [[NSMutableString alloc] initWithData:data encoding:encoder];
dispatch_async(dispatch_get_main_queue(), ^(){
[self contentIsLoaded];
});
}] resume];
});
}
return self;
}
In contentIsLoaded, the progress window is closed.
- (void) contentIsLoaded
{
... do something ...
[self.progressController close];
}
This behaviour is OK, the window is displayed and closed when necessary.
The problem occurs when I want to update the content of this window on the main queue. I tried setting a NSTimer but is is never fired, even though it is created in the main queue. So, in MyApplicationDelegate I created a GCD timer like this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
if (self->timer !=nil) dispatch_source_cancel(timer);
self.queue = dispatch_queue_create( "my session queue", DISPATCH_QUEUE_CONCURRENT);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
[self updateProgress];
});
dispatch_resume(timer);
…
}
In MyApplicationDelegate, the updateProgress method is defined as:
- (void) updateProgress
{
dispatch_async(self.queue, ^{
NSLog(#"timer method fired");
dispatch_async(dispatch_get_main_queue(), ^(){
NSLog(#"access to UI fired");
... update window (e.g. NSProgressIndicator)
}
});
if (self.shouldCancelTimer) dispatch_source_cancel(self->timer);
});
}
When running the application, The "Timer method fired" is logged every second. The "timer method fired" message (in the main queue) is logged once or twice only, then the logging seems suspended until the file has been loaded. Then this missing message appears several times in a row, as it was suspended before.
What did I wrong? I supposed that the background queue used for file loading should not affect the main queue and UI updates. Many applications behave like that and I need such behaviour as files in my App (strings, csv, json) can be hundreds of Mbytes!
For starters, have you looked into NSURLSessionTask's progress reporting APIs? There are a bunch of facilities for measuring and getting called back as data loads. It might be possible for you to avoid doing the polling, and just update your UI as these are called back - all on the main thread.
In fact, you might not even need one at all. As I recall, the networking operations carried out by NSURLSession are all done in the background anyways. I bet you can remove all of this, and just use some extra delegate callbacks from NSURLSession APIs to get this done. Way simpler too :)
Good luck!

PFUser currentUser nil after app restart on OS X

I've downloaded the latest Parse SDK for OS X, and I'm trying to retain my login after app restarts (obviously). I've used Parse before and I haven't faced this problem, neither on iOS nor OS X.
On my app start:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[Parse setApplicationId:#"XXX" clientKey:#"XXX"];
}
In my first view controller:
-(void)viewDidAppear{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(![PFUser currentUser]){
[self performSegueWithIdentifier:#"login" sender:nil];
}else{
...
}
});
}
My login succeeds, and at that point [PFUser currentUser] is valid. Then, I close the app (tried both killing and gracefully quitting). When I open it again, [PFUser currentUser] is nil. I've tried this many times, it yields the same results. Why?
After struggling for a long while, I've found the solution. I need to dispatch_async the user checking block and it starts working. So instead of:
dispatch_once(&onceToken, ^{
if(![PFUser currentUser]){
[self performSegueWithIdentifier:#"login" sender:nil];
}else{
...
}
});
I did:
dispatch_once(&onceToken, ^{
dispatch_async(dispatch_get_main_queue(), ^{
if(![PFUser currentUser]){
[self performSegueWithIdentifier:#"login" sender:nil];
}else{
...
}
});
});
And it started working. Interesting to see that something is still not initialized on viewDidAppear synchronously on main queue (yes, it IS the main queue, double checked that), but is initialized somewhere after posting to the same queue asynchronously. Parse definitely needs more quality control of their SDK.

gcdasyncsocket background file transfer

Having two devices that need to keep transferring data while in background or in LockScreen.
The main resource about backgrounding is available on https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
So far I'm looking forward to understand how is it expected to implement such above mentioned behaviour: in a scenario where a transfer is in progress and one of the apps (or both) goes into background. Obviously we have resumable transfer management working already.
I've been collecting stubs and answers about and I've ended up with the following:
Ensure every socket is backgroundable.
[socket performBlock:^{
[socket enableBackgroundingOnSocket];
}];
To keep backgrounding even when in Lock Screen, I read an answer saying that we should have something like at the end of didFinishLaunchingWithOptions but what code is in [self backgroundHandler] method?
BOOL backgroundAccepted = [[UIApplication sharedApplication]
setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
if (backgroundAccepted)
NSLog(#"background handler accepted");
return YES;
The applicationDidEnterBackground delegate method of UIApplication shows
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"=== DID ENTER BACKGROUND ===");
if([[UIDevice currentDevice] respondsToSelector:#selector(isMultitaskingSupported)])
NSLog(#"Multitasking Supported");
else
return;
// Shall I remove my KVO observers when in background?? I guess NOT, right? :D
//[[NSNotificationCenter defaultCenter] removeObserver:self];
UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"End of tolerate time. Application should be suspended now if we do not ask more 'tolerance'");
// [self askToRunMoreBackgroundTask]; This code seems to be unnecessary. I'll verify it.
}];
if (bgTask == UIBackgroundTaskInvalid)
NSLog(#"This application does not support background mode");
else
NSLog(#"Application will continue to run in background");
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
I got it working following this tutorial but looks like GCDAsyncSocket is no longer maintained so it will work only on iOS7.
http://www.objc.io/issue-5/multitasking.html
To do background file transfer under iOS 8 I am using AFNetworking library (http://afnetworking.com)

How to let DropboxAPI work in runModalForWindow

I'm using the Dropbox API for OSX. All works fine, except when I want to make calls in a modal window that is started with [NSApp runModalForWindow:thisWindow]; it seems the modal loop blocks the
DropboxAPI from processing anything.
The DBRestClient delegate methods are never called in response to for example [client loadMetadata:path]; Which is - if understand correctly - in line with what the NSApplication documentation says for this method. The question is:
Is there a way to let calls to Dropbox work from inside a modal window?
I have seen that timers can be added to the NSModalPanelRunLoopMode. Is there perhaps something similar for the DroboxAPI?
And additionally: will Dropbox calls that were started but not yet completed before this or any other modal window is displayed proceed as normal, or are they also blocked?
Yes; further investigation shows any runModalForWindow and even displaying an NSAlert.showModal will completely block the DropboxAPI. Also, inplace mouse handling loops do the same thing. Imo a major design flaw in the OSX DropboxAPI: it should have been running on a background thread. The only way around this, is to not start any user task that could involve blocking Dropbox while the API something is still running. Which is not really feasible in any non-trivial app that needs dropbox to work in the background.
Implementation of NSApplication runModalForWindow is indeed too harsh.
Here is more humanistic version:
void runWindowModal(NSWindow* pnw) {
[pnw retain];
NSApplication* app = [NSApplication sharedApplication];
NSModalSession session = [app beginModalSessionForWindow:pnw];
for( ;; ) {
if ([app runModalSession:session] != NSModalResponseContinue)
break;
NSEvent* e = [app nextEventMatchingMask: NSAnyEventMask
untilDate: [NSDate distantFuture]
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (e != nil)
[app sendEvent: e];
}
[app endModalSession: session];
[pnw release];
}
This will enable NSURLConnection and other callbacks processing while running modal loops.
Dropbox support just confirmed that the DropboxAPI on OSX works on the main runloop and indeed runModalForWindow etc. will block the API. There's no work around.
The problem is more general. All nsurl connections can run only on the main runloop. This means that webviews have the same issue. And there is a sollution to this. You have to create a modal session and run it for short amounts of time, frequently, and lock the main thread in a loop until the modal window is closed. Every time you switch back to the modal session, the main runloop will finish any scheduled tasks, and that includes the nsurlconnections from the modal session.
Now I will show you a piece of code but note that this is c# using Xamarin.Mac library. You should be able to translate this easily to objc. But if you find it difficult, you can look up solutions for nswebview in modal dialog.
//so this is in my NSWindowController - the modal dialog
IntPtr session;
bool running;
public void showDialog ()//this is the method I use to show the dialog
{
running = true;
session = NSApplication.SharedApplication.BeginModalSession (Window);
int resp = (int)NSRunResponse.Continues;
while (running && resp == (int)NSRunResponse.Continues) {
resp = (int)NSApplication.SharedApplication.RunModalSession (session);
NSRunLoop.Current.RunUntil (NSRunLoopMode.Default, NSDate.DistantFuture);
}
NSApplication.SharedApplication.EndModalSession (session);
}
[Export ("windowWillClose:")]
public void WindowWillClose (NSNotification notification)//this is the override of windowWillClose:
{
running = false;
}

NSDistributedNotifications not distributed between instances of (same) app(s)

On 10.7.2 I have trouble getting standard NSDistributedNotifications to work out of the box.
Even when brought back to just (Full XCode version at https://github.com/dirkx/Example-NSDistribtuedNotification-Failing) to something as simple as below I get:
Splendidly working notifications 'locally' (Like with a NSNotificationCenter) but
No inter-app comms when I start the app up twice (e.g. from the command line)
No notifications when another apps registers for this (or for nill)
No debug/info in the distnoted daemon logs either.
What am I missing ?
NSString * kSayNotification = #"org.webweaving.sayExample";
// Send a Distributed Notification on button press.
//
-(IBAction)buttonChange:(NSButton *)sender {
NSString * str = (sender.state == NSOnState) ? #"Yes" : #"No";
[[NSDistributedNotificationCenter defaultCenter]
postNotificationName:kSayNotification
object:str
];
}
// Update a label on receiving a Notification.
//
-(void)notif:(NSNotification *)nf {
.. snipped time string ...
// Textfield with the time of arrival and the value passed in the notification.
//
textField.stringValue = [NSString stringWithFormat:#"%#: %#",
dStr, (NSString *)nf.object
];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Register for the notifications.
//
[[NSDistributedNotificationCenter defaultCenter]
addObserver:self
selector:#selector(notif:)
name:kSayNotification
object:nil];
}
As an aside - notification watcher (https://github.com/melo/notification-watcher) does not show the Notifiactions - but the notifications are processed within the app.
Turns out that when there are no other reasons for CFRunLoop to return - the messages are queued indefinitely. This seems by design.
I found three work arounds for this - none of which is that nice - they both involve
adding a deliverImmediately:YES to the posting,
a deliverImmediately: with NSNotificationSuspensionBehaviorDeliverImmediately to the observer or
setting a timer to intentionally interrupt the runLoop every seconds.
Obviously - none of this comes cheap.
Dw

Resources