티스토리 뷰

반응형

안녕하세요 Pingu 입니다!🐧

 

이번 글에서는 Swift 공식 문서의 24번째 단원인 Automatic Reference Counting을 읽고 정리한 글을 쓰려고 합니다.

Apple Swift Document 24단원 - Automatic Reference Counting

Automatic Reference Counting

Swift는 ARC (Automatic Reference Counting)를 사용하여 앱의 메모리 사용량을 추적하고 관리합니다. ARC는 어떠한 클래스 인스턴스가 더 이상 필요하지 않을 때 클래스 인스턴스에 할당된 메모리를 자동으로 해제합니다.

 

ARC는 메모리를 관리하기 위해 코드 부분 간의 관계에 대한 추가적인 정보를 요구할 수도 있습니다. 이번 글에서는 이러한 상황을 알아보고 ARC를 사용하여 앱의 모든 메모리를 관리하는 방법을 알아보겠습니다.

 

ARC는 참조 타입인 클래스 인스턴스에만 적용됩니다. 구조체와 열거형은 값 타입이기 때문에 참조로 저장 및 전달되지 않으며 ARC도 적용되지 않습니다.


How ARC Works

클래스의 새 인스턴스를 만들 때마다 ARC는 해당 인스턴스에 대한 정보를 저장하기 위해 메모리를 할당합니다. 메모리에는 해당 인스턴스의 저장 프로퍼티, 인스턴스 타입에 대한 정보를 저장합니다. 그렇게 사용하다 인스턴스가 더 이상 필요하지 않은 경우 ARC는 해당 인스턴스에서 사용하는 메모리를 해제해야 합니다. 쓸모없는 데이터를 메모리에 남겨두는 것은 비효율적이기 때문이죠.

 

ARC가 인스턴스의 메모리를 해제하면 더 이상 해당 인스턴스의 프로퍼티에 접근하거나 인스턴스 메서드를 호출할 수 없습니다. 만약 아직 사용 중인 인스턴스를 ARC가 할당 해제한다면 앱이 충돌할 가능성이 있어 위험합니다.

 

따라서 ARC는 인스턴스가 언제까지 필요한지를 알 수 있도록 인스턴스를 참조하는 프로퍼티, 상수, 변수의 수를 추적합니다. 만약 하나 이상의 요소가 인스턴스를 참조하고 있다면 ARC는 할당을 취소하지 않습니다.

 

이를 가능하게 하기 위해 프로퍼티, 상수, 변수에 클래스 인스턴스를 할당할 때마다 강한 참조를 만듭니다. 지금까지 아무런 생각 없이 할당했던 코드들은 모두 강한 참조가 자동으로 적용되었던 코드들이었어요.


ARC in Action

그럼 이제 ARC가 어떻게 동작하는지 알아보도록 하겠습니다.

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

위의 코드는 간단하게 Person 클래스를 정의한 것입니다. 이 클래스에는 name이라는 저장 상수 프로퍼티가 정의되어 있습니다. 또한 생성자와 소멸자가 호출되면 어떠한 메시지를 출력해주도록 정의했어요.

 

var reference1: Person?
var reference2: Person?
var reference3: Person?

이러한 Person클래스를 할당한 3개의 변수를 선언합니다. 옵셔널로 선언했기 때문에 아직은 아무런 데이터가 없기 때문에 Person 인스턴스를 참조하지 않습니다.

 

reference1 = Person(name: "John Appleseed") // reference count : 1
// Prints "John Appleseed is being initialized"

reference2 = reference1 // reference count : 2
reference3 = reference1 // reference count : 3

위와 같이 변수에 Person 인스턴스를 할당해주면 생성자에 정의된 메시지가 출력되는 것을 볼 수 있어요. 이렇게 할당하게되면 reference1Person 인스턴스 사이엔 강한 참조가 추가됩니다. 따라서 ARCPerson 인스턴스가 메모리에 유지되고 해제되지 않도록 합니다. 동일한 인스턴스를 reference2,reference3에도 할당하면 해당 인스턴스에는 2 개의 강한 참조가 추가로 카운팅 됩니다. 즉 현재 카운트는 3인 것이죠.

 

