Tuesday, July 19, 2016

Objective C Retain Cycles in Blocks

Before avoiding retain cycles in Blocks, I suggest you to have a look at my post Objective C Retain cycles to get an idea of retain cycles.
Blocks have become must use now a days for development as most of the native features are now available in Block structure. 

UIView animation
NSArray enumeration

etc....

Using strong references inside a Block will also form a Retain Cycle.

Let's take an example of User class which loads his/her profile pic.


User.h


    #import "CustomImageView.h"

    @interface User : UIView{
    
    }

    -(void)loadUserPic;


   @end



User.m


        #import "User.h"

        @interface User (){
        
            CustomImageView *imgView;
        }

        @end

        @implementation User

        -(void)dealloc{
        
           NSLog(@"User Dealloc !!");
         }

        -(void)loadUserPic{
        
            imgView = [[CustomImageView allocinit];
        
            [imgView setFrame:CGRectMake(50200250150)];
        
            [imgView setBackgroundColor:[UIColor redColor]];
        
            [self addSubview:imgView];    
        
            NSString *imgURL = @"https://www.planwallpaper.com/static/
                                                                       images/i-should-buy-a-boat.jpg";
                           

            [imgView downloadImageFromURL:
                       [NSURL URLWithString:imgURL] completion:
                                                                     ^(BOOL status, UIImage *image) {
            
                       dispatch_async(dispatch_get_main_queue(), ^{
                
                                NSLog(@"Image Loaded !!");
                                [self assignPic:image];
                
                         });
            
                }];
        
        }

         -(void)assignPic:(UIImage*)img{
        
                   [imgView setImage:img];
           }


         @end



    I am using a CustomImageView for user pic, which takes care of loading the pic from url.


    CustomImageView.h


          #import 

          @interface CustomImageView : UIImageView{
          
          }

          -(void)downloadImageFromURL:(NSURL*)url completion:
                                 (void(^)(BOOL status , UIImage *image))completionHandler;


         @end



      CustomImageView.m


            #import "CustomImageView.h"

            @implementation CustomImageView

            -(void)dealloc{
            
                 NSLog(@"CustomImageView Dealloc !!");
            }

            -(void)downloadImageFromURL:(NSURL*)url completion:
                                  (void(^)(BOOL status , UIImage *image))completionHandler{
            
                   dispatch_async(dispatch_get_global_queue(
                                              DISPATCH_QUEUE_PRIORITY_HIGH0), ^{
                
                            NSData *data = [NSData dataWithContentsOfURL:url];
                            UIImage *image = [UIImage imageWithData:data];
                            completionHandler(YES,image);
                
                    });
            
             }


             @end



        NSLog statements in the Dealloc methods in both the classes help us in knowing he memory deallocation of those objects.

        Let's take an User's view instance and try to load his/her pic.








              user = [[User allocinit];
              [user setFrame:CGRectMake(10100250200)];
              [self.view addSubview:user];    

              [user loadUserPic];


          The below piece of code in the User's loadUserPic method downloads and assigns user pic to the imgView.


              [imgView downloadImageFromURL:[NSURL URLWithString:imgURL] 
                                                  completion:^(BOOL status, UIImage *image) {
                  
                      dispatch_async(dispatch_get_main_queue(), ^{
                      
                             NSLog(@"Image Loaded !!");
                             [self assignPic:image];
                      
                      });
                  

              }];


          If you see, we have given strong reference of (self) User object inside the object which can create the so called Retain Cycle.


          You don't believe me? Let me prove it :)


          While the image is getting downloaded, remove the User instance.


                    [user removeFromSuperview];

                    user = nil;


            Both user and imgView instances will get deallocated only after the image gets downloaded.  If we see the logs,


             Image Loaded !!
             User Dealloc !!
             CustomImageView Dealloc !!

            The instances got deallocated only after the image getting downloaded.


            This is because of the strong reference of user object inside the image downloading block which is stopping both the user and imgView instances deallocation.

            To avoid these Retain Cycles, We need to go for weak references as shown below.


               __weak typeof(self) weakself = self;
                
                NSString *imgURL = @"https://www.planwallpaper.com/static/images
                                                                                      /i-should-buy-a-boat.jpg";

                [imgView downloadImageFromURL:[NSURL URLWithString:imgURL] 
                                                       completion:^(BOOL status, UIImage *image) {                                 
                    dispatch_async(dispatch_get_main_queue(), ^{
                        
                           NSLog(@"Image Loaded !!");
                           [weakself assignPic:image];
                        
                    });
                    

                }];


            We are taking the weak reference of User object and using inside the block, This makes cyclic relation between the Block and User object instead of a Retain cycle. 

            Now, Let's see whether it got avoided or not. let's do the same process we did earlier and see the logs.
            While the image is getting downloaded, remove the User instance.

             User Dealloc !!
             CustomImageView Dealloc !!
             Image Loaded !!


            Excellent !!!

            As required, both the instances got deallocated and they didn't wait for image download block completion.

            This may be a simple scenario, But these Retain cycles in Blocks may cause a severe harm to your application in some complex scenarios.


            Hope this post is useful. Feel free to comment incase of any queries.




            No comments:

            Post a Comment