ARC #3

sanghoon Ahn·2021년 5월 25일
0

iOS

목록 보기
5/19

안녕하세요, dvHuni 입니다 !

ARC #1ARC #2에 이어 ARC에 대해 알아보는 마지막 포스트 입니다!

이제 ARC에 대한 내용을 마무리 지으러~
마지막으로 힘내서 가보자구요!

아자아자💪

  • 본 글은 swift docs를 참조하여 개인의 이해를 적은 글입니다. 🤓

Strong Reference Cycles for Closures

ARC #1ARC #2에서 알아본 Strong Reference Cycle은 class instance간 발생할 뿐만 아니라,

class instance인 Closure에 접근하거나, instance를 Capture하고 있는 Closure내부에서 발생합니다.

(Capture에 대한 자세한 내용은 Closure의 Captruing Values를 참고해주세요!)

Capture는 Closure 내부에서 instance의 property와 method에 접근하고자 할 때 발생하는데, 예를들어

self.somProperty 혹은 self.someMethod()와 같이 사용됩니다.

이 때, Closure에서는 self를 Capture하게되며 이는 Strong Reference Cycle을 발생시킵니다.

Closure에서 서로 다른 class instance를 reference하고 있는 것은 Closure와 각각의 instance에 대한 reference가 유지되고 있음을 의미하고, 각각의 instance에 대한 Strong Reference Cycle이 발생된다는 뜻으로 연결 됩니다.

코드를 통해 함께 보시죠 😎

class VendingMachine {
    let product: String
    let productCount: Int?

    lazy var remainCount: () -> String = {
        if let productCount = self.productCount {
            return "You Can Buy \(productCount) \(self.product)"
        } else {
            return "\(self.product) is Ready For Sale"
        }
    }

	init(product: String, productCount: Int? = nil) {
		self.product = product
		self.productCount = productCount
	}

	deinit {
		print("Vending Machine Deinitialized")
	}
}

var machine: VendingMachine? = VendingMachine(product: "Coke", productCount: 3)
// Optional인 이유는 Strong Reference Cycle의 존재를 증명하기 위해 정의하였습니다.
print(machine!.remainCount)
// "You Can Buy Coke 3"

위의 예시에서 VendingMachine class는 instance와 remainCount property간의 Strong Reference Cycle을 발생시킵니다.

remainCount Property는 Closure를 Strong Reference하고 있으며, Clouser는 내부에서 instacne의 product, productCount property를 Strong Reference하고 있습니다.

machine = nil
// reference를 해제하기 위해 nil 할당하지만, deinitalizer가 호출되지 않습니다.

조금 더 추가적인 설명을 하자면, machine에 nil을 할당하여 machine과 VendingMachine Instance간의 Strong Reference를 제거 하여도, VendingMachine Instance와 remainCount Property간의 Strong Reference가 남아 있기 때문에, VendingMachine의 deinitalizer가 호출 되지 않습니다!

Resolving Strong Reference Cycles for Closures

Class Instance와 Closuer의 Strong Reference Cycle을 제거하기 위해서는 Capture List를 사용합니다.

Capture List는 Closure의 내부에 정의하며, 내부에서 사용될 Reference Type을 정의 할 수 있습니다.

Defining a Capture List

그렇다면 Capture List를 정의해봅시다!

몇가지의 규칙과 함께 코드를 봐주시면 좋을 것 같습니다! 😄

  1. self를 포함한 각각의 Class Instance은 weak 혹은 unowned 키워드와 함께 정의합니다.
  2. 각 아이템들은 comma(,)로 구분하며, 대괄호([]) 안에 작성해야합니다.
  3. Closure가 parameter와 return type을 가지고 있다면, Capture List는 그 앞에 작성합니다.
  4. Capture List의 뒤에는 in 키워드를 작성합니다. paramter와 return type이 있다면, in 키워드는 가장 마지막에 위치합니다.
lazy var closure = { [unowned self, weak delegate = self.delegate] (index: Int, text: String) -> String in
	// closure body
}

lazy var closure1 = { [weak self] in
	self?.someMethod()
}

Weak and Unowned References