reference1 = nil // reference count : 2
reference2 = nil // reference count : 1

이젠 할당 해제를 해보겠습니다. 두 개의 변수에 nil을 할당하여 강한 참조 두 개를 제거할 수 있습니다. 하지만 아직 1개의 강한 참조가 남아있기 때문에 소멸자에 의한 메시지는 출력되지 않습니다.

 

reference3 = nil // reference count : 0
// Prints "John Appleseed is being deinitialized"

위와 같이 마지막 변수에도 할당 해제를 해주면 참조 카운트가 0이되어 드디어 인스턴스가 메모리에서 해제됩니다. 해제됨에 따라 소멸자가 호출되어 메시지가 출력되는 것을 볼 수 있습니다.

 

즉 참조 카운트가 0이 되어야 메모리에서 해제될 수 있는 것이죠.


Strong Reference Cycles Between Class Instances

방금 섹션에서 본 예에서 ARC는 인스턴스를 생성하면 참조 카운트를 추적하고 더 이상 필요하지 않을 때 즉 참조 카운트가 0이 될 때 인스턴스를 메모리에서 해제했습니다.

 

하지만 이러한 강한 참조를 사용하면 문제가 발생할 수 있습니다. 이 문제는 인스턴스에서 다른 인스턴스에 대해 강한 참조를 가지고 있을 때 참조를 한 인스턴스가 해제됐음에도 불구하고 참조를 계속 유지하는 문제인데요, 이렇게 되면 필요하지 않은 데이터를 유지해야 하기 때문에 메모리 누수가 발생합니다. 이러한 문제는 weak 참조나 unowned 참조를 사용하여 해결할 수 있는데 우선 어떻게 문제가 발생하는지 부터 살펴보도록 하겠습니다.

 

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

위의 코드가 문제를 발생하는 코드입니다. 우선 Person,Apartment라는 두 개의 클래스를 정의합니다. Person 클래스의 apartment 프로퍼티는 Apartment 클래스 인스턴스 타입이고 Apartment 클래스의 tenant 프로퍼티는 Person 클래스 인스턴스 타입입니다. 물론 이 프로퍼티들은 옵셔널 타입으로 처음엔 nil이 할당 됩니다. 두 개의 클래스 모두 소멸자를 정의해 할당 해제될 때 메시지를 출력하도록 만들었습니다.

 

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed") // Person reference count : 1
unit4A = Apartment(unit: "4A") // Apartment reference count : 1

그럼 이제 위와 같이 클래스 인스턴스를 만들어보겠습니다. john 변수는 Person 클래스의 인스턴스를 할당하고 unit4A 변수는 Apratment 클래스의 인스턴스를 할당했습니다.

 

위의 그림은 현재 john,unit4A의 강한 참조를 나타낸 그림입니다. john 변수에는 Person 인스턴스에 대한 강한 참조가 있으며 unit4A 변수에는 Apartment 인스턴스에 대한 강한 참조가 있습니다.

 

john!.apartment = unit4A // Apartment reference count : 2
unit4A!.tenant = john // Person reference count : 2

그럼 이제 위의 코드와 같이 각자의 프로퍼티에 클래스 인스턴스를 할당 해보겠습니다. 즉 john 인스턴스의 apartment 프로퍼티에 unit4A를 할당하고 unit4A 인스턴스의 tenant 프로퍼티에 john를 할당했습니다.

 

현재 john,unit4A의 강한 참조를 나타낸 그림은 위와 같습니다. 여기서 두 클래스 인스턴스에 대한 참조 카운트는 2가 됩니다.

 

john = nil // Person reference count : 1
unit4A = nil // Apartment reference count : 1

그럼 이제 위의 코드와 같이 변수에 데이터를 할당 해제 하면, 강한 참조가 하나 사라지게 되므로 참조 카운트는 1이 됩니다.

 

즉 위의 그림과 같이 Person 인스턴스와 Apartment 인스턴스 사이의 강한 참조가 유지되어 필요 없는 데이터가 메모리에서 해제되지 않고 유지되는 것이죠.


