์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Swift ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์™€ ARC๋ฅผ ํ†ตํ•œ ํšจ์œจ์  ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๐Ÿš€ Swift ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์™€ ARC๋ฅผ ํ†ตํ•œ ํšจ์œจ์  ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๋ฐฐ์šฐ๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์˜ ๋ชจ๋“  ๊ฒƒ

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

Swift๋Š” ARC(Automatic Reference Counting)๋ผ๋Š” ๋˜‘๋˜‘ํ•œ ์‹œ์Šคํ…œ์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ด์ฃผ์ง€๋งŒ, ์šฐ๋ฆฌ๊ฐ€ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๋ฉด ์˜คํžˆ๋ ค ๋…์ด ๋  ์ˆ˜๋„ ์žˆ์–ด. ๋งˆ์น˜ ์ž๋™์ฐจ์˜ ์ž๋™ ๋ณ€์†๊ธฐ์ฒ˜๋Ÿผ ํŽธ๋ฆฌํ•˜์ง€๋งŒ, ์›๋ฆฌ๋ฅผ ๋ชจ๋ฅด๋ฉด ์ œ๋Œ€๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด์•ผ! ๐Ÿš—

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

๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ๊ฐ์ฒด ์ƒ์„ฑ ๐Ÿ“ฆ ์ฐธ์กฐ ์นด์šดํŠธ +1 ARC ๊ด€๋ฆฌ ์ž๋™ ์ถ”์  ๐Ÿ” ์ฐธ์กฐ ์นด์šดํŠธ 0 ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ์ž๋™ ์ •๋ฆฌ ๐Ÿ—‘๏ธ ๊ฐ•ํ•œ์ฐธ์กฐ ์•ฝํ•œ์ฐธ์กฐ ๋ฏธ์†Œ์œ ์ฐธ์กฐ Swift ARC ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ์‚ฌ์ดํด ํšจ์œจ์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ์„ ์œ„ํ•œ ์ž๋™ ์ฐธ์กฐ ์นด์šดํŒ…

๐ŸŽฏ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ, ์™œ ์ค‘์š”ํ• ๊นŒ?

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

์ƒ์ƒํ•ด๋ด. ๋„ˆ๊ฐ€ ๋งŒ๋“  ์•ฑ์ด ์‚ฌ์šฉ์ž์˜ ์•„์ดํฐ์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ์–ด. ์ฒ˜์Œ์—” ์ž˜ ๋Œ์•„๊ฐ€๋Š”๋ฐ, ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ์ ์  ๋А๋ ค์ง€๊ณ , ๊ฒฐ๊ตญ ์•ฑ์ด ๊ฐ•์ œ ์ข…๋ฃŒ๋˜๋Š” ๊ฑฐ์•ผ. ์‚ฌ์šฉ์ž๋Š” ์•ฑ์Šคํ† ์–ด์— ๋ณ„์  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)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„! ์Šคํƒ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋‹ˆ๊นŒ ๋น ๋ฅด๊ณ , ARC์˜ ์˜ค๋ฒ„ํ—ค๋“œ๋„ ์—†๊ฑฐ๋“ . ํ•˜์ง€๋งŒ ์ƒ์†์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜ ์ฐธ์กฐ ์˜๋ฏธ๋ก ์ด ํ•„์š”ํ•˜๋ฉด Class๋ฅผ ์จ์•ผ๊ฒ ์ง€? ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด! ๐ŸŽฏ
// ์Šคํƒ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์˜ˆ์‹œ (๊ฐ’ ํƒ€์ž…)
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์ด ๋˜์ง€ ์•Š์•„! ๊ฒฐ๊ณผ๋Š”? ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜! ๐Ÿ’€

โŒ ์ˆœํ™˜ ์ฐธ์กฐ (๋ฌธ์ œ) โœ… ํ•ด๊ฒฐ๋œ ์ฐธ์กฐ ๊ฐ์ฒด A RC: 1 ๊ฐ์ฒด B RC: 1 strong strong ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐœ์ƒ! ์„œ๋กœ ์ฐธ์กฐํ•˜์—ฌ ํ•ด์ œ ๋ถˆ๊ฐ€ ๊ฐ์ฒด A RC: 1 ๊ฐ์ฒด B RC: 0 strong weak ์ •์ƒ ํ•ด์ œ ๊ฐ€๋Šฅ! ์•ฝํ•œ ์ฐธ์กฐ๋กœ ์ˆœํ™˜ ๋ฐฉ์ง€ ํ•ต์‹ฌ: weak ๋˜๋Š” unowned ์‚ฌ์šฉ! ์ˆœํ™˜ ์ฐธ์กฐ์˜ ํ•œ์ชฝ์„ ์•ฝํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ํ•ด๊ฒฐ
โš ๏ธ ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ๋“ค:

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)
    }
}

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™”: ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ

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

๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์ตœ์ ํ™” ์ „๋žต โŒ ๋น„ํšจ์œจ์  ํฐ ๊ฐ์ฒด ๋ณต์‚ฌ ๋ถˆํ•„์š”ํ•œ ์บ์‹ฑ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์  โš ๏ธ ๋ณดํ†ต ๊ธฐ๋ณธ ๊ตฌํ˜„ ์ผ๋ถ€ ์ตœ์ ํ™” ๊ด€๋ฆฌ ํ•„์š” โœ… ํšจ์œจ์  ์ฐธ์กฐ ํ™œ์šฉ ์ง€์—ฐ ๋กœ๋”ฉ ์ž๋™ ์ •๋ฆฌ ๐Ÿš€ ์ตœ๊ณ  Copy-on-Write ๋ฉ”๋ชจ๋ฆฌ ํ’€๋ง ํ”„๋กœํŒŒ์ผ๋ง ์ด๋ฏธ์ง€ ์ตœ์ ํ™” ์ ์ ˆํ•œ ํ•ด์ƒ๋„ ์ง€์—ฐ ์ดˆ๊ธฐํ™” ํ•„์š”ํ•  ๋•Œ๋งŒ ์ƒ์„ฑ ์บ์‹œ ๊ด€๋ฆฌ ๋ฉ”๋ชจ๋ฆฌ ๊ฒฝ๊ณ  ๋Œ€์‘ ๊ฐ’ ํƒ€์ž… ํ™œ์šฉ Struct ์šฐ์„  ์ž๋™ ํ•ด์ œ Autoreleasepool ํ•ต์‹ฌ: ํ•„์š”ํ•œ ๋งŒํผ๋งŒ, ํ•„์š”ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ! ๐Ÿ’ก

๐Ÿ–ผ๏ธ 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๋Š” ๋‹ค๋ฅธ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉ
๐Ÿ’ก COW ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ:

์žฅ์ :
โ€ข ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ : ์‹ค์ œ๋กœ ์ˆ˜์ •ํ•  ๋•Œ๋งŒ ๋ณต์‚ฌ
โ€ข ์„ฑ๋Šฅ ํ–ฅ์ƒ: ๋ถˆํ•„์š”ํ•œ ๋ณต์‚ฌ ๋ฐฉ์ง€
โ€ข ์•ˆ์ „์„ฑ: ๊ฐ’ ํƒ€์ž…์˜ ์žฅ์  ์œ ์ง€

๋‹จ์ :
โ€ข ๊ตฌํ˜„ ๋ณต์žก๋„ ์ฆ๊ฐ€
โ€ข ์ฐธ์กฐ ์นด์šดํŒ… ์˜ค๋ฒ„ํ—ค๋“œ
โ€ข ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ์ฃผ์˜ ํ•„์š”

์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?
โ€ข ํฐ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด๋ฅผ ์ž์ฃผ ๋ณต์‚ฌํ•  ๋•Œ
โ€ข ์ฝ๊ธฐ๊ฐ€ ์“ฐ๊ธฐ๋ณด๋‹ค ํ›จ์”ฌ ๋งŽ์„ ๋•Œ
โ€ข ๊ฐ’ ํƒ€์ž…์˜ ์˜๋ฏธ๋ก ์ด ํ•„์š”ํ•  ๋•Œ

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๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•ด!
โ€ข ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ํ™œ์šฉํ•ด. ํ˜ผ์ž ๊ณ ๋ฏผํ•˜์ง€ ๋งˆ!
โ€ข ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ!

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

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

๐ŸŒŸ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŒŸ

Swift ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์™€ ARC์˜ ๋ชจ๋“  ๊ฒƒ์„ ๋งˆ์Šคํ„ฐํ–ˆ์–ด์š”!
์ด์ œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์—†๋Š” ์™„๋ฒฝํ•œ ์•ฑ์„ ๋งŒ๋“ค ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ณ„์†ํ•ด์„œ ์—ฐ์Šตํ•˜๊ณ , ์‹คํ—˜ํ•˜๊ณ , ๋ฐฐ์šฐ์„ธ์š”!
๋‹น์‹ ์€ ์ด๋ฏธ ํ›Œ๋ฅญํ•œ iOS ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค! ๐Ÿ’ชโœจ

Happy Coding! ๐ŸŽ‰

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0