I have object with .delegate property which i manipulate in method 'doJob'. I assign this property with 'self' and my function is being called when this object finishes his job. Till now everything is fine.
Now i want to manipulate this object in a separate thread.
I'm using [NSThread detachNewThreadSelector...] to run the 'doJob' function.
In this case my delegate method not being called. I guess this is because 'self' points to new thread instead of main one. Ok. I'm passing self as argument to function while creating the thread and it still not working. What do i miss?
my current code is as follows:
- (void)mainFunction
{
[NSThread detachNewThreadSelector:#selector(doJob:) toTarget:self witObject:self];
}
- (void)doJob:(MyObject*)parentThread
{
ManipulatedObject *obj = [[ManipulatedObject alloc] init];
obj.delegate = parentThread;
[object startJob];
}
GCD will make most of your multi-threading troubles trivial. You can do something like this:
- (void)mainFunction
{
// Runs your task on a background thread with default priority.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ManipulatedObject * obj = [[ManipulatedObject alloc] init];
[obj startJob]; // I'm assuming this is sychronous.
// The callback is explicitly run on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
// Your callback here.
[obj release];
});
});
}
That's all you have to do, it's that simple. All the relevant code is inline and together.
If you want the ManipulatedObject to explicitly invoke the block, then you could add that ability to ManipulatedObject. To do so, you should:
Define the block type for convenience typedef void(^MyCallback)();
Add #property (nonatomic, copy) MyCallback block; and #synthesize block. Don't forget the copy.
Invoke the block when you need to dispatch_async(dispatch_get_main_queue(), [self block]);.
If your delegate needs to make more than one kind of callback, then you will need a block for each callback. It's a minor inconvenience, but it's worth it for all the conveniences you gain.
For a more thorough explanation of blocks and GCD, check out WWDC 2011 session 308.
Well firstly you do not need to pass self as the witObject: parameter, (which is spelt wrong) because - (void)doJob:(MyObject*)parentThread is still in the same object (self is the same in both threads), self has nothing to do with your main thread its MyObject presumable, you also have a problem were you are not creating a new autorelease pool for your doJob:, doJob: should look like
- (void)doJob:(MyObject*)parentThread
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
ManipulatedObject *obj = [[ManipulatedObject alloc] init];
obj.delegate = parentThread;
[object startJob];
[pool release];
}
you have to give us some information about how you're delegate method is being called, if it is tying to use timers or something like that then you are going to have problems because there is no runloop to add your timer to.
Related
I've been sitting on this error for hours now. I'm getting an EXC_BAD_ACCESS (code=2) on the line:
[self.downloadQueue addOperation:self.downloadOP];
I know it has to be related to memory conflicts, but I just can't find the problem. The class that manages the OperationQueues is a singleton, but i think thats not the problem.
Here's the shortened version of my .h file:
#interface GTMConnectionManager : NSObject{
}
#property (retain) GTMDownloadOperation *downloadOP;
#property (retain) NSOperationQueue *downloadQueue;
// it doesn't make a difference if I add 'nonatomic' to these properties
+ (GTMConnectionManager *)sharedConnectionManager;
-(void)downloadImageData:(NSMutableArray*)p_images andController:(UIViewController*)p_resultsController;
#end
And the important part of the .m file:
#import "GTMConnectionManager.h"
#implementation GTMConnectionManager
#synthesize downloadOP, downloadQueue;
+ (GTMConnectionManager *)sharedConnectionManager
{
static GTMConnectionManager * instance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
instance = [[super allocWithZone:nil] init];
});
return instance;
}
-(void)downloadImageData:(NSMutableArray*)p_images andController:(GTMResultsListViewController*)p_resultsController{
self.resultsController = p_resultsController;
[self.downloadQueue setMaxConcurrentOperationCount:2];
self.downloadQueue = [[[NSOperationQueue alloc]init]autorelease];
// it doesn't make a difference if I do this with or without 'autorelease'
for (int i = 0; i < [p_images count]; i++) {
GTMGeoImage *tmpImg = [p_images objectAtIndex:i];
self.downloadOP = [[[GTMDownloadOperation alloc]initWithImage:tmpImg]autorelease];
[self.downloadQueue addOperation:self.downloadOP]; //Here's the error
}
}
When I add a breakpoint just before the error-line, both self.downloadQueue and self.downloadOP are retained correctly (not nil).
The strange this is: in this very class I have a second NSOperationQueue with other NSOperations that are declared and treated in the same way as downloadQueue and downloadOP. And they work perfectly.
And yes, GTMDownloadOperation is a child-class of NSOperation and has a -(void)main method.
I don't know what to do now. If you don't have any idea about the reason of that error, how can I analyze the situation more accurately? (Product > Analyze doesn't complain about potential leak at that position).
Thanks for your help.
Oh man...
That took a while, but finally I know that the problem was in the for-loop.
The statement
self.downloadOP = [[[GTMDownloadOperation alloc]initWithImage:tmpImg]autorelease];
was accessing the variable downloadOP again and again in every iteration. Using the same NSOperation seemed to crash its retainCount.
I changed it to
GTMDownloadOperation *downloadOP = [[GTMDownloadOperation alloc]initWithImage:tmpImg];
and it works without error. Silly me.
you're not calling [super init] inside your constructor?
Assuming you're subclassing NSOperation (or NSObject etc...), you probably should!
why do you realloc and init your queue?
couldn't it be just one for your singleton class?
and...
[self.downloadQueue setMaxConcurrentOperationCount:2];
self.downloadQueue = [[[NSOperationQueue alloc]init]autorelease];
the first line is executed on an old queue, then you create a new one that have no limits
when do you call the second line (alloc a new queue) you release the old one (if any)
try this:
if (!downloadQueue){
self.downloadQueue = [[[NSOperationQueue alloc]init]autorelease];
}
[self.downloadQueue setMaxConcurrentOperationCount:2];
and remember to add:
self.downloadQueue = nil;
in your dealloc method (even if it's a singleton, and it won't be called while your app is running)
I believe the problem is in the implementation of your operation. I suggest two courses: try creating some simple block based operations that just log hello world and add them to the queue. They will most likely wok, letting you know the queue is functional. Then start adding log messages to you subclass to see which methods get called and finish properly.
This should lead you to the problem.
Im trying to create a simple callback using blocks.
I have a MainViewController which addSubView another DatePickerViewController.view i created a block like this
typedef void(^DateChangedBlock)(NSDate*);
And i have a method on my DatePickerViewController called
setOnDateChangedCallback:(DateChangedBlock)callback
I store the callback in a property of the DatePickerViewController. The DatePickerViewController's view is a UIDatePicker instance, ive bound an IBAction to the value changed to a method that does this.
- (IBAction)dateChanged:(id)sender {
if (dateChangedCallback != nil)
{
dateChangedCallback(nil);
}
}
Here is how i register the block in the MainViewController
DatePickerViewController *dateController = [[DatePickerViewController alloc] initWithNibName:#"DatePickerView" bundle:nil];
self.datePicker = dateController;
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)];
[self.view addSubview:textView];
DateChangedBlock myBlock = ^(NSDate *newDate) {
textView.text = #"testing";
};
[self.datePicker setOnDateChanged: myBlock];
[self.datePicker dateChanged:self]; // force trigger (this works).
When i force trigger the dateChanged method on the DatePickerViewController it works no problem. But when the datepicker itself triggers the method thru the IBAction i get a EXC_BAD_ACCESS error. The error occurs in this method on the "int retVal" line.
#import <UIKit/UIKit.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil); // THREAD 1: program received EXC_BAD_ACCCESS.**
[pool release];
return retVal;
}
What am i doing wrong?
You should copy your block when passing it to other method (such as attribute setter in your situation). So when setting the callback block do this:
[self.datePicker setOnDateChanged:[[myBlock copy] autorelease]];
You get the EXC_BAD_ACCESS cause block's variables used when creating the block don't get retained by the block itself. So when calling the block - variables do not exist anymore.
The accepted answer is wrong. You don't need to copy it in your MainViewController. Rather, the setOnDateChangedCallback: method which takes a block argument is responsible for copying it if it needs to store it in an instance variable (which I see it is doing, in the variable dateChangedCallback that is later used by another method). If dateChangedCallback is a synthesized property, you can accomplish this by declaring the property as copy instead of retain.
You can only update the UI from the UI thread. Since your block is running on another thread, you're getting the exception when updating the text of the textView. You can run code on the UI thread from within a block by using the main queue (dispatch_get_main_queue()).
I would like to return an array whose contents were set during a dispatch_sync block.
The code I've typically seen is something like this:
-(NSArray *)getSomeLockedList {
__block NSArray *resultList;
dispatch_sync(myQueue, ^{
// copy contents of my ivar NSMutableArray into return variable
resultList = [ivarContentList copy];
});
// add auto-release since a 'copy' was done within block
return [resultList autorelease];
}
If I'm not doing a copy of a full array but instead want to add one by one, can I skip the 'autorelease' on the return value?
-(NSArray *)getSomeLockedList {
__block NSArray *someResultKeys; // is it ever safe to do the alloc here?
dispatch_sync(myQueue, ^{
someResultKeys = [NSMutableArray array];
for (id entry in ivarContentList) {
// do some work on entry instance
[someResultKeys addObject:entry];
}
});
return someResultKeys; // autorelease not necessary?
}
Is the [NSMutableArray array] allocation safe within the dispatch_sync block for continued use of the result after the stack is completed for this method?
No, this is not safe. The problem is that when you dispatch onto the queue, any objects that are autoreleased on that queue will get collected when that queue's NSAutoreleasePool drains. You have no control over when this will be. The appropriate way to think about this is to always assume that the autorelease pool will drain the moment your block finishes executing on the queue.
The correct way to handle this in your case is to use
someResultKeys = [[NSMutableArray alloc] init];
in the queue and then call [someResultKeys autorelease] after the dispatch_sync.
This is done much easier avoiding a __block variable by just writing
NSMutableArray* someResultKeys = [NSMutableArray array];
outside the block. However, I wonder about the dispatch_sync. You know that dispatch_sync will wait until the block finishes executing? (And in case of a serial queue, that means all blocks before it have finished executing as well). Is there a good reason why you don't call the code directly?
This is an example straight from Apple's documentation -
#implementation MyClass
- (id)initWithString:(NSString *)aName
{
self = [super init];
if (self) {
name = [aName copy];
}
return self;
}
+ (MyClass *)createMyClassWithString: (NSString *)aName
{
return [[[self alloc] initWithString:aName] autorelease];
}
#end
As I would be creating a new MyClass Object in each case anyway, I want to know why I might use the Class Method createMyClassWithString:aName instead of the Instance Method initWithString:aName
Thanks
I always use the class method where possible because it results in less verbose code and if you are simply going to return the object to the caller you'd have to autorelease it anyway, if you obtained it with alloc.
The advice from Apple was poorly worded, in my opinion. People seem to be taking it as a blanket ban on autorelease. That's simply not the case. You just have to be mindful that autorelease comes withy a memory price, but it is not as high as you might think. Each runloop event except timer events comes with a new autorelease pool that gets drained on return to the runloop. So if you know the method is going to be quick there's no problem. Also, if an object is going to outlive the current event, there's no issue because the overhead ofd an object in an autorelease pool is quite small and draining the pool won't dealloc the object anyway.
The only case where you need to be careful about putting objects indiscriminately in the autorelease pool is where you have intensive processing creating lots of temporary autoreleased objects. You can relieve the pressure of these methods by creating autorelease pools and draining them as you go along. e.g.
while(someCondition)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// intensive processing
[pool drain];
}
The only reason is readable and beautiful code...
In addition, in your example the class method returns an autoreleased instance...
The short, unhelpful answer would be: where is makes sense. Neither is more correct; neither is wrong.
An example might be more use. I would use the class method if I was returning the value from a method:
- (MyClass*) doStuff {
MyClass* retv = [MyClass createMyClassWithString:#"Simon says"];
[retv someOtherMethod];
return retv;
}
By convention you return autoreleased objects, so using the class method results in slightly less typing.
I am passing an object to a secondary thread using the following code:
(void)login:(id)sender
{
platformMsgs_LoginRequest *loginRequest = [[[platformMsgs_LoginRequest alloc] init] autorelease];
//more code...
[NSThread detachNewThreadSelector:#selector(sendLoginRequest:) toTarget:self withObject:loginRequest];
//more code...
}
- (void)sendLoginRequest:(platformMsgs_LoginRequest *)loginRequest
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[loginRequest retain];
NetSuiteBinding *binding = [NetSuiteServiceSvc NetSuiteBinding];
NetSuiteBindingResponse *response = [binding loginUsingParameters:loginRequest applicationInfo:nil partnerInfo:nil];
[self performSelectorOnMainThread:#selector(loginOperationCompleted:) withObject:response waitUntilDone:NO];
[loginRequest release];
[pool drain];
}
My question is, is autorelease the right way to handle the release of this object?. Once it is passed to the secondary thread I retain it and release it when I no longer need it.
However is it possible that the autorelease, releases the object before the secondary thread has a chance to retain it?. Do I have to create an ivar for this?, so that I can release the object in the performSelectorOnMainThread?. I no longer need the object after the login so an ivar doesn't seem like the right way to go. What is the best way to handle this?. Thank you.
-Oscar
The documentation for detachNewThreadSelector:toTarget:withObject: answers your question:
The objects aTarget and anArgument are retained during the execution of the detached thread, then released.
So yes, you can autorelease the object or release it explicitly after calling detachNewThreadSelector. And you don't have to retain the object in the secondary thread.
From the docs.
detachNewThreadSelector:toTarget:withObject:
The objects aTarget and anArgument are
retained during the execution of the
detached thread, then released. The
detached thread is exited (using the
exit class method) as soon as aTarget
has completed executing the aSelector
method.
So you dont need to try this hard. Autorelease is fine here, and you should not need to retain it in the thread since the thread itself retains the argument and the target, and will release it when done.