Resolving Strong Reference Cycles Between Class Instances

방금과 같은 문제를 해결하기 위해 weak 참조와 unowned 참조(소유되지 않은 참조)를 제공합니다. 약한 참조와 unowned 참조를 사용하면 한 인스턴스가 강한 참조 주기를 만들지 않고도 참조할 수 있게 됩니다. 또한 약한 참조나 unowned 참조를 사용하면 참조 카운트를 변화시키지 않기 때문에 메모리 누수가 발생하지 않습니다.

Weak References

약한 참조는 참조하는 인스턴스를 강하게 유지하지 않는 참조로 ARC가 참조된 인스턴스를 처리하는 것에 관여하지 않습니다. 약한 참조를 사용하려면 weak 키워드를 사용해야 합니다. 약한 참조로 인스턴스를 참조하고 있더라도 해당 인스턴스는 할당 해제될 수 있고 약한 참조를 nil로 설정합니다. 이렇게 값이 변경될 수 있기 때문에 약한 참조는 항상 상수가 아닌 변수로 선언해야 하면 옵셔널 타입이어야 합니다. 그럼 아까 본 문제가 발생한 코드를 약한 참조로 해결해보겠습니다.

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

아까 본 클래스들과 동일한 두 개의 클래스입니다. 달라진 점이 있다면 Apartment 클래스의 tenant 프로퍼티에 Person 클래스 인스턴스를 약한 참조로 참조한 것이죠. 이렇게 정의하면 아까와 어떤 차이가 있을지 계속 알아보겠습니다.

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed") // Person reference count : 1
unit4A = Apartment(unit: "4A") // Apartment reference count : 1

john!.apartment = unit4A // Apartment reference count : 2
unit4A!.tenant = john // Person reference count : 1

아까와 동일하게 john, unit4A 변수를 만들고 클래스 인스턴스를 할당합니다. 그런 뒤 각자의 프로퍼티에 클래스 인스턴스를 할당합니다.

 

아까와 다르게 이번엔 약한 참조를 사용했기 때문에 위와 같이 구조가 변하게 됩니다.


여기서 만약 john 변수에 nil을 할당하면 어떻게 될까요?

john = nil // Person reference count : 0
// Apartment reference count : 1
// Prints "John Appleseed is being deinitialized"

결과는 위와 같습니다. Person 인스턴스에 대한 강한 참조가 없게 되고 그 말은 참조 카운트가 0이란 말이기 때문에 메모리에서 해제됩니다. 또한 john 변수가 메모리에서 할당 해제되며 Apartment를 참조하던 apartment 프로퍼티도 사라졌기 때문에 Apartment 인스턴스에 대한 강한 참조도 하나 사라지게 됩니다. 그럼 이제 unit4A에서 Apartment 인스턴스에 대한 강한 참조만 남게 되고 이는 아래 코드로 없애줄 수 있게 됩니다.

unit4A = nil // Apartment reference count : 0
// Prints "Apartment 4A is being deinitialized"

결과적으로 모든 참조가 해제되었고 참조 카운트도 0이 되어 메모리에서 해제됩니다.


Unowned References

약한 참조와 마찬가지로 unowned 참조도 인스턴스를 참조할 때 참조 카운트를 변경하지 않습니다. 하지만 약한 참조와 달리 unowned 참조는 참조한 어떠한 인스턴스의 수명이 동일하거나 더 길 때 사용하는데, 즉 쉽게 말해 메모리 해제되지 않는다는 확신을 가지고 있을 때 사용해야 합니다. 사용할 땐 unowned 키워드를 배치하여 사용할 수 있어요.

 

메모리 해제가 되지 않을 것이란 확신이 들 때 사용해야 하기 때문에 약한 참조와는 달리 unowned 참조는 항상 값을 가져야 합니다. 따라서 옵셔널 타입이 될 수 없죠. 만약 unowned 참조로 참조한 인스턴스가 할당 해제되었는데 접근하려고 하면 당연하게도 런타임 오류가 발생합니다.

 

그럼 unowned 참조는 어떤 경우에 사용해야 하는지 살펴볼까요?

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

