ios block w/self = endless loop - ios

I am trying to do
__weak UIButton *ptr = self.backBtn;
self.footer.defaultSelectedItem.selectionBlock = ^{
[ptr sendActionsForControlEvents:UIControlEventTouchUpInside];
};
and I'm getting an infinite loop on my code anyway.
I've already referred to:
this
this
and this
With no promising result. My program still hangs and then after a minute xcode dumps out a huge loop cycle once I run out of memory. What should I do?
EDIT
I should have also pointed out that I'm using Automatic Reference Counting (ARC).

Let your block have a flag to tell it not to execute:
__block BOOL flag = NO;
.... = ^{
if (flag) return;
flag = YES;
// rest of block code here.
flag = NO;
};

Related

NSOperation completionBlock retain cycle warning?

I have similar code in two parts of my app, xcode gives the warning for one but not the other. From my understanding of the documentation I think there should be no warning in either case.
// In one part of my code I have this
__block UploadOperation *uploadOperation = [[UploadOperation alloc] initWithPsc:self.persistentStoreCoordinator webService:self.webService];
uploadOperation.completionBlock = ^{
if (uploadOperation.uploadCount > 0) {
self.expediteNextSync = YES;
}
};
// And in another I have this
__block SyncOperation *syncOperation = [[SyncOperation alloc] initWithPsc:self.persistentStoreCoordinator webService:self.webService];
syncOperation.completionBlock = ^{
if (syncOperation.expediteNextSync) { // <--- This one gives the warning "Capturing 'syncOperation' strongly in this block is likely to lead to a retain cycle"
...
}
};
The documentation says:
In iOS 8 and later and macOS 10.10 and later, this property is set to
nil after the completion block begins executing.
So I don't think there will be any retain cycle.

How can I check a dispatch queue is empty or not?

