iOS Multithreading Issue - ios

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.

Related

How to kill multiple threads in objective-c

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.

How to make app more responsible?

When launching app first, app do prefill its local persistent store from backend. It happens on the DISPATCH_QUEUE_PRIORITY_LOW, but it tears when user scroll in tableView meantime. What else can I do? Heavy stuff are already on lowest priority.
DISPATCH_QUEUE_PRIORITY_BACKGROUND has even lower priority. If that does not help I think you could:
Pre-fill on a serial queue. Most iOS devices have more than 2 cores and 1 of those should be able to handle table scrolling.
Pre-fill slower. It may be that you fill the memory-bandwidth or flush the L2-cache during your pre-fill. This could be hard to solve. Maybe you can periodically reload all visible table cells to keep that code from going stale, but it may also interfere with user scrolling.
You could do a couple of things I believe
1) you could start with an empty dataset and when you're done getting all the data use ''' self.tableview.reloadData() ''' in your building block.
2) if that's not possible, then you could always present a loader so the rest of the UI is disabled while the data is being created, this one is pretty easy to use https://github.com/jdg/MBProgressHUD
I have one idea, but here i suggest use NSOperation (if you have enough time to refactor some of your code). You can organize your download process throw NSOperationQueue.
For example,
- (NSOperationQueue *)downloadQueue {
if (!_downloadQueue) {
_downloadQueue = [[NSOperationQueue alloc] init];
_downloadQueue.name = #"Download Queue";
_downloadQueue.maxConcurrentOperationCount = 1;
}
return _downloadQueue;
}
Subclass NSOperation add override main func, where you can write your donwload code
- (void)main {
// 4
#autoreleasepool {
// download code here
}
Next step - check when user start scrolling tableview (or whatever user interaction you want) and start/ stop operations executing on that event). for example, for uitableview it will look like:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
// 1
[self.model suspendAllOperations];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// 2
[self.model resumeAllOperations];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self.model resumeAllOperations];
}
Model here is a NSObject subclass, that handles all download process (independently of UI) where you can suspend/resume operations.
- (void)suspendAllOperations {
[_downloadQueue setSuspended:YES];
}
- (void)resumeAllOperations {
[_downloadQueue setSuspended:NO];
}
- (void)cancelAllOperations {
[_downloadQueue cancelAllOperations];
}
So, when you expect heavy operations in your UI, you can stop your background process and resume it when you need. Also you can change maxConcurrentOperationCount for your best performance (this param you can set after some testing / measurement )
Hope this helps.

NSInvocationOperation Cancel usage

Is it right if I cancel an NSInvocationOperation inside the operation?
example:
.h file:
//defined in the interface
NSInvocationOperation *op1;
NSOperationQueue *loadQueue;
.m file:
-(id)init{
op1 = [NSInvocationOperation new];
loadQueue = [NSOperationQueue new];
}
-(void)downloadData{
op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(loadServerResponse:) object:newerRequest];
[loadQueue addOperation:op1];
}
Now I have a method to download data from the server. I added a condition to check if there is an error. If so I'm cancelling that operation inside the method and calling back the same method upon retrieving the new login token.
- (void)loadServerResponse{
if(server_error){
[op1 cancel];
//fetch login token again
[self downloadData];
return;
}
Am I doing anything wrong?
First, referencing op1 (which I'm assuming is a class-level ivar) is a Bad Thing™ on multiple levels, the first of which is NSOperationQueue dispatches your operation onto a background thread, so op1 will (at best) be copied into that thread's context and no longer reference the original operation you were attempting to cancel.
I think you don't need to worry about canceling the operation, given the logic you have shared. You specifically seem to want to call downloadData after canceling, and there's no guarantee that will happen if you cancel the operation first. I would just remove the call to cancel the operation, and continue on. Normally you only cancel a running operation from an external source (say if you receive a notification that your application will enter background state).

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.

waiting in the fixed place in the code for a touch on the screen

For example in a thread (because I can't wait in main loop) I have this code :
-(void) game {
for (Players player in players) {
if (player.type == IA) { // computer plays
answer = [player play];
else {
[ui showQuestion]; // user plays with the touch screen
// here waiting for the answer touch
answer = ???????????????? // return from waiting after touch callback
}
[answersArray addObject:answer];
}
answer = [self bestAnswer : answersArray];
[ui showTheBestAnswer : answer];
}
Is there a solution to wait for an UI Event in a fixed code place ?
without blocking the main loop of course.
Thank you very much for your help,
jpdms
First of all, I highly recommend that you read Apple's Concurrency Programming Guide, included with the Xcode documentation. In general, there are much better alternatives to threads, especially in the example you provide.
However:
If the game method is executing on a separate thread, then the proper way to signal the thread is using NSCondition. Create an instance and make sure both the code above and the touch handler has access to it.
NSCondition *playerDidTouchCondition = [[NSCondition alloc] init];
In the game method, you wait on the condition like this:
[ui showQuestion];
[playerDidTouchCondition lock];
[playerDidTouchCondition wait];
[playerDidTouchCondition unlock];
// do something with answer
Your game thread will sleep until the condition has been signaled. In your touch handler you would do this:
answer = whatever the user did
[playerDidTouchCondition lock];
[playerDidTouchCondition signal]; // wake up one of the sleeping threads
[playerDidTouchCondition unlock];
The example code you have above really doesn't demonstrate the need for a separate thread, however. You could very easily store a currentPlayerIndex somewhere and proceed to the next player inside of the button handler for the answer button.
Also, you MUST ensure that any UI updates are actually happening on the main thread. I hope that your lines like [ui showQuestion] are queuing calls on the main thread. In Cocoa you can do this easily with something like: [ui performSelectorOnMainThread:#selector(showQuestion)];
You really, really, really should not be using a separate thread for this.

Resources