위의 코드에서는 Customer, CreditCard 라는 두 개의 클래스를 정의했습니다. 두 클래스의 프로퍼티 중 하나는 서로의 인스턴스 타입을 가집니다. 여기서 주의 깊게 볼 점은 CreditCard 클래스의 customer 프로퍼티는 Customer 클래스의 인스턴스를 참조할 때 unowned 참조를 사용한 점입니다. 즉 CreditCard가 존재하는 한 Customer은 반드시 존재할 것이라는 확신이 있다는 것이죠.

 

var john: Customer?

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

이젠 이 두 개의 클래스를 사용해보겠습니다. 위의 코드로 john 변수에 Customer 클래스 인스턴스를 할당합니다. 그런 뒤 CreditCard 인스턴스를 johncard 프로퍼티에 할당합니다.

 

그렇게 하면 두 개의 인스턴스는 위의 구조를 가지게 됩니다. CreditCard 인스턴스가 Customer 인스턴스에 unowned 참조를 한 것을 주의 깊게 봐야 합니다.

 

john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"

그럼 이제 위의 코드로 johnnil로 설정해 Customer 인스턴스에 대한 강한 참조를 해제합니다.

그러면 Customer 인스턴스의 참조 카운트가 0이 되어 메모리에서 해제됩니다. 이렇게 되면 CreditCard 인스턴스에 대한 강한 참조도 사라지게 되어 모든 인스턴스의 메모리가 해제됩니다.


Unowned Optional References

클래스에 옵셔널 타입으로 unowned 참조를 표시할 수 있습니다. ARC 소유권 모델에서는 옵셔널 unowned 참조와 약한 참조 모두 동일한 컨텍스트에서 사용될 수 있습니다. 차이점은 옵셔널 unowned 참조를 사용할 때 항상 유효한 객체를 참조하거나 nil로 설정되어 있는지 확인해야 한다는 점이죠.

class Department {
    var name: String
    var courses: [Course]
    init(name: String) {
        self.name = name
        self.courses = []
    }
}

class Course {
    var name: String
    unowned var department: Department
    unowned var nextCourse: Course?
    init(name: String, in department: Department) {
        self.name = name
        self.department = department
        self.nextCourse = nil
    }
}

위의 코드는 옵셔널 unowned 참조를 이해하기 위해 정의한 Department, Course 클래스이다. Course 클래스의 nextCourse를 보면 옵셔널 unowned 참조로 Course 클래스의 인스턴스를 참조합니다. 학과와 코스라고 이해하고 위 클래스를 보면 학과 클래스에는 배워야 할 과목들에 대한 코스가 있습니다. 이는 반드시 존재하기 때문에 옵셔널로 정의되지 않았습니다. 코스 클래스를 보면 어떠한 코스는 반드시 어떠한 학과의 것이기 때문에 옵셔널 타입이 아니지만 다음 코스는 있을 수도 없을 수도 있기 때문에 옵셔널로 나타낸 것입니다.

 

let department = Department(name: "Horticulture")

let intro = Course(name: "Survey of Plants", in: department)
let intermediate = Course(name: "Growing Common Herbs", in: department)
let advanced = Course(name: "Caring for Tropical Plants", in: department)

intro.nextCourse = intermediate
intermediate.nextCourse = advanced
department.courses = [intro, intermediate, advanced]

위의 코드는 정의한 클래스들을 사용한 예입니다. 위의 코드에서는 1 개의 학과와 3개의 코스를 만들었습니다. unowned 참조는 ARC가 인스턴스를 할당 해제하는 것에 관여하지 않습니다. 따라서 옵셔널 unowned 참조가 nil 일 수 있다는 점을 제외하면 unowned 참조는 ARC에서 수행하는 것과 동일하게 작동합니다.

 

위에서 만든 인스턴스들의 구조는 위의 그림과 같습니다.


Unowned References and Implicitly Unwrapped Optional Properties

지금까지 알아본 약한 참조 및 unowned 참조는 강한 참조 주기로 인해 발생할 수 있는 문제점을 해결하기 위해 사용됐었습니다.