앞서 알아보았던 Weak, Unowned Reference는 모두 instance를 Strong Hold하지 않기 때문에 Strong Reference Cycle은 발생하지 않았습니다.

Capture List에서도 동일하게 instance를 Strong Hold하지 않기 때문에 Weak, Unowned Reference는 Strong Reference Cycle을 발생시키지 않습니다.

그렇지만 두 Referecne간의 차이점은 분명히 존재합니다.

Weak Reference로 정의된 instance는 언제든지 해당 instance가 nil이 될 수 있음을 의미합니다.

따라서 Closure의 내부에서 사용 될 때는 항상 optional type이 되게됩니다.

lazy var closure1 = { [weak self] in
	self?.someMethod()
}

반면 Unowned Reference로 정의된 instance는 항상 서로를 참조하며, deallocate 또한 동시에 이루어집니다.

즉, instance를 Strong Hold 하지 않지만, instance의 존재가 보장이 됩니다.

그러므로 Weak Reference와 다르게 Closure 내부에서 instance는 optional type이 아닙니다.

NOTE
If the captured reference will never become nil, it should always be captured as an unowned reference, rather than a weak reference.

capture된 Reference가 절대 nil이 되지 않는다면, Weak reference 보다 Unowned Reference를 사용해야 합니다.


위에서 보았던 예시를 다시 한번 함께 보시죠!

class VendingMachine {
	let product: String
	let productCount: Int?

	lazy var remainCount: () -> String = { [unowned self] in
	// self는 instance가 deinit 되지 않는 이상 nil이 될 수 없기 때문에 unowned를 사용하였습니다.
		if let productCount = self.productCount {
			return "You Can Buy \(productCount) \(self.product)"
		} else {
			return "\(self.product) is Ready For Sale"
		}
	}

	init(product: String, productCount: Int? = nil) {
		self.product = product
		self.productCount = productCount
	}

	deinit {
		print("Vending Machine Deinitialized")
	}
}

Closure부분을 보시면 self를 Unowned Reference로 사용합니다.

도식화를 하면 다음과 같고, 이렇게 되면 machine에 nil을 할당하면 VendingMachine의 instance에 대한 Strong Reference가 사라지게 되므로 deinitalizer가 호출됩니다.

machine = nil
// "Vending Machine Deinitialized"

Unowned Reference를 활용하여 Closure에서 발생하는 Strong Reference Cycle을 해결하는 방법까지 알아보았습니다.

위 상황에서 unowned 대신 weak 키워드를 사용하여 Weak Reference로 해결 하여도 무방합니다!!

대신, weak self는 self가 언제든지 nil이 될 수 있다는 것을 암시하기 때문에 항상 optional type으로 사용됩니다.

lazy var remainCount: () -> String = { [weak self] in
	if let productCount = self?.productCount {
		return "You Can Buy \(productCount) \(self?.product)"
	} else {
		return "\(self?.product) is Ready For Sale"
	}
}

// self가 Optional Type 이기 때문에 self의 property에 접근 할 때, ?을 붙여줍니다.

저는 항상 weak로 capture하여, otptional을 해제하는 식으로 아래와 같이 사용했었는데, 이번기회를 통해 unowned를 더 활용할 수 있을 것 같습니다!😄

lazy var example: () -> String = { [weak self] in
	guard let self = self else { return }
	self.someMethod()
}

마무리하며...

이로써 ARC의 마지막 포스트가 마무리 되었습니다!!

이렇게 긴 포스트는 처음이였네요 😅

특히 마지막 포스트가 저에게는 큰 도움이 된 것 같습니다 😚

이 글을 읽어주시는 분들에게도 도움이 조금이나마 됐으면 좋겠네요!!

이제 ARC가 뭐냐, strong, weak 그리고 unowned에 질문이 들어오면 살짝 덜 당황 할 수 있겠죠 ?! 😁

긴 포스트 함께해주셔서 감사합니다!

또한 틀린부분이 있다면 언제든지 지적 해주시면 정말 감사합니다!

읽어주셔서 감사합니다!! 🙇‍♂️

profile
hello, iOS

0개의 댓글