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.
Related
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!!
}
}];
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]];
}
I have an instance of a NSObject class that is supposed to parse a XML and save NSManagedObjects, it does everything ok. But I need to receive a NSManagedObjectContextDidSaveNotification inside it to merge CoreData contexts.
The thing is that my instance is being deallocated sooner then I receive the notification above.
How can I prevent my instance being deallocated sooner?
Here's when I call my instance
// in my ViewController implementation
WSNoticia *wsNoticia = [WSNoticia new]; // __strong by default right?
Here's the implementation of WSNoticia:
- (id)init {
self = [super init];
if (self) {
[self parseNews];
}
return self;
}
- (void)dealloc {
// called before mergeChanges: or updateContext:
}
#pragma mark - Private Methods
- (void)parseNews {
// save context in another thread
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setUndoManager:nil]; // performance benefit
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// fetching things
}];
[blockOperation setCompletionBlock:^{
// updating and saving things
// here the NSManagedObjectContextDidSaveNotification is called (by doing [context save:nil];
}];
// add operation to queue
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:blockOperation];
}
// doesn't get called
- (void)updateContext:(NSNotification *)notification {
NSManagedObjectContext *mainContext = [self managedObjectContext];
[mainContext mergeChangesFromContextDidSaveNotification:notification];
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationNameNoticiasParsed object:self];
}
#pragma mark - NSNotificationCenter
// doesn't get called
- (void)mergeChanges:(NSNotification *)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
if ([notification object] == mainContext) {
// main context save, no need to perform the merge
return;
}
[self performSelectorOnMainThread:#selector(updateContext:) withObject:notification waitUntilDone:YES];
}
The strong lifetime qualifier is applied by default, but the lifetime of your variable is just for the method in which you declare it.
If you declare wsNoticia as an instance variable, you should be fine.
I am using custom delegate objects to do some cleanup tasks after a request finishes. ASIHTTPRequest doesn't retain delegates so I can't autorelease them. Right now this is how I am allocating and releasing the delegates.
App Delegate
MyDelegate *delegate = [[MyDelegate alloc] init];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:delegate];
MyDelegate.m
- (void)requestFinished:(ASIHTTPRequest *)request
{
[self release];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
[self release];
}
Is there a better way to do this? Having the delegates release themselves seems ugly and Xcode's build and analyze feels uncomfortable with what I'm doing.
A simple approach would be to maintain a mutable set of delgates for each active request in your main controller (the app delegate, in this case):
#interface MyAppController
{
NSMutableSet * activeDelegates;
}
#end
#implementation MyAppController
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
activeDelegates = [[NSMutableSet alloc] initWithCapacity:0];
return self;
}
- (void)dealloc
{
[activeDelegates release];
}
- (void)createRequest
{
MyDelegate *delegate = [[MyDelegate alloc] init];
[activeDelegates addObject:delegate];
[delegate release];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
...
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
MyDelegate *delegate = [request delegate];
[delegate doSomething];
[activeDelegates removeObject:delegate];
{
- (void)requestFailed:(ASIHTTPRequest *)request
{
[activeDelegates removeObject:[request delegate]];
}
#end
Why do you have a separate class purely to be a delegate? That's not how delegate objects typically work. Normally the controller that created the ASIHTTPRequest becomes the delegate, at which point you don't have to worry about releasing it because it will outlive the ASIHTTPRequest already (and if your controller gets dealloced before the ASIHTTPRequest is done, you need to cancel the request).
If You don't want to create a "controller" class for all your delegate instances, i would still at least follow the memory convention rules, and release the delegate immediately after setting it to asihhtprequest. Then i would include a propery in the delegate, something with a name managesItsOwnLifetime (BOOL) and on setting this to YES i would do a [self retain] ...
Dear community.
When i has a stable version of my application, while i don't start change code to multithread version. What was a difference between previous version:
in (void)applicationDidFinishLaunching:(NSNotification *)aNotification
i do loop to add to queue my code:
NSOperationQueue *opQueueImportUpdateFirstTimeData = [[[NSOperationQueue alloc]init]autorelease];
int i = 0;
for (NSString *carrier in currentCarriers)
{
AppController *operation = [[AppController alloc] initAndUpdateCarrier:carrier identifier:i];
[opQueueImportUpdateFirstTimeData addOperation:operation];
i++;
}
External class have:
- (id)initAndUpdateCarrier:(NSString *)forCarrier
identifier:(NSUInteger)iQuene;
{
[super init];
[self setIdentifierQuene:iQuene];
[self setStartForCarrier:forCarrier];
[self setPercentDone:0.0];
This point is a very important:
[self setDatabase:[[MySQLIXC alloc] init]];
u can't alloc other classes in process of u multithreading, i don't know why, but this is take malloc_error in whole queues
[self setAppDelegate:[[NSApplication sharedApplication] delegate]];
[self setManagedObjectContext:[[NSManagedObjectContext alloc] init]];
return self;
}
And in external class i have:
-(void) main;
{
[self makeUpdatesForCarrier:startForCarrier andTypeOfOperation:#"rates" forDirection:#"incoming"];// mySqlConnector:database];
it's a just some functions which working on local moc.
When i start application, interface didn't place it in background, and start visualization only after all queues will finish.
Before i try to alloc]init] MySQLIXC class inside my external class, but it gives me a lot of malloc_error_break exceptions, like somebody try to freeze memory for me.
Tnx.
Here is moc declaration:
in .h:
#property(retain) NSManagedObjectContext *managedObjectContext;
in .m:
#synthesize managedObjectContext;
Set persistent store coordinator:
[[self managedObjectContext] setUndoManager:nil];
[[self managedObjectContext] setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
Merge changes for with main moc:
- (void)mergeChanges:(NSNotification *)notification;
{
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
And in one place of my code i'm using a main moc (only for read information, i know that moc not thread safe):
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"DestinationsListForSale"
inManagedObjectContext:mainContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"lastUsedProfit > 0"];
[request setPredicate:predicate];
First off, CoreData is NOT thread safe. I would strongly advise that if you do not understand core data in a good deal of detail you keep you application singlethreaded or at the very least ensure that you always access the store from a single thread (probably best to use the main thread).
That said, this will not cause malloc_error_breaks AFAIK. You would see core data merge error exceptions and similar problems.
Could you show the code where you set up the moc further - just allocating and initing a moc is not enough - you have to set it's NSPersistentStoreCoordinator