Person, Apartment 클래스로 알아본 예는 약한 참조로 해결했고 Customer, CreditCard 클래스는 unowned 참조로 해결했었습니다.

 

이번에 알아볼 예는 두 개의 프로퍼티에 항상 값이 있어야 하고 초기화가 완료되었을 땐 모두 nil이면 안 되는 경우입니다. 이번 예에서는 한 클래스의 unowned 프로퍼티를 다른 클래스의 강제 언래핑 옵셔널 프로퍼티와 결합하는 것이 유용합니다.

 

이렇게 하면 참조 주기를 피하면서 초기화가 완료되면 두 프로퍼티 모두에 직접 접근할 수 있습니다. 그럼 이러한 관계를 설정하는 방법을 알아도록 하겠습니다

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

위의 코드는 Country, City라는 두 개의 클래스를 정의하며 각 클래스는 다른 클래스의 인스턴스를 프로퍼티로 가집니다. 위의 코드를 해석해 보면 어떤 나라에는 반드시 수도가 있어야 하므로 Country 클래스의 capitalCity 프로퍼티는 강제 언래핑 옵셔널 타입으로 선언되었습니다. 따라서 Country 클래스의 생성자엔 City 생성자를 호출하여 capitalCity로 설정한 것을 볼 수 있고 City 생성자는 Country 인스턴스를 매개변수로 받는데 여기선 self로 사용되었다. 따라서 Country 인스턴스가 완전히 초기화될 때까지 City 클래스의 생성자엔 self를 전달할 수 없습니다.

 

이러한 부분을 처리하기 위해 CountrycapitalCity 프로퍼티를 강제 언래핑 옵셔널 타입으로 선언합니다. capitalCity 프로퍼티는 기본값이 nil이지만 래핑 해제할 필요 없이 접근할 수 있습니다.

기본적으로 capitalCitynil을 갖기 때문에 Country의 생성자에 있는 City 생성자는 self를 전달할 수 있게 됩니다.

 

var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa"

위의 예에서 강제 언래핑된 옵셔널을 사용한다는 것은 모든 2단계 클래스 이니셜 라이저 요구사항이 충족됨을 의미합니다. capitalCity 프로퍼티는 초기화 후엔 옵셔널이 아닌 값처럼 사용하고 접근할 수 있지만 강한 참조 주기를 피할 수 있습니다.


Strong Reference Cycles for Closures

지금까지 클래스 인스턴스의 프로퍼티가 서로에 대한 강한 참조를 보유할 때 강한 참조 주기가 생성되는 것을 살펴봤습니다. 또한 이 때문에 발생하는 문제를 약한 참조와 unowned 참조를 사용해서 해결해보았습니다.

 

클래스 인스턴스의 프로퍼티에 클로저를 할당하고 해당 클로저의 본문이 인스턴스를 캡처하는 경우에도 강한 참조주기가 발생할 수 있습니다. 이러한 캡처는 클로저의 본문이 self로 인스턴스 프로퍼티나 메서드를 호출할 때 발생할 수 있습니다. 이러한 경우 모두 강한 참조 주기를 생성합니다.

 

이렇게 생성되는 강한 참조 주기는 클로저도 참조 타입이기 때문인데요, 프로퍼티에 클로저를 할당하면 해당 클로저에 대한 참조를 할당하는 것이죠. 즉 지금까지 살펴본 클래스 인스턴스의 예와 동일한 문제입니다. 지금까지는 클래스 인스턴스끼리의 참조였지만 이젠 클래스 인스턴스와 클로저 사이의 참조라는 점만 다릅니다.

 

Swift에서는 클로저 캡처 목록으로 지금까지 살펴본 문제를 해결할 수 있습니다. 이러한 해결방법을 알아보기 전에 문제가 어떻게 발생하는지를 먼저 이해해보겠습니다.

class HTMLElement {

    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}

위의 코드로 self를 참조하는 클로저를 사용할 때 강한 참조가 어떻게 생성되는지 이해해보겠습니다. 우선 위의 코드에서는 HTMLElement 클래스가 정의되어 있습니다. 일반적인 name, text 라는 프로퍼티와 lazy로 선언된 asHTML 프로퍼티를 정의했습니다. asHTML 프로퍼티는 문자열을 반환하는 클로저가 할당되어 있습니다. 하지만 lazy 프로퍼티이기 때문에 직접 사용이 되기 전까지는 아무런 역할을 하지 않습니다.

 

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"

