How to kill multiple threads in objective-c - ios

I have created a UIButton and on click event, I am showing an image in the web view. Also, I am refreshing the image in every 30 sec. But when I click on button multiple times, refresh method get called multiple time as well.
I want it to work like, It saves last click time and refreshes as per that time instead of multiple times.
What can I do for it?
I tried to kill all previous thread instead of the current thread but that's not working.
Please help if anyone already know the answer.
Below is my image refresh code:
- (void)refreshBanner:(id)obj {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (![SNRunTimeConfiguration sharedInstance].isInternetConnected) {
[self removeBannerAdWithAdState:kADViewStateNotConnectedToInternet];
return;
}
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
self.bannerPaused = YES;
return;
}
self.adView.hidden = YES;
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
topController = [SNADBannerView topViewControllerWithRootViewController:topController];
if ([self checkInViewHierarchy:self parentView:topController.view]) {
// NSLog(#"Visible View Is: %#", self.adId);
SNADMeta *meta = [[SNADDataBaseManager singletonInstance] adToShowWithBanner:YES excludeTyrooAd:YES audio:NO zoneId:self.adSoptZoneId fixedView:NO condition:nil contextualKeyword:nil onlyFromAJ:NO];
SNADAdLocationType type = SNADAdLocationTypeHeader;
if (self.bannerType == SmallViewTypeFooter) {
type = SNADAdLocationTypeFooter;
}
if (self.isFromCustomEvent) {
type = SNADAdLocationTypeAdMobBanner;
}
NSString *message = meta ? nil : kSNADOppMissReason_NoAdToShow;
[SNRunTimeConfiguration fireOpportunityForAdLocation:type zoneId:self.adSoptZoneId reason:message];
NSLog(#"******************* Opportuninty fired for refresh banner ***************************");
if (meta) {
self.meta = meta;
[self updateContentForWebAd:nil];
[self updateStatsForAd];
//fireImpression
[SNADBannerView fireImpression:self.meta];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
self.sdkMediation = [[SdkMediation alloc] init];
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:YES];
}
// Ad Height Delegate.
if ([self.meta.displayType isEqualToString:kSNADDisplayType_web]) {
self.adHeightDelegateCalled = YES;
NSInteger height = self.meta.height.integerValue;
self.bannerCH.constant = height;
if ([self.callBackDelegate respondsToSelector:#selector(adWillPresentWithHeight:adId:adType:)]) {
[self.callBackDelegate adWillPresentWithHeight:height adId:self.adId adType:SeventynineAdTypeMainStream];
}
}
} else {
[self removeBannerAdWithAdState:kADViewStateNoAdToShow];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:NO];
}
return;
}
} else {
// NSLog(#"View Which Is Not Visible Now: %#", self.adId);
}
SNAdConfiguration *configuration = [SNAdConfiguration sharedInstance];
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:configuration.autoRefRate target:self selector:#selector(refreshBanner:) userInfo:nil repeats:NO];
}];
}

Use GCD, and not NSOperationQueue.
Then you step away from your immediate task. You do lots and lots of complicated things inside refreshBanner. And you will do more complicated things to make it work when the user taps multiple times.
Think about what exactly you need. Abstract the "refresh automatically, and when the button is clicked, but not too often" into a class. Then you create a class that takes a dispatch_block_t as an action, where a caller can trigger a refresh anytime they want, and the class takes care of doing it not too often. Then you create an instance of the class, set all the needed refresh actions as its action block, refreshBanner just triggers a refresh, and that class takes care of the details.
You do that once. When you've done it, you actually learned stuff and are a better programmer than before, and you can reuse it everywhere in your application, and in new applications that are coming.

NSOperationQueue have cancelAllOperations method. But for the main queue it's not a good decision to use this method, cause main queue is shared between different application components. You can accidentally cancel some iOS/other library operation together with your own.
So you can create NSOperation instances and store them in an array. Then you can call cancel for all scheduled operations by iterating trough this array, and it will only affect your operations.
Note that block operations doesn't support cancellation. You will need to create your own NSOperation subclass, extract code from your execution block into that subclass main method. Also, you'll need to add [self isCancelled] checks that will abort your logic execution at some points.
I forgot to mention that currently your execution block is fully performed on the main queue. So, you'll need to move any heavy-lifting to background thread if you want to cancel your operation in the middle of processing from main thread.
I need to add that I agree with #gnasher729 - this doesn't look like an optimal solution for the problem.

