ios recursive call with block leaks memory - ios

I am calling a function recursively from completion block which increases the memory foot print, this most probably is leading to a block retain cycle,following is the code:
- (void)tick {
if (counter > counterLimit) {
[self finish];
return;
}
counter++;
context = [[Context alloc] init];
//this is executed in another thread
[self.executer computeWithContext:(Context*)context completion:^(NSDictionary *dictionary, Context *context_)
{
[self handleResponse];
[self tick];
}];
}

self is owning executer, executer is owning the block, the block captures self strongly (same effect as owning). you have a retain cycle.
create a weak reference for self and use that in the block exclusively.
- (void)tick {
// ...
__weak typeof(self) weakSelf = self;
[self.executer computeWithContext:(Context*)context completion:^(NSDictionary *dictionary, Context *context_)
{
typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf) { // ensure the receiver is still there to receive
[strongSelf handleResponse];
[strongSelf tick];
}
}];
}

Related

break retain cycle in a block nested in another block

Sometimes I use a block nested in another block, here is my code
- (void)test {
__weak typeof(self) weakSelf = self;
[self.viewSource fetchData:^(BOOL succeed, NSError * _Nonnull error, id _Nonnull data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.dataSource disposalData:^{
// here is the strongSelf ok? do I have to do something to avoid retain cycle?
[strongSelf updateUI];
}];
}];
}
- (void)updateUI {
}
I doubt the inner block still has a retain cycle?
[strongSelf.dataSource disposalData:^{
[strongSelf updateUI];
}];
my question is what is the correct way to break the retain cycle in such situation?
here is the additional discussion, as many friend mentioned about this, if I remove __strong typeof(weakSelf) strongSelf = weakSelf;, the inner block has no retain cycle? Is it perfectly correct?
- (void)test {
__weak typeof(self) weakSelf = self;
[self.viewSource fetchData:^(BOOL succeed, NSError * _Nonnull error, id _Nonnull data) {
[weakSelf.dataSource disposalData:^{
[weakSelf updateUI];
}];
}];
}
- (void)updateUI {
}
I think you can just create new strong reference inside nested block, like this:
- (void)test {
__weak typeof(self) weakSelf = self;
[self.viewSource fetchData:^(BOOL succeed, NSError * _Nonnull error, id _Nonnull data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.dataSource disposalData:^{
__strong typeof(weakSelf) strongSelf = weakSelf; // <- new strong ref
[strongSelf updateUI];
}];
}];
}
It will override the first strongSelf in the nested block scope. And it will be only alive during the execution of the nested block without strong reference cycle created. I think so =)

dispatch_async Nested Block

I am using dispatch_async method to execute task in main queue. But it causing retain cycle:
Following is the code snippet:
self.test = ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", self);
});
};
I am not able to get properly why it is creating retain cycle. As My controller does not have ownership of dispatch_async block.
Try using weakSelf:
__weak typeof(self) weakSelf = self;
self.test = ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", weakSelf);
});
};
If you don't want the outer block to retain self, but want the inner block to be able to keep self alive once dispatched, maybe something like this:
typeof(self) __weak weakSelf = self;
self.test = ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", strongSelf);
});
}
};

How do you manage retain counts in nested dispatch queue blocks

In the following code, how would you avoid nested blocks increasing the retain count of 'self'.
This is how I avoid nested blocks
-(void)openSession {
[self.loginManager logInWithReadPermissions:#[#"user_photos"]
fromViewController:[self.datasource mediaAccountViewControllerForRequestingOpenSession:self]
handler:[self loginHandler]];
}
-(void(^)(FBSDKLoginManagerLoginResult *result, NSError *error))loginHandler {
__weak typeof (self) weakSelf = self;
return ^ (FBSDKLoginManagerLoginResult *result, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (error) {
[strongSelf.delegate mediaAccount:strongSelf failedOpeningSessionWithError:error];
} else if (result.isCancelled) {
[strongSelf.delegate mediaAccountSessionOpeningCancelledByUser:strongSelf];
} else {
[strongSelf.delegate mediaAccountDidOpenSession:strongSelf];
}
[strongSelf notifyWithCompletion:[strongSelf completionHandler]]
};
}
-(void)notifyWithCompletion:(void(^)(void))completion {
[self notify];
completion();
}
-(void(^)(void))completionHandler {
return ^ {
//do something
};
}
But how do you avoid many nested blocks, which is often the case when you use GCD within a block ?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self parseLoadsOfData];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
});
Are there retain cycles here ?
__weak typeof(self) *weakSelfOuter = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
__strong typeof(self) *strongInnerSelf1 = weakSelfOuter;
[strongInnerSelf1 parseLoadsOfData];
__weak typeof(self) *weakInnerSelf = strongInnerSelf1;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(self) *strongInnerSelf2 = weakInnerSelf;
[strongInnerSelf2 updateUI];
});
});