그럼 이제 위의 코드와 같이 클래스를 실제로 사용해볼까요? asHTML 프로퍼티는 lazy 프로퍼티이기 때문에 초기화가 완료된 인스턴스에서는 self에 접근할 수 있습니다.

 

이렇게 HTMLElement 클래스를 사용하면 클래스 인스턴스와 asHTML에 사용되는 클로저 사이에 강한 참조 주기를 만들게 되고 위와 같은 구조를 갖게 됩니다. 이때 클로저가 self를 여러 번 참조하더라도 하나의 강한 참조만 캡처합니다.

 

paragraph = nil

이제 위의 코드로 변수를 nil로 설정해보겠습니다. 하지만 클래스 인스턴스가 소멸되었을 때 호출되는 소멸자가 호출되지 않은 것을 볼 수 있습니다. 즉 HTMLElement 인스턴스가 할당 해제되지 않았다는 것이죠. 이는 메모리 누수가 발생한 것인데 이러한 문제를 어떻게 해결할 수 있을까요?


Resolving Strong Reference Cycles for Closures

클로저를 정의할 때 캡처 목록을 정의하여 클로저와 클래스 인스턴스 사이의 강한 참조 주기로 인해 발생하는 문제를 해결할 수 있습니다. 캡처 목록은 클로저 본문 내에서 하나 이상의 참조 타입을 캡처할 때 사용할 규칙을 정의합니다. 두 개의 클래스 인스턴스 간에 참조 주기와 마찬가지로 캡처된 각 참조를 강한 참조가 아닌 약한 참조, unowned 참고로 선언하면 됩니다.

 

Swift의 클로저 내에서 self의 멤버를 참조할 땐 항상 self를 붙여주는 것이 좋습니다. (someMethod() 대신 self.someMethod() 사용)


Defining a Capture List

그럼 캡처 목록을 한 번 만들어 보겠습니다.

lazy var someClosure = {
    [unowned self, weak delegate = self.delegate]
    (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}

위의 코드와 같이 캡처 목록을 만들어 줄 수 있습니다. 캡처 목록은 클로저의 매개변수 목록 앞에 작성해주면 됩니다.

 

lazy var someClosure = {
    [unowned self, weak delegate = self.delegate] in
    // closure body goes here
}

위의 코드처럼 캡처 목록만 존재할 경우 클로저가 컨텍스트에서 추론할 수 있기 때문에 캡처 목록을 클로저의 배치하고 in 키워드를 뒤에 써주면 됩니다.


Weak and Unowned References

클로저와 캡처하는 인스턴스가 항상 서로를 참조하고 항상 동시에 할당 해제된다면 unowned 참조로 정의해도 됩니다. 하지만 캡처된 참조가 어느 시점에서 nil이 될 가능성이 있는 경우 약한 참조로 정의해야 합니다. 약한 참조는 인스턴스가 할당 해제되면 자동으로 nil이 되기 때문에 항상 옵셔널 타입 이어야 합니다.

 

class HTMLElement {

    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}

그럼 이제 아까 문제가 발생한 코드를 수정해보겠습니다. 이번엔 HTMLElement 클래스의 asHTML 프로퍼티에 캡처 목록을 작성했어요. selfunowned로 참조하여 문제를 해결한 것입니다.

 

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"

따라서 위와 같이 클래스 인스턴스를 만들고 클로저를 사용하면 참조 구조는 다음과 같이 그릴 수 있습니다.

paragraph = nil
// Prints "p is being deinitialized"

클로저에 의한 self 캡처는 unowned 참조이기 때문에 강한 참조를 만들지 않습니다. 따라서 위의 코드와 같이 변수에 nil을 할당하면 클래스 인스턴스가 할당 해제되어 소멸자가 호출되고 메시지가 출력되게 됩니다.

 

감사합니다!

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함