Cool. I was able to consistently reproduce an issue with this.
1) Start a scan operation on scanQueue and self.files only has one or two objects in it (so it will complete very quickly).
2) Call the method to start another second scan operation (which will attempt to cancel any in progress operations before it begins by calling cancelAllOperations on the scanQueue).
3) The second operation will start before the first operation completes, and even if I change the code to use a NSBlockOperation instead of addOperationWithBlock: and check the isCancelled property before doing the add/remove on the main queue within the execution block, the call to cancel can be too late and the first operation will have already added some objects to self.files on the main queue.
4). Now the second operation copied the self.files before the first operation completed. So code within the operation that checks for duplicates will fail within the second operation. When the second operation completes it will add duplicate objects to self.files. This will happen even if you copy the array on the main thread outside of the operation block, because the snapshot taken prior to the second operation no longer reflects the current state of affairs.
In the sample, they do call cancelAllOperations on the queue before the start of another operation, though it is unclear to me why because they add the operation with addOperationWithBlock: which will continue executing even if cancelled because they are not/cannot check the isCancelled property and dump out?
Changing the code to this seems to work nicely (at least in limited testing):
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSArray *currentFiles = [self.files copy];
//do scan queue operation here....
}];
The operation added to the main queue to copy currentFiles now happens after the operation added on the main queue at the end of the first scan (which potentially mutates the array) though I suspect that is not guarenteed.
I'm not sure if it necessary to use a lock, if all changes are gathered in the background in collections, but all mutations to self.files happen on the main thread? Calling copy on the array does a shallow not a deep copy so I think that your suggesstion, not to worry about it impacting performance too much unless testing proves otherwise is good advice.