My condition is that when I scroll my tableview to the bottom or to the top, I need to do some reload, refresh job that will ask for new data from the server, but I want to check if the last job is done or not. If the last request is still working, I should not fire another request.
I'm using the same background queue created from dispatch_queue_create() to deal with the httpRequest.
- (id)init {
self = [super init];
if (self) {
...
dataLoadingQueue = dispatch_queue_create(#"DataLoadingQueue", NULL);
}
return self;
}
From now on, I just use a BOOL value to detect if the job is on working or not. Something like this:
if(!self.isLoading){
dispatch_async(dataLoadingQueue, ^{
self.isLoading = YES;
[self loadDataFromServer];
});
}
I just wonder if there is any way to change the code to be like the following:
if(isQueueEmpty(dataLoadingQueue)){
dispatch_async(dataLoadingQueue, ^{
[self loadDataFromServer];
});
}
Thus I can remove the annoying BOOL value that shows everywhere and need to keep tracking on.
Why don't you instead use NSOperationQueue (check [operationQueue operationCount]) ?
If you just want to use GCD, dispatch_group_t may be fit you.
#property (atomic) BOOL isQueueEmpty;
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dataLoadingQueue, ^{
self.isQueueEmpty = NO;
//Do something
});
dispatch_group_notify(dispatchGroup, dataLoadingQueue, ^{
NSLog(#"Work is done!");
self.isQueueEmpty = YES;
});
While tasks have completed, the group will be empty and trigger the notification block in dispatch_group_notify.

How to illustrate a background task using GCD?

I want to illustrate the progress on MBProgressHUD item, but when i triger this method :
- (IBAction)signInBttn:(id)sender {
MBProgressHUD *hudd = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hudd.mode = MBProgressHUDModeAnnularDeterminate;
hudd.labelText = #"Loading";
__block float value = 0;
for (int j = 0; j<2000; j++) {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i<20000 ; i++) {
}
value += 0.001;
dispatch_async( dispatch_get_main_queue(), ^{
hudd.progress = value;
});
});
}
}
hud appears fully to 100%. This is only for my information, I dont have idea how to create background task which calculate something and when he done with e.g. 40% the HUD is refreshing to 40% of his progress. I hope I made ​​myself clear, and if anyone has time to help improve my code, thanks a lot for any answers
In this case, you can solve the problem by decoupling the updating of the counter from the updating of your HUD in your UI. Apple refers to this as "updating the state asynchronously" in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC.
Generally this isn't necessary (most of the time the stuff we're doing asynchronously is slow enough that we don't have problems), but if doing something that is running faster than the UI can hope to keep up with, you create a "dispatch source" for this. I'm going to illustrate it with a UIProgressView, but the same applies to pretty much any UI:
// create source for which we'll be incrementing a counter,
// and tell it to run the event handler in the main loop
// (because we're going to be updating the UI)
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// specify what you want the even handler to do (i.e. update the HUD or progress bar)
dispatch_source_set_event_handler(source, ^{
self.iterations += dispatch_source_get_data(source);
[self.progressView setProgress: (float) self.iterations / kMaxIterations];
});
// start the dispatch source
dispatch_resume(source);
// now, initiate the process that will update the source
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (long i = 0; i < kMaxIterations; i++)
{
// presumably, do something meaningful here
// now increment counter (and the event handler will take care of the UI)
dispatch_source_merge_data(source, 1);
}
// when all done, cancel the dispatch source
dispatch_source_cancel(source);
});
In my example, iterations is just a long property:
#property (nonatomic) long iterations;
And I defined my kMaxIterations constant as follows:
static long const kMaxIterations = 10000000l;
First off, if you want to delay execution use dispatch_after: Apple Doc since it could be that Clang is optimizing your loop (i.e. by making it not exist).
Within that block call dispatch_sync on the main thread to update the UI, since dispatch_async is not guaranteed to execute 'evenly'. Something like this ought to work...
for (...) {
dispatch_after(<some formula of i>, DEFAULT_PRIORITY, ^{
dispatch_sync(MAIN_QUEUE, ^{ hudd.progress = value });
}
}

Wait for many asynchronous calls to perform callback

I want to synchronize some data with a web service. For each item I have to make a asynchronous call.
I want to have a completion block witch is called, when each item was synchronized. For each item I am able to perform a completion block. Now, I don't know a good way how to do it.
This is the interface:
-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
NSArray* items = // get items
for (int i = 0, n = [items count]; i < n; i++) {
[self synchronizeItem:[items objectAtIndex:i] onComplete:^{
// What do do here?
}];
}
// And/or here?
}
-(void) synchronizeItemOnComplete:(CompleteBlock) block {
// do something
block();
}
How can I wait for the synchronization and then perform the block?
I tried something like this:
NSArray* items = // get items
__block int countOfItemsUntilDone = [items count];
for (int i = 0, n = countOfItemsUntilDone; i < n; i++) {
[self synchronizeItem:[items objectAtIndex:i] onComplete:^{
countOfItemsUntilDone--;
}];
}
dispatch_queue_t queue = dispatch_queue_create("wait for syncing", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
while (countOfItemsUntilDone > 0) {
usleep(1000); // wait a little bit
}
block();
});
dispatch_release(queue);
But I think this is a quite bad way. Any ideas?
Instead of spinning in a loop waiting for the counter to equal zero, check the counter value each time you decrement it, then fire an event when it reaches zero.
-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
NSArray* items = // get items
__block NSUInteger remaining = [items count];
for (ItemClass* item in items) {
[self synchronizeItemImage:item onComplete:^{
--remaining;
if (remaining == 0) {
block();
}
}];
}
}
To explain why it feels wrong, there are two things you're doing here that you should do either never or rarely:
Using background queues. This is difficult and bug-prone. Don't do it without reading up a lot about writing concurrent code. You also only really need to do this if an operation blocks for a substantial amount of time (eg., to read a file from disk, or perform an intensive calculation). Don't assume you need to do it unless you have a good reason (eg., a measurable performance problem).
Spinning in a loop, checking a variable for changes and calling sleep. You should never do this.
Also, if you're looping over the elements in an array, the for ... in syntax is much nicer (and potentially more efficient) calling objectAtIndex: on each index.
Never check or decrement shared memory in different threads like this, it can cause races. Use a dispatch group to do what you're doing.
dispatch_queue_t myBGQueue;
dispatch_group_t itemsGroup = dispatch_group_create();
for (ItemClass *item in items) {
dispatch_group_async(itemsGroup, myBGQueue, ^{
[self synchronizeItemImage:item];
});
}
/* execution will sleep here until all the blocks added in the `for` complete */
dispatch_group_wait(itemsGroup, DISPATCH_TIME_FOREVER);
dispatch_release(itemsGroup);
You can use these to use synchronously.
GCD and this
performSelector:waitUntilDone:YES

using variables in a block from a parallel scope

__block BOOL myBool = NO;
__strong MyClass *ptr = self;
self.footer.defaultSelectedItem.selectionBlock = ^{
myBool = YES;
ptr = nil;
};
This works just fine when my Scheme's Build Configuration is set to Debug, but I get an EXC_BAD_ACCESS when I run with Release. The EXC_BAD_ACCESS happens on the the following line
if(selectionBlock != nil) selectionBlock();
but if I comment out all the lines inside my selectionBlock then it runs with no error. Why does this run in Debug and not Release?
FYI I'm using Automatic Reference Counting (arc)
** EDIT in response to #RobNapier **
The original code that works in debug is:
__block BOOL flag = NO;
__strong EventsView *ptr = self;
self.footer.defaultSelectedItem.selectionBlock = ^{
if(flag) return;
flag = YES;
[ptr backTUI:nil];
flag = NO;
};
There's no reason for the extra ptr here. Using self inside the block would be more correct. The block will retain self in that case. That may cause a retain loop, so it's up to you to remember to set self.selectionBlock = nil at some point before dealloc to break the retain loop.
flag may be optimized away here. It's not clear how it could possibly be useful from the above code.
I'm always very nervous about long indirection-chains in a set operation like this one:
self.footer.defaultSelectedItem.selectionBlock = ...
I would make sure that footer, and the current defaultSelectedItem can't disappear before this runs.
Simplify the problem. Make the block just log "Running block." Then add back things until it crashes.
For the life of me I could not get this to work with a block. So instead I moved to using a setter for the Event View pointer. I'm guessing that fixed the problem with my pointer by setting up an additional ARC retain. I'm still uncertain because I never saw a zombie get logged when using the block so yeah.
When in doubt, use a selector.

Resources