Sending a notification from another thread - objective-c

I have a settings panel in my app. Whenever the user presses a button, the object is updated on another thread as the UI updates. I have a separate label on the main view that is supposed to update the object count when the object has finished updating (which I want to happen regardless of whether the settings panel is up or down). I've tried following the apple documentation regarding this very topic, but it doesn't seem to work out for me - that is, it seems that the main view controller never receives the notification for some reason. Does anyone have any suggestions on how to alert the main view controller that an object passed to another thread has finished updating? Here's the code I'm using (most of which was copied from that doc):
Object Class:
[[NSNotificationCenter defaultCenter] postNotificationName: #"ScaleCountUpdated" object: self];
Main View Controller
- (void)setUpThreadingSupport
{
if (self.notifications) {
return;
}
self.notifications = [[NSMutableArray alloc] init];
self.notificationLock = [[NSLock alloc] init];
self.notificationThread = [NSThread currentThread];
self.notificationPort = [[NSMachPort alloc] init];
[self.notificationPort setDelegate: self];
[[NSRunLoop currentRunLoop] addPort: self.notificationPort
forMode: (NSString *)kCFRunLoopCommonModes];
}
- (void)handleMachMessage:(void *)msg
{
[self.notificationLock lock];
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex: 0];
[self.notifications removeObjectAtIndex: 0];
[self.notificationLock unlock];
[self processNotification: notification];
[self.notificationLock lock];
};
[self.notificationLock unlock];
}
- (void)processNotification:(NSNotification *)notification{
if ([NSThread currentThread] != self.notificationThread) {
// Forward the notification to the correct thread.
[self.notificationLock lock];
[self.notifications addObject: notification];
[self.notificationLock unlock];
[self.notificationPort sendBeforeDate: [NSDate date]
components: nil
from: nil
reserved: 0];
} else {
[self updateScaleCount];
}
}
- (void)updateScaleCount
{
NSLog(#"[ScalesViewController - updateScaleCount]: Scales updated from notification center.");
if([UserDefinedScales areScalesGrouped] == YES){
self.groupCountLabel.text = [NSString stringWithFormat: #"Group Count: %i", [[UserDefinedScales sortedKeys] count]];
} else {
self.groupCountLabel.text = #"Group Count: 1";
}
self.scaleCountLabel.text = [NSString stringWithFormat: #"Scale Count: %i", [UserDefinedScales scaleCount]];
}
Main View Controller - View Did Load:
[self setUpThreadingSupport];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(processNotification:)
name: #"ScaleCountUpdated"
object: nil];
If you have any suggestions on how to alter this code to make it function correctly, or have another solution to offer for achieving this, it would be greatly appreciated! Thank you.

It looks to me like you are doing it correctly, i.e. register for notification and send it.
As far as I can see from your code and the information you give, you can basically completely forget about the setupThreadingSupport. You should definitely test it without it. Not sure what you want to achieve, but looks like overkill where probably a simple block would suffice. Is there a compelling reason to listen to the notification on a background thread? Why not let the notification center decide?
Log the sending and receiving of the notifications - addObserver and postNotification is really all this mechanism needs to work as expected.

I would go to a simpler implementation. The NSNotificationCenter already provides all the mechanisms you need to broadcast and receive messages across your app.
If you fire the notification from a background thread you can use a GCD dispatch_async to make make it delivered on the main thread.
Object Class
dispatch_async(dispatch_get_main_queue(), ^{
// You don't need to pass the object itself here as you are not using it later.
[[NSNotificationCenter defaultCenter] postNotificationName:#"ScaleCountUpdated"];
}
MainViewController
-(void)viewDidLoad
{
[super viewDidLoad];
// Register your controller as an observer for a specific message name
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(updateScaleCount)
name: #"ScaleCountUpdated"
object: nil];
}
- (void)updateScaleCount
{
NSLog(#"[ScalesViewController - updateScaleCount]: Scales updated from notification center.");
if([UserDefinedScales areScalesGrouped] == YES){
self.groupCountLabel.text = [NSString stringWithFormat: #"Group Count: %i", [[UserDefinedScales sortedKeys] count]];
} else {
self.groupCountLabel.text = #"Group Count: 1";
}
self.scaleCountLabel.text = [NSString stringWithFormat: #"Scale Count: %i", [UserDefinedScales scaleCount]];
}

Related

Method does not invoked on Mac OS X 10.8 Mountain Lion

I'm trying to create small application which can be used over Mac OS X 10.8.
On Mac OS X Mavericks and on Mac OS X Yosemite, my application work well but on Mac OS X 10.8, main method of my application does not invoked. To be exact, the method seems to be invoked momentary, but soon after killed with alert sound.
I know that Mac OS X 10.8 is a bit strict than the OS after it. But I think there are something wrong with my code which I can't find out.
My main method is OK because if it is called directly, it works fine without any problem. But if I call it in the method of notification selector, the problem occur.
Here's my code. I appreciate any kind of suggestion,thanks.
- (IBAction)startButton:(id)sender {
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if ( [[defaults objectForKey:#"aBookMark"] length] == 0 ) {
[self getAudioCDPath];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(runEncodeAsyncWithNotify:)
name:NSWindowDidEndSheetNotification
object:self.window];
} else {
[self runEncodeAsync];
}
}
/* This method have problem */
-(void)runEncodeAsyncWithNotify:(NSNotification *)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidEndSheetNotification
object:self.window];
encodingFlag = YES;
[_start setEnabled: NO];
[_stop setEnabled: YES];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[self encodeWithLAME];
}];
}
/* This method does not have any problem */
-(void)runEncodeAsync {
encodingFlag = YES;
[_start setEnabled: NO];
[_stop setEnabled: YES];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[self encodeWithLAME];
}];
}
My mistake is to use NSWindowDidEndSheetNotification as sheet close notification. [self getAudioCDPath]is a method which presents sheet OpenPanel and this have completionHandler: block in itself.
Since what I'd like to do is just to invoke [self runEncodeAsync], I should write it in the completionHandler: block. No need to use NSWindowDidEndSheetNotification.
So, here's modified code of getAudioCDPath. Sorry I don't show it in the question.
[aPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSURL *aDirURL = [[aPanel URLs] objectAtIndex:0];
NSData *bookmark = nil;
NSError *error = nil;
bookmark = [aDirURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&error];
if (error) {
NSLog(#"Error creating bookmark for URL (%#): %#", aDirURL, error);
}
[defaults setObject:bookmark forKey:#"aBookMark"];
[self runEncodeAsync]; // I added this to invoke main method!!
}
}];

ARC Objective-C: How can self be deallocated?

I am creating a workflow to navigate through websites, every step of the workflow has to load n frames and then knows its ready (I have to implement the timeout).
I don't understand why [self next] is giving me this error:
* -[WebWorkflow next]: message sent to deallocated instance 0x105796ef0
Considering this delegate function:
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
frameCounter++;
NSInteger frames = [(WebWorkflowStep *)[steps objectAtIndex:index] frames];
NSLog(#"Frame counter %ld of %ld", frameCounter, frames);
[self next];
}
And this next method:
-(void) next
{
if ( index < [steps count])
{
frameCounter = 0;
index = index + 1;
WebWorkflowStep *step = [steps objectAtIndex:index-1];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:step forKey:#"selector"];
[[NSNotificationCenter defaultCenter] postNotificationName:EVENT_WORKFLOW_NEXT object:nil userInfo:userInfo];
}
}
Notes:
- WebWorflow a.k.a 'self' has been created/binded by another class with strong
Like so:
#interface AController : NSObject <APIProtocol>
{
WebView *webview;
NSMutableArray *accounts;
WebWorkflow *workflow;
}
#property (strong) WebWorkflow *workflow;
...
I do create the workflow like this:
workflow = [[WebWorkflow alloc] initWithWebView:webview];
NSArray *getPicturesWorkflow = [NSArray arrayWithObjects:
[[WebWorkflowStep alloc] initWithSelector:#"open" andLoadFrames:0],
[[WebWorkflowStep alloc] initWithSelector:#"login" andLoadFrames:2],
[[WebWorkflowStep alloc] initWithSelector:#"getPictures" andLoadFrames:8],
nil];
[workflow setSteps:getPicturesWorkflow];
And it gets initialized like:
-(id)initWithWebView:(WebView *)webview
{
self = [ super init];
if(self) {
timeout = 10;
index = 0;
web = webview;
frameCounter = 0;
[web setFrameLoadDelegate:self];
}
return self;
}
The AController instance owns a web view and is the web view's delegate. The AController instance is getting released (for some reason...we'd need to see how it's owner manages it). Since it might get released during a load, it should clean up after itself as follows:
- (void)dealloc {
[web stopLoading:self]; // or webView, not sure what you call it
}
This will prevent the crash. It will also abandon the load. If you don't want to do that, you'll need to figure out why the AController instance is being released.
The first step in doing that would be a breakpoint in the dealloc method.

iOS: Passing data between views

I have two views which are created programmatically. Let's name them view1 and view2. In view1 there is a picker and a button. The idea is when the user choose value and press the button selected value to be accessable from view2. For this I use NSNotificationCenter. Here is some code.
view1.m
-(void)registerNotification
{
NSDictionary *dict = [NSDictionary dictionaryWithObject:self.selectedOption forKey:#"data"];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"pickerdata"
object:self
userInfo:dict];
}
-(void)loadSecondView
{
self.secondView = [[secondViewController alloc]init];
[self.view addSubview:self.secondView.view];
[self registerNotification];
[self.secondView release];
}
view2.m
-(id)init
{
if(self = [super init])
{
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(reciveNotification:)
name:#"pickerdata" object:nil];
}
return self;
}
-(void)reciveNotification:(NSNotification *)notification
{
if([[notification name] isEqualToString:#"pickerdata"])
{
NSLog(#"%#", [NSString stringWithFormat:#"%#", [[notification userInfo] objectForKey:#"data"]]); // The output of NSLog print selected value
// Here is assignment to ivar
self.selectedValue = [NSString stringWithFormat:#"%#", [[notification userInfo] objectForKey:#"data"]];
}
}
The problem starts here. The logic which is interested of that value is implemented in loadView method. The problems is that loadView is executed before reciveNotification method and selectedValue does not contain needed information yet.
What to do so the information provided from NSNotificationCenter to be accessible from loadView method ?
I don't know if I fully understand your question, but wouldn't it be easier to pass the value directly to the viewController instead of dealing with notifications?
-(void)loadSecondView
{
self.secondView = [[secondViewController alloc]init];
self.secondView.selectedValue = self.selectedOption;
[self.view addSubview:self.secondView.view];
[self.secondView release];
}

ASINetworkQueue inside NSOperation blocking main thread

I'm using an NSOperation to collect data that should be downloaded (takes 2-5 sec.) and afterwards I download this. I've put a ASINetworkQueue inside this NSOperation to start downloading the previously collected data.
Everything works fine but when I call cancelAllOperations on my ASINetworkQueue, the main thread blocks and the UI Freezes. Why is this happening? Everything else works fine.
Here is my Code:
- (void)main {
//ManagedObjectContext for operations
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [[NSManagedObjectContext alloc] init];
[self.managedObjectContext setPersistentStoreCoordinator: [appDelegate persistentStoreCoordinator]];
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
[self startDownload];
if (!self.downloadDidFail) {
[self moveFiles];
[self.managedObjectContext save:nil];
}
}
- (void)startDownload {
self.downloadQueue = [ASINetworkQueue queue];
self.downloadQueue.delegate = self;
[self.downloadQueue setRequestDidFailSelector:#selector(dataRequestFailed:)];
[self.downloadQueue setRequestDidFinishSelector:#selector(dataRequestFinished:)];
[self.downloadQueue setQueueDidFinishSelector:#selector(dataQueueFinished:)];
[self.downloadQueue setShouldCancelAllRequestsOnFailure:YES];
[self.downloadQueue setDownloadProgressDelegate:self.progressView];
for (File *dataFile in self.dataFiles) {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:dataFile.url]];
[request setDownloadDestinationPath:dataFile.path];
[self.downloadQueue addOperation:request];
}
}
[self.downloadQueue go];
[self.downloadQueue waitUntilAllOperationsAreFinished];
}
- (void)dataRequestFinished:(ASIHTTPRequest *)request {
NSLog(#"DL finished");
}
- (void)dataRequestFailed:(ASIHTTPRequest *)request {
DLog(#"Download failed");
self.downloadDidFail = YES;
}
- (void)dataQueueFinished:(ASINetworkQueue *)queue {
DLog(#"Finished Data Queue");
}
- (void)cancelDownload {
self.canceledDownload = YES;
[self.downloadQueue cancelAllOperations];
}
I had the same problem and solved by calling:
[queue setShouldCancelAllRequestsOnFailure:NO]
before calling:
[queue cancelAllOperations].
ASI requests responses and queue responses are deliberately moved to the main thread for library design purposes.
You have two solution:
-Subclass ASIHTTPRequest and overwrite 2 methods. (Look for in the code something like "subclass for main thread").
-Modify the library. (Easy, but personally I don't like this solution).
What does your failure delegate method do? ASIHTTPRequest will run that on the main thread by default, so if it does a lot of processing (or there are a lot of requests) this could take quite some time.

How do I post a NSNotification when using Grand Central Dispatch?

I found that as predicted when I was writing an image to file that my UI was blocked for the duration, which was not acceptable. When I write the image to file I then post an NS Notification so that I can do some other specific jobs related to that completion. Original working but UI blocking code:
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
if (jpgData) {
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self];
}
To avoid the UI blocking I have put the writeToFile: into a Grand Central Dispatch queue so it runs as a concurrent thread. But when the write is completed and the thread is done, I want to post an NSNotification. I cannot as the code is shown here because it is in a background thread. But that is the functionality I want to accomplish, realizing this is not workable code:
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
// execute save to disk as a background thread
dispatch_queue_t myQueue = dispatch_queue_create("com.wilddogapps.myqueue", 0);
dispatch_async(myQueue, ^{
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self];
}
});
});
}
What is the correct mechanism here to post this notification to gain the functionality I want ?
A couple possibilities here.
1)
How about [NSObject performSelectorOnMainThread: ...] ?
E.G.
-(void) doNotification: (id) thingToPassAlong
{
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:thingToPassAlong];
}
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
// execute save to disk as a background thread
dispatch_queue_t myQueue = dispatch_queue_create("com.wilddogapps.myqueue", 0);
dispatch_async(myQueue, ^{
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
[self performSelectorOnMainThread: #selector(doNotification:) withObject: self waitUntilDone: YES];
}
});
});
}
More details at http://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/performSelectorOnMainThread:withObject:waitUntilDone:
or 2)
Completion Callbacks
as seen at How can I be notified when a dispatch_async task is complete?
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
// execute save to disk as a background thread
dispatch_queue_t myQueue = dispatch_queue_create("com.wilddogapps.myqueue", 0);
dispatch_async(myQueue, ^{
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self];
}
});
});
}
That is already correct. However why do you need to use notification if you already dispatch_get_main_queue()?
Just use
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
//Do whatever you want with the image here
}
});
Anyway your original solution is fine. It's not blocking. Basically it'll save the file at other thread and once it's done it'll do what it takes.
What ever you do will be done on the same thread with the thread that call [[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self]; namely main thread.