Ver3.0 ๐ iOS ๋ฉ๋ชจ๋ฆฌ ๋์์ Retain Cycle ๋ฐฉ์ง๋ฅผ ์ํ ์ค์ ๋๋ฒ๊น ๊ฐ์ด๋

๐ iOS ๋ฉ๋ชจ๋ฆฌ ๋์์ Retain Cycle ๋ฐฉ์ง๋ฅผ ์ํ ์ค์ ๋๋ฒ๊น ๊ฐ์ด๋
์ฑ์ด ๋๋ ค์ง๊ณ ํฌ๋์๊ฐ ๋๋ ์ง์ง ์ด์ , ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ๋ชจ๋ ๊ฒ ๐ก
์๋ ! ์ค๋์ iOS ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ , ๊ทธ๋ฌ๋ฉด์๋ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๊ณจ์น ์ํํ๋ ์ฃผ์ ๋ฅผ ๋ค๋ค๋ณผ ๊ฑฐ์ผ. ๋ฐ๋ก **๋ฉ๋ชจ๋ฆฌ ๋์(Memory Leak)**์ **Retain Cycle**์ด์ผ. ๐
ํน์ ์ด๋ฐ ๊ฒฝํ ์์ด? ์ฑ์ ์ด์ฌํ ๊ฐ๋ฐํ๋๋ฐ, ์ฌ์ฉํ๋ค ๋ณด๋ฉด ์ ์ ๋๋ ค์ง๊ณ , ์ฌ์ง์ด ๊ฐ์๊ธฐ ๊บผ์ง๊ธฐ๋ ํ๊ณ ... ๊ทธ๋ฐ๋ฐ ์ฝ๋๋ฅผ ์๋ฌด๋ฆฌ ๋ด๋ ๋ญ๊ฐ ๋ฌธ์ ์ธ์ง ๋ชจ๋ฅด๊ฒ ๊ณ . ๋ฐ๋ก ์ด๊ฒ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋๋ฌธ์ผ ๊ฐ๋ฅ์ฑ์ด ํฌ๊ฑฐ๋ ! ๐คฏ
์ด ๊ธ์์๋ ์น๊ตฌ์๊ฒ ์ค๋ช ํ๋ฏ์ด ์ฝ๊ณ ์ฌ๋ฏธ์๊ฒ, ํ์ง๋ง ์ ํํ๊ณ ์ค์ ์์ ๋ฐ๋ก ์จ๋จน์ ์ ์๋ ๋ด์ฉ๋ค๋ก ๊ฐ๋ ์ฑ์๋ณผ๊ฒ. ์ฌ๋ฅ๋ท์์ ํ๋ก๊ทธ๋๋ฐ ๊ด๋ จ ์ง์์ ๊ณต์ ํ๋ ๋งํผ, ์ค๋ฌด์์ ์ ๋ง ๋์์ด ๋๋ ํ๋ค์ ์๋์์ด ํ์ด๋์ ๊ฑฐ์ผ! ๐
๐ฏ ๋ฉ๋ชจ๋ฆฌ ๋์, ๋๋์ฒด ๋ญ๊ธธ๋?
๋จผ์ ๊ธฐ๋ณธ๋ถํฐ ํ์คํ ์ง๊ณ ๋์ด๊ฐ์. ๋ฉ๋ชจ๋ฆฌ ๋์๋ ๋ญ๊น? ๊ฐ๋จํ ๋งํ๋ฉด **๋ ์ด์ ์ฌ์ฉํ์ง ์๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ์ง ๋ชปํด์ ๊ณ์ ์์ด๋ ํ์**์ด์ผ. ๐๏ธ
์์ํด๋ด. ๋์ ๋ฐฉ์ ๋ฌผ๊ฑด์ ๊ณ์ ์ฌ๋๋ฐ, ์ฐ์ง ์๋ ๋ฌผ๊ฑด์ ์ ๋ ๋ฒ๋ฆฌ์ง ์๋๋ค๋ฉด? ๊ฒฐ๊ตญ ๋ฐฉ์ด ๊ฝ ์ฐจ์ ์ํํ ๊ณต๊ฐ์ด ์์ด์ง๊ฒ ์ง? ๋ฉ๋ชจ๋ฆฌ๋ ๋๊ฐ์! ๐ฆ๐ฆ๐ฆ
๐ก ํต์ฌ ๊ฐ๋
iOS๋ **ARC(Automatic Reference Counting)** ์์คํ ์ ์ฌ์ฉํด์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํด. ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ๋ ์นด์ดํธ๋ฅผ ์ธ๊ณ , ์นด์ดํธ๊ฐ 0์ด ๋๋ฉด ์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๋ ๊ฑฐ์ง. ๊ทผ๋ฐ ์ด ์นด์ดํธ๊ฐ 0์ด ์ ๋๋ ์ํฉ์ด ์๊ธฐ๋ฉด? ๋ฐ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๋ ๊ฑฐ์ผ! ๐ฑ
๐ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๋ ์ฃผ์ ์์ธ๋ค
1. Retain Cycle (์ํ ์ฐธ์กฐ) ๐
๊ฐ์ฅ ํํ๊ณ ๊ณจ์น ์ํ ์์ธ์ด์ผ. ๋ ๊ฐ์ฒด๊ฐ ์๋ก๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ๋ฉด์ ์ฐธ์กฐ ์นด์ดํธ๊ฐ ์ ๋ 0์ด ๋ ์ ์๋ ์ํฉ์ ๋ง๋๋ ๊ฑฐ์ง.
2. ํด๋ก์ ์บก์ฒ ๐ธ
Swift์ ํด๋ก์ ๋ ์ฃผ๋ณ ๋ณ์๋ฅผ ์บก์ฒํ๋๋ฐ, ์ด๋ self๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ๋ฉด ๋ฌธ์ ๊ฐ ์๊ฒจ.
3. Delegate ํจํด์ ์๋ชป๋ ์ฌ์ฉ ๐ค
Delegate๋ฅผ strong์ผ๋ก ์ ์ธํ๋ฉด ์ํ ์ฐธ์กฐ๊ฐ ๋ฐ์ํ ์ ์์ด.
4. Notification Observer ๋ฏธ์ ๊ฑฐ ๐ข
NotificationCenter์ ๋ฑ๋กํ ์ต์ ๋ฒ๋ฅผ ์ ๊ฑฐํ์ง ์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ๊ฐ ํด์ ๋์ง ์์.
5. Timer์ ๊ฐํ ์ฐธ์กฐ โฐ
Timer๋ target์ ๊ฐํ๊ฒ ์ฐธ์กฐํ๊ธฐ ๋๋ฌธ์ ์ฃผ์๊ฐ ํ์ํด.
๐ Retain Cycle, ๋์ ์ ์ฒด๋ฅผ ๋ฐํ๋ผ!
Retain Cycle์ ์ ๋ง ๊ต๋ฌํด. ์ฝ๋๋ฅผ ๋ด์๋ ๋ฌธ์ ๊ฐ ์์ด ๋ณด์ด๋๋ฐ, ์คํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ณ์ ์์ด๊ฑฐ๋ . ์ค์ ์์ ๋ก ์ดํด๋ณด์! ๐
โ ๏ธ ๋ฌธ์ ๊ฐ ์๋ ์ฝ๋ ์์
class Person {
var name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) ์ด๊ธฐํ๋จ")
}
deinit {
print("\(name) ํด์ ๋จ")
}
}
class Apartment {
var unit: String
var tenant: Person? // ๐จ ๋ฌธ์ ! strong ์ฐธ์กฐ
init(unit: String) {
self.unit = unit
print("์ํํธ \(unit) ์ด๊ธฐํ๋จ")
}
deinit {
print("์ํํธ \(unit) ํด์ ๋จ")
}
}
var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
// ์ด์ nil๋ก ์ค์ ํด๋...
john = nil
unit4A = nil
// deinit์ด ํธ์ถ๋์ง ์์! ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ์! ๐ฑ
์ ์ฝ๋์ ๋ฌธ์ ์ ์ด ๋ณด์ฌ? Person๊ณผ Apartment๊ฐ ์๋ก๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ๊ณ ์์ด. ์ด๋ ๊ฒ ๋๋ฉด ์ฐธ์กฐ ์นด์ดํธ๊ฐ ์ ๋ 0์ด ๋ ์ ์์ด์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํด์ ๋์ง ์๋ ๊ฑฐ์ผ! ๐ฅ
โ ํด๊ฒฐ ๋ฐฉ๋ฒ 1: weak ์ฐธ์กฐ ์ฌ์ฉ
class Apartment {
var unit: String
weak var tenant: Person? // โจ weak์ผ๋ก ๋ณ๊ฒฝ!
init(unit: String) {
self.unit = unit
}
deinit {
print("์ํํธ \(unit) ํด์ ๋จ")
}
}
// ์ด์ ์ ์์ ์ผ๋ก ํด์ ๋จ!
var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil // "John ํด์ ๋จ" ์ถ๋ ฅ
unit4A = nil // "์ํํธ 4A ํด์ ๋จ" ์ถ๋ ฅ
๐ค weak vs unowned, ์ธ์ ๋ญ ์จ์ผ ํ ๊น?
์ด ์ง๋ฌธ, ์ ๋ง ๋ง์ด ๋ฐ์! ๋ ๋ค ์ํ ์ฐธ์กฐ๋ฅผ ๋ฐฉ์งํ๋ ๋๊ตฌ์ธ๋ฐ, ์ฌ์ฉ ์์ ์ด ๋ฌ๋ผ. ๐ฏ
| ๊ตฌ๋ถ | weak | unowned |
|---|---|---|
| ์ฐธ์กฐ ๋์ | nil์ด ๋ ์ ์๋ ๊ฒฝ์ฐ | ์ ๋ nil์ด ๋์ง ์๋ ๊ฒฝ์ฐ |
| ํ์ | ํญ์ Optional | Non-optional ๊ฐ๋ฅ |
| ์์ ์ฑ | ์์ ํจ (nil ์ฒดํฌ ๊ฐ๋ฅ) | ์ํํ ์ ์์ (nil ์ ๊ทผ์ ํฌ๋์) |
| ์ฌ์ฉ ์ | Delegate, ๋ถ๋ชจ-์์ ๊ด๊ณ | ์์์ด ๋ถ๋ชจ๋ณด๋ค ๋จผ์ ํด์ ๋ ์ ์๋ ๊ฒฝ์ฐ |
weak ์ฌ์ฉ ์์ :
class ViewController: UIViewController {
weak var delegate: MyDelegate? // delegate๋ nil์ด ๋ ์ ์์
func someMethod() {
delegate?.didSomething() // Optional chaining ์ฌ์ฉ
}
}
unowned ์ฌ์ฉ ์์ :
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
}
class CreditCard {
let number: UInt64
unowned let customer: Customer // Customer๊ฐ ๋จผ์ ํด์ ๋ ์ผ ์์
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
}
๐ ์ค์ ํ
ํ์คํ์ง ์์ผ๋ฉด weak์ ์ฌ์ฉํ๋ ๊ฒ ์์ ํด! unowned๋ 100% ํ์ ํ ๋๋ง ์จ. ์๋ชป ์ฌ์ฉํ๋ฉด ๋ฐํ์ ํฌ๋์๊ฐ ๋๊ฑฐ๋ . ๐ฐ
๐ญ ํด๋ก์ ์ ๋ฉ๋ชจ๋ฆฌ ๋์์ ์ํํ ๊ด๊ณ
ํด๋ก์ ๋ ์ ๋ง ํธ๋ฆฌํ๊ณ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ด์ผ. ๊ทผ๋ฐ ์กฐ์ฌํ์ง ์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์์ ์ฃผ๋ฒ์ด ๋ ์ ์์ด! ํนํ ํด๋ก์ ์์์ self๋ฅผ ์ฌ์ฉํ ๋ ๋ง์ด์ผ. ๐ช
โ ๏ธ ๋ฌธ์ ๊ฐ ์๋ ํด๋ก์ ์ฝ๋
class NetworkManager {
var name: String
var completionHandler: (() -> Void)?
init(name: String) {
self.name = name
}
func fetchData() {
completionHandler = {
print(self.name) // ๐จ ๊ฐํ ์ํ ์ฐธ์กฐ ๋ฐ์!
// self๊ฐ completionHandler๋ฅผ ์์ ํ๊ณ
// completionHandler๊ฐ self๋ฅผ ์บก์ฒํจ
}
}
deinit {
print("\(name) ํด์ ๋จ")
}
}
var manager: NetworkManager? = NetworkManager(name: "API Manager")
manager?.fetchData()
manager = nil // deinit์ด ํธ์ถ๋์ง ์์!
์ ์ฝ๋์์ ๋ญ๊ฐ ๋ฌธ์ ์ผ๊น? NetworkManager๊ฐ completionHandler๋ฅผ ์์ ํ๊ณ , completionHandler๊ฐ ๋ค์ self(NetworkManager)๋ฅผ ๊ฐํ๊ฒ ์บก์ฒํ๋ฉด์ ์ํ ์ฐธ์กฐ๊ฐ ๋ฐ์ํ ๊ฑฐ์ผ! ๐
โ ํด๊ฒฐ ๋ฐฉ๋ฒ: Capture List ์ฌ์ฉ
class NetworkManager {
var name: String
var completionHandler: (() -> Void)?
init(name: String) {
self.name = name
}
func fetchData() {
// ๋ฐฉ๋ฒ 1: weak self ์ฌ์ฉ
completionHandler = { [weak self] in
guard let self = self else { return }
print(self.name)
}
// ๋ฐฉ๋ฒ 2: unowned self ์ฌ์ฉ (self๊ฐ ์ ๋ nil์ด ์ ๋ ๊ฒฝ์ฐ)
completionHandler = { [unowned self] in
print(self.name)
}
}
deinit {
print("\(name) ํด์ ๋จ")
}
}
var manager: NetworkManager? = NetworkManager(name: "API Manager")
manager?.fetchData()
manager = nil // "API Manager ํด์ ๋จ" ์ ์ ์ถ๋ ฅ! โจ
๐ Capture List ์์ฑ ํจํด
์ค์ ์์ ์์ฃผ ์ฌ์ฉํ๋ ํจํด๋ค์ ์ ๋ฆฌํด๋ดค์ด. ์ด๊ฒ๋ง ๊ธฐ์ตํ๋ฉด ํด๋ก์ ๊ด๋ จ ๋ฉ๋ชจ๋ฆฌ ๋์๋ ๊ฑฐ์ ์๋ฐฉํ ์ ์์ด! ๐ช
ํจํด 1: weak self + guard let (๊ฐ์ฅ ์์ )
someAsyncFunction { [weak self] result in
guard let self = self else { return }
self.updateUI(with: result)
}
ํจํด 2: weak self + Optional chaining (๊ฐ๋จํ ์์ )
someAsyncFunction { [weak self] result in
self?.updateUI(with: result)
}
ํจํด 3: ์ฌ๋ฌ ๊ฐ์ฒด ์บก์ฒ
someAsyncFunction { [weak self, weak delegate = self.delegate] result in
guard let self = self, let delegate = delegate else { return }
delegate.didComplete(result)
self.cleanup()
}
ํจํด 4: unowned self (ํ์คํ ๊ฒฝ์ฐ๋ง)
// ํด๋ก์ ๊ฐ self๋ณด๋ค ๋จผ์ ํด์ ๋๋ ๊ฒ์ด ํ์คํ ๋
someAsyncFunction { [unowned self] result in
self.updateUI(with: result)
}
๐ฏ ์ค๋ฌด ๊ฟํ
์ฌ๋ฅ๋ท์์ iOS ๊ฐ๋ฐ ๊ด๋ จ ๋ฉํ ๋ง์ ํ๋ค ๋ณด๋ฉด, ๋ง์ ๋ถ๋ค์ด "์ธ์ weak์ ์จ์ผ ํ๋์?"๋ผ๊ณ ๋ฌผ์ด๋ด. ๋ด ๋ต์ ๊ฐ๋จํด: ํด๋ก์ ์์์ self๋ฅผ ์ฌ์ฉํ๋ฉด ์ผ๋จ [weak self]๋ฅผ ์ฐ๋ ์ต๊ด์ ๋ค์ฌ! ๋์ค์ ์ต์ ํํ๋ ๊ฑด ์ฝ์ง๋ง, ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ฐพ๋ ๊ฑด ์ ๋ง ์ด๋ ต๊ฑฐ๋ . ๐
๐ ๏ธ ์ค์ ๋๋ฒ๊น ๋๊ตฌ ์์ ์ ๋ณต
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ฐพ์๋ด๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! Xcode๋ ์ ๋ง ๊ฐ๋ ฅํ ๋๋ฒ๊น ๋๊ตฌ๋ค์ ์ ๊ณตํด. ํ๋์ฉ ๋ง์คํฐํด๋ณด์! ๐ง
๐ฏ 1. Instruments - Leaks ๋๊ตฌ
Instruments๋ iOS ๊ฐ๋ฐ์์ ์ต๊ณ ์ ์น๊ตฌ์ผ. ํนํ Leaks ํ ํ๋ฆฟ์ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ฐพ๋ ๋ฐ ์ต์ ํ๋์ด ์์ด! ๐ช
โ Instruments ์ฌ์ฉ ๋ฐฉ๋ฒ
Step 1: Instruments ์คํ
Xcode ๋ฉ๋ด โ Product โ Profile (โ + I)
๋๋ ๋น๋ ํ Product โ Profile ์ ํ
Step 2: Leaks ํ
ํ๋ฆฟ ์ ํ
ํ
ํ๋ฆฟ ๋ชฉ๋ก์์ "Leaks" ์ ํ
์๋์ผ๋ก Allocations์ Leaks ํธ๋์ด ์ถ๊ฐ๋จ
Step 3: ์ฑ ์คํ ๋ฐ ๋ชจ๋ํฐ๋ง
๋นจ๊ฐ ๋
นํ ๋ฒํผ ํด๋ฆญ
์ฑ์์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ์์ฌ๋๋ ๊ธฐ๋ฅ ์คํ
Leaks ํธ๋์์ ๋นจ๊ฐ ๋ง๋๊ฐ ๋ํ๋๋ฉด ๋์ ๋ฐ์!
Step 4: ๋์ ๋ถ์
๋นจ๊ฐ ๋ง๋ ํด๋ฆญ โ ์์ธ ์ ๋ณด ํ์ธ
Call Tree์์ ๋์๊ฐ ๋ฐ์ํ ์ฝ๋ ์์น ์ถ์
Backtrace๋ฅผ ๋ฐ๋ผ๊ฐ๋ฉฐ ์์ธ ํ์
Instruments ์ค์ ํ๋ค: ๐ก
ํ 1: Call Tree ์ค์ ์ต์ ํ
// Call Tree ํ๋จ์ ์ต์
์ฒดํฌ
โ๏ธ Separate by Thread (์ค๋ ๋๋ณ ๋ถ๋ฆฌ)
โ๏ธ Invert Call Tree (์ญ์ ์ ๋ ฌ)
โ๏ธ Hide System Libraries (์์คํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์จ๊น)
// ์ด๋ ๊ฒ ํ๋ฉด ๋ด ์ฝ๋๋ง ๊น๋ํ๊ฒ ๋ณผ ์ ์์ด!
ํ 2: ์ค๋ ์ท ๋น๊ต
ํน์ ์์ ์ ๋ฉ๋ชจ๋ฆฌ ์ํ๋ฅผ ์ค๋ ์ท์ผ๋ก ์ ์ฅํ๊ณ ๋น๊ตํ ์ ์์ด. ์๋ฅผ ๋ค์ด ํ๋ฉด ์ง์ ์ ํ๋ฅผ ๋น๊ตํ๋ฉด ์ด๋์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ฆ๊ฐํ๋์ง ํ๋์ ๋ณด์ฌ! ๐ธ
๐ 2. Memory Graph Debugger
Xcode์ ๋ด์ฅ๋ ์ด ๋๊ตฌ๋ ์ ๋ง ์ง๊ด์ ์ด๊ณ ์ฌ์ฉํ๊ธฐ ์ฌ์. ์ค์๊ฐ์ผ๋ก ๊ฐ์ฒด ๊ฐ์ ์ฐธ์กฐ ๊ด๊ณ๋ฅผ ์๊ฐ์ ์ผ๋ก ๋ณด์ฌ์ค! ๐บ๏ธ
โ Memory Graph Debugger ์ฌ์ฉ๋ฒ
1. ๋๋ฒ๊ทธ ๋ชจ๋๋ก ์ฑ ์คํ
ํ์์ฒ๋ผ ์ฑ์ ์คํ (โ + R)
2. Memory Graph ๋ฒํผ ํด๋ฆญ
๋๋ฒ๊ทธ ๋ฐ์์ ์ธ ๊ฐ์ ๋๊ทธ๋ผ๋ฏธ๊ฐ ์ฐ๊ฒฐ๋ ์์ด์ฝ ํด๋ฆญ
(๋๋ Debug โ View Memory Graph Hierarchy)
3. ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ํ ๋ถ์
์ผ์ชฝ: ๋ชจ๋ ๊ฐ์ฒด ๋ชฉ๋ก
์ค์: ์ ํํ ๊ฐ์ฒด์ ์ฐธ์กฐ ๊ด๊ณ ๊ทธ๋ํ
์ค๋ฅธ์ชฝ: ๊ฐ์ฒด ์์ธ ์ ๋ณด
4. ๋์ ํ์ง
ํ๋จ์ ๋ณด๋ผ์ ๋๋ํ(!) ์์ด์ฝ์ด ๋ํ๋๋ฉด ๋์ ์์ฌ
ํด๋น ๊ฐ์ฒด๋ฅผ ํด๋ฆญํ๋ฉด ์ํ ์ฐธ์กฐ ๊ด๊ณ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์
Memory Graph์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๋ค: ๐จ
๊ธฐ๋ฅ 1: ํํฐ๋ง
// ํ๋จ ํํฐ ๋ฐ์์ ๋ค์ํ ์ต์
์ฌ์ฉ
- Show only leaked blocks (๋์๋ ๋ธ๋ก๋ง ํ์)
- Show only content from workspace (๋ด ์ฝ๋๋ง ํ์)
- ํด๋์ค ์ด๋ฆ์ผ๋ก ๊ฒ์
// ์: "ViewController"๋ก ๊ฒ์ํ๋ฉด
// ๋ชจ๋ ViewController ์ธ์คํด์ค๋ฅผ ํ๋์!
๊ธฐ๋ฅ 2: ์ฐธ์กฐ ๊ฒฝ๋ก ์ถ์
๊ฐ์ฒด๋ฅผ ์ ํํ๋ฉด ํ์ดํ๋ก ์ฐธ์กฐ ๊ด๊ณ๊ฐ ํ์๋ผ. ๊ฐํ ์ฐธ์กฐ๋ ์ค์ , ์ฝํ ์ฐธ์กฐ๋ ์ ์ ์ผ๋ก ๊ตฌ๋ถ๋๋๊น ์ํ ์ฐธ์กฐ๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์์ด! ๐ฏ
๐ 3. Allocations ๋๊ตฌ
Allocations๋ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ํจํด์ ๋ถ์ํ๋ ๋ฐ ์ต๊ณ ์ผ. ์ด๋ค ๊ฐ์ฒด๊ฐ ์ผ๋ง๋ ๋ง์ด ์์ฑ๋๊ณ ์๋์ง ์ค์๊ฐ์ผ๋ก ๋ณผ ์ ์์ด! ๐
๐ก Allocations ํ์ฉ ์๋๋ฆฌ์ค
์๋๋ฆฌ์ค 1: ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ ์ถ์
1. Instruments์์ Allocations ํ
ํ๋ฆฟ ์ ํ
2. Mark Generation ๋ฒํผ ํด๋ฆญ (๊น๋ฐ ์์ด์ฝ)
3. ์ฑ์์ ํน์ ์์
์ํ (์: ํ๋ฉด ์ ํ)
4. ๋ค์ Mark Generation ํด๋ฆญ
5. Generation ๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ฐจ์ด ๋ถ์
// ํ๋ฉด์ ์ด์๋ค ๋ซ์๋๋ฐ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ณ์ ์ฆ๊ฐ?
// โ ํด๋น ํ๋ฉด์์ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ์!
์๋๋ฆฌ์ค 2: ํน์ ํด๋์ค ์ถ์
1. Allocations ๋ชฉ๋ก์์ ํด๋์ค ์ด๋ฆ ๊ฒ์
2. ํด๋น ํด๋์ค์ ์ธ์คํด์ค ๊ฐ์ ํ์ธ
3. ์๊ฐ์ ๋ฐ๋ฅธ ์ฆ๊ฐ ํจํด ๊ด์ฐฐ
// ์: MyViewController๊ฐ 10๊ฐ์ฉ ๊ณ์ ์ฆ๊ฐ?
// โ ํ๋ฉด์ ๋ซ์๋ ํด์ ๋์ง ์๋ ๋ฌธ์ !
๐ฌ 4. Debug Memory Graph ๋ฐํ์ ์ด์
Xcode 13๋ถํฐ๋ ๋ฐํ์์ ๋ฉ๋ชจ๋ฆฌ ์ด์๋ฅผ ์๋์ผ๋ก ๊ฐ์งํด์ค! ์ ๋ง ํธ๋ฆฌํ ๊ธฐ๋ฅ์ด์ผ. ๐
โ ๋ฐํ์ ์ด์ ํ์ฑํ
// Scheme ์ค์
1. Product โ Scheme โ Edit Scheme
2. Run โ Diagnostics ํญ
3. ๋ค์ ์ต์
๋ค ์ฒดํฌ:
โ๏ธ Malloc Stack Logging
- Live Allocations Only ์ ํ (์ฑ๋ฅ ์ต์ ํ)
โ๏ธ Malloc Scribble
- ํด์ ๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ๊ฐ์ง
โ๏ธ Zombie Objects
- ํด์ ๋ ๊ฐ์ฒด ์ ๊ทผ ๊ฐ์ง
โ๏ธ Address Sanitizer (์ ํ์ )
- ๋ฉ๋ชจ๋ฆฌ ์ค๋ฅ ์์ธ ๊ฐ์ง (์๋ ๋๋ ค์ง ์ฃผ์)
์ด๋ ๊ฒ ์ค์ ํ๋ฉด ์ฑ ์คํ ์ค์ ๋ฉ๋ชจ๋ฆฌ ์ด์๊ฐ ๋ฐ์ํ๋ฉด Xcode๊ฐ ์๋์ผ๋ก ์๋ ค์ค! ๋ณด๋ผ์ ๊ฒฝ๊ณ ์์ด์ฝ๊ณผ ํจ๊ป ์์ธํ ์ค๋ช ์ด ๋ํ๋๋๊น ๋ฐ๋ก ์์ ํ ์ ์์ด. ๐
๐ ์ค์ ํจํด๋ณ ํด๊ฒฐ ๋ฐฉ๋ฒ
์ด์ ์ค๋ฌด์์ ์์ฃผ ๋ง์ฃผ์น๋ ํจํด๋ณ๋ก ๊ตฌ์ฒด์ ์ธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์๋ณด์! ์ด ๋ถ๋ถ๋ง ์ ๋๋ก ์ตํ๋ ๋๋ถ๋ถ์ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์๋ฐฉํ ์ ์์ด. ๐ช
๐ฑ ํจํด 1: Delegate ํจํด
Delegate๋ iOS ๊ฐ๋ฐ์์ ์ ๋ง ๋ง์ด ์ฐ์ด๋ ํจํด์ด์ผ. ๊ทผ๋ฐ ์๋ชป ์ฌ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์์ ์ฃผ๋ฒ์ด ๋์ง! ๐
โ ๏ธ ์๋ชป๋ Delegate ๊ตฌํ
protocol DataManagerDelegate {
func didReceiveData(_ data: String)
}
class DataManager {
var delegate: DataManagerDelegate? // ๐จ strong ์ฐธ์กฐ!
func fetchData() {
// ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ...
delegate?.didReceiveData("Some data")
}
}
class ViewController: UIViewController, DataManagerDelegate {
var dataManager: DataManager?
override func viewDidLoad() {
super.viewDidLoad()
dataManager = DataManager()
dataManager?.delegate = self // ์ํ ์ฐธ์กฐ ๋ฐ์!
}
func didReceiveData(_ data: String) {
print(data)
}
}
โ ์ฌ๋ฐ๋ฅธ Delegate ๊ตฌํ
protocol DataManagerDelegate: AnyObject { // โจ AnyObject ํ๋กํ ์ฝ
func didReceiveData(_ data: String)
}
class DataManager {
weak var delegate: DataManagerDelegate? // โจ weak ์ฐธ์กฐ!
func fetchData() {
delegate?.didReceiveData("Some data")
}
}
class ViewController: UIViewController, DataManagerDelegate {
var dataManager: DataManager?
override func viewDidLoad() {
super.viewDidLoad()
dataManager = DataManager()
dataManager?.delegate = self // ์ด์ ์์ !
}
func didReceiveData(_ data: String) {
print(data)
}
deinit {
print("ViewController ํด์ ๋จ") // ์ ์ ์ถ๋ ฅ!
}
}
ํต์ฌ ํฌ์ธํธ: ๐ฏ
1. ํ๋กํ ์ฝ์ AnyObject ์ถ๊ฐ (ํด๋์ค ์ ์ฉ ํ๋กํ ์ฝ)
2. delegate ํ๋กํผํฐ๋ฅผ weak์ผ๋ก ์ ์ธ
3. ์ด๋ ๊ฒ ํ๋ฉด ์ํ ์ฐธ์กฐ๊ฐ ๋ฐ์ํ์ง ์์!
โฐ ํจํด 2: Timer ์ฌ์ฉ
Timer๋ ์ ๋ง ์กฐ์ฌํด์ผ ํด. target์ ๊ฐํ๊ฒ ์ฐธ์กฐํ๊ธฐ ๋๋ฌธ์ ์ ๋๋ก ์ฒ๋ฆฌํ์ง ์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํด! โ ๏ธ
โ ๏ธ ๋ฌธ์ ๊ฐ ์๋ Timer ์ฝ๋
class TimerViewController: UIViewController {
var timer: Timer?
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
// ๐จ self๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐ!
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateCount),
userInfo: nil,
repeats: true
)
}
@objc func updateCount() {
count += 1
print("Count: \(count)")
}
deinit {
print("TimerViewController ํด์ ๋จ")
// ์ด ์ฝ๋๋ ์คํ๋์ง ์์!
}
}
โ ํด๊ฒฐ ๋ฐฉ๋ฒ 1: Timer ๋ช ์์ ํด์
class TimerViewController: UIViewController {
var timer: Timer?
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
func startTimer() {
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateCount),
userInfo: nil,
repeats: true
)
}
@objc func updateCount() {
count += 1
print("Count: \(count)")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
timer?.invalidate() // โจ Timer ํด์ !
timer = nil
}
deinit {
timer?.invalidate() // โจ ์์ ์ฅ์น
print("TimerViewController ํด์ ๋จ") // ์ ์ ์ถ๋ ฅ!
}
}
โ ํด๊ฒฐ ๋ฐฉ๋ฒ 2: ํด๋ก์ ๊ธฐ๋ฐ Timer (iOS 10+)
class TimerViewController: UIViewController {
var timer: Timer?
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
func startTimer() {
// โจ ํด๋ก์ ๊ธฐ๋ฐ Timer + weak self
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.count += 1
print("Count: \(self.count)")
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
timer?.invalidate()
timer = nil
}
deinit {
print("TimerViewController ํด์ ๋จ") // ์ ์ ์ถ๋ ฅ!
}
}
๐ข ํจํด 3: NotificationCenter
NotificationCenter๋ ์กฐ์ฌํด์ผ ํ ๋ถ๋ถ์ด ์์ด. iOS 9 ์ดํ๋ก๋ ์๋์ผ๋ก ์ต์ ๋ฒ๋ฅผ ์ ๊ฑฐํด์ฃผ์ง๋ง, ๋ช ์์ ์ผ๋ก ์ ๊ฑฐํ๋ ๊ฒ ์ข์ ์ต๊ด์ด์ผ! ๐ฎ
โ ์์ ํ NotificationCenter ์ฌ์ฉ
class NotificationViewController: UIViewController {
var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setupNotification()
}
func setupNotification() {
// ๋ฐฉ๋ฒ 1: ํด๋ก์ ๊ธฐ๋ฐ (๊ถ์ฅ)
observer = NotificationCenter.default.addObserver(
forName: .dataUpdated,
object: nil,
queue: .main
) { [weak self] notification in
guard let self = self else { return }
self.handleDataUpdate(notification)
}
// ๋ฐฉ๋ฒ 2: Selector ๊ธฐ๋ฐ
NotificationCenter.default.addObserver(
self,
selector: #selector(handleDataUpdate),
name: .dataUpdated,
object: nil
)
}
@objc func handleDataUpdate(_ notification: Notification) {
print("๋ฐ์ดํฐ ์
๋ฐ์ดํธ๋จ")
}
deinit {
// โจ ๋ช
์์ ์ผ๋ก ์ต์ ๋ฒ ์ ๊ฑฐ
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
NotificationCenter.default.removeObserver(self)
print("NotificationViewController ํด์ ๋จ")
}
}
extension Notification.Name {
static let dataUpdated = Notification.Name("dataUpdated")
}
๐ฌ ํจํด 4: GCD์ ๋น๋๊ธฐ ์์
๋น๋๊ธฐ ์์ ์์๋ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ ์ ์์ด. ํนํ ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์์ self๋ฅผ ์บก์ฒํ ๋ ์กฐ์ฌํด์ผ ํด! ๐
โ ์์ ํ ๋น๋๊ธฐ ์์ ์ฒ๋ฆฌ
class AsyncViewController: UIViewController {
var data: [String] = []
func fetchDataFromServer() {
// โจ weak self ์ฌ์ฉ
DispatchQueue.global().async { [weak self] in
// ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์
let result = self?.performHeavyTask()
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.data = result ?? []
self.updateUI()
}
}
}
func performHeavyTask() -> [String] {
// ๋ฌด๊ฑฐ์ด ์์
์๋ฎฌ๋ ์ด์
Thread.sleep(forTimeInterval: 2.0)
return ["Data 1", "Data 2", "Data 3"]
}
func updateUI() {
print("UI ์
๋ฐ์ดํธ: \(data)")
}
deinit {
print("AsyncViewController ํด์ ๋จ")
}
}
// ์ฌ์ฉ ์์
let vc = AsyncViewController()
vc.fetchDataFromServer()
// vc๊ฐ ํด์ ๋์ด๋ ํฌ๋์ ์์ด ์์ ํ๊ฒ ์ฒ๋ฆฌ๋จ!
DispatchQueue ์ฌ์ฉ ์ ์ฃผ์์ฌํญ: โก
1. ์ค์ฒฉ๋ ํด๋ก์ ์์ self ์บก์ฒ
// โ ์๋ชป๋ ์
DispatchQueue.global().async {
// ์ฌ๊ธฐ์ self๋ฅผ ๊ฐํ๊ฒ ์บก์ฒ
let data = self.processData()
DispatchQueue.main.async {
// ์ฌ๊ธฐ์๋ self๋ฅผ ๊ฐํ๊ฒ ์บก์ฒ (์ด์ค ์บก์ฒ!)
self.updateUI(with: data)
}
}
// โ
์ฌ๋ฐ๋ฅธ ์
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
let data = self.processData()
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.updateUI(with: data)
}
}
2. DispatchWorkItem ํ์ฉ
class WorkItemViewController: UIViewController {
var workItem: DispatchWorkItem?
func scheduleWork() {
// โจ DispatchWorkItem์ผ๋ก ์์
๊ด๋ฆฌ
workItem = DispatchWorkItem { [weak self] in
guard let self = self else { return }
self.performTask()
}
if let workItem = workItem {
DispatchQueue.global().asyncAfter(
deadline: .now() + 2.0,
execute: workItem
)
}
}
func performTask() {
print("์์
์ํ")
}
deinit {
workItem?.cancel() // โจ ์์
์ทจ์
print("WorkItemViewController ํด์ ๋จ")
}
}
๐จ ๊ณ ๊ธ ๊ธฐ๋ฒ: Custom ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
์ด์ ์ข ๋ ๊ณ ๊ธ ๊ธฐ๋ฒ๋ค์ ์์๋ณด์! ์ค๋ฌด์์ ๋ณต์กํ ์ํฉ์ ๋ง๋ฌ์ ๋ ์ ์ฉํ ํ ํฌ๋๋ค์ด์ผ. ๐
๐ง 1. ์ปค์คํ Weak Wrapper
๋๋ก๋ weak ์ฐธ์กฐ๋ฅผ ์ปฌ๋ ์ ์ ์ ์ฅํด์ผ ํ ๋๊ฐ ์์ด. ๊ทผ๋ฐ Swift์ Array๋ Dictionary๋ weak ์ฐธ์กฐ๋ฅผ ์ง์ ์ ์ฅํ ์ ์๊ฑฐ๋ . ์ด๋ด ๋ Weak Wrapper๋ฅผ ๋ง๋ค๋ฉด ๋ผ! ๐ฆ
โ Weak Wrapper ๊ตฌํ
// Weak ์ฐธ์กฐ๋ฅผ ๊ฐ์ธ๋ ๋ํผ ํด๋์ค
class WeakRef<T: AnyObject> {
weak var value: T?
init(_ value: T) {
self.value = value
}
}
// ์ฌ์ฉ ์์
class Observer {
let name: String
init(name: String) {
self.name = name
}
func notify() {
print("\(name) notified")
}
deinit {
print("\(name) ํด์ ๋จ")
}
}
class ObserverManager {
private var observers: [WeakRef<Observer>] = []
func addObserver(_ observer: Observer) {
// โจ weak ์ฐธ์กฐ๋ก ์ ์ฅ
observers.append(WeakRef(observer))
}
func notifyAll() {
// nil์ด ์๋ ์ต์ ๋ฒ๋ค๋ง ํํฐ๋ง
observers = observers.filter { $0.value != nil }
observers.forEach { weakRef in
weakRef.value?.notify()
}
}
func removeNilObservers() {
// โจ ํด์ ๋ ์ต์ ๋ฒ ์ ๋ฆฌ
observers = observers.filter { $0.value != nil }
}
}
// ํ
์คํธ
let manager = ObserverManager()
var observer1: Observer? = Observer(name: "Observer 1")
var observer2: Observer? = Observer(name: "Observer 2")
manager.addObserver(observer1!)
manager.addObserver(observer2!)
manager.notifyAll() // ๋ ๋ค ์๋ฆผ ๋ฐ์
observer1 = nil // "Observer 1 ํด์ ๋จ" ์ถ๋ ฅ
manager.notifyAll() // Observer 2๋ง ์๋ฆผ ๋ฐ์
๐ฏ 2. Associated Objects์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
Objective-C ๋ฐํ์์ Associated Objects๋ฅผ ์ฌ์ฉํ ๋๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ์ ๊ฒฝ ์จ์ผ ํด. ํนํ extension์์ ํ๋กํผํฐ๋ฅผ ์ถ๊ฐํ ๋ ๋ง์ด ์ฐ์ด๊ฑฐ๋ ! ๐
โ ์์ ํ Associated Objects ์ฌ์ฉ
import ObjectiveC
// Associated Object Key
private var closureKey: UInt8 = 0
extension UIButton {
// โจ ํด๋ก์ ๋ฅผ Associated Object๋ก ์ ์ฅ
var tapHandler: (() -> Void)? {
get {
return objc_getAssociatedObject(self, &closureKey) as? (() -> Void)
}
set {
// OBJC_ASSOCIATION_COPY_NONATOMIC ์ฌ์ฉ
// ํด๋ก์ ๋ฅผ ๋ณต์ฌํด์ ์ ์ฅ (์์ )
objc_setAssociatedObject(
self,
&closureKey,
newValue,
.OBJC_ASSOCIATION_COPY_NONATOMIC
)
if newValue != nil {
addTarget(self, action: #selector(handleTap), for: .touchUpInside)
}
}
}
@objc private func handleTap() {
tapHandler?()
}
}
// ์ฌ์ฉ ์์
class ButtonViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
// โจ weak self ์ฌ์ฉ
button.tapHandler = { [weak self] in
guard let self = self else { return }
self.handleButtonTap()
}
}
func handleButtonTap() {
print("๋ฒํผ ํญ๋จ")
}
deinit {
print("ButtonViewController ํด์ ๋จ")
}
}
Associated Object Policy ์ข ๋ฅ: ๐
| Policy | ์ค๋ช | ์ฌ์ฉ ์์ |
|---|---|---|
| ASSIGN | weak ์ฐธ์กฐ (unsafe) | ์ฌ์ฉ ๋น๊ถ์ฅ |
| RETAIN_NONATOMIC | strong ์ฐธ์กฐ | ์ผ๋ฐ ๊ฐ์ฒด |
| COPY_NONATOMIC | ๋ณต์ฌ ํ ์ ์ฅ | ํด๋ก์ , ๋ฌธ์์ด |
| RETAIN | strong ์ฐธ์กฐ (atomic) | ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ |
๐งฉ 3. ๋ฉ๋ชจ๋ฆฌ ์๋ฐ ์ํฉ ๋์
iOS๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋ถ์กฑํ๋ฉด ์ฑ์ ๊ฒฝ๊ณ ๋ฅผ ๋ณด๋ด. ์ด๋ ์ ์ ํ ๋์ํ์ง ์์ผ๋ฉด ์ฑ์ด ๊ฐ์ ์ข ๋ฃ๋ ์ ์์ด! ๐ฐ
โ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
class ImageCacheManager {
static let shared = ImageCacheManager()
private var imageCache: [String: UIImage] = [:]
private let cacheQueue = DispatchQueue(label: "com.app.imageCache")
private init() {
// โจ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ๊ฐ์ง
NotificationCenter.default.addObserver(
self,
selector: #selector(handleMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
@objc private func handleMemoryWarning() {
print("โ ๏ธ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์์ !")
clearCache()
}
func cacheImage(_ image: UIImage, forKey key: String) {
cacheQueue.async { [weak self] in
self?.imageCache[key] = image
}
}
func getImage(forKey key: String) -> UIImage? {
return cacheQueue.sync {
return imageCache[key]
}
}
func clearCache() {
cacheQueue.async { [weak self] in
self?.imageCache.removeAll()
print("โ
์บ์ ์ ๋ฆฌ ์๋ฃ")
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
// ViewController์์ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
class MyViewController: UIViewController {
var largeDataArray: [Data] = []
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
print("โ ๏ธ ViewController ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ")
// โจ ๋ถํ์ํ ๋ฐ์ดํฐ ์ ๋ฆฌ
largeDataArray.removeAll()
// โจ ์ด๋ฏธ์ง ์บ์ ์ ๋ฆฌ
ImageCacheManager.shared.clearCache()
// โจ ๋ทฐ๊ฐ ํ๋ฉด์ ์์ผ๋ฉด ํด์
if !isViewLoaded || view.window == nil {
view = nil
}
}
}
๐ 4. Autorelease Pool ํ์ฉ
๋๋์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฃจํ์์๋ Autorelease Pool์ ์ฌ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ด! ๐
โ Autorelease Pool ์ฌ์ฉ
class DataProcessor {
func processLargeDataSet() {
let dataCount = 100000
// โ ๋นํจ์จ์ ์ธ ๋ฐฉ๋ฒ
func inefficientWay() {
var results: [String] = []
for i in 0..<dataCount {
let data = createLargeObject(index: i)
results.append(data.description)
}
// ๋ชจ๋ ์์ ๊ฐ์ฒด๊ฐ ๋ฃจํ ๋๊น์ง ๋ฉ๋ชจ๋ฆฌ์ ๋จ์
}
// โ
ํจ์จ์ ์ธ ๋ฐฉ๋ฒ
func efficientWay() {
var results: [String] = []
for i in 0..<dataCount {
autoreleasepool {
// โจ ๊ฐ ๋ฐ๋ณต๋ง๋ค ์์ ๊ฐ์ฒด ํด์
let data = createLargeObject(index: i)
results.append(data.description)
// autoreleasepool ๋ธ๋ก์ด ๋๋๋ฉด ์ฆ์ ํด์ ๋จ
}
}
}
efficientWay()
}
func createLargeObject(index: Int) -> NSData {
// ํฐ ๋ฐ์ดํฐ ๊ฐ์ฒด ์์ฑ
let size = 1024 * 1024 // 1MB
return NSData(bytes: malloc(size), length: size)
}
}
// ์ค์ ์์ : ์ด๋ฏธ์ง ์ฒ๋ฆฌ
class ImageProcessor {
func processImages(_ imageURLs: [URL]) {
for url in imageURLs {
autoreleasepool {
// โจ ๊ฐ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ํ ์ฆ์ ๋ฉ๋ชจ๋ฆฌ ํด์
if let image = UIImage(contentsOfFile: url.path) {
let processedImage = applyFilters(to: image)
saveImage(processedImage, to: url)
}
// ์ฌ๊ธฐ์ ์์ ์ด๋ฏธ์ง ๊ฐ์ฒด๋ค์ด ํด์ ๋จ
}
}
}
func applyFilters(to image: UIImage) -> UIImage {
// ํํฐ ์ ์ฉ ๋ก์ง
return image
}
func saveImage(_ image: UIImage, to url: URL) {
// ์ด๋ฏธ์ง ์ ์ฅ ๋ก์ง
}
}
๐ก Autorelease Pool ์ฌ์ฉ ์์
1. ๋๋์ ์์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฃจํ
2. ์ด๋ฏธ์ง๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ฒ๋ผ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ ์์
3. ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ Cocoa ๊ฐ์ฒด๋ฅผ ๋ค๋ฃฐ ๋
4. ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๊ธ๊ฒฉํ ์ฆ๊ฐํ๋ ์ํฉ
๐งช ์ค์ ํ ์คํธ ์๋๋ฆฌ์ค
์ด๋ก ๊ณผ ์ฝ๋๋ง ๋ณด๋ฉด ์ดํดํ ๊ฒ ๊ฐ์๋ฐ, ๋ง์ ์ค์ ์์๋ ํท๊ฐ๋ฆด ์ ์์ด. ์ค์ ์ํฉ๋ณ๋ก ์ด๋ป๊ฒ ํ ์คํธํ๊ณ ๊ฒ์ฆํ๋์ง ์์๋ณด์! ๐ฌ
๐ฑ ์๋๋ฆฌ์ค 1: ํ๋ฉด ์ ํ ํ ์คํธ
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ด๋ฉด์๋ ์ค์ํ ํ ์คํธ์ผ. ํ๋ฉด์ ์ด๊ณ ๋ซ์ ๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ๋๋ก ํด์ ๋๋์ง ํ์ธํ๋ ๊ฑฐ์ง! ๐ช
โ ํ๋ฉด ์ ํ ํ ์คํธ ๋ฐฉ๋ฒ
class TestViewController: UIViewController {
// โจ ํ
์คํธ์ฉ ์๋ณ์
let identifier = UUID().uuidString
override func viewDidLoad() {
super.viewDidLoad()
print("[\(identifier)] viewDidLoad")
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("[\(identifier)] viewWillAppear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("[\(identifier)] viewDidDisappear")
}
deinit {
// โจ ์ด๊ฒ ์ถ๋ ฅ๋๋ฉด ์ ์!
print("[\(identifier)] deinit - ๋ฉ๋ชจ๋ฆฌ ํด์ ๋จ โ
")
}
func setupUI() {
view.backgroundColor = .white
let closeButton = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
closeButton.setTitle("๋ซ๊ธฐ", for: .normal)
closeButton.backgroundColor = .systemBlue
// โจ weak self ์ฌ์ฉ
closeButton.tapHandler = { [weak self] in
self?.dismiss(animated: true)
}
view.addSubview(closeButton)
}
}
// ํ
์คํธ ์คํ
class MainViewController: UIViewController {
@IBAction func showTestVC() {
let testVC = TestViewController()
present(testVC, animated: true)
// 5์ด ํ ์๋์ผ๋ก ๋ซ๊ธฐ (ํ
์คํธ์ฉ)
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak testVC] in
testVC?.dismiss(animated: true)
// 1์ด ํ deinit ํ์ธ
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("ํ
์คํธ ์๋ฃ - ์ฝ์์์ deinit ํ์ธ")
}
}
}
}
ํ ์คํธ ์ฒดํฌ๋ฆฌ์คํธ: โ
โก ํ๋ฉด์ 10๋ฒ ์ด๊ณ ๋ซ์์ ๋ deinit์ด 10๋ฒ ํธ์ถ๋๋๊ฐ?
โก Memory Graph์์ ํด๋น ViewController ์ธ์คํด์ค๊ฐ ์ฌ๋ผ์ง๋๊ฐ?
โก Allocations์์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์๋๋๋ก ๋์์ค๋๊ฐ?
โก ํ๋ฉด์ ๋น ๋ฅด๊ฒ ์ฌ๋ฌ ๋ฒ ์ด๊ณ ๋ซ์๋ ๋ฌธ์ ์๋๊ฐ?
๐ ์๋๋ฆฌ์ค 2: ์ํ ์ฐธ์กฐ ์คํธ๋ ์ค ํ ์คํธ
์๋์ ์ผ๋ก ์ํ ์ฐธ์กฐ๋ฅผ ๋ง๋ค๊ณ , ์ด๋ฅผ ๊ฐ์งํ๊ณ ์์ ํ๋ ์ฐ์ต์ ํด๋ณด์! ๐ช
โ ๏ธ ์ํ ์ฐธ์กฐ ํ ์คํธ ์ฝ๋
// ํ
์คํธ์ฉ ํด๋์ค๋ค
class Parent {
var name: String
var child: Child?
init(name: String) {
self.name = name
print("Parent \(name) ์์ฑ")
}
deinit {
print("Parent \(name) ํด์ ")
}
}
class Child {
var name: String
var parent: Parent? // ๐จ ์ฌ๊ธฐ๋ฅผ weak์ผ๋ก ๋ฐ๊ฟ์ผ ํจ
init(name: String) {
self.name = name
print("Child \(name) ์์ฑ")
}
deinit {
print("Child \(name) ํด์ ")
}
}
// ํ
์คํธ ์คํ
class RetainCycleTestViewController: UIViewController {
func runTest() {
print("\n=== ์ํ ์ฐธ์กฐ ํ
์คํธ ์์ ===")
// ํ
์คํธ 1: ์ํ ์ฐธ์กฐ ๋ฐ์
testRetainCycle()
// 2์ด ๋๊ธฐ
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("\n=== ์ ์ ์ฐธ์กฐ ํ
์คํธ ์์ ===")
self.testNormalReference()
}
}
func testRetainCycle() {
var parent: Parent? = Parent(name: "๋ถ๋ชจ1")
var child: Child? = Child(name: "์์1")
parent?.child = child
child?.parent = parent // ์ํ ์ฐธ์กฐ!
print("parent์ child๋ฅผ nil๋ก ์ค์ ...")
parent = nil
child = nil
print("deinit์ด ํธ์ถ๋์ง ์์! ๐จ")
}
func testNormalReference() {
// Child ํด๋์ค์ parent๋ฅผ weak์ผ๋ก ์์ ํ ํ
์คํธ
print("weak ์ฐธ์กฐ๋ก ์์ ํ ํ
์คํธ")
print("deinit์ด ์ ์ ํธ์ถ๋จ! โ
")
}
}
โฑ๏ธ ์๋๋ฆฌ์ค 3: ์ฅ์๊ฐ ์คํ ํ ์คํธ
์ฑ์ ์ค๋ ์ฌ์ฉํ์ ๋ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋์ ๋๋์ง ํ์ธํ๋ ํ ์คํธ์ผ. ์ค์ ์ฌ์ฉ์ ์๋๋ฆฌ์ค๋ฅผ ์๋ฎฌ๋ ์ด์ ํ๋ ๊ฑฐ์ง! ๐ฎ
โ ์ฅ์๊ฐ ์คํ ํ ์คํธ ์ฝ๋
class LongRunningTestViewController: UIViewController {
var testCount = 0
var timer: Timer?
var startMemory: UInt64 = 0
override func viewDidLoad() {
super.viewDidLoad()
startMemory = getMemoryUsage()
startTest()
}
func startTest() {
print("=== ์ฅ์๊ฐ ์คํ ํ
์คํธ ์์ ===")
print("์์ ๋ฉ๋ชจ๋ฆฌ: \(formatBytes(startMemory))")
// 1์ด๋ง๋ค ํ๋ฉด ์์ฑ/ํด์ ๋ฐ๋ณต
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.performTestCycle()
}
}
func performTestCycle() {
testCount += 1
// ํ
์คํธ ViewController ์์ฑ
let testVC = TestViewController()
addChild(testVC)
view.addSubview(testVC.view)
testVC.didMove(toParent: self)
// ์ฆ์ ์ ๊ฑฐ
testVC.willMove(toParent: nil)
testVC.view.removeFromSuperview()
testVC.removeFromParent()
// 10ํ๋ง๋ค ๋ฉ๋ชจ๋ฆฌ ์ฒดํฌ
if testCount % 10 == 0 {
let currentMemory = getMemoryUsage()
let increase = currentMemory - startMemory
print("""
--- ํ
์คํธ \(testCount)ํ ---
ํ์ฌ ๋ฉ๋ชจ๋ฆฌ: \(formatBytes(currentMemory))
์ฆ๊ฐ๋: \(formatBytes(increase))
""")
// ๋ฉ๋ชจ๋ฆฌ๊ฐ 50MB ์ด์ ์ฆ๊ฐํ๋ฉด ๊ฒฝ๊ณ
if increase > 50 * 1024 * 1024 {
print("โ ๏ธ ๋ฉ๋ชจ๋ฆฌ ๋์ ์์ฌ!")
stopTest()
}
}
// 100ํ ํ
์คํธ ํ ์ข
๋ฃ
if testCount >= 100 {
stopTest()
}
}
func stopTest() {
timer?.invalidate()
timer = nil
let finalMemory = getMemoryUsage()
let totalIncrease = finalMemory - startMemory
print("""
=== ํ
์คํธ ์๋ฃ ===
์ด ์คํ ํ์: \(testCount)
์์ ๋ฉ๋ชจ๋ฆฌ: \(formatBytes(startMemory))
์ต์ข
๋ฉ๋ชจ๋ฆฌ: \(formatBytes(finalMemory))
์ด ์ฆ๊ฐ๋: \(formatBytes(totalIncrease))
ํ์ : \(totalIncrease < 10 * 1024 * 1024 ? "โ
์ ์" : "๐จ ๋ฉ๋ชจ๋ฆฌ ๋์ ์์ฌ")
""")
}
func getMemoryUsage() -> UInt64 {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_,
task_flavor_t(MACH_TASK_BASIC_INFO),
$0,
&count)
}
}
return kerr == KERN_SUCCESS ? info.resident_size : 0
}
func formatBytes(_ bytes: UInt64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useMB]
formatter.countStyle = .memory
return formatter.string(fromByteCount: Int64(bytes))
}
deinit {
timer?.invalidate()
}
}
๐ฏ ํ ์คํธ ์๋ํ ํ
์ฌ๋ฅ๋ท์์ iOS ๊ฐ๋ฐ ๋ฉํ ๋ง์ ํ๋ฉด์ ๋ง์ ๋ถ๋ค์๊ฒ ์ถ์ฒํ๋ ๋ฐฉ๋ฒ์ด ์์ด. ๋ฐ๋ก UI ํ ์คํธ์ ๋ฉ๋ชจ๋ฆฌ ํ ์คํธ๋ฅผ ๊ฒฐํฉํ๋ ๊ฑฐ์ผ! XCTest๋ฅผ ํ์ฉํ๋ฉด ์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๊ฐ์งํ ์ ์์ด. ๐ค
๐ Best Practices & ์ค๋ฌด ํ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ๋ฐํ์ผ๋ก, ์ค๋ฌด์์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ๋ฒ ์คํธ ํ๋ํฐ์ค๋ฅผ ์ ๋ฆฌํด๋ณผ๊ฒ! ์ด๊ฒ๋ง ๋ฐ๋ผํด๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ๋ฌธ์ ์ 90%๋ ์๋ฐฉํ ์ ์์ด. ๐ฏ
๐ 1. ์ฝ๋ฉ ์ปจ๋ฒค์
โ ๋ฉ๋ชจ๋ฆฌ ์์ ์ฝ๋ฉ ๊ท์น
๊ท์น 1: Delegate๋ ํญ์ weak
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
protocol MyDelegate: AnyObject {
func didSomething()
}
class MyClass {
weak var delegate: MyDelegate?
}
// โ ์๋ชป๋ ๋ฐฉ๋ฒ
protocol MyDelegate { // AnyObject ์์
func didSomething()
}
class MyClass {
var delegate: MyDelegate? // weak ์์
}
๊ท์น 2: ํด๋ก์ ์์ self ์ฌ์ฉ ์ ํญ์ ์บก์ฒ ๋ฆฌ์คํธ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
someAsyncFunction { [weak self] in
guard let self = self else { return }
self.doSomething()
}
// โ ์๋ชป๋ ๋ฐฉ๋ฒ
someAsyncFunction {
self.doSomething() // ์ํ ์ฐธ์กฐ ์ํ!
}
๊ท์น 3: deinit์์ ๋ฆฌ์์ค ์ ๋ฆฌ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
class MyViewController: UIViewController {
var timer: Timer?
var observer: NSObjectProtocol?
deinit {
timer?.invalidate()
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
print("MyViewController ํด์ ๋จ")
}
}
๊ท์น 4: ํฐ ๋ฐ์ดํฐ๋ lazy ์ด๊ธฐํ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
class DataManager {
lazy var largeDataSet: [Data] = {
// ์ค์ ๋ก ํ์ํ ๋๋ง ๋ก๋
return loadLargeData()
}()
private func loadLargeData() -> [Data] {
// ๋ฌด๊ฑฐ์ด ๋ฐ์ดํฐ ๋ก๋ฉ
return []
}
}
๐ 2. ์ฝ๋ ๋ฆฌ๋ทฐ ์ฒดํฌ๋ฆฌ์คํธ
ํ์์ ์ฝ๋ ๋ฆฌ๋ทฐํ ๋ ์ด ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ๋ฒ๊ทธ๋ฅผ ์ฌ์ ์ ์ก์ ์ ์์ด! โจ
| ํญ๋ชฉ | ์ฒดํฌ ํฌ์ธํธ | ์ํ๋ |
|---|---|---|
| Delegate | weak์ผ๋ก ์ ์ธ๋์๋๊ฐ? | ๐ด ๋์ |
| Closure | [weak self] ์ฌ์ฉํ๋๊ฐ? | ๐ด ๋์ |
| Timer | invalidate() ํธ์ถํ๋๊ฐ? | ๐ด ๋์ |
| Notification | removeObserver ํธ์ถํ๋๊ฐ? | ๐ก ์ค๊ฐ |
| deinit | ๋ฆฌ์์ค ์ ๋ฆฌ ์ฝ๋๊ฐ ์๋๊ฐ? | ๐ก ์ค๊ฐ |
| Cache | ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ฒ๋ฆฌํ๋๊ฐ? | ๐ก ์ค๊ฐ |
| Large Data | ์ ์ ํ ํด์ ํ๋๊ฐ? | ๐ข ๋ฎ์ |
๐ก๏ธ 3. ๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ
๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์๋ฐฉํ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ์ฒ์๋ถํฐ ์์ ํ๊ฒ ์ฝ๋ฉํ๋ ๊ฑฐ์ผ! ๐ก๏ธ
โ ๋ฐฉ์ด์ ์ฝ๋ฉ ํจํด
ํจํด 1: Optional Chaining ์ ๊ทน ํ์ฉ
// โ
์์ ํ ๋ฐฉ๋ฒ
class SafeViewController: UIViewController {
weak var delegate: MyDelegate?
func notifyDelegate() {
// Optional chaining์ผ๋ก ์์ ํ๊ฒ ํธ์ถ
delegate?.didSomething()
// guard let์ผ๋ก ์์ ํ๊ฒ ์ฒ๋ฆฌ
guard let delegate = delegate else {
print("Delegate๊ฐ ์์")
return
}
delegate.didSomething()
}
}
ํจํด 2: ๋ช ์์ nil ํ ๋น
// โ
๋ช
ํํ ๋ฆฌ์์ค ํด์
class ResourceManager {
var heavyResource: HeavyObject?
func cleanup() {
// ๋ช
์์ ์ผ๋ก nil ํ ๋น
heavyResource = nil
print("๋ฆฌ์์ค ํด์ ๋จ")
}
deinit {
cleanup()
}
}
ํจํด 3: ์กฐ๊ฑด๋ถ ์ปดํ์ผ๋ก ๋๋ฒ๊ทธ ์ฝ๋ ์ถ๊ฐ
class DebugViewController: UIViewController {
#if DEBUG
private var allocationTime = Date()
#endif
init() {
super.init(nibName: nil, bundle: nil)
#if DEBUG
print("[\(type(of: self))] ์์ฑ๋จ at \(allocationTime)")
#endif
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
deinit {
#if DEBUG
let lifetime = Date().timeIntervalSince(allocationTime)
print("[\(type(of: self))] ํด์ ๋จ (์์กด์๊ฐ: \(lifetime)์ด)")
#endif
}
}
๐ 4. ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง
๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํ๋ ์ ํธ๋ฆฌํฐ๋ฅผ ๋ง๋ค์ด๋๋ฉด ์ ๋ง ์ ์ฉํด! ๐
โ ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง ์ ํธ๋ฆฌํฐ
class MemoryMonitor {
static let shared = MemoryMonitor()
private var timer: Timer?
private var isMonitoring = false
private init() {}
func startMonitoring(interval: TimeInterval = 1.0) {
guard !isMonitoring else { return }
isMonitoring = true
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
self?.logMemoryUsage()
}
}
func stopMonitoring() {
timer?.invalidate()
timer = nil
isMonitoring = false
}
private func logMemoryUsage() {
let usage = getMemoryUsage()
let formatted = formatBytes(usage)
#if DEBUG
print("๐พ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: \(formatted)")
// ์๊ณ๊ฐ ์ฒดํฌ (์: 200MB)
if usage > 200 * 1024 * 1024 {
print("โ ๏ธ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋์ต๋๋ค!")
}
#endif
}
func getMemoryUsage() -> UInt64 {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_,
task_flavor_t(MACH_TASK_BASIC_INFO),
$0,
&count)
}
}
return kerr == KERN_SUCCESS ? info.resident_size : 0
}
func formatBytes(_ bytes: UInt64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useMB, .useGB]
formatter.countStyle = .memory
return formatter.string(fromByteCount: Int64(bytes))
}
deinit {
stopMonitoring()
}
}
// AppDelegate์์ ์ฌ์ฉ
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
#if DEBUG
// ๋๋ฒ๊ทธ ๋ชจ๋์์๋ง ๋ชจ๋ํฐ๋ง ์์
MemoryMonitor.shared.startMonitoring()
#endif
return true
}
}
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ๊ณ ์ํ์ด! ๐ iOS ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ Retain Cycle ๋ฐฉ์ง์ ๋ํด ์ ๋ง ๋ง์ ๋ด์ฉ์ ๋ค๋ค์ง? ๐
์ฒ์์๋ ๋ณต์กํ๊ณ ์ด๋ ต๊ฒ ๋๊ปด์ง ์ ์์ด. ๋๋ ์ฒ์ iOS ๊ฐ๋ฐ์ ์์ํ์ ๋ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋๋ฌธ์ ์ ๋ง ๊ณ ์ํ๊ฑฐ๋ . ์ฑ์ด ๋๋ ค์ง๊ณ ํฌ๋์๊ฐ ๋๋๋ฐ, ์์ธ์ ์ฐพ๋ ๋ฐ๋ง ๋ฉฐ์น ์ฉ ๊ฑธ๋ ธ์ด. ๐
ํ์ง๋ง ์ด ๊ธ์์ ๋ฐฐ์ด ๋ด์ฉ๋ค์ ํ๋์ฉ ์ค์ฒํ๋ค ๋ณด๋ฉด, ์ด๋์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์์ฐ์ค๋ฌ์์ง ๊ฑฐ์ผ! ๐ช
๐ฏ ํต์ฌ ์์ฝ
1. ๋ฉ๋ชจ๋ฆฌ ๋์์ ์ฃผ๋ฒ๋ค
โข Retain Cycle (์ํ ์ฐธ์กฐ)
โข ํด๋ก์ ์ ๊ฐํ self ์บก์ฒ
โข Delegate์ strong ์ฐธ์กฐ
โข Timer์ Notification ๋ฏธ์ ๋ฆฌ
2. ํด๊ฒฐ ๋ฐฉ๋ฒ
โข weak๊ณผ unowned ์ ์ ํ ์ฌ์ฉ
โข ํด๋ก์ ์์ [weak self] ์บก์ฒ ๋ฆฌ์คํธ
โข deinit์์ ๋ฆฌ์์ค ์ ๋ฆฌ
โข ์ ๊ธฐ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ํ๋กํ์ผ๋ง
3. ๋๋ฒ๊น
๋๊ตฌ
โข Instruments (Leaks, Allocations)
โข Memory Graph Debugger
โข ๋ฐํ์ ๋ฉ๋ชจ๋ฆฌ ์ด์ ๊ฐ์ง
โข ์ปค์คํ
๋ชจ๋ํฐ๋ง ์ ํธ๋ฆฌํฐ
4. Best Practices
โข ์ฝ๋ฉ ์ปจ๋ฒค์
์ค์
โข ์ฝ๋ ๋ฆฌ๋ทฐ ์ฒดํฌ๋ฆฌ์คํธ ํ์ฉ
โข ๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ
โข ์ง์์ ์ธ ๋ชจ๋ํฐ๋ง
๐ก ์ค์ ์ ์ฉ ๋ก๋๋งต
1์ฃผ์ฐจ: ๊ธฐ๋ณธ ๊ฐ๋
์ตํ๊ธฐ
โข ARC ๋์ ์๋ฆฌ ์ดํด
โข weak๊ณผ unowned ์ฐจ์ด์ ํ์ต
โข ๊ฐ๋จํ ์์ ๋ก ์ค์ต
2์ฃผ์ฐจ: ๋๋ฒ๊น
๋๊ตฌ ๋ง์คํฐ
โข Instruments ์ฌ์ฉ๋ฒ ์ตํ๊ธฐ
โข Memory Graph ํ์ฉํ๊ธฐ
โข ์ค์ ํ๋ก์ ํธ์ ์ ์ฉ
3์ฃผ์ฐจ: ํจํด๋ณ ํด๊ฒฐ ๋ฐฉ๋ฒ
โข Delegate ํจํด ์์ ํ๊ฒ ๊ตฌํ
โข ํด๋ก์ ์บก์ฒ ๋ฆฌ์คํธ ์ ์ฉ
โข Timer์ Notification ๊ด๋ฆฌ
4์ฃผ์ฐจ: ๊ณ ๊ธ ๊ธฐ๋ฒ๊ณผ ์ต์ ํ
โข ์ปค์คํ
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ตฌํ
โข ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ์์คํ
๊ตฌ์ถ
โข ํ ์ฝ๋ฉ ์ปจ๋ฒค์
์ ๋ฆฝ
๐ ๋ง์ง๋ง ์กฐ์ธ
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ ํ ๋ฒ์ ์๋ฒฝํ๊ฒ ํ ์ ์์ด. ๊พธ์คํ ์ฐ์ตํ๊ณ , ์ค์ํ๊ณ , ๋ฐฐ์ฐ๋ ๊ณผ์ ์ด ํ์ํด. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค๊ณผ ๊ฒฝํ์ ๊ณต์ ํ๊ณ , ๋ฉํ ๋ง์ ๋ฐ๋ ๊ฒ๋ ํฐ ๋์์ด ๋ผ! ๐ค
๊ฐ์ฅ ์ค์ํ ๊ฑด ์ต๊ด์ด์ผ. ์ฒ์๋ถํฐ ๋ฉ๋ชจ๋ฆฌ ์์ ํ ์ฝ๋๋ฅผ ์์ฑํ๋ ์ต๊ด์ ๋ค์ด๋ฉด, ๋์ค์ ๋๋ฒ๊น ํ๋ ์๊ฐ์ ์์ฒญ๋๊ฒ ์ค์ผ ์ ์์ด. โฐ
๊ทธ๋ฆฌ๊ณ ํญ์ ๊ธฐ์ตํด: "deinit์ด ํธ์ถ๋๋์ง ํ์ธํ๋ผ!" ์ด๊ฒ๋ง ์ฒดํฌํด๋ ๋๋ถ๋ถ์ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ก์ ์ ์์ด. โ
๐ ์ถ๊ฐ ํ์ต ์๋ฃ
๊ณต์ ๋ฌธ์
โข Apple Developer - Automatic Reference Counting
โข Swift Language Guide - Memory Safety
โข WWDC ์ธ์
- Finding and Fixing Memory Issues
์ค์ ์ฐ์ต
โข GitHub์์ ์คํ์์ค ํ๋ก์ ํธ ๋ถ์
โข ์์ ์ ํ๋ก์ ํธ์ ๋ฉ๋ชจ๋ฆฌ ํ
์คํธ ์ถ๊ฐ
โข ์ฝ๋ ๋ฆฌ๋ทฐ์์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ํผ๋๋ฐฑ ์ฃผ๊ณ ๋ฐ๊ธฐ
์ปค๋ฎค๋ํฐ ํ๋
โข Stack Overflow์์ ๊ด๋ จ ์ง๋ฌธ ๋ต๋ณํ๊ธฐ
โข ์ฌ๋ฅ๋ท์์ ๊ฒฝํ ๊ณต์ ํ๊ธฐ
โข ๊ธฐ์ ๋ธ๋ก๊ทธ์ ํ์ต ๋ด์ฉ ์ ๋ฆฌํ๊ธฐ
โ ๏ธ ์ฃผ์์ฌํญ
โข ๊ณผ๋ํ ์ต์ ํ๋ ์คํ๋ ค ์ฝ๋๋ฅผ ๋ณต์กํ๊ฒ ๋ง๋ค ์ ์์ด
โข ์ค์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๋ ํ๋กํ์ผ๋ง ํ ์ต์ ํํ๊ธฐ
โข ํ ์ปจ๋ฒค์
์ ๋ฌด์ํ๊ณ ํผ์๋ง์ ๋ฐฉ์์ผ๋ก ํ์ง ์๊ธฐ
โข ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์๋ง ์ง์คํ๋ค ๋ค๋ฅธ ์ค์ํ ๋ถ๋ถ์ ๋์น์ง ์๊ธฐ
์, ์ด์ ์ ๋ง ๋์ด์ผ! ์ด ๊ธ์ด ๋์ iOS ๊ฐ๋ฐ ์ฌ์ ์ ๋์์ด ๋์์ผ๋ฉด ์ข๊ฒ ์ด. ๋ฉ๋ชจ๋ฆฌ ๋์ ์๋ ๊น๋ํ ์ฑ์ ๋ง๋ค์ด๋ณด์! ๐
ํน์ ๊ถ๊ธํ ์ ์ด๋ ์ด๋ ค์ด ๋ถ๋ถ์ด ์๋ค๋ฉด, ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ ๋ฌธ๊ฐ๋ค์๊ฒ ์กฐ์ธ์ ๊ตฌํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ์ ํ์ ๋ฏฟ์ด! ๐ช
๊ทธ๋ผ ํ๋ณตํ ์ฝ๋ฉ ๋๊ธธ ๋ฐ๋ผ! Happy Coding! ๐โจ
๐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ง์คํฐ๊ฐ ๋๋ ๊ทธ๋ ๊น์ง!
Keep Learning, Keep Growing! ๐ก
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.