Ver3.0 ๐ ์ค์ํํธ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ARC๋ฅผ ํตํ ํจ์จ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ: ๊ฐ๋ฐ์๊ฐ ๊ผญ ์์์ผ ํ ํต์ฌ ๊ฐ์ด๋

๐ ์ค์ํํธ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ARC๋ฅผ ํตํ ํจ์จ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ: ๊ฐ๋ฐ์๊ฐ ๊ผญ ์์์ผ ํ ํต์ฌ ๊ฐ์ด๋
๋ฉ๋ชจ๋ฆฌ ๋์ ์๋ ๊น๋ํ ์ฝ๋, ARC์ ํจ๊ป๋ผ๋ฉด ๊ฐ๋ฅํฉ๋๋ค! ๐ช
์๋ , ์น๊ตฌ! ๐ ์ค๋์ Swift ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊น ํด. ์ฑ์ ๋ง๋ค๋ค ๋ณด๋ฉด "์ ๋ด ์ฑ์ด ๋๋ ค์ง์ง?", "๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ๊ฐ ๋จ๋๋ฐ ์ด๋์ ๋ฌธ์ ๊ฐ ์๊ธด ๊ฑฐ์ผ?" ๊ฐ์ ๊ณ ๋ฏผ์ ํ ๋ฒ์ฏค ํด๋ดค์ ๊ฑฐ์ผ. ๋ฐ๋ก ๊ทธ ๋ต์ด ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ์์ด!
Swift๋ ARC(Automatic Reference Counting)๋ผ๋ ๋ฉ์ง ์์คํ ์ ํตํด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํด์ค. ํ์ง๋ง "์๋"์ด๋ผ๊ณ ํด์ ์ฐ๋ฆฌ๊ฐ ์๋ฌด๊ฒ๋ ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋๋ค๋ ๊ฑด ์๋์ผ. ์คํ๋ ค ARC์ ์๋ ์๋ฆฌ๋ฅผ ์ ํํ ์ดํดํด์ผ ๋ฉ๋ชจ๋ฆฌ ๋์(Memory Leak)๋ ๊ฐํ ์ํ ์ฐธ์กฐ(Strong Reference Cycle) ๊ฐ์ ๋ฌธ์ ๋ฅผ ์๋ฐฉํ ์ ์์ง! ๐ฏ
๐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ, ์ ์ค์ํ ๊น?
์ค๋งํธํฐ์ ๋ฉ๋ชจ๋ฆฌ๋ ๋ฌดํํ์ง ์์. ํนํ iOS ๊ธฐ๊ธฐ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์๊ฒฉํด์, ์ฑ์ด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ณผ๋ํ๊ฒ ์ฌ์ฉํ๋ฉด ์์คํ ์ด ๊ฐ์ ๋ก ์ข ๋ฃ์์ผ๋ฒ๋ ค. ์ฌ์ฉ์ ์ ์ฅ์์๋ "์ฑ์ด ๊ฐ์๊ธฐ ๊บผ์ก์ด!"๋ผ๊ณ ๋๋ผ๋ ์ต์ ์ ๊ฒฝํ์ด ๋๋ ๊ฑฐ์ง. ๐ฑ
๐ก ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์ค์ํ ์ด์
โข ์ฑ ์์ ์ฑ: ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ์์ด๋ฉด ๊ฒฐ๊ตญ ํฌ๋์๋ก ์ด์ด์ ธ
โข ์ฑ๋ฅ ์ต์ ํ: ๋ถํ์ํ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ ์ฑ์ ๋๋ฆฌ๊ฒ ๋ง๋ค์ด
โข ๋ฐฐํฐ๋ฆฌ ์๋ช
: ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์๋ชป๋๋ฉด CPU ์ฌ์ฉ๋๋ ์ฆ๊ฐํด
โข ์ฌ์ฉ์ ๊ฒฝํ: ๋ถ๋๋ฌ์ด ์ฑ ๋์์ ์ข์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์์ ์์๋ผ
์์ C๋ Objective-C ์์ ์๋ ๊ฐ๋ฐ์๊ฐ ์ง์ malloc๊ณผ free๋ฅผ ์ฌ์ฉํด์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๊ณ ํด์ ํ์ด์ผ ํ์ด. ์ค์๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ์ง ์์ผ๋ฉด ๋์๊ฐ ๋ฐ์ํ๊ณ , ์ด๋ฏธ ํด์ ๋ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ๋ฉด ํฌ๋์๊ฐ ๋ฐ์ํ์ง. ์ ๋ง ๊ณจ์น ์ํ ์ผ์ด์์ด! ๐
ํ์ง๋ง Swift๋ ๋ค๋ฅด์ง! ARC ๋๋ถ์ ๋๋ถ๋ถ์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์๋ํ๋์ด ์์ด. ์ปดํ์ผ๋ฌ๊ฐ ์ ์ ํ ์์น์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ฝ๋๋ฅผ ์๋์ผ๋ก ์ฝ์ ํด์ฃผ๊ฑฐ๋ . ๊ทธ๋๋ ์ฐ๋ฆฌ๊ฐ ์ดํดํด์ผ ํ ํต์ฌ ๊ฐ๋ ๋ค์ด ์์ด. ์ง๊ธ๋ถํฐ ํ๋์ฉ ํํค์ณ๋ณด์! ๐
๐ฏ ARC์ ๊ธฐ๋ณธ ์๋ฆฌ: Reference Counting์ด๋?
ARC์ ํต์ฌ์ ์ฐธ์กฐ ์นด์ดํ (Reference Counting)์ด์ผ. ์ฝ๊ฒ ๋งํด์, ๊ฐ ๊ฐ์ฒด๋ง๋ค "๋๋ฅผ ์ฐธ์กฐํ๋ ๊ฒ ๋ช ๊ฐ์ผ?"๋ผ๋ ์นด์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฑฐ์ง. ์ด ์นด์ดํฐ๊ฐ 0์ด ๋๋ฉด "์๋ฌด๋ ๋๋ฅผ ํ์๋ก ํ์ง ์๋ค?"๋ผ๊ณ ํ๋จํ๊ณ ๋ฉ๋ชจ๋ฆฌ์์ ์๋์ผ๋ก ํด์ ๋ผ.
๐ข Reference Count์ ๋ณํ ๊ณผ์
1. ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ฉด RC = 1
2. ๋ค๋ฅธ ๋ณ์๊ฐ ๊ทธ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ๋ฉด RC += 1
3. ์ฐธ์กฐ๊ฐ ๋์ด์ง๋ฉด RC -= 1
4. RC๊ฐ 0์ด ๋๋ฉด ๋ฉ๋ชจ๋ฆฌ์์ ํด์ (deinit ํธ์ถ)
์ค์ ์ฝ๋๋ก ๋ณด๋ฉด ํจ์ฌ ์ดํดํ๊ธฐ ์ฌ์. ๊ฐ๋จํ ์์ ๋ฅผ ๋ณผ๊น? ๐
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name)์ด(๊ฐ) ์ด๊ธฐํ๋์์ต๋๋ค")
}
deinit {
print("\(name)์ด(๊ฐ) ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋์์ต๋๋ค")
}
}
// RC = 1 (์ฒ ์๋ฅผ ๊ฐ๋ฆฌํค๋ ์ฐธ์กฐ๊ฐ 1๊ฐ)
var reference1: Person? = Person(name: "์ฒ ์")
// RC = 2 (์ฒ ์๋ฅผ ๊ฐ๋ฆฌํค๋ ์ฐธ์กฐ๊ฐ 2๊ฐ)
var reference2: Person? = reference1
// RC = 3 (์ฒ ์๋ฅผ ๊ฐ๋ฆฌํค๋ ์ฐธ์กฐ๊ฐ 3๊ฐ)
var reference3: Person? = reference1
// RC = 2 (์ฐธ์กฐ ํ๋ ์ ๊ฑฐ)
reference1 = nil
// RC = 1 (์ฐธ์กฐ ํ๋ ๋ ์ ๊ฑฐ)
reference2 = nil
// RC = 0 (๋ง์ง๋ง ์ฐธ์กฐ ์ ๊ฑฐ, deinit ํธ์ถ๋จ!)
reference3 = nil
// ์ถ๋ ฅ: "์ฒ ์์ด(๊ฐ) ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋์์ต๋๋ค"
์ ์ฝ๋๋ฅผ ์คํํ๋ฉด reference3 = nil์ ์คํํ๋ ์๊ฐ deinit์ด ํธ์ถ๋๋ ๊ฑธ ๋ณผ ์ ์์ด. ์๋ํ๋ฉด ๊ทธ๋ ๋น๋ก์ Reference Count๊ฐ 0์ด ๋๊ฑฐ๋ ! ๐
๐ก Pro Tip
deinit ๋ฉ์๋์ print ๋ฌธ์ ๋ฃ์ด๋๋ฉด ๊ฐ์ฒด๊ฐ ์ธ์ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋๋์ง ํ์ธํ ์ ์์ด. ๋๋ฒ๊น ํ ๋ ์ ๋ง ์ ์ฉํ๋๊น ๊ผญ ํ์ฉํด๋ด!
โ ๏ธ ๊ฐํ ์ํ ์ฐธ์กฐ: ARC์ ๊ฐ์ฅ ํฐ ์
ARC๊ฐ ์๋ฌด๋ฆฌ ๋๋ํด๋ ํด๊ฒฐํ์ง ๋ชปํ๋ ๋ฌธ์ ๊ฐ ํ๋ ์์ด. ๋ฐ๋ก ๊ฐํ ์ํ ์ฐธ์กฐ(Strong Reference Cycle)์ผ. ๋ ๊ฐ์ฒด๊ฐ ์๋ก๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ๋ฉด Reference Count๊ฐ ์ ๋ 0์ด ๋ ์ ์์ด์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ์ง. ๐ฐ
์ค์ ์ํฉ์ ์๋ก ๋ค์ด๋ณผ๊ฒ. ์ฌ๋(Person)๊ณผ ์ํํธ(Apartment)๊ฐ ์๋ค๊ณ ์๊ฐํด๋ณด์:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) ์ด๊ธฐํ")
}
deinit {
print("\(name) ํด์ ")
}
}
class Apartment {
let 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
unit4A = nil
// deinit์ด ํธ์ถ๋์ง ์์ = ๋ฉ๋ชจ๋ฆฌ ๋์!
์ ์ฝ๋์ ๋ฌธ์ ์ ์ด ๋ณด์ด์ง? John์ Apartment๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ๊ณ , Apartment๋ John์ ๊ฐํ๊ฒ ์ฐธ์กฐํด. ๊ทธ๋์ ๋ ๋ค RC๊ฐ 1๋ก ์ ์ง๋์ด ์ ๋ 0์ด ๋ ์ ์์ด. ์ด๊ฒ ๋ฐ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์์ ์ ํ์ ์ธ ์์์ผ! ๐ฅ
โ ๏ธ ์ฃผ์์ฌํญ
๊ฐํ ์ํ ์ฐธ์กฐ๋ ์ฑ์ด ์คํ๋๋ ๋์ ๊ณ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ก์๋จน์ด. ์๊ฐ์ด ์ง๋ ์๋ก ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ค์ด๋ค๊ณ , ๊ฒฐ๊ตญ ์ฑ์ด ๋๋ ค์ง๊ฑฐ๋ ํฌ๋์๊ฐ ๋ฐ์ํ ์ ์์ด!
๐ Weak Reference: ์ํ ์ฐธ์กฐ ํด๊ฒฐ์ ์ด์
๊ฐํ ์ํ ์ฐธ์กฐ๋ฅผ ํด๊ฒฐํ๋ ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ weak reference๋ฅผ ์ฌ์ฉํ๋ ๊ฑฐ์ผ. weak ํค์๋๋ฅผ ๋ถ์ด๋ฉด Reference Count๋ฅผ ์ฆ๊ฐ์ํค์ง ์์. ์ฆ, ์ฝํ ์ฐธ์กฐ๋ ๊ฐ์ฒด๋ฅผ ๊ฐ๋ฆฌํค์ง๋ง ์์ ํ์ง๋ ์๋ ๊ฑฐ์ง! ๐
๐ฏ Weak Reference์ ํน์ง
โข Reference Count๋ฅผ ์ฆ๊ฐ์ํค์ง ์์
โข ํญ์ ์ต์
๋ ํ์
์ด์ด์ผ ํจ (var๋ก ์ ์ธ)
โข ์ฐธ์กฐํ๋ ๊ฐ์ฒด๊ฐ ํด์ ๋๋ฉด ์๋์ผ๋ก nil์ด ๋จ
โข ์ฃผ๋ก delegate ํจํด์์ ์ฌ์ฉ๋จ
์๊น ๋ฌธ์ ๊ฐ ์๋ ์ฝ๋๋ฅผ weak์ผ๋ก ๊ณ ์ณ๋ณผ๊น? ๐
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) ์ด๊ธฐํ")
}
deinit {
print("\(name) ํด์ ")
}
}
class Apartment {
let unit: String
weak var tenant: Person? // weak ํค์๋ ์ถ๊ฐ!
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 // weak ์ฐธ์กฐ๋ผ RC ์ฆ๊ฐ ์ ํจ!
john = nil // John์ RC๊ฐ 0์ด ๋์ด ํด์ ๋จ
// ์ถ๋ ฅ: "John ํด์ "
unit4A = nil // Apartment๋ ์ ์์ ์ผ๋ก ํด์ ๋จ
// ์ถ๋ ฅ: "์ํํธ 4A ํด์ "
์ง์! ๐ ์ด์ ๋ ๊ฐ์ฒด ๋ชจ๋ ์ ์์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋๋ ๊ฑธ ๋ณผ ์ ์์ด. tenant๋ฅผ weak์ผ๋ก ์ ์ธํ๊ธฐ ๋๋ฌธ์ ์ํ ์ฐธ์กฐ๊ฐ ๊นจ์ง ๊ฑฐ์ผ.
๐ก ์ธ์ weak์ ์ฌ์ฉํ ๊น?
โข Delegate ํจํด (UITableViewDelegate, UITextFieldDelegate ๋ฑ)
โข Parent-Child ๊ด๊ณ์์ Child๊ฐ Parent๋ฅผ ์ฐธ์กฐํ ๋
โข Observer ํจํด์์ ๊ด์ฐฐ์๊ฐ ๊ด์ฐฐ ๋์์ ์ฐธ์กฐํ ๋
โข ์ผ๋ฐ์ ์ผ๋ก "์์ ๊ด๊ณ๊ฐ ์๋" ์ฐธ์กฐ์ ์ฌ์ฉ
๐ช Unowned Reference: ๋ ๋ค๋ฅธ ํด๊ฒฐ์ฑ
weak๊ณผ ๋น์ทํ์ง๋ง ์กฐ๊ธ ๋ค๋ฅธ unowned reference๋ ์์ด. unowned๋ "์ด ๊ฐ์ฒด๋ ์ ๋ nil์ด ๋์ง ์์ ๊ฑฐ์ผ!"๋ผ๊ณ ํ์ ํ ๋ ์ฌ์ฉํด. weak๊ณผ์ ์ฐจ์ด์ ์ ์ ๋ฆฌํด๋ณผ๊ฒ:
| ํน์ง | weak | unowned |
|---|---|---|
| ํ์ | ํญ์ ์ต์ ๋ (var) | ์ต์ ๋ ์๋ (let ๊ฐ๋ฅ) |
| nil ๊ฐ๋ฅ์ฑ | ์ฐธ์กฐ ๋์ ํด์ ์ ์๋์ผ๋ก nil | nil์ด ๋ ์ ์์ |
| RC ์ฆ๊ฐ | ์ฆ๊ฐํ์ง ์์ | ์ฆ๊ฐํ์ง ์์ |
| ์ฌ์ฉ ์์ | ์ฐธ์กฐ๊ฐ nil์ด ๋ ์ ์์ ๋ | ์ฐธ์กฐ๊ฐ ์ ๋ nil์ด ์ ๋ ๋ |
| ์ํ์ฑ | ์์ ํจ | ์๋ชป ์ฌ์ฉ ์ ํฌ๋์ ๊ฐ๋ฅ |
unowned๋ฅผ ์ฌ์ฉํ๋ ์ ํ์ ์ธ ์์๋ฅผ ๋ณด์. ์ ์ฉ์นด๋์ ๊ณ ๊ฐ์ ๊ด๊ณ์ผ:
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 jane: Customer? = Customer(name: "Jane")
jane?.card = CreditCard(number: "1234-5678", customer: jane!)
jane = nil
// ์ถ๋ ฅ:
// "Jane ๊ณ ๊ฐ ํด์ "
// "์นด๋ 1234-5678 ํด์ "
์ ์ฉ์นด๋๋ ํญ์ ๊ณ ๊ฐ์ด ์์ด์ผ ์กด์ฌํ ์ ์์ด. ๊ณ ๊ฐ ์๋ ์ ์ฉ์นด๋๋ ์๋ฏธ๊ฐ ์์์? ๊ทธ๋์ customer๋ฅผ unowned๋ก ์ ์ธํ ๊ฑฐ์ผ. ๊ณ ๊ฐ์ด ํด์ ๋๋ฉด ์นด๋๋ ์๋์ผ๋ก ํด์ ๋์ง! ๐ด
โ ๏ธ Unowned ์ฌ์ฉ ์ ์ฃผ์์ฌํญ
unowned๋ก ์ฐธ์กฐํ ๊ฐ์ฒด๊ฐ ๋จผ์ ํด์ ๋๋ฉด ํฌ๋์๊ฐ ๋ฐ์ํด! ๊ทธ๋์ "์ด ๊ฐ์ฒด๋ ์ ๋ ๋จผ์ ํด์ ๋์ง ์์ ๊ฑฐ์ผ"๋ผ๊ณ 100% ํ์ ํ ๋๋ง ์ฌ์ฉํด์ผ ํด. ํ์ ์ด ์๋ค๋ฉด weak์ ์ฌ์ฉํ๋ ๊ฒ ์์ ํด!
๐ ํด๋ก์ ์์์ ๊ฐํ ์ํ ์ฐธ์กฐ
ํด๋ก์ (Closure)๋ Swift์์ ์ ๋ง ์์ฃผ ์ฌ์ฉํ๋ ๊ธฐ๋ฅ์ด์ผ. ๊ทธ๋ฐ๋ฐ ํด๋ก์ ๋ ์ฐธ์กฐ ํ์
์ด๋ผ์ ๊ฐํ ์ํ ์ฐธ์กฐ๋ฅผ ์ผ์ผํฌ ์ ์์ด. ํนํ ํด๋ก์ ์์์ self๋ฅผ ์ฌ์ฉํ ๋ ์กฐ์ฌํด์ผ ํด! ๐ฑ
๋ฌธ์ ๊ฐ ๋๋ ์ฝ๋๋ฅผ ๋จผ์ ๋ณผ๊น?
class HTMLElement {
let name: String
let text: String?
// ํด๋ก์ ๊ฐ self๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐ!
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)<!--\(self.name)-->"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
print("\(name) ์์ ์์ฑ")
}
deinit {
print("\(name) ์์ ํด์ ")
}
}
var heading: HTMLElement? = HTMLElement(name: "h1", text: "์๋
ํ์ธ์")
print(heading?.asHTML() ?? "")
heading = nil
// deinit์ด ํธ์ถ๋์ง ์์! ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ์!
์ ์ฝ๋์ ๋ฌธ์ ๋ ๋ญ๊น? HTMLElement ์ธ์คํด์ค๋ asHTML ํด๋ก์ ๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํ๊ณ , ํด๋ก์ ๋ ๋ด๋ถ์์ self๋ฅผ ๊ฐํ๊ฒ ์ฐธ์กฐํด. ๋ ์ํ ์ฐธ์กฐ๊ฐ ๋ฐ์ํ ๊ฑฐ์ง! ๐
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ์บก์ฒ ๋ฆฌ์คํธ(Capture List)๋ฅผ ์ฌ์ฉํด์ผ ํด:
class HTMLElement {
let name: String
let text: String?
// ์บก์ฒ ๋ฆฌ์คํธ๋ก weak self ์ฌ์ฉ!
lazy var asHTML: () -> String = { [weak self] in
guard let self = self else {
return ""
}
if let text = self.text {
return "<\(self.name)>\(text)<!--\(self.name)-->"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
print("\(name) ์์ ์์ฑ")
}
deinit {
print("\(name) ์์ ํด์ ")
}
}
var heading: HTMLElement? = HTMLElement(name: "h1", text: "์๋
ํ์ธ์")
print(heading?.asHTML() ?? "")
heading = nil
// ์ถ๋ ฅ: "h1 ์์ ํด์ " - ์ ์์ ์ผ๋ก ํด์ ๋จ!
[weak self]๋ฅผ ํด๋ก์ ์์ ๋ถ๋ถ์ ์ถ๊ฐํ๋ฉด ํด๋ก์ ๊ฐ self๋ฅผ ์ฝํ๊ฒ ์ฐธ์กฐํ๊ฒ ๋ผ. ๊ทธ๋ฆฌ๊ณ guard let self = self๋ก ์์ ํ๊ฒ ์ธ๋ํํด์ ์ฌ์ฉํ๋ฉด ๋ผ! ๐
๐ฏ ํด๋ก์ ์บก์ฒ ๋ฆฌ์คํธ ์ฌ์ฉ ๊ท์น
โข [weak self]: self๊ฐ nil์ด ๋ ์ ์์ ๋ (๋๋ถ๋ถ์ ๊ฒฝ์ฐ)
โข [unowned self]: self๊ฐ ์ ๋ nil์ด ์ ๋ ๋ (ํ์ ์ด ์์ ๋๋ง)
โข [weak delegate]: delegate ๊ฐ์ ๋ค๋ฅธ ๊ฐ์ฒด๋ ์บก์ฒ ๊ฐ๋ฅ
โข ์ฌ๋ฌ ๊ฐ ์บก์ฒ: [weak self, weak delegate] ํํ๋ก ์ฌ์ฉ
๐ก ์ค์ ํ
๋คํธ์ํฌ ์์ฒญ์ด๋ ์ ๋๋ฉ์ด์ ๊ฐ์ ๋น๋๊ธฐ ์์ ์ completion handler์์๋ ๊ฑฐ์ ํญ์ [weak self]๋ฅผ ์ฌ์ฉํด์ผ ํด. ์๋ํ๋ฉด ์์ ์ด ์๋ฃ๋๊ธฐ ์ ์ ํ๋ฉด์ด ๋ซํ ์ ์๊ฑฐ๋ !
๐จ ์ค์ ์์ : ViewController์์์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
iOS ์ฑ ๊ฐ๋ฐ์์ ๊ฐ์ฅ ํํ๊ฒ ๋ง์ฃผ์น๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ํฉ์ ์ดํด๋ณด์. ViewController์์ ๋คํธ์ํฌ ์์ฒญ์ ํ๋ ๊ฒฝ์ฐ์ผ:
import UIKit
class ProfileViewController: UIViewController {
private var userProfile: UserProfile?
override func viewDidLoad() {
super.viewDidLoad()
loadUserProfile()
}
// โ ์๋ชป๋ ์: ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฐ๋ฅ์ฑ
func loadUserProfileWrong() {
NetworkManager.shared.fetchProfile { profile in
// self๋ฅผ ๊ฐํ๊ฒ ์บก์ฒํ์ฌ ์ํ ์ฐธ์กฐ ๋ฐ์ ๊ฐ๋ฅ
self.userProfile = profile
self.updateUI()
}
}
// โ
์ฌ๋ฐ๋ฅธ ์: weak self ์ฌ์ฉ
func loadUserProfile() {
NetworkManager.shared.fetchProfile { [weak self] profile in
guard let self = self else { return }
self.userProfile = profile
self.updateUI()
}
}
private func updateUI() {
// UI ์
๋ฐ์ดํธ ๋ก์ง
print("ํ๋กํ ์
๋ฐ์ดํธ ์๋ฃ")
}
deinit {
print("ProfileViewController ํด์ ")
}
}
// ๋คํธ์ํฌ ๋งค๋์ ์์
class NetworkManager {
static let shared = NetworkManager()
func fetchProfile(completion: @escaping (UserProfile) -> Void) {
// ๋น๋๊ธฐ ๋คํธ์ํฌ ์์ฒญ ์๋ฎฌ๋ ์ด์
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
let profile = UserProfile(name: "ํ๊ธธ๋", age: 30)
DispatchQueue.main.async {
completion(profile)
}
}
}
}
struct UserProfile {
let name: String
let age: Int
}
์ ์ฝ๋์์ loadUserProfile() ๋ฉ์๋๋ฅผ ๋ณด๋ฉด [weak self]๋ฅผ ์ฌ์ฉํ์ด. ๋ง์ฝ ์ฌ์ฉ์๊ฐ ํ๋กํ ํ๋ฉด์ ์ด์๋ค๊ฐ ๋คํธ์ํฌ ์์ฒญ์ด ์๋ฃ๋๊ธฐ ์ ์ ๋ค๋ก ๊ฐ๊ธฐ๋ฅผ ๋๋ฅด๋ฉด ์ด๋ป๊ฒ ๋ ๊น? weak self ๋๋ถ์ ViewController๊ฐ ์ ์์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋ผ! ๐
๐ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋๋ฒ๊น ๋ฐฉ๋ฒ
"๋ด ์ฑ์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ์๋์ง ์ด๋ป๊ฒ ์ ์ ์์ด?"๋ผ๊ณ ๊ถ๊ธํ ๊ฑฐ์ผ. Xcode๋ ๊ฐ๋ ฅํ ๋๋ฒ๊น ๋๊ตฌ๋ค์ ์ ๊ณตํด! ๐
๐ ๏ธ ๋ฉ๋ชจ๋ฆฌ ๋์ ์ฐพ๋ ๋ฐฉ๋ฒ
1. Instruments - Leaks
โข Xcode ๋ฉ๋ด: Product โ Profile (โ+I)
โข Leaks ํ
ํ๋ฆฟ ์ ํ
โข ์ฑ์ ์คํํ๋ฉด์ ๋ฉ๋ชจ๋ฆฌ ๋์ ์ค์๊ฐ ๊ฐ์ง
โข ๋นจ๊ฐ์ ๋ง๋๊ฐ ๋ํ๋๋ฉด ๋์ ๋ฐ์!
2. Debug Memory Graph
โข ์ฑ ์คํ ์ค Debug Navigator์์ ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ํ ์์ด์ฝ ํด๋ฆญ
โข ๋ณด๋ผ์ ๋๋ํ(!)๊ฐ ์์ผ๋ฉด ์ํ ์ฐธ์กฐ ์์ฌ
โข ๊ฐ์ฒด ๊ฐ ์ฐธ์กฐ ๊ด๊ณ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์ธ ๊ฐ๋ฅ
3. deinit ๋ก๊น
โข ๋ชจ๋ ํด๋์ค์ deinit ๋ฉ์๋ ์ถ๊ฐ
โข print ๋ฌธ์ผ๋ก ํด์ ์์ ํ์ธ
โข ํด์ ๋์ด์ผ ํ๋๋ฐ ์ ๋๋ฉด ๋ฌธ์ !
์ค์ ๋ก Debug Memory Graph๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ณผ๊น? ์ฑ์ ์คํํ ์ํ์์:
// 1. ๋๋ฒ๊ทธ ๋ฐ์์ ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ํ ์์ด์ฝ ํด๋ฆญ
// 2. ์ผ์ชฝ ํจ๋์์ ์์ฌ๋๋ ๊ฐ์ฒด ์ ํ
// 3. ์ค๋ฅธ์ชฝ ํจ๋์์ ์ฐธ์กฐ ๊ด๊ณ ํ์ธ
// 4. ๋ณด๋ผ์ ๋๋ํ๊ฐ ์์ผ๋ฉด ์ํ ์ฐธ์กฐ!
// ์์: ์ํ ์ฐธ์กฐ๊ฐ ์๋ ๊ฒฝ์ฐ
class Parent {
var child: Child?
deinit { print("Parent deinit") }
}
class Child {
var parent: Parent? // ์ฌ๊ธฐ๊ฐ ๋ฌธ์ !
deinit { print("Child deinit") }
}
// ํด๊ฒฐ: weak ์ฌ์ฉ
class Child {
weak var parent: Parent? // โ
ํด๊ฒฐ!
deinit { print("Child deinit") }
}
๐ก ๋ฉ๋ชจ๋ฆฌ ํ๋กํ์ผ๋ง ํ
โข ์ ๊ธฐ์ ์ผ๋ก Instruments๋ก ์ฑ์ ํ๋กํ์ผ๋งํ๋ ์ต๊ด์ ๋ค์ฌ
โข ํนํ ํ๋ฉด ์ ํ์ด ๋ง์ ๋ถ๋ถ์ ์ง์ค์ ์ผ๋ก ํ
์คํธํด
โข ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๊ณ์ ์ฆ๊ฐํ๋ค๋ฉด ๋์๋ฅผ ์์ฌํด๋ด
โข ์ฑ์คํ ์ด ์ถ์ ์ ์๋ ๋ฐ๋์ ๋ฉ๋ชจ๋ฆฌ ํ
์คํธ๋ฅผ ๊ฑฐ์ณ์ผ ํด!
๐ฑ ์ค์ ํจํด: Delegate์์์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
iOS ๊ฐ๋ฐ์์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ Delegate ํจํด์์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ ์ ๋ง ์ค์ํด. ์ ๋๋ก ํ์ง ์์ผ๋ฉด ์ํ ์ฐธ์กฐ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ๊ฑฐ๋ ! ๐ฐ
// โ
์ฌ๋ฐ๋ฅธ Delegate ํจํด ๊ตฌํ
// 1. Protocol์ class ์ ํ ์ถ๊ฐ
protocol DataManagerDelegate: AnyObject {
func didReceiveData(_ data: String)
func didFailWithError(_ error: Error)
}
class DataManager {
// 2. delegate๋ฅผ weak์ผ๋ก ์ ์ธ
weak var delegate: DataManagerDelegate?
func fetchData() {
// ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ์๋ฎฌ๋ ์ด์
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
DispatchQueue.main.async {
self?.delegate?.didReceiveData("Hello, World!")
}
}
}
deinit {
print("DataManager ํด์ ")
}
}
class MyViewController: UIViewController, DataManagerDelegate {
private var dataManager: DataManager?
override func viewDidLoad() {
super.viewDidLoad()
dataManager = DataManager()
dataManager?.delegate = self // weak์ด๋ผ ์ํ ์ฐธ์กฐ ์์!
dataManager?.fetchData()
}
func didReceiveData(_ data: String) {
print("๋ฐ์ดํฐ ์์ : \(data)")
}
func didFailWithError(_ error: Error) {
print("์๋ฌ ๋ฐ์: \(error)")
}
deinit {
print("MyViewController ํด์ ")
}
}
// ์ฌ์ฉ ์์
var viewController: MyViewController? = MyViewController()
viewController = nil
// ์ถ๋ ฅ:
// "MyViewController ํด์ "
// "DataManager ํด์ "
์ ์ฝ๋์์ ์ค์ํ ํฌ์ธํธ๊ฐ ์ธ ๊ฐ์ง ์์ด:
๐ฏ Delegate ํจํด์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ํต์ฌ
1. Protocol์ AnyObject ์ ํ
protocol DataManagerDelegate: AnyObject
โ ํด๋์ค๋ง ์ฑํ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค์ด weak ์ฌ์ฉ ๊ฐ๋ฅ
2. Delegate๋ฅผ weak์ผ๋ก ์ ์ธ
weak var delegate: DataManagerDelegate?
โ ์ํ ์ฐธ์กฐ ๋ฐฉ์ง
3. ํด๋ก์ ์์๋ weak self
[weak self] ์ฌ์ฉ์ผ๋ก ์ด์ค ์์ ์ฅ์น
โ ๏ธ ํํ ์ค์
Protocol์ AnyObject๋ฅผ ๋ถ์ด์ง ์์ผ๋ฉด weak์ ์ฌ์ฉํ ์ ์์ด! ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ์ง. ํญ์ delegate ํจํด์ ๊ตฌํํ ๋๋ : AnyObject๋ฅผ ์์ง ๋ง์!
๐ญ ๊ณ ๊ธ ํจํด: Capture List์ ๋ค์ํ ํ์ฉ
์บก์ฒ ๋ฆฌ์คํธ๋ weak self๋ง ์๋ ๊ฒ ์๋์ผ. ์ํฉ์ ๋ฐ๋ผ ๋ค์ํ๊ฒ ํ์ฉํ ์ ์์ด! ๋ช ๊ฐ์ง ๊ณ ๊ธ ํจํด์ ์์๋ณด์. ๐
class AdvancedViewController: UIViewController {
private var networkManager: NetworkManager?
private var dataProcessor: DataProcessor?
// ํจํด 1: ์ฌ๋ฌ ๊ฐ์ฒด๋ฅผ weak์ผ๋ก ์บก์ฒ
func fetchAndProcess() {
networkManager?.fetchData { [weak self, weak dataProcessor] data in
guard let self = self,
let processor = dataProcessor else { return }
let processed = processor.process(data)
self.updateUI(with: processed)
}
}
// ํจํด 2: ๊ฐ ํ์
์ ์บก์ฒ (๋ณต์ฌ๋ณธ ์์ฑ)
func scheduleNotification() {
let currentCount = 10
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
// currentCount๋ ๊ฐ ํ์
์ด๋ผ ๋ณต์ฌ๋จ
print("Count: \(currentCount)")
self?.showAlert()
}
}
// ํจํด 3: unowned self ์ฌ์ฉ (ํ์ ์ด ์์ ๋)
func setupAnimation() {
UIView.animate(withDuration: 1.0) { [unowned self] in
// ์ ๋๋ฉ์ด์
๋ธ๋ก์ ๋ทฐ๊ฐ ์กด์ฌํ๋ ๋์๋ง ์คํ๋๋ฏ๋ก unowned ์์
self.view.alpha = 0.5
}
}
// ํจํด 4: ํน์ ํ๋กํผํฐ๋ง ์บก์ฒ
func loadImage() {
let imageView = self.imageView // ์ง์ญ ๋ณ์๋ก ๋ณต์ฌ
ImageLoader.load(from: url) { [weak self] image in
// imageView๋ ์ด๋ฏธ ์บก์ฒ๋์ด self ์์ด ์ฌ์ฉ ๊ฐ๋ฅ
imageView.image = image
self?.hideLoadingIndicator()
}
}
private var imageView: UIImageView!
private var url: URL!
private func updateUI(with data: Any) {}
private func showAlert() {}
private func hideLoadingIndicator() {}
}
๊ฐ ํจํด์ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ฅผ ์ ๋ฆฌํด๋ณผ๊ฒ:
| ํจํด | ์ฌ์ฉ ์๋๋ฆฌ์ค | ์ฅ์ |
|---|---|---|
| [weak self] | ๋น๋๊ธฐ ์์ , ๋คํธ์ํฌ ์์ฒญ | ๊ฐ์ฅ ์์ , nil ์ฒดํฌ ๊ฐ๋ฅ |
| [unowned self] | ์งง์ ์ ๋๋ฉ์ด์ , ๋๊ธฐ ์์ | ์ต์ ๋ ์ธ๋ํ ๋ถํ์ |
| ์ฌ๋ฌ ๊ฐ์ฒด ์บก์ฒ | ๋ณต์กํ ์์กด์ฑ ๊ด๊ณ | ๋ช ์์ ์์กด์ฑ ๊ด๋ฆฌ |
| ๊ฐ ํ์ ์บก์ฒ | ํ์ฌ ์ํ ์ ์ฅ ํ์ | ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ ์ค๋ ์ท |
๐ก Pro Tip: guard let self = self vs self?
[weak self]๋ฅผ ์ฌ์ฉํ ํ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก self๋ฅผ ์ฌ์ฉํ ์ ์์ด:
1. guard let self = self (๊ถ์ฅ)
โ ํ ๋ฒ๋ง ์ฒดํฌํ๊ณ ์ดํ self๋ฅผ ํธํ๊ฒ ์ฌ์ฉ
โ ์ฝ๋ ๊ฐ๋
์ฑ์ด ์ข์
2. self?.method()
โ ๋งค๋ฒ ์ต์
๋ ์ฒด์ด๋ ํ์
โ ๊ฐ๋จํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉ
๐๏ธ ๊ตฌ์กฐ์ฒด vs ํด๋์ค: ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ด์
Swift์์๋ ๊ตฌ์กฐ์ฒด(Struct)์ ํด๋์ค(Class) ์ค ์ ํํ ์ ์์ด. ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ด์ ์์ ์ด ๋์ ์ฐจ์ด๋ฅผ ์ดํดํ๋ ๊ฒ ์ค์ํด! ๐ค
๐ ๊ตฌ์กฐ์ฒด vs ํด๋์ค ๋น๊ต
๊ตฌ์กฐ์ฒด (Value Type)
โข ์คํ(Stack)์ ์ ์ฅ๋จ
โข ๋ณต์ฌ๋ ๋ ๊ฐ์ด ๋ณต์ฌ๋จ (Copy-on-Write)
โข ARC ์ ์ฉ ์ ๋จ (์๋์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ)
โข ์ํ ์ฐธ์กฐ ๊ฑฑ์ ์์
โข ์๊ณ ๊ฐ๋จํ ๋ฐ์ดํฐ์ ์ ํฉ
ํด๋์ค (Reference Type)
โข ํ(Heap)์ ์ ์ฅ๋จ
โข ๋ณต์ฌ๋ ๋ ์ฐธ์กฐ๊ฐ ๋ณต์ฌ๋จ
โข ARC๋ก ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
โข ์ํ ์ฐธ์กฐ ์ฃผ์ ํ์
โข ๋ณต์กํ ๊ฐ์ฒด๋ ์์์ด ํ์ํ ๋ ์ฌ์ฉ
// ๊ตฌ์กฐ์ฒด ์์: ์ํ ์ฐธ์กฐ ๊ฑฑ์ ์์
struct Point {
var x: Double
var y: Double
}
struct Rectangle {
var origin: Point
var size: Point
// ๊ตฌ์กฐ์ฒด๋ ๊ฐ ํ์
์ด๋ผ ๋ณต์ฌ๋จ
mutating func move(by offset: Point) {
origin.x += offset.x
origin.y += offset.y
}
}
var rect1 = Rectangle(origin: Point(x: 0, y: 0),
size: Point(x: 100, y: 100))
var rect2 = rect1 // ๊ฐ์ด ๋ณต์ฌ๋จ!
rect2.move(by: Point(x: 10, y: 10))
print(rect1.origin.x) // 0 (๋ณ๊ฒฝ ์ ๋จ)
print(rect2.origin.x) // 10 (๋ณ๊ฒฝ๋จ)
// ํด๋์ค ์์: ์ฐธ์กฐ ํ์
class Person {
var name: String
var friend: Person? // ์ํ ์ฐธ์กฐ ๊ฐ๋ฅ์ฑ!
init(name: String) {
self.name = name
}
deinit {
print("\(name) ํด์ ")
}
}
var person1: Person? = Person(name: "์ฒ ์")
var person2: Person? = Person(name: "์ํฌ")
person1?.friend = person2
person2?.friend = person1 // ์ํ ์ฐธ์กฐ!
// ํด๊ฒฐ: weak ์ฌ์ฉ
class BetterPerson {
var name: String
weak var friend: BetterPerson? // โ
์ํ ์ฐธ์กฐ ํด๊ฒฐ!
init(name: String) {
self.name = name
}
deinit {
print("\(name) ํด์ ")
}
}
๊ทธ๋ ๋ค๋ฉด ์ธ์ ๊ตฌ์กฐ์ฒด๋ฅผ ์ฐ๊ณ ์ธ์ ํด๋์ค๋ฅผ ์จ์ผ ํ ๊น? ๐คทโโ๏ธ
๐ก ๊ตฌ์กฐ์ฒด๋ฅผ ์ฌ์ฉํด์ผ ํ ๋
โข ๊ฐ๋จํ ๋ฐ์ดํฐ ๋ชจ๋ธ (์ขํ, ํฌ๊ธฐ, ๋ฒ์ ๋ฑ)
โข ๊ฐ์ ๋๋ฑ์ฑ์ด ์ค์ํ ๋
โข ๋ณต์ฌ๋ณธ์ด ๋
๋ฆฝ์ ์ผ๋ก ๋์ํด์ผ ํ ๋
โข ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ์์ ํ๊ฒ ์ฌ์ฉํ๊ณ ์ถ์ ๋
์์: Point, Size, Range, URL, Date ๋ฑ
๐ก ํด๋์ค๋ฅผ ์ฌ์ฉํด์ผ ํ ๋
โข ์์์ด ํ์ํ ๋
โข ๊ฐ์ฒด์ ์ ์ฒด์ฑ(Identity)์ด ์ค์ํ ๋
โข ์ฌ๋ฌ ๊ณณ์์ ๊ฐ์ ์ธ์คํด์ค๋ฅผ ๊ณต์ ํด์ผ ํ ๋
โข Objective-C์ ์ํธ์ด์ฉ์ด ํ์ํ ๋
์์: ViewController, Manager, Service ๋ฑ
โก ์ฑ๋ฅ ์ต์ ํ: ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ์ฝ๋ ์์ฑ
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ํ๋ค๋ ๊ฑด ๋จ์ํ ๋์๋ฅผ ๋ฐฉ์งํ๋ ๊ฒ๋ง์ด ์๋์ผ. ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํด์ ์ฑ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๊ฒ๋ ํฌํจ๋ผ! ๐ช
// โ ๋นํจ์จ์ : ํฐ ๋ฐฐ์ด์ ๊ณ์ ๋ณต์ฌ
func processLargeDataBad() {
var data = Array(repeating: 0, count: 1_000_000)
for i in 0..<data.count {="" data[i]="i" *="" 2="" ๋งค๋ฒ="" ๋ณต์ฌ="" ๋ฐ์="" ๊ฐ๋ฅ="" }="" โ
="" ํจ์จ์ :="" ์ธ๋ฑ์ค๋ก="" ์ง์ ="" ์ ๊ทผ="" func="" processlargedatagood()="" var="" data="Array(repeating:" 0,="" count:="" 1_000_000)="" for="" i="" in="" data.indices="" copy-on-write๋ก="" ์ต์ ํ๋จ="" โ="" ๋นํจ์จ์ :="" ๋ถํ์ํ="" ๊ฐ์ฒด="" ์์ฑ="" class="" imagecache="" private="" cache:="" [string:="" uiimage]="[:]" getimage(for="" key:="" string)="" -=""> UIImage? {
// ๋งค๋ฒ ์๋ก์ด UIImage ์์ฑ (๋นํจ์จ์ !)
if cache[key] == nil {
cache[key] = UIImage(named: key)
}
return cache[key]
}
}
// โ
ํจ์จ์ : lazy ์ด๊ธฐํ์ ์บ์ฑ
class BetterImageCache {
private var cache: [String: UIImage] = [:]
func getImage(for key: String) -> UIImage? {
// ์ด๋ฏธ ์์ผ๋ฉด ์ฌ์ฌ์ฉ
if let cached = cache[key] {
return cached
}
// ์์ ๋๋ง ์์ฑํ๊ณ ์บ์ฑ
if let image = UIImage(named: key) {
cache[key] = image
return image
}
return nil
}
// ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ ์บ์ ์ ๋ฆฌ
func clearCache() {
cache.removeAll()
}
}
// โ
๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ฒ๋ฆฌ
class MyViewController: UIViewController {
private let imageCache = BetterImageCache()
override func viewDidLoad() {
super.viewDidLoad()
// ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ๊ฐ์ง
NotificationCenter.default.addObserver(
self,
selector: #selector(handleMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
@objc private func handleMemoryWarning() {
print("๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ! ์บ์ ์ ๋ฆฌ ์ค...")
imageCache.clearCache()
// ๊ธฐํ ๋ถํ์ํ ๋ฐ์ดํฐ ์ ๋ฆฌ
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}</data.count>
๐ฏ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ์ฒดํฌ๋ฆฌ์คํธ
1. ํฐ ๊ฐ์ฒด ๊ด๋ฆฌ
โข ์ด๋ฏธ์ง, ๋น๋์ค ๊ฐ์ ํฐ ๋ฆฌ์์ค๋ ํ์ํ ๋๋ง ๋ก๋
โข ์ฌ์ฉ ํ ์ฆ์ ํด์ (nil ํ ๋น)
โข ์บ์ฑ ์ ๋ต ์๋ฆฝ (๋ฉ๋ชจ๋ฆฌ vs ์๋ ํธ๋ ์ด๋์คํ)
2. ์ปฌ๋ ์
์ต์ ํ
โข ๋ฐฐ์ด ํฌ๊ธฐ๋ฅผ ๋ฏธ๋ฆฌ ์์ฝ (reserveCapacity)
โข ๋ถํ์ํ ๋ณต์ฌ ๋ฐฉ์ง
โข ์ ์ ํ ์๋ฃ๊ตฌ์กฐ ์ ํ (Array vs Set vs Dictionary)
3. Lazy ์ด๊ธฐํ
โข ๋ฌด๊ฑฐ์ด ๊ฐ์ฒด๋ lazy var๋ก ์ ์ธ
โข ํ์ํ ๋๋ง ์์ฑ๋๋๋ก ์ง์ฐ ์ด๊ธฐํ
โข ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์์ ๋ถ์ฐ
// Lazy ์ด๊ธฐํ ์์
class DataManager {
// โ ์ฆ์ ์ด๊ธฐํ: ์ฌ์ฉํ์ง ์์๋ ๋ฉ๋ชจ๋ฆฌ ์ฐจ์ง
let heavyProcessor = HeavyDataProcessor()
// โ
Lazy ์ด๊ธฐํ: ํ์ํ ๋๋ง ์์ฑ
lazy var betterProcessor: HeavyDataProcessor = {
print("HeavyDataProcessor ์์ฑ๋จ")
return HeavyDataProcessor()
}()
// โ
Lazy ํ๋กํผํฐ๋ ํด๋ก์ ๋ก ๋ณต์กํ ์ด๊ธฐํ ๊ฐ๋ฅ
lazy var configuration: Configuration = {
let config = Configuration()
config.setup()
config.loadDefaults()
return config
}()
}
class HeavyDataProcessor {
init() {
print("๋ฌด๊ฑฐ์ด ๊ฐ์ฒด ์ด๊ธฐํ ์ค...")
// ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์ด๊ธฐํ ์์
}
}
class Configuration {
func setup() {}
func loadDefaults() {}
}
// ์ฌ์ฉ ์์
let manager = DataManager()
print("DataManager ์์ฑ ์๋ฃ")
// ์ถ๋ ฅ: "DataManager ์์ฑ ์๋ฃ"
// betterProcessor๋ ์์ง ์์ฑ ์ ๋จ!
manager.betterProcessor.process()
// ์ถ๋ ฅ: "HeavyDataProcessor ์์ฑ๋จ"
// ์ด์ ์ฒ์ ์ฌ์ฉํ ๋ ์์ฑ๋จ!
๐ ์ค์ ํ๋ก์ ํธ: ๋ฉ๋ชจ๋ฆฌ ์์ ํ ์ฑ ๋ง๋ค๊ธฐ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์! ๊ฐ๋จํ ์ด๋ฏธ์ง ๋ทฐ์ด ์ฑ์ธ๋ฐ, ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์๋ฒฝํ๊ฒ ๋์ด ์์ด. ๐
import UIKit
// MARK: - ์ด๋ฏธ์ง ๋ค์ด๋ก๋ (์ฑ๊ธํค ํจํด)
class ImageDownloader {
static let shared = ImageDownloader()
private init() {}
private var downloadTasks: [URL: URLSessionDataTask] = [:]
func downloadImage(
from url: URL,
completion: @escaping (Result<uiimage, error="">) -> Void
) {
// ์ด๋ฏธ ๋ค์ด๋ก๋ ์ค์ด๋ฉด ์ค๋ณต ๋ฐฉ์ง
guard downloadTasks[url] == nil else { return }
let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
defer {
// ์์
์๋ฃ ํ ํ์คํฌ ์ ๊ฑฐ
self?.downloadTasks.removeValue(forKey: url)
}
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
guard let data = data,
let image = UIImage(data: data) else {
DispatchQueue.main.async {
completion(.failure(NSError(domain: "ImageError", code: -1)))
}
return
}
DispatchQueue.main.async {
completion(.success(image))
}
}
downloadTasks[url] = task
task.resume()
}
func cancelDownload(for url: URL) {
downloadTasks[url]?.cancel()
downloadTasks.removeValue(forKey: url)
}
deinit {
print("ImageDownloader deinit")
}
}
// MARK: - ์ด๋ฏธ์ง ์บ์ ๋งค๋์
class ImageCacheManager {
static let shared = ImageCacheManager()
private init() {
setupMemoryWarningObserver()
}
private var cache = NSCache<nsurl, uiimage="">()
private func setupMemoryWarningObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
@objc private func handleMemoryWarning() {
print("โ ๏ธ ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ! ์บ์ ์ ๋ฆฌ ์ค...")
cache.removeAllObjects()
}
func setImage(_ image: UIImage, for url: URL) {
cache.setObject(image, forKey: url as NSURL)
}
func getImage(for url: URL) -> UIImage? {
return cache.object(forKey: url as NSURL)
}
func clearCache() {
cache.removeAllObjects()
}
deinit {
NotificationCenter.default.removeObserver(self)
print("ImageCacheManager deinit")
}
}
// MARK: - ์ด๋ฏธ์ง ๋ทฐ์ด ViewController
class ImageViewerViewController: UIViewController {
private let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
private let activityIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .large)
indicator.translatesAutoresizingMaskIntoConstraints = false
return indicator
}()
private let imageURL: URL
private let downloader = ImageDownloader.shared
private let cacheManager = ImageCacheManager.shared
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()
setupUI()
loadImage()
}
private func setupUI() {
view.backgroundColor = .white
view.addSubview(imageView)
view.addSubview(activityIndicator)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
private func loadImage() {
// ์บ์ ํ์ธ
if let cachedImage = cacheManager.getImage(for: imageURL) {
print("โ
์บ์์์ ์ด๋ฏธ์ง ๋ก๋")
imageView.image = cachedImage
return
}
// ๋ค์ด๋ก๋
activityIndicator.startAnimating()
downloader.downloadImage(from: imageURL) { [weak self] result in
guard let self = self else { return }
self.activityIndicator.stopAnimating()
switch result {
case .success(let image):
print("โ
์ด๋ฏธ์ง ๋ค์ด๋ก๋ ์ฑ๊ณต")
self.imageView.image = image
self.cacheManager.setImage(image, for: self.imageURL)
case .failure(let error):
print("โ ์ด๋ฏธ์ง ๋ค์ด๋ก๋ ์คํจ: \(error)")
self.showErrorAlert()
}
}
}
private func showErrorAlert() {
let alert = UIAlertController(
title: "์ค๋ฅ",
message: "์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "ํ์ธ", style: .default))
present(alert, animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ํ๋ฉด์ด ์ฌ๋ผ์ง ๋ ๋ค์ด๋ก๋ ์ทจ์
downloader.cancelDownload(for: imageURL)
}
deinit {
print("ImageViewerViewController deinit - ๋ฉ๋ชจ๋ฆฌ ์ ์ ํด์ !")
}
}</nsurl,></uiimage,>
์ ์ฝ๋์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ํฌ์ธํธ๋ฅผ ์ ๋ฆฌํด๋ณผ๊ฒ:
๐ฏ ์ฝ๋์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ํฌ์ธํธ
1. Weak Self ์ฌ์ฉ
โข ๋ค์ด๋ก๋ completion handler์์ [weak self] ์ฌ์ฉ
โข ํ๋ฉด์ด ๋ซํ๋ ๋ฉ๋ชจ๋ฆฌ ๋์ ์์
2. ์บ์ ๊ด๋ฆฌ
โข NSCache ์ฌ์ฉ์ผ๋ก ์๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
โข ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ์ ์๋์ผ๋ก ์บ์ ์ ๋ฆฌ
3. ์์
์ทจ์
โข viewWillDisappear์์ ๋ค์ด๋ก๋ ์ทจ์
โข ๋ถํ์ํ ๋คํธ์ํฌ ์์
๋ฐฉ์ง
4. Deinit ํ์ธ
โข ๋ชจ๋ ํด๋์ค์ deinit ๊ตฌํ
โข ๋ฉ๋ชจ๋ฆฌ ํด์ ์ฌ๋ถ ํ์ธ ๊ฐ๋ฅ
์ด๋ฐ ์์ผ๋ก ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ผ๋์ ๋๊ณ ์ฝ๋ฉํ๋ฉด, ์์ ์ ์ด๊ณ ์ฑ๋ฅ ์ข์ ์ฑ์ ๋ง๋ค ์ ์์ด! ๐
๐ ์ฌ๋ฅ๋ท์์ Swift ๊ฐ๋ฐ ๋ฐฐ์ฐ๊ธฐ
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ์ฒ์์๋ ์ด๋ ต๊ฒ ๋๊ปด์ง ์ ์์ด. ํ์ง๋ง ๊ฑฑ์ ํ์ง ๋ง! ์ฌ๋ฅ๋ท์๋ Swift ๊ฐ๋ฐ์ ๊ฐ๋ฅด์ณ์ฃผ๋ ์ ๋ฌธ๊ฐ๋ค์ด ๋ง์. ์ค์ ๊ฒฝํ์ด ํ๋ถํ ๊ฐ๋ฐ์๋ค์๊ฒ 1:1๋ก ๋ฐฐ์ฐ๋ฉด ํจ์ฌ ๋น ๋ฅด๊ฒ ์ค๋ ฅ์ ํฅ์์ํฌ ์ ์์ง! ๐
ํนํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ฐ์ ๋ณต์กํ ์ฃผ์ ๋ ํผ์ ๊ณต๋ถํ๋ค ๋งํ ๋๊ฐ ๋ง์. ๊ทธ๋ด ๋ ์ฌ๋ฅ๋ท์์ ๋ฉํ ๋ฅผ ์ฐพ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ๊ฑฐ๋, ์ค์ ํ๋ก์ ํธ๋ฅผ ํจ๊ป ์งํํ๋ฉด์ ๋ฐฐ์ฐ๋ฉด ์ด๋ก ๋ง ๊ณต๋ถํ ๋๋ณด๋ค ํจ์ฌ ํจ๊ณผ์ ์ด๊ฑฐ๋ ! ๐ช
๐ก ์ฌ๋ฅ๋ท ํ์ฉ ํ
โข Swift ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ ๋ฌธ๊ฐ์๊ฒ ์ฝ๋ ๋ฆฌ๋ทฐ ๋ฐ๊ธฐ
โข ์ค์ ํ๋ก์ ํธ ๋ฉํ ๋ง์ผ๋ก ์ค๋ ฅ ํฅ์
โข ๋งํ๋ ๋ถ๋ถ์ ์ง๋ฌธํ๊ณ ๋น ๋ฅด๊ฒ ํด๊ฒฐ
โข ํฌํธํด๋ฆฌ์ค ํ๋ก์ ํธ ํจ๊ป ๋ง๋ค๊ธฐ
๐ ์ถ๊ฐ ํ์ต ๋ฆฌ์์ค์ ํ
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๋ง์คํฐํ๋ ค๋ฉด ๊ณ์ ์ฐ์ตํ๊ณ ๊ณต๋ถํด์ผ ํด. ๋ช ๊ฐ์ง ์ ์ฉํ ๋ฆฌ์์ค์ ํ์ ๊ณต์ ํ ๊ฒ! ๐
๐ฏ ํ์ต ๋ก๋๋งต
์ด๊ธ ๋จ๊ณ
โข ARC ๊ธฐ๋ณธ ๊ฐ๋
์ดํด
โข Strong, Weak, Unowned ์ฐจ์ด ํ์
โข ๊ฐ๋จํ ์์ ๋ก ์ค์ต
โข deinit์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํด์ ํ์ธ
์ค๊ธ ๋จ๊ณ
โข ํด๋ก์ ์บก์ฒ ๋ฆฌ์คํธ ํ์ฉ
โข Delegate ํจํด ๊ตฌํ
โข Instruments๋ก ๋ฉ๋ชจ๋ฆฌ ํ๋กํ์ผ๋ง
โข ์ค์ ํ๋ก์ ํธ์ ์ ์ฉ
๊ณ ๊ธ ๋จ๊ณ
โข ๋ณต์กํ ์ํ ์ฐธ์กฐ ํด๊ฒฐ
โข ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ ๊ธฐ๋ฒ
โข ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
โข ๋ฉํฐ์ค๋ ๋ฉ ํ๊ฒฝ์์์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
// ์ฐ์ต ๋ฌธ์ : ๋ค์ ์ฝ๋์ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ์ฐพ์๋ณด์ธ์!
class Node {
var value: Int
var next: Node?
var previous: Node? // ๋ฌธ์ ๋ฐ์ ๊ฐ๋ฅ!
init(value: Int) {
self.value = value
}
deinit {
print("Node \(value) ํด์ ")
}
}
// ์๋ฐฉํฅ ์ฐ๊ฒฐ ๋ฆฌ์คํธ ์์ฑ
var node1: Node? = Node(value: 1)
var node2: Node? = Node(value: 2)
node1?.next = node2
node2?.previous = node1 // ์ํ ์ฐธ์กฐ!
node1 = nil
node2 = nil
// deinit์ด ํธ์ถ๋์ง ์์!
// ํด๊ฒฐ์ฑ
: weak ์ฌ์ฉ
class BetterNode {
var value: Int
var next: BetterNode?
weak var previous: BetterNode? // โ
weak์ผ๋ก ํด๊ฒฐ!
init(value: Int) {
self.value = value
}
deinit {
print("BetterNode \(value) ํด์ ")
}
}
๐ก ์ค์ ๋๋ฒ๊น ํ
1. ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ํ ํ์ฉ
โข Xcode์ Debug Memory Graph๋ก ์๊ฐ์ ํ์ธ
โข ๋ณด๋ผ์ ๋๋ํ ์ฐพ๊ธฐ
โข ์ฐธ์กฐ ์ฒด์ธ ์ถ์
2. Instruments ํ์ฉ
โข Leaks ํ
ํ๋ฆฟ์ผ๋ก ๋์ ๊ฐ์ง
โข Allocations๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
โข ์๊ฐ์ ๋ฐ๋ฅธ ๋ฉ๋ชจ๋ฆฌ ๋ณํ ๊ด์ฐฐ
3. ๋ก๊น
์ ๋ต
โข ๋ชจ๋ ํด๋์ค์ deinit ์ถ๊ฐ
โข ์ค์ํ ๊ฐ์ฒด์ ์์ฑ/ํด์ ๋ก๊ทธ
โข ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ๊ฐ์ง ๋ฐ ๋์
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์, ์ฌ๊ธฐ๊น์ง Swift์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ARC์ ๋ํด ๊น์ด ์๊ฒ ์์๋ดค์ด! ์ฒ์์๋ ๋ณต์กํด ๋ณด์ด์ง๋ง, ํต์ฌ ์๋ฆฌ๋ฅผ ์ดํดํ๊ณ ๋๋ฉด ๊ทธ๋ ๊ฒ ์ด๋ ต์ง ์์. ๐
๐ฏ ํต์ฌ ์์ฝ
1. ARC๋ ์๋์ด์ง๋ง ์๋ฒฝํ์ง ์๋ค
โข Reference Counting์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
โข ์ํ ์ฐธ์กฐ๋ ๊ฐ๋ฐ์๊ฐ ํด๊ฒฐํด์ผ ํจ
2. Weak๊ณผ Unowned๋ฅผ ์ ์ ํ ์ฌ์ฉํ์
โข Delegate๋ ํญ์ weak
โข ํด๋ก์ ์์๋ [weak self]
โข ํ์ ์ด ์์ผ๋ฉด weak์ด ์์
3. ๋ฉ๋ชจ๋ฆฌ ํ๋กํ์ผ๋ง์ ์ต๊ดํํ์
โข Instruments๋ก ์ ๊ธฐ ์ ๊ฒ
โข deinit์ผ๋ก ํด์ ํ์ธ
โข ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ๋์ ๋ก์ง ๊ตฌํ
4. ๊ตฌ์กฐ์ฒด์ ํด๋์ค๋ฅผ ์ ์ ํ ์ ํํ์
โข ๊ฐ๋จํ ๋ฐ์ดํฐ๋ ๊ตฌ์กฐ์ฒด
โข ๋ณต์กํ ๊ฐ์ฒด๋ ํด๋์ค
โข ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ด์ ๊ณ ๋ ค
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ Swift ๊ฐ๋ฐ์์ ํ์ ์ญ๋์ด์ผ. ์ฒ์์๋ ์ค์๋ ํ๊ณ ๋ฉ๋ชจ๋ฆฌ ๋์๋ ๋ง๋ค๊ฒ ์ง๋ง, ๊ทธ๊ฒ ๋ค ๊ฒฝํ์ด ๋์ด ์ค๋ ฅ์ด ์์ด๋ ๊ฑฐ์ผ. ์ค์ํ ๊ฑด ํฌ๊ธฐํ์ง ์๊ณ ๊ณ์ ์ฐ์ตํ๋ ๊ฑฐ์ง! ๐ช
ํน์ ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋ ๋ ๊น์ด ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ์ค์ ๊ฒฝํ์ด ํ๋ถํ ๊ฐ๋ฐ์๋ค๊ณผ ํจ๊ปํ๋ฉด ํจ์ฌ ๋น ๋ฅด๊ฒ ์ฑ์ฅํ ์ ์์ด! ๐
์, ์ด์ ๋น์ ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ง์คํฐ๊ฐ ๋ ์ค๋น๊ฐ ๋์์ด! ์ค๋ ๋ฐฐ์ด ๋ด์ฉ์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด๊ณ , ์์ ์ ์ด๊ณ ์ฑ๋ฅ ์ข์ ์ฑ์ ๋ง๋ค์ด๋ณด์. ํ์ดํ ! ๐
๐ Happy Coding with Swift! ๐
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๋ง์คํฐํ๊ณ ์ต๊ณ ์ iOS ๊ฐ๋ฐ์๊ฐ ๋์ด๋ณด์ธ์!
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

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