I have resolved the issue.
Multiple threads created because a new view is created every time I call the API to display image. So now I am removing views if any available before displaying image, then only last object remains and refresh is called as per last called time.
Every View has it's own object that's why multiple threads has created.
By removing views my issue has been resolved.
Thanks everyone for replying.

Related

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)
{
...
}

Best way to return a function value and call another method after async animation block or timer has ended

I'm currently developing an iPad book-style app, where I'm using a view controller to manage the main window, and then I use a number of page controllers equal to the number of pages in the book. All the page controllers inherit from a base class, PageController, where I defined the main methods used in every page, as well as the variables.
So, my view controller tracks the current page using an object of PageController type, and when I want to load another page, the view controller calls a method (transitionToNextPage), and this method returns the next page controller.
For example, if the current page is number 2, its class is Page02Controller, and the next page class is Page03Controller, which is returned from the Page02Controller.
The issue I've been fighting with, and to which I'm asking for some advice, is when I need to call the transitionToNextPage method, and the method returns when it's still doing some actions, like animating the transition (loading some frames, for example). For example, in the code below, I call the method transitionToNextPage and I start a timer to load some frames. However, the function returns right after the timer starts, and it counts for about 1 second.
- (PageController *)transitionToNextPage{
if ([self.timerAnimationAngel isValid]) {
[self.timerAnimationAngel invalidate];
self.timerAnimationAngel = nil;
}
if ([self.timerAnimationFeather isValid]) {
[self.timerAnimationFeather invalidate];
self.timerAnimationFeather = nil;
}
[super hideTransitionButtons];
[NSTimer scheduledTimerWithTimeInterval:1.f/(float)self.filenamesImagesTransition.count target:self selector:#selector(updateTransitionViews:) userInfo:nil repeats:YES];
self.imageViewTransition = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[NSString stringWithMainBundle:self.filenamesImagesTransition[0]]]];
self.imageViewTransition.layer.zPosition = kZPositionTransition;
[self.mainView addSubview:self.imageViewTransition];
return [[Page02Controller alloc] initPageNumber:2 withView:self.mainView withViewController:self.mainController];
}
So far, I have all the code working as I intend, but I don't think I'm doing in the best way. What I'm doing is calling a method from the super class of page controller when the timer ends, as shown in the code below:
- (void)updateTransitionViews:(NSTimer *)timer{
static int indexImageTransition = 0;
if (indexImageTransition >= self.filenamesImagesTransition.count) {
[super clearAllViewsIncludeBackground:YES];
[timer invalidate];
timer = nil;
[super loadNextPage];
}
else{
self.imageViewTransition.image = [UIImage imageWithContentsOfFile:[NSString stringWithMainBundle:self.filenamesImagesTransition[indexImageTransition]]];
indexImageTransition++;
}
}
And the [super loadNextPage] calls a method in the view controller, and is defined in PageController (the super class) as:
- (void)loadNextPage{
SEL selector = NSSelectorFromString(#"loadNextPage");
if([self.mainController respondsToSelector:selector]){
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.mainController performSelector:selector];
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
}
else{
NSLog(#"Selector not found.");
}
}
The method transitionToNextPage is called from the view controller the following way:
- (IBAction)buttonNextPage:(id)sender {
NSLog(#"Next page button pressed.");
self.currentPage = [self.currentPage transitionToNextPage];
self.pageNumber = self.currentPage.pageNumber;
}
And finally, the method loadNextPage called from the PageController is defined in the view controller as:
- (void)loadNextPage{
[self.currentPage loadPage];
}
The loadPage is now called from the next page, because it was already set before in self.currentPage = [self.currentPage transitionToNextPage];.
Here I lose the reference to the previous page, but it's not yet dealloc'ed because it still has a timer running, so I'm not having any issues doing this.
In the long term, and for future reusability, maybe the code is a bit confusing, but this way I can call the methods in the correct order, only when the timer finishes.
Another way that I was doing before was using notifications. I was listening to a notification called "#loadNextPage" which was posted by the current page controller when the timer was done. That way, I wasn't calling 3 or 4 additional methods in order to load the next page, but was only calling one.
I think the main advantage I have when using notifications is that the code is much simpler, and I just need to post/listen to a notification and call a single method.
I already thought about using a delegate, but I don't think it can be applied here the way I'm doing the transitions between pages.
I constantly run tests and measure the performance about memory and processor usage, and so far it's doing great. I don't have any memory leaks, and the app runs smoothly both on an iPad 2 with iOS 8 (non-retina) and iPad 4 with iOS 7 (retina).
EDIT:
I'm going to call the next page controller from inside the animation block or timer, without passing any information to the view controller.
I was assuming that it would retain the pages and cause leaks or have bad memory management performance, but that's not true. :)
Timers and similar hacks are simply wrong here. So is blocking or polling as a way of waiting. This is the standard asynchronous pattern. Look at any of the many Cocoa asynchronous methods to see how to deal with it, such as presentViewController:animated:completion:. Instead of writing a method transitionToNextPage, you write a method transitionToNextPageWithCompletion:. It takes a block parameter. When everything is over, the method calls the block, thus calling back into your code.

iOS: Processing UI events in NSRunLoop

I'm working on an iPad app that needs to spawn a dialog mid-function for some user interaction. In order to wait for the dialog, I run an NSRunLoop, however, this is preventing events on the dialog from being processed. This is how I spawn the dialog:
NSArray* listOfCompatibleTypes = [[NSArray alloc] initWithArray:[listOfCompatibleTypesAndSizesAsSet allObjects]];
[secondaryImplantChooserDialog setModalPresentationStyle:UIModalPresentationFormSheet];
[secondaryImplantChooserDialog setDefinesPresentationContext:YES];
[self presentViewController:secondaryImplantChooserDialog animated:NO completion:nil];
And the runloop is like this:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
while (secondaryImplantChooserDialog.fDialogDone != YES)
{
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
The dialog box has a popover that contains a tableview. When I comment out the runloop, the table view's didSelectRowAtIndexPath(…) method gets called. When the loop is active, however, that method doesn't get called until the dialog gets dismissed.
I realize this isn't exactly an iOS-kosher design, so I should probably refactor it, but I was wondering if there was any way to process UI events while the runloop is active.
Polling is very rarely a good solution, particularly in a user-interactive environment like an iOS app. The functionality you are looking for is a semaphore - where you can block execution in one part of your app until another has completed. You can do this with Grand Central Dispatch but this is probably adding complexity when a better solution is to re-factor.
From what I understand you have a method which is performing some calculation or business-logic and it determines that additional information is needed. You could refactor something like:
if ([self haveEnoughInfo]) {
[self performFinalCalc];
} else {
[self gatherMoreInformation]; // Use a delegate or completion block to invoke [self performFinalCalc] once more information is gathered
}

iOS Multithreading Issue

I am quite new to iOS development and I'm facing a multithreading issue.
I'm using KTPhotobrowser with SDWebImage to create a photo and video gallery.
I have to load some external data on each picture, and I don't want to affect the smoothness of the gallery's scroll view.
So, I'm trying to do that using NSOperation and NSOperationQueue, but I'm not sure I'm doing right.
What I want is to stop the loading process if the user doesn't stay on the picture and keep scrolling.
My current code:
//setCurrentIndex is called when the scrollView is scrolled
- (void)setCurrentIndex:(NSInteger)newIndex {
[loadingQueue_cancelAllOperations];
currentIndex_ = newIndex;
[self loadPhoto:currentIndex_];
NSInvocationOperation *InvocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(loadInfosAtIndex:) object:newIndex];
[loadingQueue_ addOperation:InvocationOp];
[InvocationOp release];
[selfloadPhoto:currentIndex_ + 1];
[selfloadPhoto:currentIndex_ - 1];
[selfunloadPhoto:currentIndex_ + 2];
[selfunloadPhoto:currentIndex_ - 2];
}
-(void) loadInfosAtIndex:(NSInteger)index {
if (index < 0 || index >= photoCount_) {
return;
}
KTPhotoView* photoView = [photoViews_ objectAtIndex:index];
if([photoView infosAlreadyLoaded]){
NSLog(#"%d Already Loaded", index);
return;
}
//Get the extra information by calling a web service
photoView.infosAlreadyLoaded = YES;
}
I don't think this is the proper way to do this... has anyone got any advice?
Instead of relying on a scheduling-based cancel, which can leave you in an uncertain state, have a cancelling instance variable that has atomic access (either via an atomic property or a BOOL ivar with a mutex).
Then, instead of [loadingQueue_cancelAllOperations], you simply set the canceling flag to YES and check it in loadInfosAtIndex periodically. This is essentially polling for the cancel, and if the code is involved, it can be a pain. But it has the advantage of letting you be able to handle the cancel gracefully by reading the flag. As a part of handling, you can set an isRunning flag (also needs to be atomic/mutexed) to NO and exit the thread by returning.
In the main thread, after setting the cancelling flag to YES, you can wait till the isRunning flag is NO before opening up a new thread.
[loadingQueue_ cancelAllOperations], simply doesn't cancel the operations immediately, it only cancels the operations in the queue that has not yet started executing.
If the operation in the queue has already started then it is removed only after the operation is completed. More about cancelAllOperations
I think you might want to use GCD async for that where you might remove the currently executing block.

NSOperationQueue cancel specific operations

The problem is that I manage scrollView with lots of tiles in it.
Each visible tile display image loaded from URL or (after first URL load) cached file in background. Invisible tiles recycles (set new frame and redraw).
Image load depends on tile position.
With long range scroll there is multiple redraw called for each tile: each tile loads (and display) different image several times before display the correct one.
So problem is to cancel all previously added operations for tile before add new.
I subclass NSInvocationOperation just to contain context object to detect operation attached to and before add new operation I canceling all operation for same tile:
-(void)loadWithColler:(TileView *)coller {
if (queue == nil) {
queue = [NSOperationQueue new];
}
NSInvocationOperationWithContext *loadImageOp = [NSInvocationOperationWithContext alloc];
[loadImageOp initWithTarget:self selector:#selector(loadImage:) object:loadImageOp];
[loadImageOp setContext:coller];
[queue setSuspended:YES];
NSArray *opers = [queue operations];
for (NSInvocationOperationWithContext *nextOperation in opers) {
if ([nextOperation context] == coller) {
[nextOperation cancel];
}
}
[queue addOperation:loadImageOp];
[queue setSuspended:NO];
[loadImageOp release];
}
And in operation itself I check isCancelled:
-(void)loadImage:(NSInvocationOperationWithContext *)operation {
if (operation.isCancelled) return;
TileView *coller = [operation context];
/* TRY TO GET FILE FROM CACHE */
if (operation.isCancelled) return;
if (data) {
/* INIT WITH DATA IF LOADED */
} else {
/* LOAD FILE FROM URL AND CACHE IT */
}
if (operation.isCancelled) return;
NSInvocationOperation *setImageOp = [[NSInvocationOperation alloc] initWithTarget:coller selector:#selector(setImage:) object:cachedImage];
[[NSOperationQueue mainQueue] addOperation:setImageOp];
[setImageOp release];
}
But it is do nothing. Some times early returns works but tiles still load many images before the correct one.
So how could I success? And could this lots of unneeded operations cause delays on main thread when scrolling? (Because delays are exists and I do not know why...all load in background..)
Update:
With NSLog:
isCancelled while executing:
>
cancel loadImage method for:
>
So canceling work.
Now I save reference to last operation in TileView object and perform setImage operation only if invoked operation is equal to TileView operation.
Doesn't make any difference...
Looks like there IS number of operations to load different images to one tile invoked one after another.
Any another suggestions?
For clearance:
There is singleton DataLoader (all code from it). And all tiles has call to it in drowRect:
[[DataLoader sharedDataLoader] loadWithColler:self];
Update:
NSInvocationOperation subclass:
#interface NSInvocationOperationWithContext : NSInvocationOperation {
id context;
}
#property (nonatomic,retain,readwrite) id context;
#end
#implementation NSInvocationOperationWithContext
#synthesize context;
- (void)dealloc
{
[context release];
[super dealloc];
}
#end
Thanks a lot for any help!
SOLUTION:
From answer below: need to subclass from NSOperation
As I subclass NSOperation and put all loadImage: code into it "main" method (just move all code here and nothing else) and all work just perfect!
As about scroll delaying: it occurs cause loading images to UIImageView (it takes long time because of decompress and rasterize (as I understood).
So better way is to use CATiledLayer. It loads data in background and do it much faster.
The delays on main thread is due to a the mode of the runloop while you scroll. I suggest you to watch the WWDC2011 networking app sessions. I don't know if it is fine to subclass an NSInvocationOperation that is a concrete subclass of NSOperation. I will subclass NSOperation instead. For my experience if you like to avoid sluggish scrolling, you should create NSOperation subclasses that load their main on a specific thread for networking operation (you must create it). There is a wonderful sample code from apple https://developer.apple.com/library/ios/#samplecode/MVCNetworking/Introduction/Intro.html
The way that NSOperationQueue works with respect to "setSuspended" is that it won't start to run newly added NSOperations added to it after that point, and won't start to run any that are currently in it that haven't started running yet. Are you sure your operations you're trying to cancel haven't already started yet?
Also - does your NSOperation subclass correctly deal with Key Value Observing? Concurrent Queue subclassed NSOperations have to call willChangeValueForKey and didChangeValueForKey for some properties here - but doesn't look like that's the issue as your queue doesn't set isConcurrent. Just FYI if you go that route.

Resources