object of NSOperation doesn't removed from NSOperationQueue after executing - ios

In my subclass of NSOperation I set 4 flags, and when an operation finishes its execution it is not removed to NSOperation queue, where it was added at the beginning, this thing cause a lot of issues in my app.
I suppose that the way I set these flags is not correct, could you please help with it. cause I really spend a lot of time on identifying this issue.
#property(assign, nonatomic) BOOL isCancelled;
#property(nonatomic, getter=isExecuting) BOOL executing;
#property(nonatomic, getter=isFinished) BOOL finished;
#property(readonly, getter=isAsynchronous) BOOL asynchronous;
//in initialisation
- (id)initWithURL:(NSURL*)url andRaw:(NSInteger)row
{
if (![super init])
return nil;
[self setTargetURL:url];
return self;
}
//the way I override KVO
- (BOOL)isExecuting
{
NSLog(#"Exec");
return (self.defaultSession != nil);//it doesn't work
}
- (BOOL)isFinished
{
NSLog(#"Finished");
return (self.defaultSession == nil); //it doesn't work, so I explicitly set the value
}
- (BOOL)isAsynchronous
{
return YES;
}
- (void)cancel
{
[super cancel];
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.isExecuting = NO;
self.isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
if(self.downloadTask.state == NSURLSessionTaskStateRunning)
[self.downloadTask cancel];
[self finish];
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.defaultSession = nil; //NSURLSession
self.isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
}
Thank you in advance
EDIT:
finally I found the issue - it was NSURLSession inside the queue. It kept strong reference to the queue and didn't allow it to be deallocated and removed from NSOperationQueue.

I have done the exact same thing, albiet in Swift.
There are a couple of aspects that I have implemented differently and are listed below:
I have noticed that we do not have to override cancel() method in the
Asynchronous operation. The default behavior of NSOperation's cancel
method is to set the self.cancelled boolean to true.
self.executing should be set to true only inside the overriden start() method of operation, but not in the init ( Not sure if this wil cause any issues). Also before setting the self.executing = true in the start() method, I ensure that the boolean self.cancelled is false. If self.cancelled = true, we should set self.finished = true.
Also , I have created a "didSet" property observer for the isExecuting and isFinished properties where I call the willChangeValueForKey and didChangeValueForKey. I am not 100% sure how to replicate the didSet behavior in Obj C. ( This says to overrride setter)
Please refer to the Ray Wenderlich video tutorials about "Concurrency" where Sam DAvies explains the creation of NSoperation subclass for Asynchronous operation. Please note, that it is only for subscribers and it is explained in Swift. I believe if you fix points 1 and 2, you should see your issues being fixed.

Related

Completion Block of concurrent NSOperation not being called everytime

I would like to run a concurrent NSOperation. For this reason, I have extended the NSOperation class and overridden the start and finish methods. It looks something like this:
#import "AVFrameConversionOP.h"
#implementation AVFrameConversionOP //extends NSOperation
- (void)start
{
[self willChangeValueForKey:#"isFinished"];
_isFinished = NO;
[self didChangeValueForKey:#"isFinished"];
// Begin
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
if (_isCancelled == YES) {
NSLog(#"** OPERATION CANCELED **");
[self willChangeValueForKey:#"isFinished"];
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self performTask];
[self finish];
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
NSLog(#"operationfinished, _isFinished is %d", _isFinished);
if (_isCancelled == YES)
{
NSLog(#"** OPERATION CANCELED **");
}
}
- (void)performTask
{
//The task is performed here
sleep(0.1); //This is just for demonstration purposes
}
#end
I have defined the completion block for this NSOperation in the main view controller in the viewDidLoad method:
- (void)viewDidLoad {
NSLog(#"viewDidLoad!");
[super viewDidLoad];
static int counter = 0;
_frameConvOP = [[AVFrameConversionOP alloc] init]; //Initializing the modified NSOperation class
__weak typeof(self) weakSelf = self;
_frameConvOP.completionBlock = ^ {
counter++;
//NSLogs don't seem to work here
/*if(counter>1){
exit(-1); //Never happens because counter only ever becomes =1
}*/
};
[[VideoPreviewer instance] setNSOperation:_frameConvOP]; //This is where I send the NSOperation object to the class where I'm using it
}
I'm starting the NSOperation by doing [frameConvOP start] inside of a loop. It is called continuously every time a frame is decoded, so that is to say it is called very often. I can see that the finish and start methods of the NSOperation are being called frequently through the logs. And the functions inside these methods are also being correctly called. However, the NSLogs inside the completion blocked aren't logged. I put an exit(-1) inside the completion block and the app crashed, but the NSLogs never show up. So I put a static counter in there and it only increments to 1. So the completion block is only being called once.
Can anyone explain why the completion block is being called only once? Is it because of the way I'm (incorrectly) handling the KVO for _isFinished and _isExecuting? The completion block should be called when _isFinished is set to YES and as far as I can see, I am doing that in the finish block. And the finish block is being called quite frequently as it should be.
Any help, guidance or direction is appreciated. Please let me know if I have made any errors or if I have been less-than-clear in the post.
Thanks,
From the NSOperation class reference:
An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again.
You are not allowed to invoke -start multiple times on a single operation object.
Another thing, in your -finish method, you need to properly nest the -willChange... and -didChange... calls. That is, you need to call -didChange... in the reverse order of the -willChange... calls:
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];

UITextFieldDelegate textFieldShouldReturn with ReactiveCocoa

I am trying to implement UITextFieldDelegate textFieldShouldReturn handling with ReactiveCocoa. Unfortunately the subscribeNext block is run when I subscribe for the signal.
The implementation using delegation would be:
- (void)viewDidLoad
{
...
self.myTextField.delegate = self;
}
...
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (textField == self.myTextField) {
NSLog(#"Let's go!");
}
return YES;
}
In ReactiveCocoa I have added a category for UITextField in a similar fashion like UITextView+RACSignalSupport.
#implementation UITextField (RACKeyboardSupport)
static void RACUseDelegateProxy(UITextField *self)
{
if (self.delegate == self.rac_delegateProxy) return;
self.rac_delegateProxy.rac_proxiedDelegate = self.delegate;
self.delegate = (id)self.rac_delegateProxy;
}
- (RACDelegateProxy *)rac_delegateProxy
{
RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd);
if (proxy == nil) {
proxy = [[RACDelegateProxy alloc] initWithProtocol:#protocol(UITextFieldDelegate)];
objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return proxy;
}
- (RACSignal *)rac_keyboardReturnSignal
{
#weakify(self);
RACSignal *signal = [[[[RACSignal
defer:^{
#strongify(self);
return [RACSignal return:RACTuplePack(self)];
}]
concat:[self.rac_delegateProxy signalForSelector:#selector(textFieldShouldReturn:)]]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:#"%# -rac_keyboardReturnSignal", [self rac_description]];
RACUseDelegateProxy(self);
return signal;
}
#end
Here subscribeNext block is executed even if the Return Key was never pressed:
- (void)viewDidLoad
{
...
[self.myTextField.rac_keyboardReturnSignal subscribeNext:^(id x) {
Log(#"Let's go with RAC!");
}];
}
I have to use skip:1 to avoid that problem:
- (void)viewDidLoad
{
...
[[self.myTextField.rac_keyboardReturnSignal skip:1] subscribeNext:^(id x) {
Log(#"Let's go with RAC!");
}];
}
Any idea why this happens?
Solution:
- (RACSignal *)rac_keyboardReturnSignal
{
RACSignal *signal = [[[self.rac_delegateProxy
signalForSelector:#selector(textFieldShouldReturn:)]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:#"%# -rac_keyboardReturnSignal", [self rac_description]];
RACUseDelegateProxy(self);
return signal;
}
You are returning a signal that immediately returns a value in your defer block, then concat-ing new values onto the stream when textFieldShouldReturn is invoked.
The code in UITextView+RACSignalSupport.m is calling reduceEach in order to return a string value that is extracted from the UITextView instance. The defer is used to merely have an initial value generated upon subscription.
Basically, I don't think you want the defer at all for your use case.

Recursive method with block and stop arguments

I've written a category on UIView that allows me to walk the view hierarchy:
UIView+Capture.h
typedef void(^MSViewInspectionBlock)(UIView *view, BOOL *stop);
#interface UIView (Capture)
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block;
#end
UIView+Capture.m
#implementation UIView (Capture)
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block
{
BOOL stop = NO;
[self inspectViewHeirarchy:block stop:stop];
}
#pragma - Private
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block stop:(BOOL)stop
{
if (!block || stop) {
return;
}
block(self, &stop);
for (UIView *view in self.subviews) {
[view inspectViewHeirarchy:block stop:stop];
if (stop) {
break;
}
}
}
#end
Which you can use like so:
[[[UIApplication sharedApplication] keyWindow] inspectViewHeirarchy:^(UIView *view, BOOL *stop) {
if ([view isMemberOfClass:[UIScrollView class]]) {
NSLog(#"Found scroll view!");
*stop = YES;
}
}];
Everything works fine, except setting stop to YES. This appears to have absolutely no effect whatsoever. Ideally, I'd like this to halt the recursion, so when I've found the view I want to take some action on I don't have to continue to traverse the rest of the view hierarchy.
I'm pretty dense when it comes to using blocks, so it may be something completely obvious. Any help will be greatly appreciated.
The way you're using a block is exactly the same as using a C function. So there's nothing special you really need to know about blocks. Your code should work but note the difference between passing stop as a BOOL * to your block and to create a new local when you recurse.
It looks like you're expecting calls down to inspectViewHierarchy:stop: to affect the outer stop variable. That won't happen unless you pass it as a reference. So I think what you want is:
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block stop:(BOOL *)stop
...and appropriate other changes.
I assume you want to return all the way out from the top-level inspectViewHierarchy when the user sets stop to YES.
(Incidentally, you spelled “hierarchy” wrong and you should use a prefix on methods you add to standard classes.)
#implementation UIView (Capture)
- (void)micpringle_visitSubviewsRecursivelyWithBlock:(MSViewInspectionBlock)block
{
BOOL stop = NO;
[self inspectViewHierarchy:block stop:&stop];
}
#pragma - Private
- (void)micpringle_visitSubviewsRecursivelyWithBlock:(MSViewInspectionBlock)block stop:(BOOL *)stop
{
block(self, stop);
if (*stop)
return;
for (UIView *view in self.subviews) {
[view micpringle_visitSubviewsRecursivelyWithBlock:block stop:stop];
if (*stop)
break;
}
}
#end
- (BOOL) inspectViewHeirarchy:(MSViewInspectionBlock)block
{
BOOL stop = NO;
block(self, &stop);
if (stop)
return YES;
for (UIView *view in self.subviews) {
if ([view inspectViewHeirarchy:block])
return YES;
}
return NO;
}
Try this:
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block
{
__block BOOL stop = NO;
[self inspectViewHeirarchy:block stop:stop];
}
Blocks, by nature, copy the variables and context in which they are declared.
Even though you are passing the boolean as a reference, it's possible that it's using a copy of the context and not the true stop.
This is just a wild guess but, inside inspectViewHierarchy:stop: do something like:
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block stop:(BOOL)stop
{
if (!block || stop) {
return;
}
// Add these changes
__block BOOL blockStop = stop;
block(self, &blockStop);
for (UIView *view in self.subviews) {
[view inspectViewHeirarchy:block stop:stop];
if (stop) {
break;
}
}
}
This may be a long shot and I'm not 100% sure it will work without having your project, but it's worth a shot.
Also, refactor your method so "heirarchy" is actually spelled "hierarchy" :] It's good for reusability and for keeping a good code base ;)
wouldn't you want to check the status of 'stop' directly after you invoke the block? It doesn't help to invoke it after you call inspectViewHierarchy:stop: because you are passing a copy of 'stop' to that method instead of the reference.

Delay before apply object changes (similar like setNeedsDisplay)

I have object with properties and when I change some of properties, I do a lot of calculations. If I change one property, then another, calculations execute 2 times. I want do all calculations 1 time. I see 3 solution:
Make method commit. But I don't wanna do excess methods.
Make method for change all properties. The same cause why I don't wanna do this. And what to do if I will have much more properties in future.
Make delay after delay commit changes. I really do not know what consequences this may turn into. Something like this:
.
#interface TestObject : NSObject
#property (nonatomic, assign) float x;
#property (nonatomic, assign) float y;
#end
#implementation TestObject
{
BOOL flag;
}
- (id)init
{
self = [super init];
if(self){
flag = NO;
}
return self;
}
- (void)setX:(float)x
{
_x = x;
[self delayCommit];
}
- (void)setY:(float)y
{
_y = y;
[self delayCommit];
}
- (void)delayCommit
{
if(flag == NO){
flag = YES;
[self performSelector:#selector(commit) withObject:nil afterDelay:0];
}
}
- (void)commit
{
flag = NO;
NSLog(#"Do a lot of calculations....");
}
#end
Is third solution good practice? I want simple interface, I don't want excess methods.
If you really want a behavior like that, you should then have a timer that every N seconds, checks if any of the properties are changed, and does the computations. Something like:
- (void)setX:(float)x
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(_mySem, DISPATCH_TIME_FOREVER);
_x = x;
flag = YES;
dispatch_semaphore_signal(_mySem);
});
}
- (void)setY:(float)y
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(_mySem, DISPATCH_TIME_FOREVER);
_y = y;
flag = YES;
dispatch_semaphore_signal(_mySem);
});
}
- (void)updateCycle
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(_mySem, DISPATCH_TIME_FOREVER);
if(flag){
flag = NO;
NSLog(#"Do a lot of calculations....");
}
dispatch_semaphore_signal(_mySem);
});
}
In your init method you would have to initialise the timer and the semaphore with
_mySem = dispatch_semaphore_create(1);
[NSTimer scheduledTimerWithTimeInterval:N
target:self
selector:#selector(updateCycle)
userInfo:nil
repeats:YES];
The way to use it would then just be:
[object setX:4.5f];
[object setY:0.3f];
Depending on how many properties you have, this may work for you instead:
- (void)setX:(float)x y:(float)y
{
_x = x;
_y = y;
[self commit];
}
Here, you set more than one property with one method call. You know you have all the values you need, so you can call commit directly.
I would suggest you not to try to be too fancy with delayed commits (unless you really have to) and to use commit as a separate call and perform your long calculation in a separate thread, e.g.:
- (void)setX:(float)x
{
_x = x;
}
- (void)setY:(float)y
{
_y = y;
}
- (void)commitChanges
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// ... Long calculations go here ...
dispatch_async(dispatch_get_main_queue(), ^{
// ... Once you done with your long calculation, put your UI code here
// ... (e.g. display results of your calculation in text box)
});
});
}
then from your main code will look like:
[obj setX:123.0f];
[obj setY:345.0f];
[obj commitChanges];

Call the my method some amount on cocos2d

I have a method that draws a one sprite on the screen with the animation effects,
if I call in init this method
if( (self=[super init]) ) {
// ....
[self myMethod];
// .....
}
Then he does it once on my project
When I call by schedule
-(void)schedulMyMethod:(ccTime)dt {
[self myMethod];
}
if( (self=[super init]) ) {
// ....
[self schedule:#selector(schedulMyMethod:) interval:0.5];
// .....
}
It runs for an unlimited times
I need so that I can call the my method some amount
You mean, you want to have it repeat N times? You'll need to keep state on times remaining for it to run.
#property (nonatomic, assign) NSInteger timesToRunMyMethod;
- (void)beginRunningMyMethod {
self.timesToRunMyMethod = 100; // N==100
[self myMethod];
}
- (void)myMethod {
self.timesToRunMyMethod--;
// do stuff
if (self.timesToRunMyMethod > 0) {
// i used native delayed execution, you can replace it with whatever cocos2d offers if you want
[self performSelector:#selector(myMethod) withObject:nil afterDelay:0.5];
}
}
And it's probably wrong to start this on init. Is it a view controller? Then you can use viewDidAppear or willAppear.

Resources