์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ” iOS ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์™€ Retain Cycle ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์‹ค์ „ ๋””๋ฒ„๊น… ๊ฐ€์ด๋“œ

๐Ÿ” iOS ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์™€ Retain Cycle ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์‹ค์ „ ๋””๋ฒ„๊น… ๊ฐ€์ด๋“œ

์•ฑ์ด ๋А๋ ค์ง€๊ณ  ํฌ๋ž˜์‹œ๊ฐ€ ๋‚˜๋Š” ์ง„์งœ ์ด์œ , ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์˜ ๋ชจ๋“  ๊ฒƒ ๐Ÿ’ก

์•ˆ๋…•! ์˜ค๋Š˜์€ iOS ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ• , ๊ทธ๋Ÿฌ๋ฉด์„œ๋„ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ณจ์น˜ ์•„ํŒŒํ•˜๋Š” ์ฃผ์ œ๋ฅผ ๋‹ค๋ค„๋ณผ ๊ฑฐ์•ผ. ๋ฐ”๋กœ **๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜(Memory Leak)**์™€ **Retain Cycle**์ด์•ผ. ๐Ÿ˜…

ํ˜น์‹œ ์ด๋Ÿฐ ๊ฒฝํ—˜ ์žˆ์–ด? ์•ฑ์„ ์—ด์‹ฌํžˆ ๊ฐœ๋ฐœํ–ˆ๋Š”๋ฐ, ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด ์ ์  ๋А๋ ค์ง€๊ณ , ์‹ฌ์ง€์–ด ๊ฐ‘์ž๊ธฐ ๊บผ์ง€๊ธฐ๋„ ํ•˜๊ณ ... ๊ทธ๋Ÿฐ๋ฐ ์ฝ”๋“œ๋ฅผ ์•„๋ฌด๋ฆฌ ๋ด๋„ ๋ญ๊ฐ€ ๋ฌธ์ œ์ธ์ง€ ๋ชจ๋ฅด๊ฒ ๊ณ . ๋ฐ”๋กœ ์ด๊ฒŒ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋•Œ๋ฌธ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ํฌ๊ฑฐ๋“ ! ๐Ÿคฏ

์ด ๊ธ€์—์„œ๋Š” ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•˜๋“ฏ์ด ์‰ฝ๊ณ  ์žฌ๋ฏธ์žˆ๊ฒŒ, ํ•˜์ง€๋งŒ ์ •ํ™•ํ•˜๊ณ  ์‹ค์ „์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ๋‚ด์šฉ๋“ค๋กœ ๊ฐ€๋“ ์ฑ„์›Œ๋ณผ๊ฒŒ. ์žฌ๋Šฅ๋„ท์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ด€๋ จ ์ง€์‹์„ ๊ณต์œ ํ•˜๋Š” ๋งŒํผ, ์‹ค๋ฌด์—์„œ ์ •๋ง ๋„์›€์ด ๋˜๋Š” ํŒ๋“ค์„ ์•„๋‚Œ์—†์ด ํ’€์–ด๋†“์„ ๊ฑฐ์•ผ! ๐Ÿš€

iOS ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ตฌ์กฐ ํž™ ๋ฉ”๋ชจ๋ฆฌ ๊ฐ์ฒด ์ €์žฅ ๊ณต๊ฐ„ ๋™์  ํ• ๋‹น ARC๊ฐ€ ๊ด€๋ฆฌ ์Šคํƒ ๋ฉ”๋ชจ๋ฆฌ ์ง€์—ญ ๋ณ€์ˆ˜ ์ €์žฅ ์ž๋™ ๊ด€๋ฆฌ ๋น ๋ฅธ ์ ‘๊ทผ ์ •์  ๋ฉ”๋ชจ๋ฆฌ ์ „์—ญ ๋ณ€์ˆ˜ ํ”„๋กœ๊ทธ๋žจ ์‹œ์ž‘์‹œ ํ• ๋‹น ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ 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]๋ฅผ ์“ฐ๋Š” ์Šต๊ด€์„ ๋“ค์—ฌ! ๋‚˜์ค‘์— ์ตœ์ ํ™”ํ•˜๋Š” ๊ฑด ์‰ฝ์ง€๋งŒ, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ์ฐพ๋Š” ๊ฑด ์ •๋ง ์–ด๋ ต๊ฑฐ๋“ . ๐Ÿ”

