Study

🍎 WWDC : ARC in Swift

_yunie 2024. 8. 2. 20:07

ARC

수명주기가 다한 객체의 할당을 해제하여 메모리를 관리하는 방식

- 참조 횟수를 추적하여 객체의 수명을 결정함

- Swift 컴파일러에 의해 수행됨

 

retain | 참조가 시작되는 시점에 삽입

release | 참조가 끝나는 시점에 삽입

 

컴파일 시점에 처리됨

RC가 0으로 떨어지면 객체가 메모리에서 해제됨

class Travler {
	var name: String
	var destingation: String?
}

func test() {
	// 객체 생명주기 시작
	let t1 = Traveler(name: "Lily") // Referece Start(1) 
	// init => RC(t1) = 1
	
	// retain
	// => RC(t1, t2) = 2
	let t2 = t1 // End(1), Start(2) 
	// release : t1의 참조 끝
	// => RC(t2) = 1
	
	t2.destination = "Big Sur" // End(2)
	// release : t2 사용 끝 
	// => RC = 0
	
	print("Done")
	// => 메모리 해제

 

 

RC

Swift의 기본참조는 강한 참조(Strong)

 

weak / unowned

- RC를 추가하지 않아 순환 참조를 방지하기 위해 주로 사용됨

 

순환 참조 예시

class Traveler {
    var name: String
    var account: Account?
    func printSummary() {
        if let account = account {
            print("\\(name) has \\(account.points) points")
        }
    }
}

class Account {
    var traveler: Traveler
    var points: Int
}

func test() {
    let traveler = Traveler(name: "Lily") 
	  // T = 1
	  
    let account = Account(traveler: traveler, points: 1000)
    // T = 2, A = 1
    // -> Traveler 참조 중 
    
    traveler.account = account
    // A = 2
    // -> Account 참조 중
    // 여기가 account에 대한 마지막 사용 시점
    // => A = 1
    
    traveler.printSummary()
    // Traveler에 대한 마지막 사용 시점
    // => T = 1
}

// Traveler -> Account / Account -> Traveler의 참조 카운트가 하나씩 남게됨
// Traveler가 메모리에서 해제되면 Account의 RC도 줄어서 해제 가능

 

위 코드는 결과적으로 각 객체들이 절대 메모리에서 해제되지 않음

이는 곧 메모리 누수로 이어짐

이러한 문제를 방지하기 위해 사용하는 것이 unowned / weak !

 

unowned

- 메모리 해제 후 접근 시 런타임 에러 발생

 

weak 

- 메모리 해제 후 참조 시 nil 반환

 

Weak 활용 예시

class Traveler {
    var name: String
    var account: Account?
}

class Account {
    weak var traveler: Traveler?
    var points: Int
    func printSummary() {
        print("\\(traveler!.name) has \\(points) points")
        // Force Unwrapping : 에러 발생 
    }
    
    func printSummary2() {
	    if let traveler = traveler {
		    print("\\(traveler.name) has \\(points) points")
        // Silent Bug : 프로그램이 원하는대로 동작하지않는데, 문제 발생 X 
      }
    }
}

func test() {
    let traveler = Traveler(name: "Lily")
    // T = 1
    // Traveler 더 사용되지 않음 => T = 0
    
    let account = Account(traveler: traveler, points: 1000)
    // A = 1 
    // T는 weak 상태로 +1 (X)
    
    traveler.account = account
    // A = 2
    
    account.printSummary()
    // Traveler == nil
    // => A = 1
    // printSummary() 결과가 출력이 될 수도 있는데, 이 때 출력이 되면 관찰된 객체 수명 
    
    // Travler가 참조 해제된 상태에서 접근하여 사용할 수 있게 사용 가능
    withExtendedLifetime(travelre) {
	    account.printSummarty()
	  }
	  
	  // defer: 함수의 가장 마지막에 실행
	  // - 생명주기 임의로 늘려버리기 
	  defer { withExtendedLifetime(travelre) {} }
	  account.printSummary
}

 

withExtendedLifetime()

객체의 생명주기를 확장하고 싶을 때 사용해줄 수 있음

생명주기와 관련된 버그를 쉽게 해결할 수 있어보이지만 해당 주기에 대한 책임을 오로지 개발자가 지게 됨

weak에 대해 매번 적용해줘야 하며, 적용하지 않을 경우 버그가 일어날 가능성이 있음

그러므로 통제불능 시 해당 메서드가 반복적으로 등장해 유지보수 비용이 상승할 수 있음 

'Study' 카테고리의 다른 글

📚 Concurrency Docs 내 일부  (0) 2024.09.11
🍎 WWDC21 : Explore structured concurrency in Swift  (0) 2024.09.07
👀 Continuation 알아보기  (0) 2024.08.24
🍎 WWDC21: Meet async/await in Swift  (0) 2024.08.20
📚 ARC Docs 정리  (0) 2024.08.06