Nested completion block not being called

I've got a nested completion block set to initialize an SKProduct. If I put a breakpoint right before the completion block, the inside block executes OK (I can poll for _product) but the completion block never fires.
If I call completion() immediately within this block it executes, but not if I call it within the completion block of the nested block.
- (void)initializeProduct:(NSString*)bundleId completion:(void(^)(BOOL finished, NSError* error))completion
{
// completion(YES, nil); If I call completion here, it executes
NSSet* dataSet;
dataSet = [[NSSet alloc] initWithArray:#[bundleId]];
[IAPShare sharedHelper].iap.production = NO;
if(![IAPShare sharedHelper].iap) {
NSSet* dataSet = [[NSSet alloc] initWithObjects:bundleId, nil];
[IAPShare sharedHelper].iap = [[IAPHelper alloc] initWithProductIdentifiers:dataSet];
[IAPShare sharedHelper].iap.products = #[bundleId];
}
[[IAPShare sharedHelper].iap requestProductsWithCompletion:^(SKProductsRequest* request,SKProductsResponse* response)
{
if(response > 0 ) {
_product = [[IAPShare sharedHelper].iap.products objectAtIndex:0]; // We get this far
// ^ breakpoint on this line shows _product is now an SKProduct
completion(YES, nil); // but this never fires
}
else
{
completion(YES, error);
// ^ yes, error is defined in my code, I'm being lazy
}
}];
}
I'm calling it in a method like this with another completion block (and it never enters the block):
- (void)priceForBundleId:(NSString *)bundleId completion:(void(^)(NSString* price))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NO), ^{
[self initializeProduct:bundleId completion:^(BOOL finished, NSError *error) {
// Breakpoint placed here never catches
if (!error)
{
NSString* price = [RSStore priceAsStringForLocale:_product.priceLocale price:_product.price];
[defaults setValue:price forKey:#"bigBoxPrice"];
completion(price);
}
else
{
completion(nil);
}
}];
});
}

order of operations with addOperationWithBlock

I am facing some weird results with addOperationWithBlock.
My function looks something like this:
-(void) myFunction{
NSLog(#"VISITED");
..
for (NSDictionary *Obj in myObjects)
{
[operationQueue addOperationWithBlock:^(void){
MyObject* tmp = [self tediousFunction:Obj];
// threadTempObjects is member NSMutableArray
[self.threadTempObjects addObject:tmp];
NSLog(#"ADDED");
}];
}
[operationQueue addOperationWithBlock:^(void){
[self.myArray addObjectsFromArray:self.threadTempObjects];
for(myObject *myObj in self.myArray)
{
// MAIN_QUEUE
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[self updateUI:myObj];
}];
}
}];
[operationQueue addOperationWithBlock:^(void){
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[self filterResults];
}];
}];
}
My dictionary contains 4 values, and therefore the ADDED shows in the log 4 times.
BUT,
when I check inside the filterResults, I see that there are only 2 objects inside myArray. Meaning that the 4 times the operationQueue was called did not end before the filterResults operation was called (although it was added later!)
I thought that the operationQueue is serial and that I can count on it that when I add an operation it would be added right after the last operation.
So it is weird that only 2 operations are in the array in the aftermath.
What am I missing? Thanks
From what you shared as your initialisation code we can learn that operationQueue is not serial, meaning it will execute operations, and allocate thread up until the system set maximal thread count (same as with GCD).
This mean that operations added to operationQueue are running in parallel.
To run them serially set the maxConcurrentOperationCount to 1.
Try something like:
__block __weak id weakSelf = self;
[operationQueue setMaxConcurrentOperationCount:1];
for (NSDictionary *Obj in myObjects)
{
[operationQueue addOperationWithBlock:^{
MyObject* tmp = [weakSelf tediousFunction:Obj];
// threadTempObjects is member NSMutableArray
[weakSelf.threadTempObjects addObject:tmp];
NSLog(#"ADDED");
}];
}
[operationQueue addOperationWithBlock:^{
[weakSelf.myArray addObjectsFromArray:weakSelf.threadTempObjects];
for(myObject *myObj in weakSelf.myArray)
{
// MAIN_QUEUE
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf updateUI:myObj];
}];
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf filterResults];
}];
}
}];
But, this is equal (or even less efficient) to simply:
__block __weak id weakSelf = self;
[operationQueue addOperationWithBlock:^{
for (NSDictionary *Obj in myObjects) {
MyObject* tmp = [weakSelf tediousFunction:Obj];
// threadTempObjects is member NSMutableArray
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf updateUI:tmp];
}];
[weakSelf.myArray addObject:tmp];
NSLog(#"ADDED");
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf filterResults];
}];
}];

Resources