ํด๋กœ์ € ์บก์ฒ˜ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ViewController var closure: (() -> Void)? func setupClosure() Closure ์บก์ฒ˜๋œ ๋ณ€์ˆ˜๋“ค: self (strong) otherValue someData ๊ฐ•ํ•œ ์ฐธ์กฐ ์—ญ์ฐธ์กฐ Retain Cycle! ํ•ด๊ฒฐ์ฑ…: weak self closure = { [weak self] in guard let self = 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 ํ•ด์ œ๋จ")
    }
}
๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ํŒจํ„ด๋ณ„ ํ•ด๊ฒฐ ์ „๋žต Delegate ๋ฌธ์ œ: strong ์ฐธ์กฐ ํ•ด๊ฒฐ: weak delegate ํ”„๋กœํ† ์ฝœ: AnyObject โœ“ ์•ˆ์ „ Timer ๋ฌธ์ œ: target ๊ฐ•์ฐธ์กฐ ํ•ด๊ฒฐ: invalidate() ๋˜๋Š” ํด๋กœ์ € + weak โœ“ ์•ˆ์ „ Notification ๋ฌธ์ œ: ์˜ต์ €๋ฒ„ ๋ฏธ์ œ๊ฑฐ ํ•ด๊ฒฐ: removeObserver deinit์—์„œ ์ œ๊ฑฐ โœ“ ์•ˆ์ „ GCD/Async ๋ฌธ์ œ: ํด๋กœ์ € ์บก์ฒ˜ ํ•ด๊ฒฐ: [weak self] guard let ์ฒดํฌ โœ“ ์•ˆ์ „ ์ฒดํฌ๋ฆฌ์ŠคํŠธ โ–ก Delegate๋Š” weak์œผ๋กœ ์„ ์–ธํ–ˆ๋Š”๊ฐ€? โ–ก Timer๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด invalidate ํ–ˆ๋Š”๊ฐ€? โ–ก NotificationCenter ์˜ต์ €๋ฒ„๋ฅผ ์ œ๊ฑฐํ–ˆ๋Š”๊ฐ€? โ–ก ํด๋กœ์ €์—์„œ [weak self]๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๊ฐ€? โ–ก deinit์ด ํ˜ธ์ถœ๋˜๋Š”์ง€ ํ™•์ธํ–ˆ๋Š”๊ฐ€? โ–ก Memory Graph๋กœ ๊ฒ€์ฆํ–ˆ๋Š”๊ฐ€? ๋””๋ฒ„๊น… ์ˆœ์„œ 1 Instruments๋กœ ๋ˆ„์ˆ˜ ๊ฐ์ง€ 2 Memory Graph๋กœ ์ฐธ์กฐ ํ™•์ธ 3 ํŒจํ„ด๋ณ„ ํ•ด๊ฒฐ์ฑ… ์ ์šฉ 4 ์žฌํ…Œ์ŠคํŠธ ๋ฐ ๊ฒ€์ฆ

๐ŸŽจ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•: 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
    }
}
๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ Best Practices ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋ฐฉ์–ด์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ํ•ต์‹ฌ ์›์น™ 1. Delegate๋Š” ํ•ญ์ƒ weak์œผ๋กœ ์„ ์–ธ 2. ํด๋กœ์ €์—์„œ self ์‚ฌ์šฉ ์‹œ [weak self] ํ•„์ˆ˜ 3. Timer์™€ Notification์€ ๋ฐ˜๋“œ์‹œ ์ •๋ฆฌ 4. deinit์—์„œ ๋ฆฌ์†Œ์Šค ํ•ด์ œ ํ™•์ธ 5. ์ •๊ธฐ์ ์œผ๋กœ Memory Graph๋กœ ๊ฒ€์ฆ

๐Ÿš€ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด. ๊ณ ์ƒํ–ˆ์–ด! ๐Ÿ‘ 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
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.