Here's a code snippet that illustrates the problem
__weak id ptr = nil;
__weak id ptr2 = nil;
#autoreleasepool {
void (^block)(void) = [^{
NSLog(#"hahaha");
} copy];
block();
[[(id)block rac_willDeallocSignal] subscribeCompleted:^{
NSLog(#"block will dealloc");
}];
ptr = block;
int blockRetainCount = CFGetRetainCount((__bridge CFTypeRef)block);
NSObject *obj = [[NSObject alloc] init];
[obj.rac_willDeallocSignal subscribeCompleted:^{
NSLog(#"Obj dealloc");
}];
ptr2 = obj;
int objRetainCount = CFGetRetainCount((__bridge CFTypeRef)obj);
NSLog(#"oK");
}
NSLog(#"after pool %# %#" , ptr, ptr2);
When I run this snippet, I will see Obj dealloc printed to the console but not block will dealloc. After the autorelease pool also I will see ptr still containing a valid refence to the block while ptr2 has been correctly reset to nil as expected. Why is there this difference? When do blocks get dealloc'ed at all?
Multiple problems that should convince you to not ever try such a thing:
One, CFGetRetainCount isn't guaranteed to return anything meaningful in the presence of ARC.
Two, there is no guarantee that a block uses retain counts at all. Blocks that don't capture any variables are allocated as static variables; copy, retain and so on do nothing at all to them, and they are never allocated or deallocated.
Third, there is no guarantee that any block will ever call dealloc when it is deallocated. And no guarantee that it will happen in the next OS or iOS version.
Related
I read the Apple doc about the function of #autoreleasepool. It says in a new thread, the programmer should declare a new autorelease pool using the following code in ARC.
#autoreleasepool{
//etc
}
But as my test, I did not add the #autoreleasepool block in my code, it still run well.My code is like below.
[NSThread detachNewThreadSelector:#selector(threadTest) toTarget:self withObject:nil];
- (void)threadTest
{
// #autoreleasepool {
for (int i=0; i<1000000; i++) {
NSNumber *num = [NSNumber numberWithInt:i];
[NSString stringWithFormat:#"%#",num];
}
// }
}
I used the Xcode Leaks instruments to see whether it has any memory leaks, but I did not find any memory leaks. So the result is that "It seems it is not required to declare the #autoreleasepool block in a secondary thread", is my word correct, can someone clarify this?
Could somebody, please, explain me why I get error EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) in dispatch_semaphore_wait in the following code:
-(void) initialize {
dispatch_queue_t queue = dispatch_queue_create("My queue", NULL);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
self.queue = queue;
self.sem = sem;
self.myarray = [[NSMutableArray alloc]init];
[self.myarray addObject: [[MyObject alloc] init]];
}
-(MyObject *) method1 {
//do something
dispatch_semaphore_wait(self.sem, DISPATCH_TIME_FOREVER);
MyObject *obj = [self.myarray objectAtIndex:0];
dispatch_barrier_sync(self.queue, ^{
[self.myarray removeObjectAtIndex:0];
});
return obj;
}
-(void) method2:(MyObject *)object {
//do something
dispatch_barrier_async(self.queue, ^{
[self.myarray addObject:object];
dispatch_semaphore_signal(self.sem);
});
}
I found similar question Why does this code cause "EXC_BAD_INSTRUCTION"?, but in my case I am using ARC and I do not write explicitly nowhere dispatch_release(sem);
The sem you created in your initialize method is locally scoped to that method. It needs to be accessible to the other methods. If you have an iVar named sem that you're attempting to assign, you're shadowing it by declaring a local variable in initialize. (Same thing with queue, by the way.)
Also, you appear to have a typo here, in that you call dispatch_semaphore_wait(sen, DISPATCH_TIME_FOREVER); (i.e. se n vs se m)
You are allowing simultaneous access to the array self.myarray without sufficient protection. You modify the array with -addObject: and -removeObjectAtIndex: on the serial queue self.queue but you read from it using -objectAtIndex: without any protection. That means you may be reading from it at the same time you're writing to it, which is not safe. You need to also put the -objectAtIndex: call on the serial queue.
Also, you are using barrier functions with a serial queue, which doesn't make any sense.
-(MyObject *) method1 {
//do something
dispatch_semaphore_wait(self.sem, DISPATCH_TIME_FOREVER);
__block MyObject *obj;
dispatch_sync(self.queue, ^{
obj = [self.myarray objectAtIndex:0];
[self.myarray removeObjectAtIndex:0];
});
return obj;
}
-(void) method2:(MyObject *)object {
//do something
dispatch_async(self.queue, ^{
[self.myarray addObject:object];
dispatch_semaphore_signal(self.sem);
});
}
This kind of crash will happen when you are running a (vector)extension which is not supported on your CPU.
For example, in xcode 5 under "project-settings / build-settings / Code Generation, set the
"Enable Additional Vector extensions" to "AVX2". Build your executable.
Now run it on an:
Intel Core i5: it's going to crash (wherever the compiler decided to use avx2) with 'exc_i386_invop subcode=0x0'.
Intel Core i7: it will work.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I have some code which requires the use of blocks. The block fetches a number of data items from a web service, and then possibly needs to fetch more, and then more again after that, then returns all of the data items once it has all required. I'm unsure how to put this into code. Here's an example of what I mean:
NSMutableArray *array = [[NSMutableArray alloc] init];
[webService getLatestItemsWithCount:50 completion:^(NSArray *objects) {
//Some code to deal with these items.
if (moreItemsNeeded == YES) {
//I now need it to loop this block until I'm done
}
}];
How can I get this to work?
EDIT:
Ok, this is what i'm working with - it's the Evernote API. It should be a better example of what I need:
[noteStore findNotesMetadataWithFilter:filter
offset:0
maxNotes:100
resultSpec:resultSpec
success:^(EDAMNotesMetadataList *metadataList) {
for (EDAMNoteMetadata *metadata in metadataList.notes) {
NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];
if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970) {
[array addObject:metadata];
}
else {
arrayComplete = YES;
}
}
//I need it to loop this code, increasing the offset, until the array is complete.
}failure:^(NSError *error) {
NSLog(#"Failure: %#", error);
}];
I prefer to use the fixed-point combinator structure to write block recursion. This way I don't have to mess with __block variables or risk a retain cycle when I forget to set the block to nil at the end of the recursion. All credit for this goes to Mike Ash who shared this code snippet.
Here's my version of his code (which I placed in a globally shared file so I can access this function from anywhere):
// From Mike Ash's recursive block fixed-point-combinator strategy (https://gist.github.com/1254684)
dispatch_block_t recursiveBlockVehicle(void (^block)(dispatch_block_t recurse))
{
// assuming ARC, so no explicit copy
return ^{ block(recursiveBlockVehicle(block)); };
}
typedef void (^OneParameterBlock)(id parameter);
OneParameterBlock recursiveOneParameterBlockVehicle(void (^block)(OneParameterBlock recurse, id parameter))
{
return ^(id parameter){ block(recursiveOneParameterBlockVehicle(block), parameter); };
}
I know this looks super weird and confusing... but it's not too bad once you understand it. Here's what a simple recursive block might look like:
dispatch_block_t run = recursiveBlockVehicle(^(dispatch_block_t recurse)
{
if (! done)
{
// Continue recursion
recurse();
}
else
{
// End of recursion
}
});
run();
When you call recursiveBlockVehicle, you're passing a block that contains your code. recursiveBlockVehicle's job is take this block that you passed and do three things:
Execute the block
Pass the block back through recursiveBlockVehicle and pass that resultant as the parameter to the block
Encapsulate steps 1 and 2 within a simple block and return that
Now, inside your block's code, if you were to call the special recurse block parameter, you're in turn calling your own block all over again (achieving recursion). The nice thing about this strategy is that the memory management is fairly straight-forward. The use of parameters to pass your own code back to yourself reduces the risk of retain cycles. I use this method instead of defining a __block variable of my code because I'm afraid I might forget to set the __block variable to nil at the end of a recursion and result in a nasty retain cycle.
With that in mind, here's how I would implement your function:
OneParameterBlock run = recursiveOneParameterBlockVehicle(^(OneParameterBlock recurse, id parameter)
{
NSNumber *offset = parameter;
[noteStore
findNotesMetadataWithFilter:filter
offset:offset.intValue
maxNotes:100
resultSpec:resultSpec
success:^(EDAMNotesMetadataList *metadataList)
{
for (EDAMNoteMetadata *metadata in metadataList.notes)
{
NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];
if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970)
{
[array addObject:metadata];
}
else
{
arrayComplete = YES;
}
}
//I need it to loop this code, increasing the offset, until the array is complete.
if (! arrayComplete)
{
recurse([NSNumber numberWithInt:offset.intValue + 100]);
}
}
failure:^(NSError *error)
{
NSLog(#"Failure: %#", error);
}];
});
run(#0);
Again, note that you're not calling callback (the block object) inside of the block itself. The reason why is because the block is passing itself as a parameter recurse and executing recurse is how you achieve recursion.
Also, (in case you've actually read this far and wanted to see more), here's a wikipedia page on FPC: http://en.wikipedia.org/wiki/Fixed-point_combinator
Lastly, I have not personally tested the retain cycle issue of a __block variable. However, Rob Mayoff did a fantastic analysis on the issue: https://stackoverflow.com/a/13091475/588253
You should create a variable that references the block to make possible the recursive invocation. It must be noted that at the moment that you assign the block, it is still nil, so if you call it inside the block itself (aka recursively), you'll get a crash while trying to execute a nil block. So the block should have a *__block* storage:
void (^__block myBlock) (NSArray*) = ^(NSArray *objects) {
//Some code to deal with these items.
if (moreItemsNeeded == YES) {
//I now need it to loop this block until I'm done
myBlock(objects);
myBlock= nil; // Avoid retain cycle
}
}];
[webService getLatestItemsWithCount:50 completion: myBlock];
The block in your specific case is "translated" as this one:
void (^__block handler) (EDAMNotesMetadataList)= ^(EDAMNotesMetadataList* metadataList) {
for (EDAMNoteMetadata *metadata in metadataList.notes) {
NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];
if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970) {
[array addObject:metadata];
}
else {
arrayComplete = YES;
}
}
//I need it to loop this code, increasing the offset, until the array is complete.
if(!arrayComplete)
handler(metadataList);
handler= nil; // Avoid retain cycle
};
Then you can normally call that method passing myBlock as argument.
About retain cycles
To avoid a retain cycle, you should set to nil the pointer to the block when the recursion finishes.
Your code will be simpler to understand and less prone to leaking the block if you don't make the block recursive. Instead, wrap it in a method, and make the block call the method if it needs to keep searching.
This example is based on the code in your question:
- (void)appendNotesMetadataToArray:(NSMutableArray *)array
untilDate:(NSDate *)date withFilter:(EDAMNoteFilter *)filter
offset:(int32_t)offset resultSpec:(EDAMNotesMetadataResultSpec *)resultSpec
{
static const int32_t kBatchSize = 100;
[noteStore findNotesMetadataWithFilter:filter
offset:offset maxNotes:kBatchSize resultSpec:resultSpec
success:^(EDAMNotesMetadataList *metadataList) {
BOOL searchComplete = NO;
for (EDAMNoteMetadata *metadata in metadataList.notes) {
NSDate *timestamp = [NSDate endDateFromEDAMTimestamp:metadata.updated];
if ([timestamp compare:date] == NSOrderedDescending) {
[array addObject:metadata];
} else {
searchComplete = YES;
}
}
if (!searchComplete) {
[self appendNotesMetadataToArray:array untilDate:date
withFilter:filter offset:offset + kBatchSize
resultSpec:resultSpec];
}
} failure:^(NSError *error) {
NSLog(#"Failure: %#", error);
}];
}
With this design, you don't need to declare a reference to the block with an inscrutable type signature, and you don't have to worry about the block leaking because it references itself.
In this design, each call to the method creates a new block. The block references self, and (I assume) self references noteStore, and noteStore references the block, so there is a retain cycle. But when the block finishes executing, noteStore releases the block, breaking the retain cycle.
This is (as far as I've been able to figure out) - sort of an annoying connundrum - and one of blocks' few shortcomings... The following is the go-to archetype I refer to if I REALLY wanna make sure I'm being safe about it..
// declare a "recursive" prototype you will refer to "inside" the block.
id __block (^enumerateAndAdd_recurse)(NSArray*);
// define the block's function - like normal.
id (^enumerateAndAdd) (NSArray*) = ^(NSArray*kids){
id collection = CollectionClass.new;
for (ArrayLike* littleDarling in kids)
[collection add:enumerateAndAdd_recurse(littleDarling)];
return collection;
};
enumerateAndAdd_recurse = enumerateAndAdd; // alias the block "to itself" before calling.
enumerateAndAdd(something); // kicks it all off, yay.
I thought that I had quite understood weak references and blocks, however when trying the below code snippets, there are a few things that I don't understand.
Method test1: all fine the object is not retained
Method test2: I don't understand why the object seems to get retained until the end of method test3! Even explicitly setting object = nil at the end of method test2 does not change anything.
Method test3: the object is not retained. Why is method test2 not behaving like this?
As a side question, I was actually wondering if weak variables are thread safe? ie if I will never get any BAD_ACCESS exception when trying to access a weak variable from different threads.
#interface Object : NSObject
#property (nonatomic) NSInteger index;
#end
#implementation Object
- (id)initWithIndex:(NSInteger) index {
if (self = [super init]) {
_index = index;
}
return self;
}
- (void)dealloc {
NSLog(#"Deallocating object %d", _index);
}
#end
Test methods
- (void) test1 {
NSLog(#"test1");
Object* object = [[Object alloc] initWithIndex:1];
NSLog(#"Object: %#", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
//NSLog(#"Weak object: %#", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
- (void) test2 {
NSLog(#"test2");
Object* object = [[Object alloc] initWithIndex:2];
NSLog(#"Object: %#", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(#"Weak object: %#", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
- (void) test3 {
NSLog(#"test3");
Object* object = [[Object alloc] initWithIndex:3];
NSLog(#"Object: %#", object);
NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(#"Weak object: %#", [weakObject nonretainedObjectValue]);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
- (void) test {
[self test1];
[NSThread sleepForTimeInterval:3];
[self test2];
[NSThread sleepForTimeInterval:3];
[self test3];
}
The output of the above is:
2013-05-11 19:09:56.753 test[1628:c07] test1
2013-05-11 19:09:56.754 test[1628:c07] Object: <Object: 0x7565940>
2013-05-11 19:09:57.755 test[1628:c07] Exiting method
2013-05-11 19:09:57.756 test[1628:c07] Deallocating object 1
2013-05-11 19:09:58.759 test[1628:1503] Exiting dispatch
2013-05-11 19:10:00.758 test[1628:c07] test2
2013-05-11 19:10:00.758 test[1628:c07] Object: <Object: 0x71c8260>
2013-05-11 19:10:00.759 test[1628:1503] Weak object: <Object: 0x71c8260>
2013-05-11 19:10:01.760 test[1628:c07] Exiting method
2013-05-11 19:10:02.760 test[1628:1503] Exiting dispatch
2013-05-11 19:10:04.761 test[1628:c07] test3
2013-05-11 19:10:04.762 test[1628:c07] Object: <Object: 0x71825f0>
2013-05-11 19:10:04.763 test[1628:1503] Weak object: <Object: 0x71825f0>
2013-05-11 19:10:05.764 test[1628:c07] Exiting method
2013-05-11 19:10:05.764 test[1628:c07] Deallocating object 3
2013-05-11 19:10:05.767 test[1628:c07] Deallocating object 2
2013-05-11 19:10:06.764 test[1628:1503] Exiting dispatch
I have two observations on your three tests before I touch on a few of your questions:
Your testing is complicated by the fact that you're running all three tests in a row, not yielding back to the run loop, and thus your autorelease pool is not getting flushed (so it making things look like they're persisting longer than they normally would). You should do you testing, one test at a time, to really understand what's going on. It's not good if you're drawing conclusions about the lifespan of some object, whereas you really may just be experiencing some artifact of the fact that you're not letting the autorelease pool from being flushed.
You are doing all of these tests as dispatch_async, which starts the dispatched block very quickly, sometimes more quickly than it takes the underlying object to fall out of scope and you're often accessing the weakObject as one of the first steps in the dispatched block. I'd suggest using dispatch_after (so you're really giving the calling method a chance to let the variables fall out of scope), so you'll better see what's going on.
Your tests are a good data point, but I think it's useful to also test the same stuff using dispatch_after and do one test at a time with fewer of those sleepForTimeInterval. It feels like some of the idiosyncrasies of your tests are counterfeiting some key behavior.
Anyway you ask:
Method test2: I don't understand why the object seems to get retained until the end of method test3! Even explicitly setting object = nil at the end of method test2 does not change anything.
It's undoubtedly fallen into the autorelease pool, which won't be drained until the test method is done.
To my prior points, try doing test2 again, but have the operation wait two seconds before accessing the weakObject (or get rid of all of these sleepForTimeInterval statements and use dispatch_after instead of dispatch_sync):
- (void) test2 {
NSLog(#"test2");
Object* object = [[Object alloc] initWithIndex:2];
NSLog(#"Object: %#", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
[NSThread sleepForTimeInterval:2]; // new sleep
NSLog(#"Weak object: %#", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
// [NSThread sleepForTimeInterval:1]; // not really necessary
NSLog(#"Exiting method");
}
You'll see that this behaves more like you expected.
Method test3: the object is not retained. Why is method test2 not behaving like this?
Needless to say, your test3 is seriously bad news, easily crashing itself. For example, try commenting out the sleep line:
- (void) test3 {
NSLog(#"test3");
Object* object = [[Object alloc] initWithIndex:3];
NSLog(#"Object: %#", object);
NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(#"Weak object: %#", [weakObject nonretainedObjectValue]);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
// [NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
It strikes me that it's behaving less like weak and more like unsafe_unretained.
As a side question, I was actually wondering if weak variables are thread safe? ie if I will never get any BAD_ACCESS exception when trying to access a weak variable from different threads.
You can get exceptions in a lot of ways. If you pass weakObject to some method that requires that it not be nil (e.g. NSMutableArray method addObject), you'll get an exception. You can also get exceptions if you dereference ivars for a nil object pointer, e.g. obj->objectIvar. For example, imagine a Object instance method, doSomethingLater, which uses a weak reference to ensure that it doesn't retain the Object, but then has a local strong reference so it can dereference the ivar:
- (void)doSomethingLater
{
__weak Object *weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
Object *strongSelf = weakSelf;
NSLog(#"%d", strongSelf->_index); // **BAD** - this can crash of `self` has been released by this point
});
}
Thus, you usually replace the above with:
- (void)doSomethingLater
{
__weak Object *weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
Object *strongSelf = weakSelf;
if (strongSelf) {
NSLog(#"%d", strongSelf->_index);
}
});
}
To be perfectly honest, though, the details of why that first code sample can crash and the second can't is less important than the obvious fact that judicious use of your object references in asynchronous programming is important, and failure to handle the situations carefully can result in exceptions. Frequently, checking that the weakObject is not nil can prevent many of these sorts of issues (with some caveats that I'm not going to go into). This is less important when calling object methods (because sending any message to nil results in nil), but it is important when your weakObject is a parameter or being dereferenced for an ivar.
But to be clear, none of that really has any bearing on thread-safety, though. You achieve thread safety through proper handling of synchronization, such as locking mechanisms or through judicious use of queues (either serial queue; or the reader/writer pattern of a concurrent queue with dispatch_barrier_async for writes and dispatch_sync for reads).
Just because you have code where you're handling object references carefully so you don't get exceptions, doesn't mean you've achieved thread-safety. There's a whole other layer of concerns that thread-safety entails.
I am using an NSOperation subclass (called PointsOperation) to do some calculations in the background in my app. Due to user behaviour, these calculations might need to be canceled, and new calculations started. In that case, I will create a new PointsOperation instance, and add it to the same NSOperationQueue as the first one. As the first thing in the main method of the PointsOperation, it will check whether another operation is already running, and cancel it.
Because the operations are using some shared caches, they cannot be (and don't need to be) running in parallel. Therefore, the second operation will wait until the first one has finished. The resulting code for the main method looks something like this:
static NSOperation *currentOperation = nil;
- (void) main
{
// setting up autorelease pool, catching exceptions, etc
#synchronized(lock) {
if (currentOperation != nil) {
[currentOperation cancel];
[currentOperation waitUntilFinished];
}
currentOperation = self;
}
while (!calculationsFinished && ![self isCancelled]) {
// do calculations
}
currentOperation = nil;
// releasing autorelease pool, etc
}
This all works fine, the first operation gets cancelled, and the second waits for it to finish, and then starts calculating.
The problem is: it takes 3-10 seconds between the first operation ending the main method, and the second one to come out of the waitUntilFinished.
Does anybody have seen this before and knows what to do about that?
I have also tried, instead of the waitUntilFinished, to make the second operation dependent on the first, with "addDependency:" (in the init method, rather than the main). That also works, but has the same problem: the start of the second operation is a number of seconds behind the finish of the first method.
Despite of its name, the cancel method does not magically cancel the operation.
Canceling an operation does not immediately force it to stop what it
is doing. Although respecting the value returned by the isCancelled is
expected of all operations, your code must explicitly check the value
returned by this method and abort as needed.
http://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html
If you did not write your code to check isCancelled property, so the operation's thread will run to the end no matter you cancel it or not.
I tried to reproduce the issue here but my code just work fine with no such delay. This is my code:
#interface PointsOperation : NSOperation {
#private
bool calculationsFinished;
}
#property (nonatomic, assign) int tag;
#end
#implementation PointsOperation
#synthesize tag;
static NSOperation *currentOperation = nil;
static NSString* lock = #"LOCK";
- (void) main
{
NSLog(#"Before autoreleasepool with tag: %d", tag);
#autoreleasepool {
NSLog(#"Before lock");
// setting up autorelease pool, catching exceptions, etc
#synchronized(lock) {
if (currentOperation != nil) {
NSLog(#"Before cancel");
[currentOperation cancel];
NSLog(#"Before waitUntilFinished");
NSDate* beforeWait = [NSDate date];
[currentOperation waitUntilFinished];
NSLog(#"After waitUntilFinished took %f seconds", [[NSDate date] timeIntervalSinceDate:beforeWait]);
}
currentOperation = self;
}
NSLog(#"Before while loop");
int i = 0;
while (!calculationsFinished && ![self isCancelled]) {
// do calculations
[NSThread sleepForTimeInterval:1];
NSLog(#"Inside while loop = %d", i);
calculationsFinished = (++i > 10);
}
NSLog(#"After while loop: i = %d", i);
currentOperation = nil;
// releasing autorelease pool, etc
}
NSLog(#"%#", #"End of method");
}
#end
And here's how I use it:
NSOperationQueue* q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 4;
for (int i = 0; i < 10; i++) {
[q addOperation:[PointsOperation new]];
}
The result of time took by waitUntilFinished came in two categories:
After waitUntilFinished took 1.002624 seconds
and
After waitUntilFinished took 0.000749 seconds
which depends on the timing of the call I think.
Maybe you should provide more of your code if possible, as problem might be somewhere else in your code.