why use "willChangeValueForKey:" in SDWebImage? - ios

There is a class named "SDWebImageDownloaderOperation" which has the following method in SDWebImageView lib.
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
however, rs does not override automaticallyNotifiesObserversForKey: and don't implement observeValueForKeyPath:ofObject:change:context: methods,so what is the purpose of writing
[self willChangeValueForKey:#"isFinished"]
and
[self didChangeValueForKey:#"isFinished"]

The key reason here is that SDWebImageDownloaderOperation is a subclass of NSOperation and this class doesn't work with normal KVO notification like all the other classes.
This operation performs some task on background, so you need to inform the operating system when your operation is finished and when is still running.
Apple suggests here to explicitly call willChangeValueForKey and didChangeValueForKey.
If you are looking for a better explanation why NSOPerations don't have normal KVO you can read this answer Why does NSOperation disable automatic key-value observing?

Related

captured self gets released inside a dispatch_async

I am experiencing an EXC_BAD_ACCESS error inside my app within a block that, as of my understanding, should actually capture everything involved in this, making it impossible that it is released inside the block. (ARC is enabled)
Here is my code:
- (void)_perform_async_onqueue:(void (^)(void))task {
dispatch_async(self.workerQueue, task);
}
- (void)cancel {
[self _perform_async_onqueue:^{
// operation is strongly retained by self.
// operation is also retained by an operation queue.
// within `cancel` the operation is released from the operation queue
[self.operation cancel];
}
}];
This crashes inside [self.operation cancel]. self.operation is a subclass of NSOperation. The operation's cancel method in detail:
- (void)cancel {
[self willChangeValueForKey:#"isCancelled"];
_cancelled = YES;
[self didChangeValueForKey:#"isCancelled"];
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_finished = YES;
_executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"]; // CRASH (debugger lists `self` [= the operation] as `nil` in this line
}
As of my understanding, the first self should be retained inside the dispatched block. As self has a strong reference to operation, this should also be in memory until the end of the block execution. How can this lead to a EXC_BAD_ACCESS crash?
EDIT :
Don't implement cancel method in your NSOperation subclass. Just call :
[self.operation cancel];
A good tuto here
Methods don't have strong references to self. If self is deallocated while a method is being called, you are on your own. (That's why you get a warning if you call a method on a weak object, because the object might go away while the method is running).
You can assign SomeClass* myself = self; and that will keep self alive until the end of the method.
You can also implement your own dealloc (which just calls [super dealloc] automatically) and set a breakpoint to find out when exactly self is deallocated, to understand things better.
I've found the issue:
It turns out that the described behaviour is working totally fine. The issue is that the NSOperation that is used here is a custom, asynchronous operation that has implemented cancel in the wrong way: If you cancel an operation before it has been started by the NSOperationQueue the operation queue then overreleases the operation. This is the reason for the operation being deallocated even if there is still a strong reference that should hold it.
I'll go file a rdar for that.

Determining pending operations with NSObject

I am displaying a UIView containing a button giving the user an option to undo something. The view stays visible for a few seconds, then closes. I am creating the view as follows:
[self performSelector:#selector(endUndoOption) withObject:self afterDelay:delay];
Then canceling it if necessary using the following:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(endUndoOption) object:self];
Is there any way to determine if there is an operation scheduled (in this case, endUndoOption)? Or if the timer has begun? Currently I am doing this with a BOOL flag but was wondering if there is a way to check to see if there has been one queued? THanks!
If you check Cocoa Pods (http://cocoapods.org) BlocksKit pod, http://zwaldowski.github.io/BlocksKit/, there is a special category on NSObject with two very useful methods:
+ (id)bk_performBlock:(void (^)(void))block afterDelay:(NSTimeInterval)delay;
which returns an id which is cancellation handle.
And
+ (void)bk_cancelBlock:(id)handle;
to cancel your scheduled perform.
So, to achieve your target, you can store the cancellation handle in some property, e.g.
self.endUndoCancellationHandle = [[self class] bk_performBlock:^{
[self endUndoOption];
self.endUndoCancellationHandle = nil;
} afterDelay:delay];
then cancellation:
if (self.endUndoCancellationHandle)
{
[[self class] bk_cancelBlock:self.endUndoCancellationHandle];
self.endUndoCancellationHandle = nil;
}
To check if something is scheduled, just check if you currently have the handle:
if (self.endUndoCancellationHandle)
{
...
}

AsyncTask implementation using NSOperation in iOS

I implemented AsyncTask(Android) in iOS using NSOperation subclass.
-(id)initWithParam:(NSArray *)params{
if (self = [super init]) {
paramsArray = params;
}
return self;
}
- (void)start {
#autoreleasepool {
if (self.isCancelled)
return;
NSInteger result;
result = [self doInBackground:paramsArray];
dispatch_async(dispatch_get_main_queue(), ^{
[self postExecute:result];
});
}
}
- (BOOL) doInBackground: (NSArray *) parameters{
BOOL status = false;
int i;
for (i=0; i<100000; i++) {
NSLog(#"printing i::%d",i);
}
if (i == 100000) {
status = YES;
}
return status;
}
- (void) postExecute: (BOOL) deviceState{
if (deviceState) {
NSLog(#"Finished");
}
}
-(BOOL)isConcurrent{
return YES;
}
This is the way I implemented in iOS.Please suggest and any thing I want to add for this functionality.
And also, when can I call isExecuting and isFinished in NSOperation subclass
In answer to your question, unfortunately, no, this implementation is not correct. Close, but not quite there.
A couple of things:
Your example is not concurrent operation. In the case of NSOperation, the term "concurrent operation" (now called an "asynchronous operation") has a special meaning. An asynchronous operation is one that that continues to run asynchronously even after the start method finishes. And you designate this by returning YES from isConcurrent (in older iOS versions) and isAsynchronous (in contemporary iOS versions). But the example operation in the question is completely done when start ends, so therefore is not an asynchronous operation and therefore isConcurrent and isAsynchronous should therefore return NO.
Let's assume in this example that you changed isConcurrent to return NO, as would be appropriate with what you're performing in start. That would fix this operation. But don't let this confuse you. If you added this operation to your own operation queue, it would still run asynchronously/concurrently with respect to the main queue, it's just that it's an operation that automatically finishes when start finishes, and therefore isAsynchronous/isConcurrent should return NO.
You ask "when can I call isExecuting and isFinished?" Well, you generally don't call those methods. You generally implement those methods. Specifically, when an asynchronous operation eventually finishes, you must ensure that the app (a) posts KVN for the isExecuting and isFinished keys; and (b) you override isExecuting and isFinished to ensure that they correspondingly return the appropriate values.
You only have to implement this isExecuting and isFinished code when the operation is truly an asynchronous/concurrent operation. See the Configuring Operations for Concurrent Execution section of the Concurrency Programming Guide: Operation Queues. Also see the introductory section of the NSOperation class definition.
Whether your operation should be asynchronous or not (or even whether you need to subclass NSOperation at all) is unclear. It depends entirely upon what task you want to perform, and whether the task itself runs asynchronously or not.

How do I wait for a method to finish before calling it again?

I am building a simple messaging app using Parse's framework. I have a method called displayMessages. This is called each time the phone receives a push.
However, as this message is doing work in the Parse database I don't want to call it again if it's already running. I want to wait until it is finished and then call it.
I am using the following code:
-(void)receivedPush
{
[self displayMessages];
}
and:
-(void)displayMessages
{
//code here
}
If received push is called I want it to wait until displayMessages is finished before calling it. Could someone please point me in the right direction with this?
UPDATE
I tried using the NSOperationQueue method and realised that although this does work for waiting for displayMessages it doesn't result in the required behavior.
In displayMessages I have: [PFObject deleteAllInBackground:toDelete]; it's actually this I need to wait for completion before calling displayMessages again.
Create a NSOperationQueue and set the maxConcurrentOperationCount to 1. Implement your data access method as an operation (possibly block-type operation) and submit it to the queue. (I like this better than gcd since you can do cancellation or test the number of items already in the queue.)
Note that if the method actually displays things, you'll need to dispatch back to the main queue for UI work.
You could use a NSOperationQueue with maxConcurrentOperationCount set to 1.
Declare the NSOperationQueue as an iVar of your class, initialize it in the init method and set
[_opQueue setMaxConcurrentOperationCount:1];
and then when you receive the push:
- (void)receivedPush {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(displayMessages) object:nil];
[_opQueue addOperation:op];
}
Shortest and simples would be creating BOOL isExecuting and checking if you can call method based on that (changing values before execution but after check and after execution)
How about this for a fairly lightweight solution:
#property (nonatomic, assign) BOOL needsToDisplayMessages;
#property (nonatomic, assign) BOOL displayingMessages;
Then
-(void)receivedPush
{
if (!self.displayingMessages) {
[self displayMessages];
} else {
self.needsToDisplayMessages = YES;
}
}
-(void)displayMessages
{
self.needsToDisplayMessages = NO;
self.displayingMessages = YES;
//long-running code here
self.displayingMessages = NO;
if (self.needsToDisplayMessages) {
[self displayMessages]
}
(ignoring concurrency issues ... for which you could use GCD in displayMessages or NSOperationQueue as per a couple of the other answers)
With your new updated requirement, you can use deleteAllInBackground:block:. According to document:
"Deletes a collection of objects all at once asynchronously and executes the block when done."
Why not schedule each message handling using:
-(void)receivedPush
{
dispatch_async(dispatch_get_main_queue(), ^{
/* Show the update on the display */
NSLog(#"Handling new messages");
NSArray *newMessages=<populate with new messages>;
[handler displayMessages:newMessages];
});
}
This will queue up your handling of each set as they come in. Only one displayMessages will run at a time.

Parsing JSON data from WebService on a background thread

I have a method that builds a package, sends it to a web service, gets a package back, opens it and returns me a nsdictionary. How can I call it on a background queue in order to display a HUD while it requests the data?
You could detach a new thread like following
- (void) fetchData
{
//Show Hud
//Start thread
[NSThread detachNewThreadSelector:#selector(getDataThreaded)
toTarget:self
withObject:nil];
}
- (void) getDataThreaded
{
//Start Fetching data
//Hide hud from main UI thread
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI if you have to
//Hide Hud
});
}
Grand central dispatch (gcd) provides great support for doing what you ask. Running something in the background using gcd is simple:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0) ^{
NSDictionary* data = [self fetchAndParseData];
dispatch_async(dispatch_get_main_queue(), ^{
[self dataRetrieved:data];
});
});
This call will return immediately (so your UI will continue to be responsive) and dataRetrieved will be called when the data is ready.
Now, depending on how fetchAndParse data works it may need to be more complicated. If you NSURLConnection or something similar, you might need to create an NSRunLoop to process data callbacks on the gcd thread. NSURLConnection for the most part is asynchronous anyway (though callbacks like didReceiveData will be routed through the UI thread) so you can use gcd only to do the parsing of the data when all the data has been retrieved. It depends on how asynchronous you want to be.
In addition to previous replies, why don't you use NSOperation and NSOperationQueue classes? These classes are abstractions under GCD and they are very simple to use.
I like NSOperation class since it allows to modularize code in apps I usually develop.
To set up a NSOperation you could just subclass it like
//.h
#interface MyOperation : NSOperation
#end
//.m
#implementation MyOperation()
// override the main method to perform the operation in a different thread...
- (void)main
{
// long running operation here...
}
Now in the main thread you can provide that operation to a queue like the following:
MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]];
[[self someQueue] addOperation:op];
P.S. You cannot start an async operation in the main method of a NSOperation. When the main finishes, delegates linked with that operations will not be called. To say the the truth you can but this involves to deal with run loop or concurrent behaviour.
Here some links on how to use them.
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
https://developer.apple.com/cocoa/managingconcurrency.html
and obviously the class reference for NSOperation

Resources