NSOperation KVO isFinished - ios

Im trying to subclass a NSOperation, and read some sample from,
they say: when the task finished, using KVO of NSOperation, to finish the operation,
code here:
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"]
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
then isFinished get called
- (BOOL) isFinished{
return(finished);
}
anyone could explain this to me? why isFinished gets called, will the isFinished finish the operation? as I understanded, do KVO manually need [self didChangeValueForKey:#"isExecuting"]; and I didnt see code like addobserver: and observeValueForKeyPath:
I write
-(void)call
{
[self willChangeValueForKey:#"isVip"];
[self didChangeValueForKey:#"isVip"];
}
-(void)isVip
{
NSLog(#"Im vip");
}
isVip is not called when do [self call];

The NSOperationQueue implementation will observe the "isFinished" property of your operation (using KVO) so it knows when to remove it from the queue. isFinished is most likely being called by internal Apple code after it has been told of the change to its value.

Adding to quellish answer, this is how you would override executing, finished, cancelled.
//.m
#interface MyOperation () //class extension, make these otherwise read-only properties read-write, we must synthesize
#property(atomic, assign, readwrite, getter=isExecuting) BOOL executing;
#property(atomic, assign, readwrite, getter=isFinished) BOOL finished;
#property(atomic, assign, readwrite, getter=isCancelled) BOOL cancelled;
#end
#implementation CoreLocationOperation
#synthesize executing, finished, cancelled;
+ (BOOL)automaticallyNotifiesObserversForKey {
return YES;
}
+ (NSSet *)keyPathsForValuesAffectingIsCancelled {
NSSet *result = [NSSet setWithObject:#"cancelled"];
return result;
}
+ (NSSet *)keyPathsForValuesAffectingIsExecuting {
NSSet *result = [NSSet setWithObject:#"executing"];
return result;
}
+ (NSSet *)keyPathsForValuesAffectingIsFinished {
NSSet *result = [NSSet setWithObject:#"finished"];
return result;
}
- (void)start {
//..
//You can use self.executing = YES; (note we can change executing which would otherwise be read-only because we synthesized our own ivar.
[self setExecuting:YES];
...
}
- (void)cancel {
//..
//super will change the properties executing/finished for us or we can do it manually
[super cancel];
...
}
#end
I think this is clearer than having
[self willChangeValueForKey:_NSURLOperationIsFinished];
[self setIsFinished:YES];
[self didChangeValueForKey:_NSURLOperationIsFinished];

First, you should not need to do manual KVO notifications. For an NSOperation subclass KVO notifications should be sent automatically unless your class has opted out of automatic KVO notifications by implementing +automaticallyNotifiesObserversForKey or +automaticallyNotifiesObserversOf<Key> to return NO.
NSOperation subclasses are not very useful unless they are added to an NSOperationQueue. When an operation is added to a queue, the queue uses KVO to observe the properties that indicate state changes, such as finished, executing, cancelled, etc. Note that these are not isFinished, isExecuting, or isCancelled - those are the names of the synthesized get accessors for those properties.
In your question you include this code:
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"]
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
What you're doing here is sending manual KVO notifications for the get accessors, not the properties being observed. Instead, this would accomplish what you seem to be trying to do:
[self setFinished:YES];
[self setExecuting:NO];
Rather than accessing instance variables directly, use the accessor methods. This will correctly send automatic change notifications for these properties.
If you are truly paranoid about KVO and want to send notifications for the get accessor key paths such as isFinished, register your property as a dependency of the key path:
+ (NSSet *) keyPathsForValuesAffectingIsFinished {
NSSet *result = [NSSet setWithObject:#"finished"];
return result;
}
Registering dependencies is part of KVO compliance.

Related

NSOperation Subclass performance or leak

Following is the subclass implementation of NSOperation Subclass
The operation will be use to asynchronously download Image from server.
-(void) main{
#autoreleasepool {
//NSURLConnection
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:#"A URl"]];
_downloadConnection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
}
-(BOOL)isConcurrent{
return YES;
}
-(BOOL)isExecuting{
return _isExecuting;
}
-(BOOL)isFinished{
return _isFinished;
}
-(void)finish{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished =YES;
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
}
-(void)cancel{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished =YES;
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
[connection cancel];
connection=nil;
[self finish];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
//Other Code
[connection cancel];
connection=nil;
[self finish];
}
Please let me know if there might be anything that I might have missed out within the code, so as to avoid leaks and check all KVO are handled properly.
I see a couple of issues:
Your finish and cancel routines are calling willChangeValueForKey twice for each key. Obviously that second call should be didChangeValueForKey.
I would advise against implementing cancel method. The default implementation does some other stuff. Do not implement that method. If you really want to, there are changes I'd advise here (at very least, cancel connection, too; call super; some other things, too), but I'd simply advise against implementing it at all and detect cancellation in didReceiveData (see point #5).
This code does not appear to be setting _isExecuting (nor do appropriate KVO) when the operation starts. Maybe that's in a start method you neglected to share with us?
Likewise, that start method should check to see if the operation has been canceled already and if so, stop the operation immediately.
In didReceiveData, are you checking isCancelled, as well? Making operations cancelable is one of the main reasons you use operation queues.
You're starting a NSURLConnection within an operation (presumably with the intent to add this operation to some random queue). But NSURLConnection will not work correctly unless you either schedule it on the main run loop (the easy solution) or you create your own run loop alive (there are a variety of techniques of that).
For example, to tell the operation to schedule the connection in the main run loop, you could do something like:
_downloadConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:FALSE];
[_downloadConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] runLoopModes:NSRunLoopCommonModes];
[_downloadConnection start];
I'm typing that without benefit of Xcode, so if I mistyped a method forgive me, but it illustrates the idea: Create connection with startImmediately of FALSE, schedule it on the main run loop, and only then should you start it.
If connectionDidFinishLoading is called, it is entirely unnecessary to call [connection cancel].
As of iOS 7, isConcurrent has been deprecated in favor of isAsynchronous. If you need to support earlier iOS versions, though, keep isConcurrent.
By the way, while I think it might work the way you've laid it out, it is generally advised to implement properties called executing and finished:
#property (nonatomic, readwrite, getter=isExecuting) BOOL executing;
#property (nonatomic, readwrite, getter=isFinished) BOOL finished;
I then manually synthesize:
#synthesize finished = _finished;
#synthesize executing = _executing;
And then I implement manual setters (but rely upon the synthesized getters and ivars):
- (void)setExecuting:(BOOL)executing
{
if (_executing != executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished
{
if (_finished != finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
But if you do that, you can now just set self.executing = FALSE (or whatever) and it (a) does appropriate KVO, saving you from littering your code with all sorts of KVO calls; and (b) but saves you from having to implement properties and getters manually.

NSOperation accessing isCancelled in main

Current status
I have created a custom NSOperation object and I want to update some data when it is cancelled.
I've followed what said in this answer and I didn't override the cancel method.
Here is my header:
// MyOperation.h
#interface MyOperation : NSOperation {
}
#property (nonatomic, retain) OtherDataClass *dataClass;
#end
And the implementation
// MyOperation.m
#implementation MyOperation
#synthesize dataClass;
- (void)main {
if ([self isCancelled]) {
[self.dataClass setStatusCanceled];
NSLog(#"Operation cancelled");
}
// Do some work here
NSLog(#"Working... working....")
[self.dataClass setStatusFinished];
NSLog(#"Operation finished");
}
#end
The question
I have several operations in a queue. I was expecting that, when I call cancelAllOperations in the queue, I'll get the "Operation cancelled" text in the log and the status updated in my other class but it is not working. The main method is not being called for the operations in the queue.
Why is this happening and how can I solve it?
Notes
I've tried to overwrite the cancel method with this:
- (void)cancel {
[super cancel];
[self.dataClass setStatusCanceled];
NSLog(#"Operation cancelled");
}
It is working but I've read that this method should not be overridden.
When you call cancelAllOperations, the operations which are already started will have isCancelled set to YES. operations which aren't already started won't start.

setDelegate self and using delegates in ASINetworkQueue. Selector not found when request finished

I have the following class:
File_Downloadmanager.h:
#import "ASINetworkQueue.h"
#interface File_Downloadmanager : NSObject {
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename;
-(void)initDownloadQueue; // creates a new download queue and sets delegates
-(void)startDownload; // starts the download queue
-(void)requestFinished;
-(void)requestFailed;
-(void)queueFinished;
#property(retain) ASINetworkQueue *downloadQueue;
#end
File_Downloadmanager.m:
#implementation File_Downloadmanager
#synthesize downloadQueue;
-(void)initDownloadQueue{
NSLog(#"Init DownloadQueue");
// Stop anything already in the queue before removing it
[[self downloadQueue] cancelAllOperations];
[self setDownloadQueue:[ASINetworkQueue queue]];
[[self downloadQueue] setDelegate:self];
[[self downloadQueue] setRequestDidFinishSelector:#selector(requestFinished:)];
[[self downloadQueue] setRequestDidFailSelector:#selector(requestFailed:)];
[[self downloadQueue] setQueueDidFinishSelector:#selector(queueFinished:)];
[self downloadQueue].shouldCancelAllRequestsOnFailure = NO;
}
-(void)startDownload{
NSLog(#"DownloadQueue Go");
[downloadQueue go];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// If no more elements are queued, release the queue
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Request finished");
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
//... Handle failure
NSLog(#"Request failed");
}
- (void)queueFinished:(ASINetworkQueue *)queue
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Queue finished");
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename{
...SourceCode for creating the request...
// add operation to queue
[[self downloadQueue] addOperation:request];
}
In another class a function is called an inside that function I'm doing the following:
-(void)downloadFiles{
File_Downloadmanager * downloadhandler = [[File_Downloadmanager alloc]init];
// initialize download queue
[downloadhandler initDownloadQueue];
for (int i = 0; i < [meetingObjects count]; i++) {
....some other code to get the objectID, userID, etc.
[downloadhandler addRequestToDownloadQueue:ID :[loginData stringForKey:#"userId"] :[NSString stringWithFormat:#"%#%#",currentObject.id,currentObject.name]]
}
[downloadhandler startDownload];
}
Everything works fine and the download begins. But when the first file is downloaded, I get an error in the ASINetworkQueue class that my selector "requestFinished" can't be called (I don't have the exact message, can't start the app at the moment, but the failure code was exc_bad_access code=1).
Is the time of declaration / initialization of my File_Downloadmanager object the problem? Because the function "downloadFiles" is called, the DownloadManager object created, the requests added and then the "downloadFiles" method returns because the queue works async?
I haven't used the ASI networking stuff before, but have seen lots of references to it on the net.
It sounds to me like the ASINetworkQueue class expects it's delegate to conform to a specific protocol. If it's set up correctly, you should get a warning when you try to assign yourself as the delegate of the ASINetworkQueue object but have not declared that your class conforms to the appropriate protocol. If you DO include a protocol declaration, then you should get a warning that you have not implemented required methods from that protocol.
Try cleaning your project and rebuilding, and then look carefully for warnings, specifically on your line:
[[self downloadQueue] setDelegate:self];
EDIT: I just downloaded one of the ASIHTTPRequest projects, and to my dismay, the delegate property of the ASINetworkQueue class does not have to conform to a specific protocol. This is bad programming style. If you set up a delegate, you should make the delegate pointer conform to a specific protocol.
Also, be aware that the ASI networking classes have not been maintained for several years now and are getting badly out of date. There are better alternatives out there, and you should look at moving to a different networking framework.
It looks like the downloadhandler object that ASINetworkQueue is attempting to send the requestFinished message to no longer exists at the time that the message is sent to it, as it's probably being deallocated when the downloadFiles method finishes executing. Instead of making the downloadhandler object local to the downloadFiles method, instead make it a (strong, nonatomic) property within the class that contains the downloadFiles method. That way, you can ensure that it will still exist when requestFinished is called.

iphone nsoperation application freezes

I have made a subclass of NSOperation called ˚ to achieve multiple movie downloads . In the appDelegate.m , I have made an object of NSOperationQueue .
- (void)applicationDidFinishLaunching:(UIApplication *)application {
queue = [[NSOperationQueue alloc] init];
[queue setMaximumConcurrentOperationCount:5]
}
MovieDownloadOperation depends on a class called Downloader which actually downloads the movie
and gives callback movieCompletelyDownloadedWithUrl: .
Then , I have made a property called downloadState in MovieDownloadOperation . It has different values like "STARTED" , "DOWNLOADING" , "COMPLETED" , "ERROR".
MyDownloadOperation looks like
-(id)initWithUrl:(NSURL *)url
{
if (self = [super init])
{
_downloader = [[Downloader alloc] initWithUrl:url];
_downloadState = #"STARTED" ;
}
}
-(void)main
{
while(1)
{
if ([_downloadState isEqualToString:#"COMPLETED"])
{
NSLog(#"movie downloaded successfully");
break ;
}
}
}
-(void)movieCompletelyDownloadedWithUrl:(NSURL *)url
{
_downloadState = #"COMPLETED" ;
}
This works well for one movie , but when I try to download more than one movie , the UI freezes until the first is downloaded . I think the the problem is the while loop inside the main method , is there a better way to check if the _downloadState is changed to "COMPLETED" ??
It's unclear why the UI freezes with multiple operations, but not with only one download. But, your code sample provokes a couple of thoughts:
Concurrent Operation:
Rather than having a while loop in main, and you'd generally would define your operation to be concurrent (i.e. return YES from isConcurrent). Then movieCompletelyDownloadedWithUrl would post the isFinished event, which would trigger the completion of the operation.
In terms of how to make a concurrent operation, you might define properties for executing and finished:
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
You'd probably want to have a strong property for the URL and the downloader:
#property (nonatomic, strong) NSURL *url;
#property (nonatomic, strong) Downloader *downloader;
And then you might have the following code in the operation subclass:
#synthesize finished = _finished;
#synthesize executing = _executing;
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (id)initWithUrl:(NSURL *)url
{
self = [self init];
if (self) {
// Note, do not start downloader here, but just save URL so that
// when the operation starts, you have access to the URL.
_url = url;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)main
{
// start the download here
self.downloader = [[Downloader alloc] initWithUrl:self.url];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
// you haven't shown how this is called, but I'm assuming you'll fix the downloader
// to call this instance method when it's done
- (void)movieCompletelyDownloadedWithUrl:(NSURL *)url
{
[self completeOperation];
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
So, with these methods, you might then have movieCompletelyDownloadedWithUrl call completeOperation like above, which will ensure that isExecuting and isFinished notifications get posted. You'd also want to respond to cancellation event, too, making sure to cancel the download if the operation is canceled.
See Configuring Operations for Concurrent Execution section of the Concurrency Programming Guide for more details.
Don't initiate download until main:
I don't see your main method initiating the download. That makes me nervous that your Downloader initialization method, initWithURL, might be initiating the download, which would be bad. You don't want downloads initiating when you create the operation, but rather you shouldn't do that until the operation starts (e.g. start or main). So, in my above example, I only have initWithURL save the URL, and then main is what starts the download.
Using NSURLConnectionDataDelegate methods in NSOperation:
As an aside, you didn't share how your operation is doing the network request. If you're using NSURLConnectionDataDelegate methods, when you get rid of that while loop in main, you might have problems if you don't schedule the NSURLConnection in a particular run loop. For example, you might do:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
If you're not using NSURLConnectionDataDelegate methods, or if you've already addressed this run loop issue, then ignore this counsel, but, bottom line, when you fix the main method in your operation, you might expose the NSURLConnection issue that your old main might have hidden from you.
How does Downloader invoke moveCompleteDownloadedWithUrl?
BTW, you're not showing how Downloader could possibly invoke moveCompleteDownloadedWithUrl. That looks suspicious, but I'm just hoping you simplified your code when you posted it. But if you're not using a protocol-delegate pattern or completion block pattern, then I'd be very nervous about how your multiple Downloader objects are informing the respective MyDownloadOperation objects that the download is done. Personally, I might be inclined to refactor these two differ classes into one, but that's a matter of personal taste.
You can use NSTimer to check whether your download is completed or not. It'll not freezes your UI
NSTimer *localTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(checkDownloadStatus) userInfo:nil repeats:YES];
-(void)checkDownloadStatus
{
if ([_downloadState isEqualToString:#"COMPLETED"])
{
NSLog(#"movie downloaded successfully");
[localTimer invalidate];
}
}

IOS Thread in UIView can not be release

i make a AdView extends UIView like this
AdView:
//nerver call dealloc when adview release
-(void)dealloc
{
//stop thread
bStart = NO;
//...
[super dealloc];
}
-(id)init
{
//.....
bStart = YES;
//the self will retain by NSThread,i try to call [self performBackground..:onThrad] or timer the same too.
NSThread* thead = [[NSThread alloc] initWithTagert:self ...:#select(onThread)];
[thread start];
[thread release];
}
-(void)onThread
{
while(bStart)
{
//....
}
}
the controller
{
AdView* view = [[AdView alloc] init];
view.delegate = self;// i am ture delegate is not retain
[self.view addSubView:view];
[view release]
}
Adview has never to call dealloc when contoller release,
who konws how to fix it.
As others noted you are passing self to the target initialization which retains it. That's why you have an extra retain causing the object not being deallocated.
That said, let me give you two pieces of advice here:
Use ARC. It's 2013, we suffered with manual reference counting for about enough time.
Use GCD. It's 2013, we suffered with manual threads management for about enough time.
A modern version of your code would look like
- (instancetype)init {
//...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self doStuffAsynchronously];
});
//...
}
- (void)doStuffAsynchronously { ... }
EDIT
As JFS advices in the comments, if you need to start and stop the background execution you should consider using a NSOperation within a NSOperationQueue. A naive (but still functional) implementation would be:
#property (nonatomic, strong) NSOperationQueue * operationQueue;
//...
- (instancetype)init {
//...
self.operationQueue = [NSOperationQueue new];
[operationQueue addOperationWithBlock:^{
[self doStuffAsynchronously];
}];
//...
}
- (void)doStuffAsynchronously { ... }
- (void)stopDoingStuff {
[self.operationQueue cancelAllOperations];
}
A neater approach, though, would be to subclass NSOperation, starting it by adding it to a queue and stopping it by invoking stop.
The thread retains the target self in start. So the object can not go away as long as the thread runs.
The controller should stop the thread by calling something like adView.bStart = NO; (which of course you have to implement).

Resources