Ver3.0 ๐ Swift ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ARC๋ฅผ ํตํ ํจ์จ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์๋ฒฝ ๊ฐ์ด๋

๐ Swift ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ARC๋ฅผ ํตํ ํจ์จ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์๋ฒฝ ๊ฐ์ด๋
์๋
! ๐ ์ค๋์ Swift ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊ฒ. ํน์ ์ฑ์ ๋ง๋ค๋ค๊ฐ ๊ฐ์๊ธฐ ํฌ๋์๊ฐ ๋๊ฑฐ๋, ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ณ์ ๋์ด๋์ ์ฑ์ด ๋๋ ค์ง ๊ฒฝํ ์์ด? ๊ทธ๊ฒ ๋ฐ๋ก ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ ๋๋ก ์ ํด์ ์๊ธฐ๋ ๋ฌธ์ ์ผ! ๐ฑ
Swift๋ ARC(Automatic Reference Counting)๋ผ๋ ๋๋ํ ์์คํ
์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํด์ฃผ์ง๋ง, ์ฐ๋ฆฌ๊ฐ ์ ๋๋ก ์ดํดํ์ง ๋ชปํ๋ฉด ์คํ๋ ค ๋
์ด ๋ ์๋ ์์ด. ๋ง์น ์๋์ฐจ์ ์๋ ๋ณ์๊ธฐ์ฒ๋ผ ํธ๋ฆฌํ์ง๋ง, ์๋ฆฌ๋ฅผ ๋ชจ๋ฅด๋ฉด ์ ๋๋ก ํ์ฉํ ์ ์๋ ๊ฒ์ฒ๋ผ ๋ง์ด์ผ! ๐
์ด ๊ธ์์๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ๊ธฐ์ด๋ถํฐ ์ค์ ํ
ํฌ๋๊น์ง, ์ฌ๋ฅ๋ท์์ ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ๋ ์ ๋ฌธ๊ฐ๋ค๋ ๊ฐ์กฐํ๋ ํต์ฌ ๋ด์ฉ๋ค์ ์น๊ตฌ์๊ฒ ์ค๋ช
ํ๋ฏ ์ฝ๊ณ ์ฌ๋ฏธ์๊ฒ ํ์ด๋ณผ๊ฒ!
๐ฏ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ, ์ ์ค์ํ ๊น?
ํ๋ก๊ทธ๋๋ฐ์ ์ฒ์ ๋ฐฐ์ธ ๋๋ "๊ทธ๋ฅ ์ฝ๋๋ง ์ ์ง๋ฉด ๋๋ ๊ฑฐ ์๋?"๋ผ๊ณ ์๊ฐํ๊ธฐ ์ฌ์. ํ์ง๋ง ์ค์ ๋ก ์ฑ์ ๋ง๋ค๋ค ๋ณด๋ฉด ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์ผ๋ง๋ ์ค์ํ์ง ๋ผ์ ๋ฆฌ๊ฒ ๋๋ผ๊ฒ ๋ผ! ๐
์์ํด๋ด. ๋๊ฐ ๋ง๋ ์ฑ์ด ์ฌ์ฉ์์ ์์ดํฐ์์ ์คํ๋๊ณ ์์ด. ์ฒ์์ ์ ๋์๊ฐ๋๋ฐ, ์๊ฐ์ด ์ง๋ ์๋ก ์ ์ ๋๋ ค์ง๊ณ , ๊ฒฐ๊ตญ ์ฑ์ด ๊ฐ์ ์ข
๋ฃ๋๋ ๊ฑฐ์ผ. ์ฌ์ฉ์๋ ์ฑ์คํ ์ด์ ๋ณ์ 1์ ๊ณผ ํจ๊ป "์ฐ๋ ๊ธฐ ์ฑ"์ด๋ผ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋จ๊ธฐ๊ฒ ์ง? ๐ญ
์ด๋ฐ ์ํฉ์ด ๋ฐ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์(Memory Leak)๋ ์ํ ์ฐธ์กฐ(Retain Cycle) ๋๋ฌธ์ ๋ฐ์ํ๋ ๊ฑฐ์ผ. Swift์ ARC๋ ์ด๋ฐ ๋ฌธ์ ๋ค์ ์๋ฐฉํ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ง๋ง, ์ฐ๋ฆฌ๊ฐ ์ ๋๋ก ์ดํดํ๊ณ ์ฌ์ฉํด์ผ ํด!
๐ฑ ์ค์ ์ฌ๋ก: ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์คํจ์ ๊ฒฐ๊ณผ
์ฌ๋ก 1: ์ฌ์ง ํธ์ง ์ฑ
ํ ๊ฐ๋ฐ์๊ฐ ๋ง๋ ์ฌ์ง ํธ์ง ์ฑ์ด ์์์ด. ์ฌ์ฉ์๊ฐ ์ฌ์ง์ ํธ์งํ ๋๋ง๋ค ๋ฉ๋ชจ๋ฆฌ์ ์ด๋ฏธ์ง๊ฐ ๊ณ์ ์์๊ณ , 5-6์ฅ๋ง ํธ์งํด๋ ์ฑ์ด ํฌ๋์ ๋ฌ์ด. ์์ธ์? ํธ์ง์ด ๋๋ ์ด๋ฏธ์ง๋ฅผ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ํ์ง ์์๊ธฐ ๋๋ฌธ์ด์ผ! ๐ผ๏ธ
์ฌ๋ก 2: ์ฑํ
์ฑ
์ค์๊ฐ ์ฑํ
์ฑ์์ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ ๋๋ง๋ค ๋ธ๋ฆฌ๊ฒ์ดํธ ํจํด์ ์ํ ์ฐธ์กฐ๊ฐ ๋ฐ์ํ์ด. ์ฑํ
๋ฐฉ์ ๋๊ฐ๋ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋์ง ์์์, 10๊ฐ ์ฑํ
๋ฐฉ์ ๋์๋ค๋๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด 10๋ฐฐ๋ก ๋์ด๋ฌ์ง! ๐ฌ
์ด๋ฐ ๋ฌธ์ ๋ค์ ๋ชจ๋ ARC์ ์๋ ์๋ฆฌ๋ฅผ ์ ๋๋ก ์ดํดํ์ง ๋ชปํด์ ๋ฐ์ํ ๊ฑฐ์ผ. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ํ๋ก๊ทธ๋๋ฐ ๋ฉํ ๋ง์ ๋ฐ๋ ๊ฒ๋ ์ด๋ฐ ์ค์๋ฅผ ์ค์ด๋ ์ข์ ๋ฐฉ๋ฒ์ด์ง!
๐ง ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ๊ธฐ์ด: ์คํ๊ณผ ํ
๋ณธ๊ฒฉ์ ์ผ๋ก ARC๋ฅผ ์ดํดํ๊ธฐ ์ ์, ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ด๋ป๊ฒ ๊ตฌ์ฑ๋์ด ์๋์ง ์์์ผ ํด. ์ปดํจํฐ ๋ฉ๋ชจ๋ฆฌ๋ ํฌ๊ฒ ์คํ(Stack)๊ณผ ํ(Heap)์ผ๋ก ๋๋์ด์ ธ ์์ด. ์ด ๋์ ์ฐจ์ด๋ฅผ ์ดํดํ๋ ๊ฒ ์ ๋ง ์ค์ํด! ๐
๐ ์คํ(Stack) ๋ฉ๋ชจ๋ฆฌ
์คํ์ ๋ง์น ์ฑ
์ ์์๋์ ๊ฒ์ฒ๋ผ ์๋ํด. ๊ฐ์ฅ ๋์ค์ ์ฌ๋ฆฐ ์ฑ
์ ๊ฐ์ฅ ๋จผ์ ๊บผ๋ด๋ LIFO(Last In First Out) ๋ฐฉ์์ด์ผ.
ํน์ง:
โข ๋น ๋ฅธ ์๋: ๋ฉ๋ชจ๋ฆฌ ํ ๋น๊ณผ ํด์ ๊ฐ ๋งค์ฐ ๋น ๋ฆ โก
โข ์๋ ๊ด๋ฆฌ: ํจ์๊ฐ ๋๋๋ฉด ์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํด์
โข ํฌ๊ธฐ ์ ํ: ์คํ ํฌ๊ธฐ๋ ์ ํ์ (๋ณดํต 1-8MB)
โข ์ ์ฅ ๋์: ๊ฐ ํ์
(Value Type) - Int, Double, Struct, Enum ๋ฑ
์์:
ํจ์ ์์์ ์ ์ธํ ์ง์ญ ๋ณ์๋ค์ด ์คํ์ ์ ์ฅ๋ผ. ํจ์๊ฐ ๋๋๋ฉด? ์๋์ผ๋ก ์ฌ๋ผ์ ธ! ์ ๊ฒฝ ์ธ ํ์๊ฐ ์์ง. ๐
๐๏ธ ํ(Heap) ๋ฉ๋ชจ๋ฆฌ
ํ์ ์ข ๋ ์์ ๋ก์ด ๊ณต๊ฐ์ด์ผ. ํ์ํ ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋น๋ฐ๊ณ , ์ง์ ๊ด๋ฆฌํด์ผ ํด.
ํน์ง:
โข ๋๋ฆฐ ์๋: ์คํ๋ณด๋ค ํ ๋น/ํด์ ๊ฐ ๋๋ฆผ ๐
โข ์๋ ๊ด๋ฆฌ: ๊ฐ๋ฐ์๊ฐ ๊ด๋ฆฌํด์ผ ํจ (Swift๋ ARC๊ฐ ๋์์ค!)
โข ํฐ ํฌ๊ธฐ: ์์คํ
๋ฉ๋ชจ๋ฆฌ๊ฐ ํ์ฉํ๋ ๋งํผ ์ฌ์ฉ ๊ฐ๋ฅ
โข ์ ์ฅ ๋์: ์ฐธ์กฐ ํ์
(Reference Type) - Class, Closure ๋ฑ
์ ํ์ด ๋๋ฆด๊น?
ํ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ ๋ "์ด๋์ ๋ฃ์๊น?" ๊ณ ๋ฏผํด์ผ ํด. ๋น ๊ณต๊ฐ์ ์ฐพ๊ณ , ์ ์ ํ ํฌ๊ธฐ์ธ์ง ํ์ธํ๊ณ , ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํ ์ ์์ด์ ๋๊ธฐํ๋ ํ์ํ๊ฑฐ๋ ! ๐
// ์คํ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์์ (๊ฐ ํ์
)
struct Point {
var x: Int
var y: Int
}
func createPoint() {
let point = Point(x: 10, y: 20) // ์คํ์ ํ ๋น
print(point)
} // ํจ์ ์ข
๋ฃ ์ ์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํด์ !
// ํ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์์ (์ฐธ์กฐ ํ์
)
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
func createPerson() {
let person = Person(name: "๊น์ฒ ์", age: 25) // ํ์ ํ ๋น
print(person.name)
} // ํจ์ ์ข
๋ฃ ํ์๋ ARC๊ฐ ๊ด๋ฆฌํ ๋๊น์ง ๋ฉ๋ชจ๋ฆฌ์ ๋จ์์์
๐ ARC(Automatic Reference Counting)์ ์๋ ์๋ฆฌ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ARC์ ๋ํด ์์๋ณผ ์๊ฐ์ด์ผ! ARC๋ Swift์ ํต์ฌ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์์คํ
์ด์ผ. ์ด๋ฆ์์ ์ ์ ์๋ฏ์ด ์๋์ผ๋ก ์ฐธ์กฐ๋ฅผ ์ธ๋(Counting) ์์คํ
์ด์ง! ๐ข
ARC์ ๊ธฐ๋ณธ ์๋ฆฌ๋ ์ ๋ง ๊ฐ๋จํด. ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ฉด ์ฐธ์กฐ ์นด์ดํธ๊ฐ 1์ด ๋๊ณ , ๋๊ตฐ๊ฐ ๊ทธ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ ๋๋ง๋ค ์นด์ดํธ๊ฐ 1์ฉ ์ฆ๊ฐํด. ๋ฐ๋๋ก ์ฐธ์กฐ๊ฐ ๋์ด์ง๋ฉด 1์ฉ ๊ฐ์ํ๊ณ , ์นด์ดํธ๊ฐ 0์ด ๋๋ฉด? ๋ฉ๋ชจ๋ฆฌ์์ ์๋์ผ๋ก ํด์ ๋ผ! ๐
๐ ์ฐธ์กฐ ์นด์ดํธ์ ์๋ช ์ฃผ๊ธฐ
| ๋จ๊ณ | ๋์ | ์ฐธ์กฐ ์นด์ดํธ | ์ค๋ช |
|---|---|---|---|
| 1๏ธโฃ ์์ฑ | ๊ฐ์ฒด ์ธ์คํด์ค ์์ฑ | 1 | ๋ฉ๋ชจ๋ฆฌ์ ๊ฐ์ฒด ํ ๋น |
| 2๏ธโฃ ์ฐธ์กฐ ์ถ๊ฐ | ๋ค๋ฅธ ๋ณ์๊ฐ ์ฐธ์กฐ | 2, 3, 4... | ์ฐธ์กฐํ ๋๋ง๋ค +1 |
| 3๏ธโฃ ์ฐธ์กฐ ํด์ | ๋ณ์๊ฐ nil์ด ๋๊ฑฐ๋ ์ค์ฝํ ๋ฒ์ด๋จ | 3, 2, 1... | ํด์ ๋ ๋๋ง๋ค -1 |
| 4๏ธโฃ ๋ฉ๋ชจ๋ฆฌ ํด์ | deinit ํธ์ถ | 0 | ๋ฉ๋ชจ๋ฆฌ์์ ์์ ํ ์ ๊ฑฐ |
class Dog {
let name: String
init(name: String) {
self.name = name
print("\(name) ์์ฑ๋จ! ๐")
}
deinit {
print("\(name) ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋จ! ๐")
}
}
// ์ค์ ๋์ ์์
func testARC() {
print("=== ARC ํ
์คํธ ์์ ===")
var dog1: Dog? = Dog(name: "๋ฉ๋ฉ์ด") // RC: 1
var dog2: Dog? = dog1 // RC: 2 (๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐ)
var dog3: Dog? = dog1 // RC: 3
print("ํ์ฌ ์ฐธ์กฐ ์นด์ดํธ: 3")
dog1 = nil // RC: 2
print("dog1 ํด์ ํ ์ฐธ์กฐ ์นด์ดํธ: 2")
dog2 = nil // RC: 1
print("dog2 ํด์ ํ ์ฐธ์กฐ ์นด์ดํธ: 1")
dog3 = nil // RC: 0 -> deinit ํธ์ถ!
print("dog3 ํด์ ํ ์ฐธ์กฐ ์นด์ดํธ: 0")
print("=== ARC ํ
์คํธ ์ข
๋ฃ ===")
}
testARC()
/* ์ถ๋ ฅ ๊ฒฐ๊ณผ:
=== ARC ํ
์คํธ ์์ ===
๋ฉ๋ฉ์ด ์์ฑ๋จ! ๐
ํ์ฌ ์ฐธ์กฐ ์นด์ดํธ: 3
dog1 ํด์ ํ ์ฐธ์กฐ ์นด์ดํธ: 2
dog2 ํด์ ํ ์ฐธ์กฐ ์นด์ดํธ: 1
dog3 ํด์ ํ ์ฐธ์กฐ ์นด์ดํธ: 0
๋ฉ๋ฉ์ด ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋จ! ๐
=== ARC ํ
์คํธ ์ข
๋ฃ ===
*/
์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ARC๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ๋ช ํํ๊ฒ ์ ์ ์์ง? ๐ deinit์ด ํธ์ถ๋๋ ์์ ์ด ๋ฐ๋ก ์ฐธ์กฐ ์นด์ดํธ๊ฐ 0์ด ๋๋ ์๊ฐ์ด์ผ. ์ด๊ฒ ARC์ ํต์ฌ์ด์ผ!
โข Strong Reference (๊ฐํ ์ฐธ์กฐ): ๊ธฐ๋ณธ ์ฐธ์กฐ ๋ฐฉ์. ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ์ฆ๊ฐ์ํด
โข Weak Reference (์ฝํ ์ฐธ์กฐ): ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ์ฆ๊ฐ์ํค์ง ์์. ์ฐธ์กฐ ๋์์ด ํด์ ๋๋ฉด ์๋์ผ๋ก nil
โข Unowned Reference (๋ฏธ์์ ์ฐธ์กฐ): ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ์ฆ๊ฐ์ํค์ง ์์. ์ฐธ์กฐ ๋์์ด ํญ์ ์กด์ฌํ๋ค๊ณ ๊ฐ์
์ด ์ธ ๊ฐ์ง ์ฐธ์กฐ ๋ฐฉ์์ ์ธ์ , ์ด๋ป๊ฒ ์ฌ์ฉํ๋๋๊ฐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ํต์ฌ์ด์ผ! ๐
โ ๏ธ ์ํ ์ฐธ์กฐ(Retain Cycle): ARC์ ์ต๋ ์
ARC๋ ์ ๋ง ๋๋ํ์ง๋ง, ํ ๊ฐ์ง ์น๋ช
์ ์ธ ์ฝ์ ์ด ์์ด. ๋ฐ๋ก ์ํ ์ฐธ์กฐ(Retain Cycle)์ผ! ์ด๊ฑด ๋ ๊ฐ ์ด์์ ๊ฐ์ฒด๊ฐ ์๋ก๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ๋ฉด์ ๋ฐ์ํ๋ ๋ฌธ์ ์ผ. ๐ฑ
์์ํด๋ด. A๊ฐ B๋ฅผ ์ฐธ์กฐํ๊ณ , B๊ฐ A๋ฅผ ์ฐธ์กฐํ๋ ์ํฉ. A์ ์ฐธ์กฐ ์นด์ดํธ๋ 1 (B๊ฐ ์ฐธ์กฐ), B์ ์ฐธ์กฐ ์นด์ดํธ๋ 1 (A๊ฐ ์ฐธ์กฐ). ์ด ์ํ์์ ์ธ๋ถ ์ฐธ์กฐ๋ฅผ ๋ชจ๋ ๋์ด๋ A์ B๋ ์๋ก๋ฅผ ์ฐธ์กฐํ๊ณ ์์ด์ ์ฐธ์กฐ ์นด์ดํธ๊ฐ 0์ด ๋์ง ์์! ๊ฒฐ๊ณผ๋? ๋ฉ๋ชจ๋ฆฌ ๋์! ๐
1. ๋ธ๋ฆฌ๊ฒ์ดํธ ํจํด
๋ทฐ ์ปจํธ๋กค๋ฌ์ ๋ธ๋ฆฌ๊ฒ์ดํธ๊ฐ ์๋ก๋ฅผ ์ฐธ์กฐํ ๋
2. ํด๋ก์ ์บก์ฒ
ํด๋ก์ ์์์ self๋ฅผ ๊ฐํ๊ฒ ์บก์ฒํ ๋
3. ๋ถ๋ชจ-์์ ๊ด๊ณ
๋ถ๋ชจ ๊ฐ์ฒด๊ฐ ์์์ ์ฐธ์กฐํ๊ณ , ์์์ด ๋ถ๋ชจ๋ฅผ ์ฐธ์กฐํ ๋
4. ํ์ด๋จธ์ ํ๊ฒ
Timer๊ฐ ํ๊ฒ ๊ฐ์ฒด๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ ๋
์ด๋ฐ ์ํฉ๋ค์ ์ธ์งํ๊ณ ์์ด์ผ ์ค์๋ฅผ ์ค์ผ ์ ์์ด! ๐ฏ
// โ ์ํ ์ฐธ์กฐ ๋ฐ์ ์์
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? // ๊ฐํ ์ฐธ์กฐ!
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 // Person -> Apartment ๊ฐํ ์ฐธ์กฐ
unit4A?.tenant = john // Apartment -> Person ๊ฐํ ์ฐธ์กฐ
// ์ํ ์ฐธ์กฐ ๋ฐ์! ๐
john = nil // Person์ RC๋ ์ฌ์ ํ 1 (Apartment๊ฐ ์ฐธ์กฐ)
unit4A = nil // Apartment์ RC๋ ์ฌ์ ํ 1 (Person์ด ์ฐธ์กฐ)
// deinit์ด ํธ์ถ๋์ง ์์ = ๋ฉ๋ชจ๋ฆฌ ๋์!
// โ
ํด๊ฒฐ ๋ฐฉ๋ฒ: weak ์ฌ์ฉ
class ApartmentFixed {
var unit: String
weak var tenant: Person? // ์ฝํ ์ฐธ์กฐ๋ก ๋ณ๊ฒฝ!
init(unit: String) {
self.unit = unit
print("์ํํธ \(unit) ์์ฑ")
}
deinit {
print("์ํํธ \(unit) ํด์ ") // ์ด์ ํธ์ถ๋จ!
}
}
var jane: Person? = Person(name: "Jane")
var unit5B: ApartmentFixed? = ApartmentFixed(unit: "5B")
jane?.apartment = unit5B as? Apartment
unit5B?.tenant = jane
jane = nil // Person์ RC๊ฐ 0์ด ๋จ -> deinit ํธ์ถ!
unit5B = nil // Apartment์ RC๊ฐ 0์ด ๋จ -> deinit ํธ์ถ!
// ์ ์์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํด์ ! โจ
๐ช Weak vs Unowned: ์ธ์ ๋ญ ์จ์ผ ํ ๊น?
์ํ ์ฐธ์กฐ๋ฅผ ํด๊ฒฐํ๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ, weak๊ณผ unowned! ๋ ๋ค ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ์ฆ๊ฐ์ํค์ง ์์ง๋ง, ์ฌ์ฉ ๋ฐฉ๋ฒ๊ณผ ์๊ธฐ๊ฐ ๋ฌ๋ผ. ์ด ์ฐจ์ด๋ฅผ ์ ๋๋ก ์ดํดํ๋ ๊ฒ ์ ๋ง ์ค์ํด! ๐ค
๐ต Weak Reference (์ฝํ ์ฐธ์กฐ)
ํน์ง:
โข ํญ์ Optional ํ์
์ด์ด์ผ ํจ (var๋ก ์ ์ธ)
โข ์ฐธ์กฐ ๋์์ด ํด์ ๋๋ฉด ์๋์ผ๋ก nil์ด ๋จ
โข ์ฐธ์กฐ ๋์์ ์๋ช
์ฃผ๊ธฐ๊ฐ ๋ ์งง์ ๋ ์ฌ์ฉ
โข ์์ ํ์ง๋ง ์ฝ๊ฐ์ ์ค๋ฒํค๋ ์กด์ฌ
์ธ์ ์ฌ์ฉํ ๊น?
์ฐธ์กฐํ๋ ๊ฐ์ฒด๊ฐ ๋จผ์ ํด์ ๋ ๊ฐ๋ฅ์ฑ์ด ์์ ๋! ์๋ฅผ ๋ค์ด ๋ธ๋ฆฌ๊ฒ์ดํธ ํจํด์์ ๋ธ๋ฆฌ๊ฒ์ดํธ๋ ์ธ์ ๋ nil์ด ๋ ์ ์์ผ๋๊น weak์ ์จ์ผ ํด. ๐ฏ
class ViewController {
weak var delegate: ViewControllerDelegate? // weak ์ฌ์ฉ!
func someAction() {
// delegate๊ฐ nil์ผ ์ ์์ผ๋ ์ต์
๋ ์ฒด์ด๋ ์ฌ์ฉ
delegate?.didPerformAction()
}
}
protocol ViewControllerDelegate: AnyObject { // class ํ๋กํ ์ฝ์ด์ด์ผ ํจ
func didPerformAction()
}
// ์ค์ ์ฌ์ฉ ์์
class ParentViewController: ViewControllerDelegate {
var childVC: ViewController?
func setupChild() {
childVC = ViewController()
childVC?.delegate = self // weak์ด๋ฏ๋ก ์ํ ์ฐธ์กฐ ์์!
}
func didPerformAction() {
print("์ก์
์ํ๋จ!")
}
deinit {
print("ParentViewController ํด์ ") // ์ ์ ํธ์ถ๋จ!
}
}
๐ฃ Unowned Reference (๋ฏธ์์ ์ฐธ์กฐ)
ํน์ง:
โข Optional์ด ์๋์ด๋ ๋จ (let์ผ๋ก ์ ์ธ ๊ฐ๋ฅ)
โข ์ฐธ์กฐ ๋์์ด ํด์ ๋์ด๋ nil์ด ๋์ง ์์
โข ์ฐธ์กฐ ๋์์ด ํญ์ ์กด์ฌํ๋ค๊ณ ๊ฐ์
โข ํด์ ๋ ๊ฐ์ฒด์ ์ ๊ทผํ๋ฉด ํฌ๋์ ๋ฐ์! ๐ฅ
โข weak๋ณด๋ค ์ฝ๊ฐ ๋ ๋น ๋ฆ (nil ์ฒดํฌ ์ค๋ฒํค๋ ์์)
์ธ์ ์ฌ์ฉํ ๊น?
์ฐธ์กฐํ๋ ๊ฐ์ฒด๊ฐ ์ ๋ ๋จผ์ ํด์ ๋์ง ์์ ๋! ์๋ฅผ ๋ค์ด ์ ์ฉ์นด๋์ ๊ณ ๊ฐ์ ๊ด๊ณ์ฒ๋ผ, ์ ์ฉ์นด๋๋ ํญ์ ๊ณ ๊ฐ์ด ์์ด์ผ ์กด์ฌํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํด. ๐ฏ
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
print("\(name) ๊ณ ๊ฐ ์์ฑ")
}
deinit {
print("\(name) ๊ณ ๊ฐ ํด์ ")
}
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned ์ฌ์ฉ!
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
print("์นด๋ \(number) ์์ฑ")
}
deinit {
print("์นด๋ \(number) ํด์ ")
}
}
// ์ฌ์ฉ ์์
var john: Customer? = Customer(name: "John")
john?.card = CreditCard(number: "1234-5678-9012-3456", customer: john!)
// Customer๊ฐ ํด์ ๋๋ฉด CreditCard๋ ์๋์ผ๋ก ํด์ ๋จ
john = nil
// ์ถ๋ ฅ:
// John ๊ณ ๊ฐ ํด์
// ์นด๋ 1234-5678-9012-3456 ํด์
// โ ๏ธ ์ฃผ์: ์ด๋ ๊ฒ ํ๋ฉด ํฌ๋์!
var card: CreditCard?
do {
var tempCustomer: Customer? = Customer(name: "Temp")
card = CreditCard(number: "9999", customer: tempCustomer!)
tempCustomer = nil // Customer ํด์
}
// print(card?.customer.name) // ๐ฅ ํฌ๋์! customer๊ฐ ์ด๋ฏธ ํด์ ๋จ
weak์ ์ฌ์ฉํด์ผ ํ ๋:
โข ๋ธ๋ฆฌ๊ฒ์ดํธ ํจํด
โข ๋ถ๋ชจ-์์ ๊ด๊ณ์์ ์์์ด ๋ถ๋ชจ๋ฅผ ์ฐธ์กฐ
โข ์ต์ ๋ฒ ํจํด
โข ์ฐธ์กฐ ๋์์ด ๋จผ์ ํด์ ๋ ๊ฐ๋ฅ์ฑ์ด ์๋ ๋ชจ๋ ๊ฒฝ์ฐ
unowned๋ฅผ ์ฌ์ฉํด์ผ ํ ๋:
โข ์ฐธ์กฐ ๋์์ด ํญ์ ์กด์ฌํ๋ค๊ณ 100% ํ์ ํ ๋
โข ์๋ช ์ฃผ๊ธฐ๊ฐ ๋ช ํํ๊ฒ ์ฐ๊ฒฐ๋ ๊ฒฝ์ฐ
โข ์ฝ๊ฐ์ ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ ๊ฒฝ์ฐ
๐ก ํ์ ์ด ์์ง ์์ผ๋ฉด? weak์ ์ฌ์ฉํด! ์์ ์ด ์ต์ฐ์ ์ด์ผ. ํฌ๋์๋ณด๋ค๋ ์ฝ๊ฐ์ ์ฑ๋ฅ ์ ํ๊ฐ ํจ์ฌ ๋์. ๐
| ๋น๊ต ํญ๋ชฉ | weak | unowned | strong (๊ธฐ๋ณธ) |
|---|---|---|---|
| ์ฐธ์กฐ ์นด์ดํธ ์ฆ๊ฐ | โ ์๋์ค | โ ์๋์ค | โ ์ |
| ํ์ | ํญ์ Optional | Optional ๊ฐ๋ฅ | ๋ชจ๋ ๊ฐ๋ฅ |
| ํด์ ์ ๋์ | ์๋์ผ๋ก nil | ๊ทธ๋๋ก ์ ์ง | RC ๊ฐ์ |
| ์์ ์ฑ | โ ์์ | โ ๏ธ ์ฃผ์ ํ์ | โ ์์ |
| ์ฑ๋ฅ | ์ฝ๊ฐ ๋๋ฆผ | ๋น ๋ฆ | ๋ณดํต |
| ์ฌ์ฉ ์๊ธฐ | ๋ถํ์คํ ๋ | ํ์คํ ๋ | ๊ธฐ๋ณธ |
๐ ํด๋ก์ ์ ์บก์ฒ ๋ฆฌ์คํธ: ๊ฐ์ฅ ํํ ํจ์
Swift ๊ฐ๋ฐ์๋ค์ด ๊ฐ์ฅ ๋ง์ด ์ค์ํ๋ ๋ถ๋ถ์ด ๋ฐ๋ก ํด๋ก์ ์์์ ์ํ ์ฐธ์กฐ์ผ! ํด๋ก์ ๋ ์ฃผ๋ณ ์ปจํ
์คํธ์ ๋ณ์๋ค์ "์บก์ฒ"ํ๋๋ฐ, ์ด๋ self๋ฅผ ๊ฐํ๊ฒ ์บก์ฒํ๋ฉด ์ํ ์ฐธ์กฐ๊ฐ ๋ฐ์ํด. ๐ฑ
ํนํ ๋น๋๊ธฐ ์์
, ์ ๋๋ฉ์ด์
, ๋คํธ์ํฌ ์ฝ๋ฐฑ ๋ฑ์์ ํด๋ก์ ๋ฅผ ๋ง์ด ์ฌ์ฉํ๋๋ฐ, ์ฌ๊ธฐ์ ์ค์ํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๊ธฐ ์ฌ์. ์ฌ๋ฅ๋ท์์ iOS ๊ฐ๋ฐ ๋ฉํ ๋ง์ ๋ฐ์ ๋๋ ์ด ๋ถ๋ถ์ ๊ฐ์ฅ ๊ฐ์กฐํ๋ค๊ณ ํด! ๐ฑ
ํด๋ก์ ๋ ์ฐธ์กฐ ํ์ ์ด์ผ. ๊ทธ๋์ ํด๋ก์ ๊ฐ self๋ฅผ ์บก์ฒํ๊ณ , self๊ฐ ๊ทธ ํด๋ก์ ๋ฅผ ํ๋กํผํฐ๋ก ๊ฐ์ง๊ณ ์์ผ๋ฉด? ์ํ ์ฐธ์กฐ ๋ฐ์! ํนํ ๋ค์ ์ํฉ๋ค์ ์กฐ์ฌํด์ผ ํด:
โข ์๋ฃ ํธ๋ค๋ฌ(Completion Handler)
โข ์ ๋๋ฉ์ด์ ๋ธ๋ก
โข ํ์ด๋จธ ์ฝ๋ฐฑ
โข ๋น๋๊ธฐ ๋คํธ์ํฌ ์์ฒญ
โข GCD ๋์คํจ์น ํ
โข Combine ๊ตฌ๋
์ด๋ฐ ์ํฉ์์๋ ํญ์ ์บก์ฒ ๋ฆฌ์คํธ๋ฅผ ์ฌ์ฉํด์ผ ํด! ๐ฏ
// โ ์ํ ์ฐธ์กฐ ๋ฐ์ ์์
class NetworkManager {
var completionHandler: (() -> Void)?
func fetchData() {
// self๋ฅผ ๊ฐํ๊ฒ ์บก์ฒ -> ์ํ ์ฐธ์กฐ!
completionHandler = {
self.processData() // ๐ ๋ฌธ์ !
}
}
func processData() {
print("๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ค...")
}
deinit {
print("NetworkManager ํด์ ") // ํธ์ถ๋์ง ์์!
}
}
// โ
ํด๊ฒฐ ๋ฐฉ๋ฒ 1: [weak self] ์ฌ์ฉ
class NetworkManagerFixed1 {
var completionHandler: (() -> Void)?
func fetchData() {
completionHandler = { [weak self] in
self?.processData() // self๊ฐ nil์ผ ์ ์์
}
}
func processData() {
print("๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ค...")
}
deinit {
print("NetworkManager ํด์ ") // ์ ์ ํธ์ถ!
}
}
// โ
ํด๊ฒฐ ๋ฐฉ๋ฒ 2: [weak self] + guard let ํจํด
class NetworkManagerFixed2 {
var completionHandler: (() -> Void)?
func fetchData() {
completionHandler = { [weak self] in
guard let self = self else { return }
// ์ด์ self๋ฅผ ์์ ํ๊ฒ ์ฌ์ฉ ๊ฐ๋ฅ
self.processData()
self.updateUI()
}
}
func processData() {
print("๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ค...")
}
func updateUI() {
print("UI ์
๋ฐ์ดํธ ์ค...")
}
deinit {
print("NetworkManager ํด์ ") // ์ ์ ํธ์ถ!
}
}
// โ
ํด๊ฒฐ ๋ฐฉ๋ฒ 3: [unowned self] ์ฌ์ฉ (ํ์คํ ๋๋ง!)
class NetworkManagerFixed3 {
var completionHandler: (() -> Void)?
func fetchData() {
completionHandler = { [unowned self] in
// self๊ฐ ํญ์ ์กด์ฌํ๋ค๊ณ ํ์ ํ ๋๋ง ์ฌ์ฉ
self.processData()
}
}
func processData() {
print("๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ค...")
}
deinit {
print("NetworkManager ํด์ ")
}
}
๐ฏ ์ค์ ์์: ๋คํธ์ํฌ ์์ฒญ
class ViewController {
var data: [String] = []
// โ ์๋ชป๋ ์์
func loadDataWrong() {
URLSession.shared.dataTask(with: URL(string: "https://api.example.com")!) { data, response, error in
// ํด๋ก์ ๊ฐ self๋ฅผ ๊ฐํ๊ฒ ์บก์ฒ!
self.data = self.parseData(data) // ๐ ์ํ ์ฐธ์กฐ ๊ฐ๋ฅ์ฑ
self.updateUI()
}.resume()
}
// โ
์ฌ๋ฐ๋ฅธ ์์ 1: weak self
func loadDataCorrect1() {
URLSession.shared.dataTask(with: URL(string: "https://api.example.com")!) { [weak self] data, response, error in
guard let self = self else { return }
self.data = self.parseData(data)
DispatchQueue.main.async { [weak self] in
self?.updateUI() // UI ์
๋ฐ์ดํธ๋ ๋ฉ์ธ ์ค๋ ๋์์!
}
}.resume()
}
// โ
์ฌ๋ฐ๋ฅธ ์์ 2: ์บก์ฒ ๋ฆฌ์คํธ ํ์ฉ
func loadDataCorrect2() {
URLSession.shared.dataTask(with: URL(string: "https://api.example.com")!) { [weak self] data, response, error in
guard let self = self,
let data = data else { return }
let parsedData = self.parseData(data)
DispatchQueue.main.async { [weak self] in
self?.data = parsedData
self?.updateUI()
}
}.resume()
}
func parseData(_ data: Data?) -> [String] {
// ๋ฐ์ดํฐ ํ์ฑ ๋ก์ง
return []
}
func updateUI() {
print("UI ์
๋ฐ์ดํธ!")
}
deinit {
print("ViewController ํด์ ")
}
}
1. ๊ธฐ๋ณธ ์์น: ํด๋ก์ ์์ self๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ํญ์ ์บก์ฒ ๋ฆฌ์คํธ๋ฅผ ๊ณ ๋ คํด!
2. weak self ํจํด:
[weak self] in
guard let self = self else { return }์ด ํจํด์ ์ต๊ดํํ๋ฉด ๋๋ถ๋ถ์ ๋ฌธ์ ๋ฅผ ์๋ฐฉํ ์ ์์ด! ๐ก๏ธ
3. ์ฌ๋ฌ ๋ณ์ ์บก์ฒ:
[weak self, weak delegate, unowned parent] inํ์ํ ๋งํผ ์บก์ฒ ๋ฆฌ์คํธ์ ์ถ๊ฐ ๊ฐ๋ฅํด!
4. ๊ฐ ํ์ ์บก์ฒ:
[weak self, value = someValue] in๊ฐ ํ์ ์ ๋ณต์ฌ๋๋ฏ๋ก ์ํ ์ฐธ์กฐ ๊ฑฑ์ ์์ด!
5. ์งง์ ์๋ช ์ฃผ๊ธฐ:
์ ๋๋ฉ์ด์ ์ฒ๋ผ ๊ธ๋ฐฉ ๋๋๋ ํด๋ก์ ๋ weak ์์ด๋ ๊ด์ฐฎ์ ์ ์์ด. ํ์ง๋ง ์์ ์ ์ํด ์ฌ์ฉํ๋ ๊ฒ ์ข์! ๐
// ์ค์ ์์: ์ ๋๋ฉ์ด์
๊ณผ ํ์ด๋จธ
class AnimationViewController {
var timer: Timer?
var isAnimating = false
// โ
์ ๋๋ฉ์ด์
- weak self ์ฌ์ฉ
func startAnimation() {
UIView.animate(withDuration: 1.0, animations: { [weak self] in
self?.view.alpha = 0.5
}) { [weak self] finished in
self?.isAnimating = false
print("์ ๋๋ฉ์ด์
์๋ฃ!")
}
}
// โ
ํ์ด๋จธ - weak self ์ฌ์ฉ (์ค์!)
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
self?.updateTime()
}
}
func updateTime() {
print("์๊ฐ ์
๋ฐ์ดํธ: \(Date())")
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
deinit {
stopTimer() // ํ์ด๋จธ๋ ๋ฐ๋์ ์ ๋ฆฌ!
print("AnimationViewController ํด์ ")
}
}
// Combine ํ๋ ์์ํฌ ์์
import Combine
class CombineViewController {
var cancellables = Set<AnyCancellable>()
var dataPublisher = PassthroughSubject<String, Never>()
func setupSubscription() {
// โ
Combine์์๋ weak self ์ฌ์ฉ!
dataPublisher
.sink { [weak self] value in
self?.handleData(value)
}
.store(in: &cancellables)
}
func handleData(_ data: String) {
print("๋ฐ์ดํฐ ์์ : \(data)")
}
deinit {
print("CombineViewController ํด์ ")
}
}
๐ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋๋ฒ๊น : ๋ฌธ์ ์ฐพ๊ธฐ
์ด๋ก ์ ์๊ฒ ๋๋ฐ, ์ค์ ๋ก ๋ด ์ฝ๋์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ์๋์ง ์ด๋ป๊ฒ ์ ์ ์์๊น? ๐ค Xcode๋ ์ ๋ง ๊ฐ๋ ฅํ ๋๋ฒ๊น ๋๊ตฌ๋ค์ ์ ๊ณตํด! ์ด ๋๊ตฌ๋ค์ ํ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์์ด. ๐ง
๐ ๏ธ Instruments: Memory Leaks ๋๊ตฌ
Instruments๋ Xcode์ ๋ด์ฅ๋ ํ๋กํ์ผ๋ง ๋๊ตฌ์ผ. ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ฐพ๋ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๋ฐฉ๋ฒ์ด์ง!
์ฌ์ฉ ๋ฐฉ๋ฒ:
1๏ธโฃ Xcode์์ Product โ Profile (โ + I)
2๏ธโฃ Leaks ํ
ํ๋ฆฟ ์ ํ
3๏ธโฃ ์ฑ ์คํํ๋ฉด์ ์ฌ๋ฌ ํ๋ฉด ์ด๋
4๏ธโฃ ๋นจ๊ฐ์ ๋ง๋๊ฐ ๋ํ๋๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ๊ฒฌ! ๐จ
5๏ธโฃ ํด๋น ๋ถ๋ถ ํด๋ฆญํ๋ฉด ์ด๋์ ๋์๊ฐ ๋ฐ์ํ๋์ง ํ์ธ ๊ฐ๋ฅ
ํ: ํ๋ฉด์ ์ฌ๋ฌ ๋ฒ ์๋ค ๊ฐ๋ค ํด๋ด. ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ณ์ ์ฆ๊ฐํ๋ฉด ๋ฌธ์ ๊ฐ ์๋ ๊ฑฐ์ผ! ๐
๐ Memory Graph Debugger
Xcode์ Memory Graph Debugger๋ ์ค์๊ฐ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ํ๋ฅผ ์๊ฐํํด์ค!
์ฌ์ฉ ๋ฐฉ๋ฒ:
1๏ธโฃ ์ฑ ์คํ ์ค ๋๋ฒ๊ทธ ๋ฐ์ ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ํ ์์ด์ฝ ํด๋ฆญ
2๏ธโฃ ์ผ์ชฝ์ ๋ชจ๋ ๊ฐ์ฒด๋ค์ด ๋ํ๋จ
3๏ธโฃ ๋ณด๋ผ์ ๋๋ํ(!)๊ฐ ์์ผ๋ฉด ์ํ ์ฐธ์กฐ ์์ฌ! ๐
4๏ธโฃ ๊ฐ์ฒด๋ฅผ ์ ํํ๋ฉด ์ฐธ์กฐ ๊ด๊ณ๋ฅผ ๊ทธ๋ํ๋ก ๋ณด์ฌ์ค
5๏ธโฃ ์ํ ์ฐธ์กฐ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์ธ ๊ฐ๋ฅ!
์ฅ์ : ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์์ด์ ๋๋ฒ๊น
์ด ์ ๋ง ํธํด! ๐ฏ
๐งช deinit์ผ๋ก ํ์ธํ๊ธฐ
๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ deinit์ print๋ฌธ์ ๋ฃ๋ ๊ฑฐ์ผ!
๋ฐฉ๋ฒ:
class MyViewController: UIViewController {
deinit {
print("โ
MyViewController๊ฐ ์ ์์ ์ผ๋ก ํด์ ๋จ!")
}
}
// ํ๋ฉด์ ๋๊ฐ๋๋ฐ deinit์ด ํธ์ถ๋์ง ์์ผ๋ฉด?
// -> ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ์! ๐จ
ํ: ๋ชจ๋ ๋ทฐ ์ปจํธ๋กค๋ฌ์ ์ค์ํ ํด๋์ค์ deinit์ ์ถ๊ฐํด๋ด. ๊ฐ๋ฐ ์ค์ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐ๋ก๋ฐ๋ก ๋ฐ๊ฒฌํ ์ ์์ด! ๐
1. ํ๋ฉด ์ ํ ํ ์คํธ
โข ํ๋ฉด์ ์ฌ๋ฌ ๋ฒ ์๋ค ๊ฐ๋ค ํด๋ณด๊ธฐ
โข deinit์ด ํธ์ถ๋๋์ง ํ์ธ
โข Memory Graph์์ ์ธ์คํด์ค ๊ฐ์ ํ์ธ
2. ํด๋ก์ ํ์ธ
โข ๋ชจ๋ ํด๋ก์ ์ [weak self] ์๋์ง ์ฒดํฌ
โข ํนํ ๋น๋๊ธฐ ์์ , ํ์ด๋จธ, ์ ๋๋ฉ์ด์
3. ๋ธ๋ฆฌ๊ฒ์ดํธ ํ์ธ
โข ๋ชจ๋ ๋ธ๋ฆฌ๊ฒ์ดํธ๊ฐ weak์ธ์ง ํ์ธ
โข ํ๋กํ ์ฝ์ด AnyObject๋ฅผ ์์ํ๋์ง ํ์ธ
4. ํ์ด๋จธ์ ์ต์ ๋ฒ
โข Timer๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ๋์ invalidate
โข NotificationCenter ์ต์ ๋ฒ ์ ๊ฑฐ
โข KVO ์ต์ ๋ฒ ์ ๊ฑฐ
5. ์ ๊ธฐ์ ์ธ ํ๋กํ์ผ๋ง
โข ์ฃผ์ ๊ธฐ๋ฅ ๊ฐ๋ฐ ํ Instruments ์คํ
โข ๋ฆด๋ฆฌ์ฆ ์ ์ ์ฒด ์ฑ ํ๋กํ์ผ๋ง
์ด ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์ต๊ดํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์์ด! โ
// ์ค์ ๋๋ฒ๊น
์์
class DebuggableViewController: UIViewController {
var timer: Timer?
var observations: [NSKeyValueObservation] = []
override func viewDidLoad() {
super.viewDidLoad()
print("๐ข \(type(of: self)) viewDidLoad")
setupTimer()
setupObservations()
}
func setupTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.updateUI()
}
}
func setupObservations() {
let observation = observe(\.view.frame) { [weak self] _, _ in
self?.handleFrameChange()
}
observations.append(observation)
}
func updateUI() {
print("UI ์
๋ฐ์ดํธ")
}
func handleFrameChange() {
print("ํ๋ ์ ๋ณ๊ฒฝ")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("๐ก \(type(of: self)) viewWillDisappear")
}
deinit {
print("โ
\(type(of: self)) deinit - ์ ์ ํด์ !")
timer?.invalidate()
observations.removeAll()
// deinit์ด ํธ์ถ๋์ง ์์ผ๋ฉด?
// -> ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ์! ์์ ๋ก๊ทธ๋ค์ ๋ณด๊ณ ์์ธ ์ถ์ !
}
}
// ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฐ์ง ํฌํผ
class MemoryLeakDetector {
static func track(_ object: AnyObject, file: String = #file, line: Int = #line) {
let className = String(describing: type(of: object))
print("๐ [\(file):\(line)] \(className) ์์ฑ๋จ")
// ์ฝํ ์ฐธ์กฐ๋ก ๊ฐ์ฒด ์ถ์
weak var weakObject = object
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
if weakObject != nil {
print("โ ๏ธ [\(file):\(line)] \(className) 5์ด ํ์๋ ๋ฉ๋ชจ๋ฆฌ์ ์กด์ฌ - ๋์ ์์ฌ!")
} else {
print("โ
[\(file):\(line)] \(className) ์ ์ ํด์ ๋จ")
}
}
}
}
// ์ฌ์ฉ ์์
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
MemoryLeakDetector.track(self)
}
}
โก ์ฑ๋ฅ ์ต์ ํ: ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ
๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๋ ๊ฒ๋ ์ค์ํ์ง๋ง, ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ๋ ์ ๋ง ์ค์ํด! ํนํ ๋ชจ๋ฐ์ผ ๊ธฐ๊ธฐ๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ํ์ ์ด๋๊น ๋์ฑ ์ ๊ฒฝ ์จ์ผ ํด. ๐ฑ๐จ
๐ผ๏ธ 1. ์ด๋ฏธ์ง ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
์ด๋ฏธ์ง๋ ์ฑ์์ ๊ฐ์ฅ ๋ง์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฐจ์งํด! ํนํ ๊ณ ํด์๋ ์ด๋ฏธ์ง๋ ์ ๋ง ์ํํด. ๐ฑ
// โ ๋นํจ์จ์ : ์๋ณธ ํฌ๊ธฐ ๊ทธ๋๋ก ๋ก๋
class BadImageViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
func loadImage() {
// 4K ์ด๋ฏธ์ง๋ฅผ ์์ imageView์ ํ์ -> ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น!
imageView.image = UIImage(named: "huge_image.jpg")
}
}
// โ
ํจ์จ์ : ํ์ํ ํฌ๊ธฐ๋ก ๋ฆฌ์ฌ์ด์ง
class GoodImageViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
func loadImage() {
guard let image = UIImage(named: "huge_image.jpg") else { return }
// imageView ํฌ๊ธฐ์ ๋ง๊ฒ ๋ฆฌ์ฌ์ด์ง
let targetSize = imageView.bounds.size
imageView.image = resizeImage(image: image, targetSize: targetSize)
}
func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: targetSize))
}
}
// ๋ ๋์ ๋ฐฉ๋ฒ: ๋ค์ด์ํ๋ง
func downsampleImage(at url: URL, to size: CGSize) -> UIImage? {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, imageSourceOptions) else {
return nil
}
let maxDimensionInPixels = max(size.width, size.height) * UIScreen.main.scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
return nil
}
return UIImage(cgImage: downsampledImage)
}
}
// ๐ ์ต๊ณ : ์ด๋ฏธ์ง ์บ์ฑ + ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
class OptimizedImageManager {
static let shared = OptimizedImageManager()
private let cache = NSCache<NSString, UIImage>()
init() {
// ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ ์บ์ ์ ๋ฆฌ
NotificationCenter.default.addObserver(
self,
selector: #selector(clearCache),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
// ์บ์ ํฌ๊ธฐ ์ ํ (50MB)
cache.totalCostLimit = 50 * 1024 * 1024
}
func loadImage(named name: String, size: CGSize) -> UIImage? {
let cacheKey = "\(name)_\(size.width)x\(size.height)" as NSString
// ์บ์์์ ํ์ธ
if let cachedImage = cache.object(forKey: cacheKey) {
return cachedImage
}
// ์ด๋ฏธ์ง ๋ก๋ ๋ฐ ๋ฆฌ์ฌ์ด์ง
guard let image = UIImage(named: name) else { return nil }
let resized = resizeImage(image: image, targetSize: size)
// ์บ์์ ์ ์ฅ (์ด๋ฏธ์ง ํฌ๊ธฐ๋ฅผ cost๋ก ์ค์ )
if let resized = resized,
let data = resized.pngData() {
cache.setObject(resized, forKey: cacheKey, cost: data.count)
}
return resized
}
@objc func clearCache() {
print("โ ๏ธ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ! ์ด๋ฏธ์ง ์บ์ ์ ๋ฆฌ")
cache.removeAllObjects()
}
func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: targetSize))
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
โฐ 2. ์ง์ฐ ์ด๊ธฐํ (Lazy Initialization)
ํ์ํ ๋๋ง ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ์ฝํ ์ ์์ด! ๐ฏ
// โ ๋นํจ์จ์ : ๋ชจ๋ ๊ฒ์ ๋ฏธ๋ฆฌ ์์ฑ
class BadViewController: UIViewController {
let heavyObject1 = HeavyObject() // ์ฌ์ฉํ์ง ์์๋ ์์ฑ๋จ
let heavyObject2 = HeavyObject()
let heavyObject3 = HeavyObject()
override func viewDidLoad() {
super.viewDidLoad()
// heavyObject1๋ง ์ฌ์ฉํ๋๋ฐ ๋๋จธ์ง๋ ๋ฉ๋ชจ๋ฆฌ ์ฐจ์ง!
heavyObject1.doSomething()
}
}
// โ
ํจ์จ์ : ํ์ํ ๋๋ง ์์ฑ
class GoodViewController: UIViewController {
lazy var heavyObject1 = HeavyObject() // ์ฒ์ ์ ๊ทผํ ๋ ์์ฑ
lazy var heavyObject2 = HeavyObject()
lazy var heavyObject3 = HeavyObject()
// ๋ ๋ณต์กํ ์ด๊ธฐํ๋ ๊ฐ๋ฅ
lazy var complexObject: ComplexObject = {
let object = ComplexObject()
object.configure()
return object
}()
override func viewDidLoad() {
super.viewDidLoad()
// heavyObject1๋ง ์์ฑ๋จ!
heavyObject1.doSomething()
}
}
// ๐ ์ค์ ์์: ํ
์ด๋ธ๋ทฐ ์
class OptimizedTableViewCell: UITableViewCell {
// ์ด๋ฏธ์ง๋ทฐ๋ฅผ lazy๋ก ์ ์ธ
lazy var thumbnailImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
// ๋ณต์กํ ๋ทฐ๋ฅผ lazy๋ก ์ ์ธ
lazy var detailContainerView: UIView = {
let view = UIView()
view.backgroundColor = .systemGray6
view.layer.cornerRadius = 8
// ๋ณต์กํ ์๋ธ๋ทฐ ์ถ๊ฐ...
return view
}()
func configure(with data: CellData) {
// ํ์ํ ๊ฒฝ์ฐ์๋ง ๋ทฐ ์์ฑ
if data.hasImage {
thumbnailImageView.image = data.image
contentView.addSubview(thumbnailImageView)
}
if data.hasDetail {
contentView.addSubview(detailContainerView)
}
}
}
class HeavyObject {
init() {
print("HeavyObject ์์ฑ - ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ!")
}
func doSomething() {
print("์์
์ํ")
}
}
class ComplexObject {
func configure() {
print("๋ณต์กํ ์ค์ ์ํ")
}
}
struct CellData {
let hasImage: Bool
let image: UIImage?
let hasDetail: Bool
}
๐ 3. Autoreleasepool ํ์ฉ
๋ฐ๋ณต๋ฌธ์์ ๋ง์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋๋ autoreleasepool์ ์ฌ์ฉํด์ผ ํด!
// โ ๋นํจ์จ์ : ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ณ์ ์์
func processImagesWrong() {
for i in 0..<10000 {
let image = createLargeImage(index: i)
processImage(image)
// ์ด๋ฏธ์ง๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ์ ์์!
}
// ์ฌ๊ธฐ์์ผ ๋ฉ๋ชจ๋ฆฌ ํด์
}
// โ
ํจ์จ์ : autoreleasepool ์ฌ์ฉ
func processImagesCorrect() {
for i in 0..<10000 {
autoreleasepool {
let image = createLargeImage(index: i)
processImage(image)
// ๋ธ๋ก์ด ๋๋๋ฉด ์ฆ์ ๋ฉ๋ชจ๋ฆฌ ํด์ !
}
}
}
// ๐ ์ค์ ์์: ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
class DataProcessor {
func processLargeDataset() {
let dataset = loadLargeDataset() // 10๋ง ๊ฐ์ ๋ฐ์ดํฐ
var results: [ProcessedData] = []
for (index, data) in dataset.enumerated() {
autoreleasepool {
let processed = processData(data)
results.append(processed)
// ์งํ ์ํฉ ์ถ๋ ฅ (1000๊ฐ๋ง๋ค)
if index % 1000 == 0 {
print("์ฒ๋ฆฌ ์ค: \(index)/\(dataset.count)")
}
}
}
saveResults(results)
}
func loadLargeDataset() -> [RawData] {
// ๋๋ ๋ฐ์ดํฐ ๋ก๋
return []
}
func processData(_ data: RawData) -> ProcessedData {
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ (๋ฉ๋ชจ๋ฆฌ ๋ง์ด ์ฌ์ฉ)
return ProcessedData()
}
func saveResults(_ results: [ProcessedData]) {
print("๊ฒฐ๊ณผ ์ ์ฅ: \(results.count)๊ฐ")
}
}
struct RawData {}
struct ProcessedData {}
func createLargeImage(index: Int) -> UIImage {
// ํฐ ์ด๋ฏธ์ง ์์ฑ
return UIImage()
}
func processImage(_ image: UIImage) {
// ์ด๋ฏธ์ง ์ฒ๋ฆฌ
}
โ ์ด๋ฏธ์ง ๊ด๋ จ
โข ํ์ ํฌ๊ธฐ์ ๋ง๊ฒ ๋ฆฌ์ฌ์ด์ง
โข ๋ค์ด์ํ๋ง ํ์ฉ
โข ์ ์ ํ ์ด๋ฏธ์ง ํฌ๋งท ์ฌ์ฉ (JPEG vs PNG)
โข ์ด๋ฏธ์ง ์บ์ฑ ๊ตฌํ
โ ๊ฐ์ฒด ์์ฑ
โข lazy ํ๋กํผํฐ ํ์ฉ
โข ์ฑ๊ธํค ํจํด ์ ์ ํ ์ฌ์ฉ
โข ๊ฐ์ฒด ํ๋ง ๊ณ ๋ ค
โ ์ปฌ๋ ์
โข ์ ์ ํ ์ปฌ๋ ์ ํ์ ์ ํ
โข ์์ฝ ์ฉ๋ ์ค์ (reserveCapacity)
โข ๋ถํ์ํ ๋ณต์ฌ ๋ฐฉ์ง
โ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ๋์
โข didReceiveMemoryWarning ๊ตฌํ
โข ์บ์ ์ ๋ฆฌ
โข ๋ถํ์ํ ๋ฐ์ดํฐ ํด์
โ ํ๋กํ์ผ๋ง
โข ์ ๊ธฐ์ ์ธ Instruments ์คํ
โข ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
โข ๋ณ๋ชฉ ์ง์ ํ์ ๋ฐ ๊ฐ์
์ด๋ฐ ์ต์ ํ๋ค์ ์ฑ์ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์์ผ์ค! ๐
๐จ ์ค์ ํจํด: ์์ฃผ ์ฌ์ฉํ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ํจํด
์ด์ ์ค์ ์์ ์์ฃผ ๋ง์ฃผ์น๋ ์ํฉ๋ค๊ณผ ๊ทธ ํด๊ฒฐ ํจํด์ ์์๋ณผ๊ฒ! ์ด ํจํด๋ค์ ์ตํ๋๋ฉด ๋๋ถ๋ถ์ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ์๋ฐฉํ ์ ์์ด. ๐ช
๐ฏ ํจํด 1: ๋ธ๋ฆฌ๊ฒ์ดํธ ํจํด
// โ
์ฌ๋ฐ๋ฅธ ๋ธ๋ฆฌ๊ฒ์ดํธ ๊ตฌํ
protocol CustomViewDelegate: AnyObject { // AnyObject ํ์!
func customViewDidTap(_ view: CustomView)
func customView(_ view: CustomView, didChangeValue value: String)
}
class CustomView: UIView {
weak var delegate: CustomViewDelegate? // weak ํ์!
private var value: String = "" {
didSet {
delegate?.customView(self, didChangeValue: value)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
delegate?.customViewDidTap(self)
}
}
class ViewController: UIViewController, CustomViewDelegate {
var customView: CustomView!
override func viewDidLoad() {
super.viewDidLoad()
customView = CustomView()
customView.delegate = self // ์ํ ์ฐธ์กฐ ์์!
}
func customViewDidTap(_ view: CustomView) {
print("๋ทฐ ํญ๋จ!")
}
func customView(_ view: CustomView, didChangeValue value: String) {
print("๊ฐ ๋ณ๊ฒฝ: \(value)")
}
deinit {
print("ViewController ํด์ ") // ์ ์ ํธ์ถ๋จ!
}
}
๐ฏ ํจํด 2: ํด๋ก์ ๊ธฐ๋ฐ ์ฝ๋ฐฑ
// โ
์ฌ๋ฐ๋ฅธ ํด๋ก์ ์ฝ๋ฐฑ ๊ตฌํ
class NetworkService {
typealias CompletionHandler = (Result<Data, Error>) -> Void
func fetchData(completion: @escaping CompletionHandler) {
URLSession.shared.dataTask(with: URL(string: "https://api.example.com")!) { data, response, error in
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}.resume()
}
}
class DataViewController: UIViewController {
let networkService = NetworkService()
var data: Data?
func loadData() {
// โ
weak self ์ฌ์ฉ
networkService.fetchData { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let data):
self.data = data
DispatchQueue.main.async { [weak self] in
self?.updateUI()
}
case .failure(let error):
DispatchQueue.main.async { [weak self] in
self?.showError(error)
}
}
}
}
func updateUI() {
print("UI ์
๋ฐ์ดํธ")
}
func showError(_ error: Error) {
print("์๋ฌ: \(error)")
}
deinit {
print("DataViewController ํด์ ")
}
}
๐ฏ ํจํด 3: ์ต์ ๋ฒ ํจํด
// โ
์ฌ๋ฐ๋ฅธ NotificationCenter ์ฌ์ฉ
class NotificationViewController: UIViewController {
var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setupNotifications()
}
func setupNotifications() {
// weak self ์ฌ์ฉ
observer = NotificationCenter.default.addObserver(
forName: .dataUpdated,
object: nil,
queue: .main
) { [weak self] notification in
self?.handleDataUpdate(notification)
}
}
func handleDataUpdate(_ notification: Notification) {
print("๋ฐ์ดํฐ ์
๋ฐ์ดํธ๋จ")
}
deinit {
// ์ต์ ๋ฒ ์ ๊ฑฐ ํ์!
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
print("NotificationViewController ํด์ ")
}
}
extension Notification.Name {
static let dataUpdated = Notification.Name("dataUpdated")
}
// ๐ ๋ ๋์ ๋ฐฉ๋ฒ: Combine ์ฌ์ฉ (iOS 13+)
import Combine
class CombineNotificationViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
setupNotifications()
}
func setupNotifications() {
NotificationCenter.default
.publisher(for: .dataUpdated)
.sink { [weak self] notification in
self?.handleDataUpdate(notification)
}
.store(in: &cancellables)
}
func handleDataUpdate(_ notification: Notification) {
print("๋ฐ์ดํฐ ์
๋ฐ์ดํธ๋จ")
}
deinit {
// cancellables๊ฐ ์๋์ผ๋ก ์ ๋ฆฌ๋จ!
print("CombineNotificationViewController ํด์ ")
}
}
๐ฏ ํจํด 4: ํ์ด๋จธ ๊ด๋ฆฌ
// โ ์๋ชป๋ ํ์ด๋จธ ์ฌ์ฉ
class BadTimerViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// Timer๊ฐ self๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐ -> ์ํ ์ฐธ์กฐ!
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(timerFired),
userInfo: nil,
repeats: true
)
}
@objc func timerFired() {
print("ํ์ด๋จธ ์คํ")
}
deinit {
print("BadTimerViewController ํด์ ") // ํธ์ถ๋์ง ์์!
}
}
// โ
์ฌ๋ฐ๋ฅธ ํ์ด๋จธ ์ฌ์ฉ
class GoodTimerViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
func startTimer() {
// ํด๋ก์ ๊ธฐ๋ฐ ํ์ด๋จธ + weak self
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
self?.timerFired()
}
}
func timerFired() {
print("ํ์ด๋จธ ์คํ")
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopTimer() // ํ๋ฉด ๋๊ฐ ๋ ํ์ด๋จธ ์ ๋ฆฌ
}
deinit {
stopTimer() // ์์ ์ ์ํด deinit์์๋ ์ ๋ฆฌ
print("GoodTimerViewController ํด์ ") // ์ ์ ํธ์ถ!
}
}
// ๐ ๋ ๋์ ๋ฐฉ๋ฒ: ํ์ด๋จธ ๋ํผ ํด๋์ค
class SafeTimer {
private var timer: Timer?
private weak var target: AnyObject?
private var action: (() -> Void)?
init(timeInterval: TimeInterval, repeats: Bool, target: AnyObject, action: @escaping () -> Void) {
self.target = target
self.action = action
timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: repeats) { [weak self] _ in
guard let self = self, self.target != nil else {
self?.invalidate()
return
}
self.action?()
}
}
func invalidate() {
timer?.invalidate()
timer = nil
action = nil
}
deinit {
invalidate()
}
}
class SafeTimerViewController: UIViewController {
var safeTimer: SafeTimer?
override func viewDidLoad() {
super.viewDidLoad()
safeTimer = SafeTimer(timeInterval: 1.0, repeats: true, target: self) { [weak self] in
self?.timerFired()
}
}
func timerFired() {
print("์์ ํ ํ์ด๋จธ ์คํ")
}
deinit {
print("SafeTimerViewController ํด์ ") // ์ ์ ํธ์ถ!
}
}
๐ฏ ํจํด 5: ๋ถ๋ชจ-์์ ๊ด๊ณ
// โ
์ฌ๋ฐ๋ฅธ ๋ถ๋ชจ-์์ ๊ด๊ณ ๊ตฌํ
class Parent {
var name: String
var children: [Child] = [] // ๊ฐํ ์ฐธ์กฐ
init(name: String) {
self.name = name
print("\(name) ๋ถ๋ชจ ์์ฑ")
}
func addChild(_ child: Child) {
children.append(child)
child.parent = self
}
deinit {
print("\(name) ๋ถ๋ชจ ํด์ ")
}
}
class Child {
var name: String
weak var parent: Parent? // ์ฝํ ์ฐธ์กฐ๋ก ์ํ ์ฐธ์กฐ ๋ฐฉ์ง!
init(name: String) {
self.name = name
print("\(name) ์์ ์์ฑ")
}
deinit {
print("\(name) ์์ ํด์ ")
}
}
// ์ฌ์ฉ ์์
func testParentChild() {
var parent: Parent? = Parent(name: "๋ถ๋ชจ")
let child1 = Child(name: "์์1")
let child2 = Child(name: "์์2")
parent?.addChild(child1)
parent?.addChild(child2)
print("๋ถ๋ชจ์ ์์๋ค: \(parent?.children.map { $0.name } ?? [])")
print("์์1์ ๋ถ๋ชจ: \(child1.parent?.name ?? "์์")")
parent = nil // ๋ถ๋ชจ ํด์ -> ์์๋ค๋ ์๋ ํด์ !
}
testParentChild()
/* ์ถ๋ ฅ:
๋ถ๋ชจ ๋ถ๋ชจ ์์ฑ
์์1 ์์ ์์ฑ
์์2 ์์ ์์ฑ
๋ถ๋ชจ์ ์์๋ค: ["์์1", "์์2"]
์์1์ ๋ถ๋ชจ: ๋ถ๋ชจ
๋ถ๋ชจ ๋ถ๋ชจ ํด์
์์1 ์์ ํด์
์์2 ์์ ํด์
*/
๐ฌ ๊ณ ๊ธ ์ฃผ์ : Copy-on-Write์ ๊ฐ ํ์ ์ต์ ํ
Swift์ ๊ฐ ํ์ (Struct, Enum)์ ๋ณต์ฌ๋ ๋ ์ค์ ๋ก๋ Copy-on-Write(COW) ์ต์ ํ๋ฅผ ์ฌ์ฉํด! ์ด๊ฒ ๋ญ์ง ์์๋ณด์. ๐ค
๐ Copy-on-Write๋?
๊ฐ ํ์ ์ ๋ณต์ฌ๋ ๋ ์ค์ ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ณต์ฌํ๋ ๊ฒ ์๋๋ผ, ์์ ์ด ์ผ์ด๋ ๋๋ง ๋ณต์ฌํด! ์ด๊ฒ ๋ฐ๋ก COW์ผ. ์์ฒญ๋ ์ฑ๋ฅ ์ต์ ํ์ง! โก
// Swift์ Array๋ COW๋ฅผ ์ฌ์ฉ
var array1 = [1, 2, 3, 4, 5] // ๋ฉ๋ชจ๋ฆฌ ํ ๋น
var array2 = array1 // ๋ณต์ฌํ์ง ์๊ณ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์ฐธ์กฐ!
// ์ด ์์ ์์ array1๊ณผ array2๋ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํด
// ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ํ์ธ
print("array1 ์ฃผ์: \(array1.withUnsafeBufferPointer { $0.baseAddress })")
print("array2 ์ฃผ์: \(array2.withUnsafeBufferPointer { $0.baseAddress })")
// ๊ฐ์ ์ฃผ์!
array2.append(6) // ์ด์ ๋ณต์ฌ ๋ฐ์!
// ์ด์ ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํด
print("array1 ์ฃผ์: \(array1.withUnsafeBufferPointer { $0.baseAddress })")
print("array2 ์ฃผ์: \(array2.withUnsafeBufferPointer { $0.baseAddress })")
// ๋ค๋ฅธ ์ฃผ์!
print("array1: \(array1)") // [1, 2, 3, 4, 5]
print("array2: \(array2)") // [1, 2, 3, 4, 5, 6]
๐ ๏ธ ์ปค์คํ COW ๊ตฌํ
์ฐ๋ฆฌ๋ COW๋ฅผ ๊ตฌํํ ์ ์์ด! ํฐ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋ ์ ์ฉํด. ๐ฏ
// COW๋ฅผ ์ฌ์ฉํ๋ ์ปค์คํ
์ปฌ๋ ์
struct LargeDataStructure {
// ์ค์ ๋ฐ์ดํฐ๋ ํด๋์ค๋ก ๋ํ (์ฐธ์กฐ ํ์
)
private final class Storage {
var data: [Int]
init(data: [Int]) {
self.data = data
}
func copy() -> Storage {
return Storage(data: data)
}
}
private var storage: Storage
init(data: [Int]) {
self.storage = Storage(data: data)
}
// ์ฝ๊ธฐ ์ ์ฉ ์ ๊ทผ - ๋ณต์ฌ ์์
var data: [Int] {
return storage.data
}
// ์ฐ๊ธฐ ์ ๊ทผ - COW ์ ์ฉ
mutating func append(_ value: Int) {
// isKnownUniquelyReferenced: ์ฐธ์กฐ๊ฐ ํ๋๋ฟ์ธ์ง ํ์ธ
if !isKnownUniquelyReferenced(&storage) {
print("๐ ๋ณต์ฌ ๋ฐ์!")
storage = storage.copy()
}
storage.data.append(value)
}
mutating func removeAll() {
if !isKnownUniquelyReferenced(&storage) {
print("๐ ๋ณต์ฌ ๋ฐ์!")
storage = storage.copy()
}
storage.data.removeAll()
}
}
// ์ฌ์ฉ ์์
var struct1 = LargeDataStructure(data: Array(1...1000))
var struct2 = struct1 // ๋ณต์ฌํ์ง ์์!
print("struct1 ๋ฐ์ดํฐ ๊ฐ์: \(struct1.data.count)") // 1000
print("struct2 ๋ฐ์ดํฐ ๊ฐ์: \(struct2.data.count)") // 1000
struct2.append(1001) // ์ด์ ๋ณต์ฌ ๋ฐ์!
// ์ถ๋ ฅ: ๐ ๋ณต์ฌ ๋ฐ์!
print("struct1 ๋ฐ์ดํฐ ๊ฐ์: \(struct1.data.count)") // 1000
print("struct2 ๋ฐ์ดํฐ ๊ฐ์: \(struct2.data.count)") // 1001
// ๐ ์ค์ ์์: ์ด๋ฏธ์ง ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด
struct ImageData {
private final class Storage {
var pixels: [UInt8]
let width: Int
let height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
self.pixels = Array(repeating: 0, count: width * height * 4) // RGBA
}
func copy() -> Storage {
let newStorage = Storage(width: width, height: height)
newStorage.pixels = pixels
return newStorage
}
}
private var storage: Storage
init(width: Int, height: Int) {
self.storage = Storage(width: width, height: height)
}
var width: Int { storage.width }
var height: Int { storage.height }
// ํฝ์
์ฝ๊ธฐ - ๋ณต์ฌ ์์
func getPixel(x: Int, y: Int) -> (r: UInt8, g: UInt8, b: UInt8, a: UInt8) {
let index = (y * width + x) * 4
return (
storage.pixels[index],
storage.pixels[index + 1],
storage.pixels[index + 2],
storage.pixels[index + 3]
)
}
// ํฝ์
์ฐ๊ธฐ - COW ์ ์ฉ
mutating func setPixel(x: Int, y: Int, r: UInt8, g: UInt8, b: UInt8, a: UInt8) {
if !isKnownUniquelyReferenced(&storage) {
storage = storage.copy()
}
let index = (y * width + x) * 4
storage.pixels[index] = r
storage.pixels[index + 1] = g
storage.pixels[index + 2] = b
storage.pixels[index + 3] = a
}
}
// ์ฌ์ฉ ์์
var image1 = ImageData(width: 1920, height: 1080)
var image2 = image1 // ๋ณต์ฌํ์ง ์์! ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ!
// ์ฝ๊ธฐ๋ง ํ๋ฉด ๋ณต์ฌ ์ ๋จ
let pixel = image1.getPixel(x: 100, y: 100)
// ์ฐ๊ธฐ๋ฅผ ํ๋ฉด ๋ณต์ฌ ๋ฐ์
image2.setPixel(x: 100, y: 100, r: 255, g: 0, b: 0, a: 255)
// ์ด์ image1๊ณผ image2๋ ๋ค๋ฅธ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉ
์ฅ์ :
โข ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ : ์ค์ ๋ก ์์ ํ ๋๋ง ๋ณต์ฌ
โข ์ฑ๋ฅ ํฅ์: ๋ถํ์ํ ๋ณต์ฌ ๋ฐฉ์ง
โข ์์ ์ฑ: ๊ฐ ํ์ ์ ์ฅ์ ์ ์ง
๋จ์ :
โข ๊ตฌํ ๋ณต์ก๋ ์ฆ๊ฐ
โข ์ฐธ์กฐ ์นด์ดํ ์ค๋ฒํค๋
โข ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ์ฃผ์ ํ์
์ธ์ ์ฌ์ฉํ ๊น?
โข ํฐ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด๋ฅผ ์์ฃผ ๋ณต์ฌํ ๋
โข ์ฝ๊ธฐ๊ฐ ์ฐ๊ธฐ๋ณด๋ค ํจ์ฌ ๋ง์ ๋
โข ๊ฐ ํ์ ์ ์๋ฏธ๋ก ์ด ํ์ํ ๋
Swift์ ๊ธฐ๋ณธ ์ปฌ๋ ์ (Array, Dictionary, Set)์ ์ด๋ฏธ COW๋ฅผ ์ฌ์ฉํ๋๊น, ์ง์ ๊ตฌํํ ํ์๋ ๊ฑฐ์ ์์ด! ํ์ง๋ง ์๋ฆฌ๋ฅผ ์ดํดํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ํฐ ๋์์ด ๋ผ. ๐
๐ ์ค์ ํ๋ก์ ํธ: ๋ฉ๋ชจ๋ฆฌ ์์ ํ ์ฑ ๋ง๋ค๊ธฐ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์! ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์๋ฒฝํ ๊ฐ๋จํ ์ด๋ฏธ์ง ๋ทฐ์ด ์ฑ์ด์ผ. ๐ฑโจ
import UIKit
// MARK: - ์ด๋ฏธ์ง ์บ์ ๋งค๋์
class ImageCacheManager {
static let shared = ImageCacheManager()
private let cache = NSCache<NSString, UIImage>()
private let fileManager = FileManager.default
private let cacheDirectory: URL
private init() {
// ์บ์ ๋๋ ํ ๋ฆฌ ์ค์
let paths = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
cacheDirectory = paths[0].appendingPathComponent("ImageCache")
// ๋๋ ํ ๋ฆฌ ์์ฑ
try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
// ์บ์ ์ค์
cache.totalCostLimit = 100 * 1024 * 1024 // 100MB
cache.countLimit = 50 // ์ต๋ 50๊ฐ
// ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ต์ ๋ฒ
NotificationCenter.default.addObserver(
self,
selector: #selector(handleMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
@objc private func handleMemoryWarning() {
print("โ ๏ธ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ! ์บ์ ์ ๋ฆฌ ์ค...")
cache.removeAllObjects()
}
func loadImage(url: URL, completion: @escaping (UIImage?) -> Void) {
let cacheKey = url.absoluteString as NSString
// 1. ๋ฉ๋ชจ๋ฆฌ ์บ์ ํ์ธ
if let cachedImage = cache.object(forKey: cacheKey) {
print("โ
๋ฉ๋ชจ๋ฆฌ ์บ์์์ ๋ก๋: \(url.lastPathComponent)")
completion(cachedImage)
return
}
// 2. ๋์คํฌ ์บ์ ํ์ธ
let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
if let diskImage = UIImage(contentsOfFile: fileURL.path) {
print("๐พ ๋์คํฌ ์บ์์์ ๋ก๋: \(url.lastPathComponent)")
cache.setObject(diskImage, forKey: cacheKey)
completion(diskImage)
return
}
// 3. ๋คํธ์ํฌ์์ ๋ค์ด๋ก๋
print("๐ ๋คํธ์ํฌ์์ ๋ค์ด๋ก๋: \(url.lastPathComponent)")
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let self = self,
let data = data,
let image = UIImage(data: data) else {
completion(nil)
return
}
// ๋ฉ๋ชจ๋ฆฌ ์บ์์ ์ ์ฅ
self.cache.setObject(image, forKey: cacheKey, cost: data.count)
// ๋์คํฌ ์บ์์ ์ ์ฅ
try? data.write(to: fileURL)
completion(image)
}.resume()
}
func clearCache() {
cache.removeAllObjects()
try? fileManager.removeItem(at: cacheDirectory)
try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
print("๐๏ธ ์บ์ ์์ ์ ๋ฆฌ")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
// MARK: - ์ด๋ฏธ์ง ๋ทฐ์ด ์
class ImageViewerCell: UICollectionViewCell {
private lazy var imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
private lazy var activityIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .medium)
indicator.hidesWhenStopped = true
return indicator
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
contentView.addSubview(imageView)
contentView.addSubview(activityIndicator)
imageView.frame = contentView.bounds
activityIndicator.center = contentView.center
}
func configure(with url: URL) {
imageView.image = nil
activityIndicator.startAnimating()
// weak self ์ฌ์ฉ!
ImageCacheManager.shared.loadImage(url: url) { [weak self] image in
DispatchQueue.main.async { [weak self] in
self?.activityIndicator.stopAnimating()
self?.imageView.image = image
}
}
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
activityIndicator.stopAnimating()
}
}
// MARK: - ์ด๋ฏธ์ง ๋ทฐ์ด ๋ทฐ ์ปจํธ๋กค๋ฌ
class ImageViewerViewController: UIViewController {
private var collectionView: UICollectionView!
private var imageURLs: [URL] = []
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
loadImageURLs()
}
private func setupUI() {
view.backgroundColor = .systemBackground
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: view.bounds.width / 2 - 15, height: 200)
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .systemBackground
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(ImageViewerCell.self, forCellWithReuseIdentifier: "ImageCell")
view.addSubview(collectionView)
// ์บ์ ์ ๋ฆฌ ๋ฒํผ
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "์บ์ ์ ๋ฆฌ",
style: .plain,
target: self,
action: #selector(clearCache)
)
}
private func loadImageURLs() {
// ์ํ ์ด๋ฏธ์ง URL๋ค
imageURLs = (1...20).map { index in
URL(string: "https://picsum.photos/400/300?random=\(index)")!
}
collectionView.reloadData()
}
@objc private func clearCache() {
ImageCacheManager.shared.clearCache()
collectionView.reloadData()
}
deinit {
print("โ
ImageViewerViewController ํด์ ")
}
}
// MARK: - UICollectionView DataSource & Delegate
extension ImageViewerViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageURLs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageViewerCell
cell.configure(with: imageURLs[indexPath.item])
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailVC = ImageDetailViewController(imageURL: imageURLs[indexPath.item])
navigationController?.pushViewController(detailVC, animated: true)
}
}
// MARK: - ์ด๋ฏธ์ง ์์ธ ๋ทฐ ์ปจํธ๋กค๋ฌ
class ImageDetailViewController: UIViewController {
private let imageURL: URL
private lazy var imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
return iv
}()
init(imageURL: URL) {
self.imageURL = imageURL
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
imageView.frame = view.bounds
view.addSubview(imageView)
loadImage()
}
private func loadImage() {
// weak self ์ฌ์ฉ!
ImageCacheManager.shared.loadImage(url: imageURL) { [weak self] image in
DispatchQueue.main.async { [weak self] in
self?.imageView.image = image
}
}
}
deinit {
print("โ
ImageDetailViewController ํด์ ")
}
}
// MARK: - ์ฑ ๋ธ๋ฆฌ๊ฒ์ดํธ (๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง)
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
startMemoryMonitoring()
return true
}
private func startMemoryMonitoring() {
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
self?.logMemoryUsage()
}
}
private func logMemoryUsage() {
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)
}
}
if kerr == KERN_SUCCESS {
let usedMemory = Double(info.resident_size) / 1024.0 / 1024.0
print("๐ ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: \(String(format: "%.2f", usedMemory)) MB")
}
}
}
1. ์ด๋ฏธ์ง ์บ์ฑ
โข NSCache ์ฌ์ฉ์ผ๋ก ์๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
โข ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ ์๋ ์ ๋ฆฌ
โข ๋์คํฌ ์บ์๋ก ๋ฉ๋ชจ๋ฆฌ ๋ถ๋ด ๊ฐ์
2. ์ฝํ ์ฐธ์กฐ
โข ๋ชจ๋ ํด๋ก์ ์์ [weak self] ์ฌ์ฉ
โข ์ํ ์ฐธ์กฐ ์๋ฒฝ ๋ฐฉ์ง
3. ์ ์ฌ์ฌ์ฉ
โข prepareForReuse๋ก ์ด๋ฏธ์ง ์ ๋ฆฌ
โข ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง
4. ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง
โข ์ค์๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ถ์
โข ๋ฌธ์ ์กฐ๊ธฐ ๋ฐ๊ฒฌ
5. deinit ํ์ธ
โข ๋ชจ๋ ๋ทฐ ์ปจํธ๋กค๋ฌ์์ deinit ๋ก๊ทธ
โข ๋ฉ๋ชจ๋ฆฌ ๋์ ์ฆ์ ๊ฐ์ง
์ด๋ฐ ํจํด๋ค์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์์ด! ๐
๐ ๋ง๋ฌด๋ฆฌ: ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ง์คํฐ๊ฐ ๋๋ ๊ธธ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ๐ Swift์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ARC์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ฐฐ์ ์ง? ์ด์ ์ ๋ฆฌํด๋ณผ๊น!
๐ ํต์ฌ ์์ฝ
1. ARC์ ๊ธฐ๋ณธ ์๋ฆฌ ๐
โข ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌ
โข ์นด์ดํธ๊ฐ 0์ด ๋๋ฉด ์๋ ํด์
โข ํ ๋ฉ๋ชจ๋ฆฌ์ ์ฐธ์กฐ ํ์
์๋ง ์ ์ฉ
2. ์ํ ์ฐธ์กฐ ๋ฐฉ์ง โ ๏ธ
โข weak: ์ฐธ์กฐ ๋์์ด ๋จผ์ ํด์ ๋ ์ ์์ ๋
โข unowned: ์ฐธ์กฐ ๋์์ด ํญ์ ์กด์ฌํ ๋
โข ํ์ ์ด ์์ผ๋ฉด weak ์ฌ์ฉ!
3. ํด๋ก์ ์บก์ฒ ๋ฆฌ์คํธ ๐
โข [weak self] ํจํด ์ต๊ดํ
โข guard let self = self ํ์ฉ
โข ๋น๋๊ธฐ ์์
์์ ํนํ ์ฃผ์
4. ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ โก
โข ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ง
โข lazy ํ๋กํผํฐ ํ์ฉ
โข autoreleasepool ์ฌ์ฉ
โข ์ ์ ํ ์บ์ฑ ์ ๋ต
5. ๋๋ฒ๊น
๋๊ตฌ ๐
โข Instruments์ Leaks
โข Memory Graph Debugger
โข deinit ๋ก๊น
โข ์ ๊ธฐ์ ์ธ ํ๋กํ์ผ๋ง
6. ์ค์ ํจํด ๐ฏ
โข ๋ธ๋ฆฌ๊ฒ์ดํธ๋ ํญ์ weak
โข ํ์ด๋จธ๋ ๋ฐ๋์ invalidate
โข ์ต์ ๋ฒ๋ ์ ๊ฑฐ ํ์
โข ๋ถ๋ชจ-์์ ๊ด๊ณ ์ฃผ์
๐ ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ๊ธฐ
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๋ง์คํฐํ๋ ค๋ฉด ๊ณ์ ์ฐ์ตํ๊ณ ๊ฒฝํ์ ์์์ผ ํด! ๋ค์ ๋จ๊ณ๋ค์ ์ถ์ฒํด:
1. ์ค์ ํ๋ก์ ํธ ๋ง๋ค๊ธฐ
โข ์ด๋ฏธ์ง ํธ์ง ์ฑ
โข ์ค์๊ฐ ์ฑํ
์ฑ
โข ๋น๋์ค ํ๋ ์ด์ด
โข ๊ฒ์ ์ฑ
2. ์คํ์์ค ๋ถ์
โข ์ ๋ช
ํ iOS ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฝ๋ ์ฝ๊ธฐ
โข ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ํจํด ํ์ต
โข ๋ฒ ์คํธ ํ๋ํฐ์ค ์ต๋
3. ์ปค๋ฎค๋ํฐ ์ฐธ์ฌ
โข Stack Overflow์์ ์ง๋ฌธ/๋ต๋ณ
โข GitHub ์ด์ ์ฐธ์ฌ
โข ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๋ฉํ ๋ง ๋ฐ๊ธฐ
4. ์ง์์ ์ธ ํ์ต
โข WWDC ์ธ์
์์ฒญ
โข Swift Evolution ํ๋ก์ฐ
โข ๊ธฐ์ ๋ธ๋ก๊ทธ ๊ตฌ๋
5. ์ฑ๋ฅ ์ธก์ ์ต๊ด
โข ๋งค ๋ฆด๋ฆฌ์ฆ ์ ํ๋กํ์ผ๋ง
โข ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
โข ์ฑ๋ฅ ์งํ ์ถ์
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ ์ฒ์์ ์ด๋ ต๊ฒ ๋๊ปด์ง ์ ์์ด. ํ์ง๋ง ์๋ฆฌ๋ฅผ ์ดํดํ๊ณ ํจํด์ ์ตํ๋ฉด ์์ฐ์ค๋ฝ๊ฒ ๋ชธ์ ๋ฐฐ๊ฒ ๋ผ! ๐ฑ
๊ธฐ์ตํด์ผ ํ ๊ฒ๋ค:
โข ์๋ฒฝํ ์ฝ๋๋ ์์ด. ๊ณ์ ๊ฐ์ ํ๋ฉด ๋ผ!
โข ์ค์๋ ๋ฐฐ์์ ๊ธฐํ์ผ. ํฌ๋์๋ฅผ ๋๋ ค์ํ์ง ๋ง!
โข ๋๊ตฌ๋ฅผ ํ์ฉํด. Xcode๋ ์ ๋ง ๊ฐ๋ ฅํด!
โข ์ปค๋ฎค๋ํฐ๋ฅผ ํ์ฉํด. ํผ์ ๊ณ ๋ฏผํ์ง ๋ง!
โข ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ!
๊ฐ์ฅ ์ค์ํ ๊ฒ:
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ ๋จ์ํ ๊ธฐ์ ์ด ์๋๋ผ ์ฌ์ฉ์ ๊ฒฝํ์ด์ผ! ์ฐ๋ฆฌ๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ๊ด๋ฆฌํ๋ฉด ์ฌ์ฉ์๋ค์ ๋ ๋น ๋ฅด๊ณ ์์ ์ ์ธ ์ฑ์ ๊ฒฝํํ ์ ์์ด. ๊ทธ๊ฒ ๋ฐ๋ก ์ฐ๋ฆฌ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ด์ฌํ ๊ณต๋ถํ๋ ์ด์ ์ง! ๐ช
์, ์ด์ ๋๊ฐ์ ๋ฉ์ง ์ฑ์ ๋ง๋ค์ด๋ด! ๋ฉ๋ชจ๋ฆฌ ๋์ ์๋, ์๋ฒฝํ๊ฒ ์ต์ ํ๋ ์ฑ์ ๋ง๋ค ์ ์์ ๊ฑฐ์ผ! ํ์ดํ ! ๐โจ
๐ ์ถํํฉ๋๋ค! ๐
Swift ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ARC์ ๋ชจ๋ ๊ฒ์ ๋ง์คํฐํ์ด์!
์ด์ ๋ฉ๋ชจ๋ฆฌ ๋์ ์๋ ์๋ฒฝํ ์ฑ์ ๋ง๋ค ์ค๋น๊ฐ ๋์์ต๋๋ค.
๊ณ์ํด์ ์ฐ์ตํ๊ณ , ์คํํ๊ณ , ๋ฐฐ์ฐ์ธ์!
๋น์ ์ ์ด๋ฏธ ํ๋ฅญํ iOS ๊ฐ๋ฐ์์
๋๋ค! ๐ชโจ
Happy Coding! ๐
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.