Thursday, June 16, 2016

Objective C Retain cycles

In iOS memory management is based on the retain count. If an object's retain count is 0, OS releases it from memory.

If an object-A is having a weak reference to object-B means,



  @interface ObjectA : NSObject
     @property (nonatomicweak) ObjectB *objB;

  @end

  1. The retain count of object-B won't get incremented by 1. It will be the old value.
  2. If object-B gets released from memory, accessing object-B using Object-A gives (null) as it is having a weak reference.
If an object-A is having a strong reference to object-B means,



   @interface ObjectA : NSObject
     @property (nonatomicstrong) ObjectB *objB;

   @end


  1. The retain count of object-B get's incremented by 1. That means Object-A is telling OS that, I do need this object. 
  2. Object-A is taking the ownership of Object-B.

Let's take a live example and discuss in detail.

I have Owner and Pet classes.

Owner.h:



   #import "Pet.h"

   @interface Owner : NSObject

     @property (nonatomicNSString *name;
     @property (nonatomicstrongPet *pet;


   @end


Owner.m:



   #import "Owner.h"

   @implementation Owner

   - (void)dealloc{
    
        NSLog(@"Owner Deallocation!!!");
   }


   @end


Pet.h:



    @interface Pet : NSObject

      @property (nonatomicNSString *name;


    @end


Pet.m:



   #import "Pet.h"

   @implementation Pet

    - (void)dealloc{
    
         NSLog(@"Pet Deallocation!!!");

    }


Added dealloc method in both classes to know whether they are getting released from memory or not.

I ma taking a Owner object and a pet object and assigning pet object as Owner's pet.
If you look at the Owner.h file, Owner is having strong reference to Pet.
        



        Owner *owner = [[Owner allocinit];
        owner.name = @"Karunakar";
        
        Pet *pet = [[Pet allocinit];
        pet.name = @"Tiger";
        
        owner.pet = pet;
        
        NSLog(@"%@ is having %@ pet.",owner.name,owner.pet.name);  

      Karunakar is having Tiger pet.

        
So If I release pet object here,



  pet = nil;

  NSLog(@"%@ is having %@ pet.",owner.name,owner.pet.name);  

  Karunakar is having Tiger pet.



Pet object dealloc will not get called.

As owner is having strong reference to pet object, owner.pet is still there in memory.

But the moment we do,



  owner.pet = nil;

  Pet Deallocation!!!

  Karunakar is having (null) pet.



That means, The owner object has to release pet's strong reference to get it released from memory.

When we did,



   owner.pet = nil;


pet object's retain count becomes 0 as no one is owning that object, OS will release it from memory. That is why pet object's dealloc method got called.


This is the story of strong reference.

Now, What If we need owner object reference also in pet object and it is also a strong reference.

Pet.h:



    @class Owner//forward declaration of Owner class

    @interface Pet : NSObject

      @property (nonatomicNSString *name;
      @property (nonatomicstrongOwner *owner;


    @end


Let's give owner as strong reference to pet object.



        Owner *owner = [[Owner allocinit];
        owner.name = @"Karunakar";
        
        Pet *pet = [[Pet allocinit];
        pet.name = @"Tiger";
        
        owner.pet = pet;

        pet.owner = owner;


Now, both owner and pet are strongly referenced to each other. 

In pet.h,



  
@property (nonatomicweakOwner *owner;


Let's see what happens If I do,



   
pet = nil;
   owner = nil;
   NSLog(@"%@ is having %@ pet.",owner.name,pet.name);

   (null) is having (null) pet.



Though I made both objects nil, The dealloc methods of those methods are not getting called.
This because of strong reference between both objects. Which results in memory leak.

This is called retain cycle. Two objects should not have strong reference between them.

To avoid these retain cycle, One should have weak reference to other object. Which one is our call. Technically Parent should have strong reference to Child and Child should have weak reference to Parent.

In our case, Let's give pet's owner as weak reference and see whether there will be a memory leak.



    pet = nil;
    owner = nil;

    NSLog(@"%@ is having %@ pet.",owner.name,pet.name);

   Owner Deallocation!!!
   Pet Deallocation!!!

   (null) is having (null) pet.


Perfect! Both objects got deallocated.

The weak reference to at least one object will create a cyclic relationship between the objects, Instead of a retain cycle which causes memory leaks.

Instead of giving a weak reference, We can avoid the retain cycle in the above scenario by doing,



    pet.owner = nil;
    pet = nil;

    owner.pet = nil;
    owner = nil;

   Owner Deallocation!!!
   Pet Deallocation!!!

   (null) is having (null) pet.



That means, pet is releasing it's owner first after that pet is getting deallocated. Same like owner is releasing it's pet first and then getting deallocated. This is working fine, But what If we have tons of strong references. We need to check all those and make them nil before releasing an object. So it's quite meaningful to make a cyclic relationship by giving at least one reference as weak between objects.


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




No comments:

Post a Comment