Home ARC in Swift
Post
Cancel

ARC in Swift

One of the key factors for the success of any application is its performance. Effective Memory Management in an application is crucial for better performance of the application. iOS applications developed using Swift language rely on ARC for tracking and managing application memory usage. ARC uses reference counting to manage the application memory.

What is ARC?

ARC is a memory management tool which tracks and manages the application memory. It is a compiler-driven feature. It observes the reference count of an object of a class to manage and free the memory. So the question is what is reference count?

Reference count in simple language is the “number of pointers to any object”.

  public typealias Amount = Int

  public class Trip {
    public var city: String
    public var totalSpending: Amount
    
    public init(city: String, totalSpending: Int) {
        self.city = city
        self.totalSpending = totalSpending
    }
  }
  //Uses of Trip Class
  let goaTrip = Trip(city: "Panji", totalSpending: 50000) //init is one pointer to Object of
  print("I spent Rs \(goaTrip.totalSpending) in goa trip")
  
  var tripToBeach = [goaTrip] // One more pointer to goaTrip
  var recentTrip = goaTrip              // One more pointer to goaTrip
  print("I spent Rs \(recentTrip.totalSpending) during recentTrip")

Reference counting only applies to class instances, so struct & enum do not participate in reference counting.

How ARC Works?

As mentioned, ARC is a compiler-driven feature. It inserts retain and release statements during compilation.

  • Retain - increase the reference count.
  • Release - decrease the reference count.
#TechShorts
ARC is the compiler-driven feature and it injects retain/release statements in a code during compilation time.

How does the compiler decide when to insert retain/release?

Object life cycle in Swift begins with init and ends at last use.

ARC decide based on the above when to add retain and release statement.

  public typealias Amount = Int

  public class Trip {
    public var city: String
    public var totalSpending: Amount
    
    public init(city: String, totalSpending: Int) {
        self.city = city
        self.totalSpending = totalSpending
    }
  }

The object life cycle in Swift is different from the other language. In most of the other languages, it ends at the closing brace.

However, code should not be written based on this assumption as it might change with future compiler changes. For example, the following code is a good example of a team nightmare that suddenly a working code stopped.

  func explainWrongAssumptionUses() {
    //My trip List
    let goaTrip = Trip(city: "Panji", totalSpending: 50000)
    weak var weakRefrenceToGoaTrip = goaTrip
    
    if let strongRefrenceToGoaTrip =  weakRefrenceToGoaTrip {
        print("I used weak reference here: Goa Trip Rs \(strongRefrenceToGoaTrip.totalSpending)   
  during the recent trip")
    }
  }

In the above case, it is wrong to assume that goaWeakTrip will always be there. The last use of the object is weak var weakRefrenceToGoaTrip = goaTrip and the compiler will insert release after this statement.

So print statement here totally depends upon how the compiler works. if an Object’s life cycle in Swift begins with init and ends at last use. is strictly followed by the compiler then 50000 is the result. In the future, due to some drawbacks of this approach, Apple decided the scope based on the brace start and end then the result will be different.

ARC need your help.

ARC cannot detect or break **Reference cycle **. If object holds a strong reference to each other directly or indirectly in that case memory cannot be released of the objects.

In the Direct case, the Trip and TravelDiary objects are strongly held together. Therefore, releasing one object requires the release of the other object as well. We can break the reference cycle here by making reference weak in the Trip object for the TravelDiary.

In the Indirect case, the Person object is strongly held TravelDiary Object. TravelDiary strongly holds the trip object and further Trip holds the person object strongly. Therefore, releasing an Object in this scenario requires other two objects should be released. We can break the reference cycle here by keeping Person Object in the Trip object weak.

Weak & Unowned Reference Cycle

To break the reference cycle weak and unowned keywords can be used. Both reference type does not participate in reference counting.

  public class TravelDiary {
    fileprivate var travels: [Trip]
    public init() {
        self.travels = []
    }
    
    public func addTravel(_ travel: Trip) {
        self.travels.append(travel)
    }
  }

Weak reference can be used when you are not sure about the life cycle of the referred object. It will automatically be set to nil if the object reference count becomes zero. The only constraint with weak is must be optional.

  public class Trip {
    public var city: String
    public var totalSpending: Amount
    public weak var travelDiary: TravelDiary?
    
    
    public init(city: String, totalSpending: Int) {
        self.city = city
        self.totalSpending = totalSpending
    }
 }

Unowned reference can be used when you are sure about the life cycle of the referred object and it will not be deallocated. It can be used with an object which has an equal or greater life cycle. Unowned references can be declared as non-optional and faster access as compared to weak ones. The weak need to unwrap to access the value.

  public class Trip {
    public var city: String
    public var totalSpending: Amount
    public weak var travelDiary: TravelDiary?
    
    
    public init(city: String, totalSpending: Int) {
        self.city = city
        self.totalSpending = totalSpending
    }
 }
  let travelDiary = TravelDiary()

  //unowned uses
  let manaliTrip = Trip(city: "Manali", totalSpending: 40000, travelDiary: travelDiary)

  //weak uses
  let manaliTrip = Trip(city: "Manali", totalSpending: 40000)
  manaliTrip.travelDiary = travelDiary
#TechShorts
A weak reference is automatically set to nil if no strong reference remains and a weak reference is always optional. Unowned references will refer to deallocated memory if no strong reference remains, so the application can crash. Unowned is always non-optional.

Bonus.

There are safe ways to access the weak reference to avoid unexpected results or fatal exceptions (force unwrap) at run time.

  public class Trip {
    public var city: String
    public var totalSpending: Amount
    public weak var travelDiary: TravelDiary?
    
    public var isExistInTravelDiary: Bool {
        if let travelDiary = self.travelDiary {
            return travelDiary.travels.filter{
                $0.city == self.city
            }.first != nil
        }
        return false
    }
    
    public init(city: String, totalSpending: Int) {
        self.city = city
        self.totalSpending = totalSpending
    }
  }

   public class TravelDiary {
    fileprivate var travels: [Trip]
    public init() {
        self.travels = []
    }
    
    public func addTravel(_ travel: Trip) {
        self.travels.append(travel)
    }
  }

Option 1: withExtendedLifetime

  func prepareTravelDiary() {
    //Manali Trip
    let travelDiary = TravelDiary()
    let manaliTrip = Trip(city: "Manali", totalSpending: 40000)
    manaliTrip.travelDiary = travelDiary
    
    travelDiary.addTravel(manaliTrip)
    
    withExtendedLifetime(travelDiary) {
        let existString = manaliTrip.isExistInTravelDiary ? "exist": "not exist"
        print("Manali Travel \(existString) in Travel Diary")
    }
  }

Option 2: Strong Reference Capture

  func prepareStrongTravelDiary() {
    //Manali Trip
    let travelDiary = TravelDiary()
    let manaliTrip = Trip(city: "Manali", totalSpending: 40000)
    manaliTrip.travelDiary = travelDiary
    
    travelDiary.addTravel(manaliTrip)
    processTrip(manaliTrip)
  }

  func processTrip(_ trip: Trip) {
    //somewhere in code in other method
    if let travelDiary = trip.travelDiary {
        let existString = trip.isExistInTravelDiary ? "exist" : "not exist"
        print("\(trip.city) Travel \(existString) in Travel Diary")
    }
  }
This post is licensed under CC BY 4.0 by the author.

Distribute iOS Beta Build (Scheme & Configurations)

Game - Connect Balls

Comments powered by Disqus.