์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Grand Central Dispatch์™€ OperationQueue๋กœ ์ •๋ณตํ•˜๋Š” Swift ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์˜ ์„ธ๊ณ„

๐Ÿš€ Grand Central Dispatch์™€ OperationQueue๋กœ ์ •๋ณตํ•˜๋Š” Swift ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์˜ ์„ธ๊ณ„

๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ, ์ด์ œ ์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ์ดํ•ดํ•ด๋ณด์ž!

์•ˆ๋…•! ๐Ÿ˜Š ์˜ค๋Š˜์€ Swift ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ ๊ฑฐ์•ผ. ํ˜น์‹œ ์•ฑ์ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๊ฑฐ๋‚˜ ํ™”๋ฉด์ด ๋ฉˆ์ถฐ์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ต๋‹ตํ•ดํ•˜๋Š” ๊ฒฝํ—˜ ์žˆ์ง€? ๊ทธ๊ฑด ๋ฐ”๋กœ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด์•ผ. ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ํ•ต์‹ฌ ๊ธฐ์ˆ ์ด ๋ฐ”๋กœ Grand Central Dispatch(GCD)์™€ OperationQueue์•ผ!

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

๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ ๊ฐœ๋…๋„ Main Thread UI Updates User Input ๐ŸŽจ Background Network Data Process File I/O โš™๏ธ GCD Queue Serial Queue Concurrent Global Queue ๐Ÿ”„ ์ž‘์—… ์ „๋‹ฌ ํ ๊ด€๋ฆฌ

๐ŸŽญ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์ด ๋ญ๊ธธ๋ž˜?

๋จผ์ € ๊ธฐ๋ณธ ๊ฐœ๋…๋ถ€ํ„ฐ ์žก๊ณ  ๊ฐ€์ž! ์Šค๋ ˆ๋“œ(Thread)๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ํ๋ฆ„์˜ ๋‹จ์œ„์•ผ. ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“œ๋Š” ์•ฑ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋Š”๋ฐ, ์ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” UI๋ฅผ ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž์˜ ํ„ฐ์น˜ ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์•„์ฃผ ์ค‘์š”ํ•œ ๋…€์„์ด์•ผ. ๐ŸŽจ

๐Ÿ’ก ํ•ต์‹ฌ ํฌ์ธํŠธ

๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” UI ์ „๋‹ด ์Šค๋ ˆ๋“œ์•ผ! ๋งŒ์•ฝ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด๋‚˜ ๋ณต์žกํ•œ ๊ณ„์‚ฐ ๊ฐ™์€ ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ํ•˜๋ฉด, ํ™”๋ฉด์ด ๋ฉˆ์ถฐ๋ฒ„๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž๋Š” "์ด ์•ฑ ๋ญ์•ผ? ๐Ÿ˜ค"๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ฒŒ ๋ผ. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์ด๋Ÿฐ ๋ฌด๊ฑฐ์šด ์ž‘์—…๋“ค์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๋กœ ๋ณด๋‚ด์•ผ ํ•ด!

๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•ด์„œ ์ž‘์—…์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ:

๐Ÿ• ํ”ผ์ž ๊ฐ€๊ฒŒ ๋น„์œ 

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

๐ŸŒŸ Grand Central Dispatch (GCD) ์™„์ „ ์ •๋ณต

GCD๋Š” Apple์ด ๋งŒ๋“  ์ €์ˆ˜์ค€(low-level) API๋กœ, ๋ฉ€ํ‹ฐ์ฝ”์–ด ํ”„๋กœ์„ธ์„œ์—์„œ ๋™์‹œ์„ฑ ์ฝ”๋“œ๋ฅผ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์•ผ. C ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด์ ธ์„œ ์„ฑ๋Šฅ์ด ์ •๋ง ๋›ฐ์–ด๋‚˜๊ณ , Swift์—์„œ๋„ ์•„์ฃผ ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

๐Ÿ“ฆ Dispatch Queue์˜ ์ข…๋ฅ˜

GCD์˜ ํ•ต์‹ฌ์€ ๋ฐ”๋กœ Dispatch Queue์•ผ. ํ(Queue)๋Š” ์ž‘์—…๋“ค์„ ๋‹ด์•„๋‘๋Š” ๋Œ€๊ธฐ์—ด์ธ๋ฐ, ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€ ํƒ€์ž…์ด ์žˆ์–ด:

๐Ÿ”น Serial Queue (์ง๋ ฌ ํ)

ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ž‘์—…๋งŒ ์‹คํ–‰ํ•ด. ์ž‘์—…๋“ค์ด ์ˆœ์„œ๋Œ€๋กœ ์ค„์„ ์„œ์„œ ๊ธฐ๋‹ค๋ฆฌ๋‹ค๊ฐ€, ์•ž ์ž‘์—…์ด ๋๋‚˜๋ฉด ๋‹ค์Œ ์ž‘์—…์ด ์‹œ์ž‘๋ผ. ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ์ž‘์—…์— ์‚ฌ์šฉํ•ด!

์˜ˆ์‹œ: ํŒŒ์ผ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ์“ฐ๊ธฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŠธ๋žœ์žญ์…˜

๐Ÿ”น Concurrent Queue (๋™์‹œ ํ)

์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด. ์ž‘์—…๋“ค์ด ์‹œ์ž‘ ์ˆœ์„œ๋Š” ๋ณด์žฅ๋˜์ง€๋งŒ, ๋๋‚˜๋Š” ์ˆœ์„œ๋Š” ๋ณด์žฅ๋˜์ง€ ์•Š์•„. ๋…๋ฆฝ์ ์ธ ์ž‘์—…๋“ค์„ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•  ๋•Œ ์‚ฌ์šฉํ•ด!

์˜ˆ์‹œ: ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ, ๋ณ‘๋ ฌ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

๐ŸŽฏ Main Queue์™€ Global Queue

GCD๋Š” ์‹œ์Šคํ…œ์ด ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘” ํ๋“ค์„ ์ œ๊ณตํ•ด. ์ด๊ฑธ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๋”ฐ๋กœ ํ๋ฅผ ๋งŒ๋“ค ํ•„์š” ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด!

Main Queue (๋ฉ”์ธ ํ)

๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋Š” Serial Queue์•ผ. UI ์—…๋ฐ์ดํŠธ๋Š” ๋ฐ˜๋“œ์‹œ ์—ฌ๊ธฐ์„œ ํ•ด์•ผ ํ•ด! ์ด๊ฑด ์ •๋ง ์ค‘์š”ํ•œ ๊ทœ์น™์ด์•ผ. ๐ŸŽจ

// ๋ฉ”์ธ ํ์—์„œ UI ์—…๋ฐ์ดํŠธ
DispatchQueue.main.async {
    self.label.text = "์ž‘์—… ์™„๋ฃŒ!"
    self.activityIndicator.stopAnimating()
    self.tableView.reloadData()
}

// ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ (์ฃผ์˜: ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด ๋ฐ๋“œ๋ฝ!)
DispatchQueue.main.sync {
    // ์ด ์ฝ”๋“œ๋Š” ์œ„ํ—˜ํ•ด! ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด ์•ฑ์ด ๋ฉˆ์ถฐ๋ฒ„๋ ค
}
Global Queue (๊ธ€๋กœ๋ฒŒ ํ)

์‹œ์Šคํ…œ์ด ๊ด€๋ฆฌํ•˜๋Š” Concurrent Queue์•ผ. QoS(Quality of Service) ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์žˆ์–ด. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์— ์‚ฌ์šฉํ•˜๋ฉด ๋ผ!

// QoS ์šฐ์„ ์ˆœ์œ„๋ณ„ ๊ธ€๋กœ๋ฒŒ ํ ์‚ฌ์šฉ
// 1. User Interactive - ๊ฐ€์žฅ ๋†’์€ ์šฐ์„ ์ˆœ์œ„ (UI ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋“ฑ)
DispatchQueue.global(qos: .userInteractive).async {
    // ์ฆ‰๊ฐ์ ์ธ ๋ฐ˜์‘์ด ํ•„์š”ํ•œ ์ž‘์—…
}

// 2. User Initiated - ๋†’์€ ์šฐ์„ ์ˆœ์œ„ (์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์ž‘ํ•œ ์ž‘์—…)
DispatchQueue.global(qos: .userInitiated).async {
    // ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ž‘์—… (๊ฒ€์ƒ‰, ํŒŒ์ผ ์—ด๊ธฐ ๋“ฑ)
}

// 3. Default - ๊ธฐ๋ณธ ์šฐ์„ ์ˆœ์œ„
DispatchQueue.global(qos: .default).async {
    // ์ผ๋ฐ˜์ ์ธ ์ž‘์—…
}

// 4. Utility - ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ (์ง„ํ–‰ ํ‘œ์‹œ๊ฐ€ ์žˆ๋Š” ๊ธด ์ž‘์—…)
DispatchQueue.global(qos: .utility).async {
    // ๋‹ค์šด๋กœ๋“œ, ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๋“ฑ
}

// 5. Background - ๊ฐ€์žฅ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ (์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•˜๋Š” ์ž‘์—…)
DispatchQueue.global(qos: .background).async {
    // ๋ฐฑ์—…, ๋™๊ธฐํ™”, ์ธ๋ฑ์‹ฑ ๋“ฑ
}

๐ŸŽ“ QoS ์„ ํƒ ํŒ

QoS๋Š” ์‹œ์Šคํ…œ์ด CPU ์‹œ๊ฐ„๊ณผ ์—๋„ˆ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ๋ฐฐ๋ถ„ํ• ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ํžŒํŠธ์•ผ. ๋†’์€ ์šฐ์„ ์ˆœ์œ„์ผ์ˆ˜๋ก ๋” ๋งŽ์€ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ›์ง€๋งŒ, ๋ฐฐํ„ฐ๋ฆฌ๋„ ๋” ๋นจ๋ฆฌ ์†Œ๋ชจ๋ผ. ์ž‘์—…์˜ ์„ฑ๊ฒฉ์— ๋งž๊ฒŒ ์ ์ ˆํ•œ QoS๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด! ๐Ÿ”‹

๐Ÿ”ง ์ปค์Šคํ…€ Dispatch Queue ๋งŒ๋“ค๊ธฐ

๋•Œ๋กœ๋Š” ํŠน์ • ๋ชฉ์ ์„ ์œ„ํ•œ ์ „์šฉ ํ๊ฐ€ ํ•„์š”ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ๊ทธ๋Ÿด ๋• ์ง์ ‘ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด!

// Serial Queue ์ƒ์„ฑ
let serialQueue = DispatchQueue(label: "com.myapp.serialQueue")

// Concurrent Queue ์ƒ์„ฑ
let concurrentQueue = DispatchQueue(label: "com.myapp.concurrentQueue", 
                                    attributes: .concurrent)

// QoS์™€ ํ•จ๊ป˜ ์ƒ์„ฑ
let customQueue = DispatchQueue(label: "com.myapp.customQueue",
                                qos: .userInitiated,
                                attributes: .concurrent)

// ์‚ฌ์šฉ ์˜ˆ์‹œ
serialQueue.async {
    print("์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์ž‘์—… 1")
}

serialQueue.async {
    print("์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์ž‘์—… 2")
}

concurrentQueue.async {
    print("๋™์‹œ์— ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ์ž‘์—… 1")
}

concurrentQueue.async {
    print("๋™์‹œ์— ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ์ž‘์—… 2")
}

โšก async vs sync - ๋น„๋™๊ธฐ์™€ ๋™๊ธฐ์˜ ์ฐจ์ด

GCD์—์„œ ์ž‘์—…์„ ํ์— ์ถ”๊ฐ€ํ•  ๋•Œ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์–ด. ์ด ์ฐจ์ด๋ฅผ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๋Š” ๊ฒŒ ์ •๋ง ์ค‘์š”ํ•ด! ๐Ÿ’ฏ

async (๋น„๋™๊ธฐ)

์ž‘์—…์„ ํ์— ๋„ฃ๊ณ  ๋ฐ”๋กœ ๋‹ค์Œ ์ฝ”๋“œ๋กœ ๋„˜์–ด๊ฐ€. ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์•„. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์ด๊ฑธ ์‚ฌ์šฉํ•ด!

sync (๋™๊ธฐ)

์ž‘์—…์„ ํ์— ๋„ฃ๊ณ  ๊ทธ ์ž‘์—…์ด ์™„์ „ํžˆ ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค. ์ž‘์—…์ด ๋๋‚˜์•ผ ๋‹ค์Œ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋ผ. ์กฐ์‹ฌํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ด!

// async ์˜ˆ์‹œ - ๋…ผ๋ธ”๋กœํ‚น
print("1. ์‹œ์ž‘")
DispatchQueue.global().async {
    print("2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ค‘...")
    sleep(2)
    print("3. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์™„๋ฃŒ")
}
print("4. ๊ณ„์† ์ง„ํ–‰")

// ์ถœ๋ ฅ ์ˆœ์„œ: 1 -> 4 -> 2 -> 3 (4๊ฐ€ 2๋ณด๋‹ค ๋จผ์ €!)

// sync ์˜ˆ์‹œ - ๋ธ”๋กœํ‚น
print("1. ์‹œ์ž‘")
DispatchQueue.global().sync {
    print("2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ค‘...")
    sleep(2)
    print("3. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์™„๋ฃŒ")
}
print("4. ์ž‘์—… ์™„๋ฃŒ ํ›„ ์ง„ํ–‰")

// ์ถœ๋ ฅ ์ˆœ์„œ: 1 -> 2 -> 3 -> 4 (์ˆœ์„œ๋Œ€๋กœ!)

// โš ๏ธ ๋ฐ๋“œ๋ฝ ์ฃผ์˜!
DispatchQueue.main.async {
    // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ ์ค‘
    DispatchQueue.main.sync {
        // ์ด๊ฑด ๋ฐ๋“œ๋ฝ! ๋ฉ”์ธ ํ๊ฐ€ ์ž๊ธฐ ์ž์‹ ์„ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ๋จ
        print("์‹คํ–‰๋˜์ง€ ์•Š์•„!")
    }
}

๐ŸŽช ์‹ค์ „ ํŒจํ„ด: ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ํ›„ UI ์—…๋ฐ์ดํŠธ

๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด์ด์•ผ! ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด๋‚˜ ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ํ•˜๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ UI์— ๋ฐ˜์˜ํ•˜๋Š” ๊ฑฐ์ง€. ๐Ÿ˜Ž

// ํŒจํ„ด 1: ๊ธฐ๋ณธ ํ˜•ํƒœ
func loadUserData() {
    // ๋กœ๋”ฉ ํ‘œ์‹œ
    activityIndicator.startAnimating()
    
    // ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
    DispatchQueue.global(qos: .userInitiated).async {
        // ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด๋‚˜ ๋ฌด๊ฑฐ์šด ์ž‘์—…
        let userData = self.fetchUserDataFromServer()
        
        // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ UI ์—…๋ฐ์ดํŠธ
        DispatchQueue.main.async {
            self.nameLabel.text = userData.name
            self.emailLabel.text = userData.email
            self.activityIndicator.stopAnimating()
        }
    }
}

// ํŒจํ„ด 2: ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€ ๋™์‹œ ๋‹ค์šด๋กœ๋“œ
func downloadImages(urls: [URL]) {
    let group = DispatchGroup()
    var images: [UIImage] = []
    
    for url in urls {
        group.enter()
        DispatchQueue.global(qos: .userInitiated).async {
            if let data = try? Data(contentsOf: url),
               let image = UIImage(data: data) {
                images.append(image)
            }
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        // ๋ชจ๋“  ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ
        self.displayImages(images)
    }
}

// ํŒจํ„ด 3: ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํฌํ•จ
func processData() {
    DispatchQueue.global(qos: .userInitiated).async {
        do {
            let result = try self.heavyComputation()
            
            DispatchQueue.main.async {
                self.showResult(result)
            }
        } catch {
            DispatchQueue.main.async {
                self.showError(error)
            }
        }
    }
}

๐Ÿ”„ Dispatch Group์œผ๋กœ ์—ฌ๋Ÿฌ ์ž‘์—… ๊ด€๋ฆฌํ•˜๊ธฐ

์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋น„๋™๊ธฐ ์ž‘์—…์ด ๋ชจ๋‘ ๋๋‚ฌ์„ ๋•Œ ํŠน์ • ๋™์ž‘์„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? DispatchGroup์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ! ์ •๋ง ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ด์•ผ. ๐ŸŽฏ

// ๋ฐฉ๋ฒ• 1: enter/leave ์‚ฌ์šฉ
let group = DispatchGroup()

group.enter()
DispatchQueue.global().async {
    print("์ž‘์—… 1 ์‹œ์ž‘")
    sleep(2)
    print("์ž‘์—… 1 ์™„๋ฃŒ")
    group.leave()
}

group.enter()
DispatchQueue.global().async {
    print("์ž‘์—… 2 ์‹œ์ž‘")
    sleep(1)
    print("์ž‘์—… 2 ์™„๋ฃŒ")
    group.leave()
}

group.enter()
DispatchQueue.global().async {
    print("์ž‘์—… 3 ์‹œ์ž‘")
    sleep(3)
    print("์ž‘์—… 3 ์™„๋ฃŒ")
    group.leave()
}

// ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ์‹คํ–‰
group.notify(queue: .main) {
    print("๐ŸŽ‰ ๋ชจ๋“  ์ž‘์—… ์™„๋ฃŒ!")
}

// ๋ฐฉ๋ฒ• 2: async(group:) ์‚ฌ์šฉ
let group2 = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)

queue.async(group: group2) {
    print("๊ทธ๋ฃน ์ž‘์—… 1")
}

queue.async(group: group2) {
    print("๊ทธ๋ฃน ์ž‘์—… 2")
}

group2.notify(queue: .main) {
    print("๊ทธ๋ฃน ์ž‘์—… ๋ชจ๋‘ ์™„๋ฃŒ!")
}

// ๋ฐฉ๋ฒ• 3: wait์œผ๋กœ ๋™๊ธฐ์ ์œผ๋กœ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ (์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉ!)
let group3 = DispatchGroup()

group3.enter()
DispatchQueue.global().async {
    sleep(2)
    print("์ž‘์—… ์™„๋ฃŒ")
    group3.leave()
}

// ์ตœ๋Œ€ 5์ดˆ ๋Œ€๊ธฐ
let result = group3.wait(timeout: .now() + 5)
if result == .success {
    print("์‹œ๊ฐ„ ๋‚ด์— ์™„๋ฃŒ๋จ")
} else {
    print("ํƒ€์ž„์•„์›ƒ!")
}

โš ๏ธ wait() ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

wait()๋Š” ํ˜„์žฌ ์Šค๋ ˆ๋“œ๋ฅผ ๋ธ”๋กœํ‚นํ•ด. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด UI๊ฐ€ ๋ฉˆ์ถ”๋‹ˆ๊นŒ ์ ˆ๋Œ€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ๋งˆ! ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, notify()๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋” ์•ˆ์ „ํ•ด. ๐Ÿšจ

๐ŸŽฒ Dispatch Barrier๋กœ ์ฝ๊ธฐ/์“ฐ๊ธฐ ๋™๊ธฐํ™”

Concurrent Queue์—์„œ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์“ธ ๋•Œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์–ด. ์ด๋Ÿด ๋•Œ Barrier๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด!

class ThreadSafeArray<T> {
    private var array: [T] = []
    private let queue = DispatchQueue(label: "com.myapp.arrayQueue", 
                                      attributes: .concurrent)
    
    // ์ฝ๊ธฐ๋Š” ๋™์‹œ์— ๊ฐ€๋Šฅ
    func get(at index: Int) -> T? {
        var result: T?
        queue.sync {
            guard index < array.count else { return }
            result = array[index]
        }
        return result
    }
    
    // ์“ฐ๊ธฐ๋Š” ๋ฐฐ๋ฆฌ์–ด๋กœ ๋…์ 
    func append(_ element: T) {
        queue.async(flags: .barrier) {
            self.array.append(element)
        }
    }
    
    // ์ „์ฒด ๋ฐฐ์—ด ์ฝ๊ธฐ
    var allElements: [T] {
        var result: [T] = []
        queue.sync {
            result = array
        }
        return result
    }
    
    // ์“ฐ๊ธฐ ์ž‘์—…
    func removeAll() {
        queue.async(flags: .barrier) {
            self.array.removeAll()
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let safeArray = ThreadSafeArray<Int>()

// ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™์‹œ์— ์ถ”๊ฐ€ํ•ด๋„ ์•ˆ์ „!
DispatchQueue.concurrentPerform(iterations: 100) { index in
    safeArray.append(index)
}

print("์ด ๊ฐœ์ˆ˜: \(safeArray.allElements.count)")

Barrier๋Š” ๋งˆ์น˜ ๋„๋กœ์˜ ์ฐจ๋‹จ๊ธฐ ๊ฐ™์•„. ์“ฐ๊ธฐ ์ž‘์—…์ด ์‹คํ–‰๋  ๋•Œ๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ์ž‘์—…์„ ๋ง‰๊ณ , ์“ฐ๊ธฐ๊ฐ€ ๋๋‚˜๋ฉด ๋‹ค์‹œ ์ฝ๊ธฐ ์ž‘์—…๋“ค์ด ๋™์‹œ์— ์‹คํ–‰๋  ์ˆ˜ ์žˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฐ์ดํ„ฐ ๊ฒฝ์Ÿ(Race Condition)์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์ง€! ๐Ÿšง

โฐ Dispatch After๋กœ ์ง€์—ฐ ์‹คํ–‰

ํŠน์ • ์‹œ๊ฐ„ ํ›„์— ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ด. Timer๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๊ณ  ํŽธ๋ฆฌํ•ด!

// 3์ดˆ ํ›„ ์‹คํ–‰
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
    print("3์ดˆ๊ฐ€ ์ง€๋‚ฌ์–ด!")
    self.showMessage("์‹œ๊ฐ„ ์ดˆ๊ณผ")
}

// ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ๋„ ๊ฐ€๋Šฅ
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
    print("0.5์ดˆ ํ›„ ์‹คํ–‰")
}

// ์‹ค์ „ ์˜ˆ์‹œ: ์Šคํ”Œ๋ž˜์‹œ ํ™”๋ฉด
func showSplashScreen() {
    splashView.isHidden = false
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        UIView.animate(withDuration: 0.5) {
            self.splashView.alpha = 0
        } completion: { _ in
            self.splashView.isHidden = true
            self.splashView.alpha = 1
        }
    }
}

// ์—ฌ๋Ÿฌ ๋‹จ๊ณ„ ์ง€์—ฐ ์‹คํ–‰
func showSequentialMessages() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        print("1์ดˆ ํ›„: ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€")
    }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        print("2์ดˆ ํ›„: ๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€")
    }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
        print("3์ดˆ ํ›„: ์„ธ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€")
    }
}

๐Ÿ” Dispatch Semaphore๋กœ ๋™์‹œ ์‹คํ–‰ ์ œํ•œ

๋™์‹œ์— ์‹คํ–‰๋˜๋Š” ์ž‘์—…์˜ ๊ฐœ์ˆ˜๋ฅผ ์ œํ•œํ•˜๊ณ  ์‹ถ์„ ๋•Œ Semaphore๋ฅผ ์‚ฌ์šฉํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋™์‹œ์— 3๊ฐœ๊นŒ์ง€๋งŒ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

// ๋™์‹œ์— 3๊ฐœ๊นŒ์ง€๋งŒ ์‹คํ–‰
let semaphore = DispatchSemaphore(value: 3)

func downloadFile(url: URL) {
    DispatchQueue.global().async {
        semaphore.wait() // ์„ธ๋งˆํฌ์–ด ํš๋“ (์นด์šดํŠธ -1)
        
        print("๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘: \(url)")
        // ์‹ค์ œ ๋‹ค์šด๋กœ๋“œ ์ž‘์—…
        sleep(2)
        print("๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ: \(url)")
        
        semaphore.signal() // ์„ธ๋งˆํฌ์–ด ๋ฐ˜ํ™˜ (์นด์šดํŠธ +1)
    }
}

// 10๊ฐœ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์‹œ๋„ (ํ•˜์ง€๋งŒ ๋™์‹œ์—๋Š” 3๊ฐœ๋งŒ)
let urls = (1...10).map { URL(string: "https://example.com/file\($0)")! }
urls.forEach { downloadFile(url: $0) }

// ์‹ค์ „ ์˜ˆ์‹œ: API ์š”์ฒญ ์ œํ•œ
class APIManager {
    private let semaphore = DispatchSemaphore(value: 5) // ๋™์‹œ 5๊ฐœ ์ œํ•œ
    
    func request(endpoint: String, completion: @escaping (Data?) -> Void) {
        DispatchQueue.global(qos: .userInitiated).async {
            self.semaphore.wait()
            
            // API ์š”์ฒญ
            let data = self.performRequest(endpoint)
            
            DispatchQueue.main.async {
                completion(data)
            }
            
            self.semaphore.signal()
        }
    }
    
    private func performRequest(_ endpoint: String) -> Data? {
        // ์‹ค์ œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๋กœ์ง
        return nil
    }
}

๐ŸŽฏ OperationQueue ์™„์ „ ์ •๋ณต

์ž, ์ด์ œ OperationQueue์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž! GCD๊ฐ€ ์ €์ˆ˜์ค€ API๋ผ๋ฉด, OperationQueue๋Š” ๊ณ ์ˆ˜์ค€ API์•ผ. ๋” ๋งŽ์€ ๊ธฐ๋Šฅ๊ณผ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜์ง€. ๐ŸŽจ

OperationQueue๋Š” Objective-C ์‹œ๋Œ€๋ถ€ํ„ฐ ์žˆ๋˜ ๊ธฐ์ˆ ์ธ๋ฐ, ์ง€๊ธˆ๋„ ์—ฌ์ „ํžˆ ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์šฉํ•ด. ํŠนํžˆ ๋ณต์žกํ•œ ์ž‘์—… ์˜์กด์„ฑ์ด๋‚˜ ์ทจ์†Œ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•  ๋•Œ GCD๋ณด๋‹ค ํ›จ์”ฌ ํŽธ๋ฆฌํ•ด!

๐Ÿงฉ Operation์˜ ์ข…๋ฅ˜

OperationQueue์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” ์ž‘์—…์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€ ํƒ€์ž…์ด ์žˆ์–ด:

๐Ÿ”ธ BlockOperation

ํด๋กœ์ € ํ˜•ํƒœ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ž‘์—…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด. ๋น ๋ฅด๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹์•„!

let operation = BlockOperation {
    print("์ž‘์—… ์‹คํ–‰!")
}

queue.addOperation(operation)

๐Ÿ”ธ Custom Operation

Operation ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์•„ ๋ณต์žกํ•œ ์ž‘์—…์„ ์บก์Аํ™”ํ•  ์ˆ˜ ์žˆ์–ด. ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์•„!

class MyOperation: Operation {
    override func main() {
        print("์ปค์Šคํ…€ ์ž‘์—…!")
    }
}

๐ŸŽช OperationQueue ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

// OperationQueue ์ƒ์„ฑ
let operationQueue = OperationQueue()

// ํ ์ด๋ฆ„ ์„ค์ • (๋””๋ฒ„๊น…์— ์œ ์šฉ)
operationQueue.name = "com.myapp.operationQueue"

// ๋™์‹œ ์‹คํ–‰ ๊ฐœ์ˆ˜ ์ œํ•œ
operationQueue.maxConcurrentOperationCount = 3

// QoS ์„ค์ •
operationQueue.qualityOfService = .userInitiated

// BlockOperation ์ถ”๊ฐ€
let operation1 = BlockOperation {
    print("์ž‘์—… 1 ์‹คํ–‰")
    sleep(2)
    print("์ž‘์—… 1 ์™„๋ฃŒ")
}

operationQueue.addOperation(operation1)

// ํด๋กœ์ €๋กœ ์ง์ ‘ ์ถ”๊ฐ€
operationQueue.addOperation {
    print("์ž‘์—… 2 ์‹คํ–‰")
}

// ์—ฌ๋Ÿฌ ์ž‘์—… ํ•œ๋ฒˆ์— ์ถ”๊ฐ€
let operations = (1...5).map { index in
    BlockOperation {
        print("์ž‘์—… \(index)")
    }
}
operationQueue.addOperations(operations, waitUntilFinished: false)

// ๋ฉ”์ธ ํ ์‚ฌ์šฉ
OperationQueue.main.addOperation {
    // UI ์—…๋ฐ์ดํŠธ
    self.label.text = "์™„๋ฃŒ!"
}

๐Ÿ”— ์ž‘์—… ์˜์กด์„ฑ ์„ค์ •ํ•˜๊ธฐ

OperationQueue์˜ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ์ž‘์—… ์˜์กด์„ฑ์ด์•ผ! ํŠน์ • ์ž‘์—…์ด ๋‹ค๋ฅธ ์ž‘์—…๋ณด๋‹ค ๋จผ์ € ์™„๋ฃŒ๋˜์–ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•ด. ์ด๊ฑด GCD๋กœ๋Š” ๊ตฌํ˜„ํ•˜๊ธฐ ๋ณต์žกํ•œ๋ฐ, OperationQueue๋Š” ์ •๋ง ์‰ฌ์›Œ! ๐ŸŽฏ

// ์ž‘์—… ์ƒ์„ฑ
let downloadOperation = BlockOperation {
    print("1. ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ์ค‘...")
    sleep(2)
    print("1. ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ!")
}

let filterOperation = BlockOperation {
    print("2. ํ•„ํ„ฐ ์ ์šฉ ์ค‘...")
    sleep(1)
    print("2. ํ•„ํ„ฐ ์ ์šฉ ์™„๋ฃŒ!")
}

let uploadOperation = BlockOperation {
    print("3. ์—…๋กœ๋“œ ์ค‘...")
    sleep(1)
    print("3. ์—…๋กœ๋“œ ์™„๋ฃŒ!")
}

// ์˜์กด์„ฑ ์„ค์ •: ๋‹ค์šด๋กœ๋“œ -> ํ•„ํ„ฐ -> ์—…๋กœ๋“œ ์ˆœ์„œ
filterOperation.addDependency(downloadOperation)
uploadOperation.addDependency(filterOperation)

// ํ์— ์ถ”๊ฐ€ (์ˆœ์„œ๋Š” ์ƒ๊ด€์—†์–ด!)
let queue = OperationQueue()
queue.addOperations([uploadOperation, filterOperation, downloadOperation], 
                    waitUntilFinished: false)

// ์‹คํ–‰ ์ˆœ์„œ: downloadOperation -> filterOperation -> uploadOperation

// ๋ณต์žกํ•œ ์˜์กด์„ฑ ์˜ˆ์‹œ
let fetchDataOp = BlockOperation { print("๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ") }
let parseDataOp = BlockOperation { print("๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ") }
let saveDataOp = BlockOperation { print("๋ฐ์ดํ„ฐ ์ €์žฅ") }
let updateUIOp = BlockOperation { print("UI ์—…๋ฐ์ดํŠธ") }

parseDataOp.addDependency(fetchDataOp)
saveDataOp.addDependency(parseDataOp)
updateUIOp.addDependency(saveDataOp)

let queue2 = OperationQueue()
queue2.addOperations([updateUIOp, saveDataOp, parseDataOp, fetchDataOp], 
                     waitUntilFinished: false)

๐Ÿ’ก ์˜์กด์„ฑ ํ™œ์šฉ ํŒ

์˜์กด์„ฑ์„ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด "๋กœ๊ทธ์ธ -> ํ”„๋กœํ•„ ๊ฐ€์ ธ์˜ค๊ธฐ -> ์นœ๊ตฌ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ -> UI ์—…๋ฐ์ดํŠธ" ๊ฐ™์€ ์ˆœ์ฐจ์ ์ธ ์ž‘์—…์„ ์˜์กด์„ฑ์œผ๋กœ ์—ฐ๊ฒฐํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๋ช…ํ™•ํ•ด์ ธ! ๐Ÿ“Š

๐Ÿ›‘ ์ž‘์—… ์ทจ์†Œํ•˜๊ธฐ

OperationQueue์˜ ๋˜ ๋‹ค๋ฅธ ๊ฐ•์ ์€ ์ž‘์—…์„ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์•ผ! ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์ค‘๋‹จํ•˜๊ฑฐ๋‚˜, ํ™”๋ฉด์„ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ ๋ถˆํ•„์š”ํ•œ ์ž‘์—…์„ ๋ฉˆ์ถœ ์ˆ˜ ์žˆ์–ด. ๐Ÿšซ

// ์ทจ์†Œ ๊ฐ€๋Šฅํ•œ Operation ๋งŒ๋“ค๊ธฐ
let cancellableOperation = BlockOperation {
    for i in 1...10 {
        // ์ทจ์†Œ ํ™•์ธ
        if BlockOperation.current()?.isCancelled == true {
            print("์ž‘์—…์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
            return
        }
        
        print("์ง„ํ–‰ ์ค‘: \(i * 10)%")
        sleep(1)
    }
    print("์ž‘์—… ์™„๋ฃŒ!")
}

let queue = OperationQueue()
queue.addOperation(cancellableOperation)

// 2์ดˆ ํ›„ ์ทจ์†Œ
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    cancellableOperation.cancel()
}

// ํ์˜ ๋ชจ๋“  ์ž‘์—… ์ทจ์†Œ
queue.cancelAllOperations()

// Custom Operation์—์„œ ์ทจ์†Œ ์ฒ˜๋ฆฌ
class DownloadOperation: Operation {
    let url: URL
    
    init(url: URL) {
        self.url = url
    }
    
    override func main() {
        guard !isCancelled else { return }
        
        print("๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘: \(url)")
        
        // ๋‹ค์šด๋กœ๋“œ ์ž‘์—…์„ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ๋‚˜๋ˆ ์„œ ์ทจ์†Œ ํ™•์ธ
        for i in 1...10 {
            if isCancelled {
                print("๋‹ค์šด๋กœ๋“œ ์ทจ์†Œ๋จ")
                return
            }
            
            // ์‹ค์ œ ๋‹ค์šด๋กœ๋“œ ๋กœ์ง
            sleep(1)
            print("์ง„ํ–‰: \(i * 10)%")
        }
        
        print("๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ!")
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let downloadOp = DownloadOperation(url: URL(string: "https://example.com/file")!)
queue.addOperation(downloadOp)

// ํ•„์š”์‹œ ์ทจ์†Œ
downloadOp.cancel()

๐ŸŽจ Custom Operation ๋งŒ๋“ค๊ธฐ

๋ณต์žกํ•œ ์ž‘์—…์€ Custom Operation์œผ๋กœ ๋งŒ๋“ค๋ฉด ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹๊ณ  ์ฝ”๋“œ๋„ ๊น”๋”ํ•ด์ ธ. ์‹ค์ „์—์„œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด์ด์•ผ! ๐ŸŽญ

// ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ Operation
class ImageDownloadOperation: Operation {
    let url: URL
    var downloadedImage: UIImage?
    
    init(url: URL) {
        self.url = url
        super.init()
    }
    
    override func main() {
        // ์ทจ์†Œ ํ™•์ธ
        guard !isCancelled else { return }
        
        // ๋‹ค์šด๋กœ๋“œ
        if let data = try? Data(contentsOf: url),
           let image = UIImage(data: data) {
            downloadedImage = image
        }
    }
}

// ๋น„๋™๊ธฐ Operation (๊ณ ๊ธ‰)
class AsyncOperation: Operation {
    enum State: String {
        case ready, executing, finished
        
        fileprivate var keyPath: String {
            return "is\(rawValue.capitalized)"
        }
    }
    
    var state = State.ready {
        willSet {
            willChangeValue(forKey: newValue.keyPath)
            willChangeValue(forKey: state.keyPath)
        }
        didSet {
            didChangeValue(forKey: oldValue.keyPath)
            didChangeValue(forKey: state.keyPath)
        }
    }
    
    override var isReady: Bool {
        return super.isReady && state == .ready
    }
    
    override var isExecuting: Bool {
        return state == .executing
    }
    
    override var isFinished: Bool {
        return state == .finished
    }
    
    override var isAsynchronous: Bool {
        return true
    }
    
    override func start() {
        if isCancelled {
            state = .finished
            return
        }
        
        main()
        state = .executing
    }
    
    override func cancel() {
        super.cancel()
        state = .finished
    }
}

// ๋น„๋™๊ธฐ ๋„คํŠธ์›Œํฌ Operation
class NetworkOperation: AsyncOperation {
    let url: URL
    var data: Data?
    
    init(url: URL) {
        self.url = url
    }
    
    override func main() {
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self = self else { return }
            
            if !self.isCancelled {
                self.data = data
            }
            
            self.state = .finished
        }.resume()
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let queue = OperationQueue()

let imageOp = ImageDownloadOperation(url: URL(string: "https://example.com/image.jpg")!)
let networkOp = NetworkOperation(url: URL(string: "https://api.example.com/data")!)

// ์™„๋ฃŒ ๋ธ”๋ก ์„ค์ •
imageOp.completionBlock = {
    if let image = imageOp.downloadedImage {
        OperationQueue.main.addOperation {
            // UI ์—…๋ฐ์ดํŠธ
            print("์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ!")
        }
    }
}

queue.addOperations([imageOp, networkOp], waitUntilFinished: false)

โš–๏ธ ์šฐ์„ ์ˆœ์œ„ ์„ค์ •ํ•˜๊ธฐ

Operation์—๋Š” ์šฐ์„ ์ˆœ์œ„๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด. ์ค‘์š”ํ•œ ์ž‘์—…์„ ๋จผ์ € ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ด!

let highPriorityOp = BlockOperation {
    print("๋†’์€ ์šฐ์„ ์ˆœ์œ„ ์ž‘์—…")
}
highPriorityOp.queuePriority = .veryHigh

let normalOp = BlockOperation {
    print("๋ณดํ†ต ์šฐ์„ ์ˆœ์œ„ ์ž‘์—…")
}
normalOp.queuePriority = .normal

let lowPriorityOp = BlockOperation {
    print("๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ์ž‘์—…")
}
lowPriorityOp.queuePriority = .veryLow

let queue = OperationQueue()
queue.addOperations([lowPriorityOp, normalOp, highPriorityOp], 
                    waitUntilFinished: false)

// ์šฐ์„ ์ˆœ์œ„ ๋ ˆ๋ฒจ
// .veryHigh
// .high
// .normal (๊ธฐ๋ณธ๊ฐ’)
// .low
// .veryLow

๐Ÿ†š GCD vs OperationQueue: ์–ธ์ œ ๋ญ˜ ์“ธ๊นŒ?

์ž, ์ด์ œ ๋‘˜ ๋‹ค ๋ฐฐ์› ์œผ๋‹ˆ ์–ธ์ œ ๋ญ˜ ์จ์•ผ ํ• ์ง€ ๊ณ ๋ฏผ๋˜์ง€? ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ ์„ ๋น„๊ตํ•ด๋ณด์ž! ๐Ÿค”

GCD vs OperationQueue GCD ์žฅ์  โœ“ ๊ฐ€๋ณ๊ณ  ๋น ๋ฅธ ์„ฑ๋Šฅ โœ“ ๊ฐ„๋‹จํ•œ API โœ“ C ๊ธฐ๋ฐ˜์œผ๋กœ ์˜ค๋ฒ„ํ—ค๋“œ ์ ์Œ โœ“ ์‹œ์Šคํ…œ ๋ ˆ๋ฒจ ์ตœ์ ํ™” ๋‹จ์  โœ— ์ž‘์—… ์ทจ์†Œ ์–ด๋ ค์›€ โœ— ์˜์กด์„ฑ ๊ด€๋ฆฌ ๋ณต์žก โœ— ์ž‘์—… ์ƒํƒœ ์ถ”์  ๋ถˆํŽธ ์‚ฌ์šฉ ์ผ€์ด์Šค โ€ข ๊ฐ„๋‹จํ•œ ๋น„๋™๊ธฐ ์ž‘์—… โ€ข ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ โ€ข ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘ โ€ข ์‹œ์Šคํ…œ ๋ ˆ๋ฒจ ์ž‘์—… OperationQueue ์žฅ์  โœ“ ์ž‘์—… ์ทจ์†Œ ์‰ฌ์›€ โœ“ ์˜์กด์„ฑ ๊ด€๋ฆฌ ๊ฐ„ํŽธ โœ“ ์šฐ์„ ์ˆœ์œ„ ์„ค์ • ๊ฐ€๋Šฅ โœ“ KVO ์ง€์› ๋‹จ์  โœ— GCD๋ณด๋‹ค ๋ฌด๊ฑฐ์›€ โœ— ์•ฝ๊ฐ„์˜ ์˜ค๋ฒ„ํ—ค๋“œ โœ— ํ•™์Šต ๊ณก์„  ์žˆ์Œ ์‚ฌ์šฉ ์ผ€์ด์Šค โ€ข ๋ณต์žกํ•œ ์ž‘์—… ํ๋ฆ„ โ€ข ์ทจ์†Œ ๊ธฐ๋Šฅ ํ•„์š”์‹œ โ€ข ์ž‘์—… ์˜์กด์„ฑ ์žˆ์„ ๋•Œ โ€ข ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ž‘์—…

๐ŸŽฏ ์„ ํƒ ๊ฐ€์ด๋“œ

GCD๋ฅผ ์‚ฌ์šฉํ•ด:
โ€ข ๊ฐ„๋‹จํ•œ ๋น„๋™๊ธฐ ์ž‘์—… (๋„คํŠธ์›Œํฌ ์š”์ฒญ, ์ด๋ฏธ์ง€ ๋กœ๋”ฉ)
โ€ข ์„ฑ๋Šฅ์ด ๋งค์šฐ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ
โ€ข ๋น ๋ฅด๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์„ ๋•Œ
โ€ข ์ž‘์—… ์ทจ์†Œ๋‚˜ ์˜์กด์„ฑ์ด ํ•„์š” ์—†์„ ๋•Œ

OperationQueue๋ฅผ ์‚ฌ์šฉํ•ด:
โ€ข ๋ณต์žกํ•œ ์ž‘์—… ์›Œํฌํ”Œ๋กœ์šฐ
โ€ข ์ž‘์—… ์ทจ์†Œ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•  ๋•Œ
โ€ข ์ž‘์—… ๊ฐ„ ์˜์กด์„ฑ์ด ์žˆ์„ ๋•Œ
โ€ข ์ž‘์—…์„ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ
โ€ข ์ž‘์—… ์ƒํƒœ๋ฅผ ์ถ”์ ํ•ด์•ผ ํ•  ๋•Œ

๐ŸŽช ์‹ค์ „ ์˜ˆ์ œ: ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋” ๋งŒ๋“ค๊ธฐ

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

๐Ÿš€ GCD ๋ฒ„์ „

class GCDImageDownloader {
    private let downloadQueue = DispatchQueue(label: "com.myapp.imageDownload", 
                                              attributes: .concurrent)
    private let cacheQueue = DispatchQueue(label: "com.myapp.imageCache")
    private var imageCache: [URL: UIImage] = [:]
    
    func downloadImages(urls: [URL], 
                       progress: @escaping (Int, Int) -> Void,
                       completion: @escaping ([UIImage]) -> Void) {
        let group = DispatchGroup()
        var downloadedImages: [UIImage] = []
        var completedCount = 0
        
        for url in urls {
            group.enter()
            downloadQueue.async {
                // ์บ์‹œ ํ™•์ธ
                var cachedImage: UIImage?
                self.cacheQueue.sync {
                    cachedImage = self.imageCache[url]
                }
                
                if let image = cachedImage {
                    // ์บ์‹œ๋œ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
                    downloadedImages.append(image)
                    completedCount += 1
                    
                    DispatchQueue.main.async {
                        progress(completedCount, urls.count)
                    }
                    
                    group.leave()
                } else {
                    // ๋‹ค์šด๋กœ๋“œ
                    if let data = try? Data(contentsOf: url),
                       let image = UIImage(data: data) {
                        // ์บ์‹œ์— ์ €์žฅ
                        self.cacheQueue.async(flags: .barrier) {
                            self.imageCache[url] = image
                        }
                        
                        downloadedImages.append(image)
                        completedCount += 1
                        
                        DispatchQueue.main.async {
                            progress(completedCount, urls.count)
                        }
                    }
                    
                    group.leave()
                }
            }
        }
        
        group.notify(queue: .main) {
            completion(downloadedImages)
        }
    }
    
    func clearCache() {
        cacheQueue.async(flags: .barrier) {
            self.imageCache.removeAll()
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let downloader = GCDImageDownloader()
let imageURLs = [
    URL(string: "https://example.com/image1.jpg")!,
    URL(string: "https://example.com/image2.jpg")!,
    URL(string: "https://example.com/image3.jpg")!
]

downloader.downloadImages(urls: imageURLs) { completed, total in
    print("์ง„ํ–‰: \(completed)/\(total)")
    // ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ์—…๋ฐ์ดํŠธ
} completion: { images in
    print("๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ! ์ด \(images.count)๊ฐœ")
    // ์ด๋ฏธ์ง€ ํ‘œ์‹œ
}

๐ŸŽจ OperationQueue ๋ฒ„์ „

class ImageDownloadOperation: Operation {
    let url: URL
    var downloadedImage: UIImage?
    
    init(url: URL) {
        self.url = url
        super.init()
    }
    
    override func main() {
        guard !isCancelled else { return }
        
        if let data = try? Data(contentsOf: url) {
            guard !isCancelled else { return }
            downloadedImage = UIImage(data: data)
        }
    }
}

class OperationImageDownloader {
    private let downloadQueue = OperationQueue()
    private var imageCache: [URL: UIImage] = [:]
    private let cacheLock = NSLock()
    
    init() {
        downloadQueue.name = "com.myapp.imageDownloadQueue"
        downloadQueue.maxConcurrentOperationCount = 4
    }
    
    func downloadImages(urls: [URL],
                       progress: @escaping (Int, Int) -> Void,
                       completion: @escaping ([UIImage]) -> Void) {
        var operations: [ImageDownloadOperation] = []
        var downloadedImages: [UIImage] = []
        var completedCount = 0
        
        for url in urls {
            // ์บ์‹œ ํ™•์ธ
            cacheLock.lock()
            if let cachedImage = imageCache[url] {
                cacheLock.unlock()
                downloadedImages.append(cachedImage)
                completedCount += 1
                DispatchQueue.main.async {
                    progress(completedCount, urls.count)
                }
                continue
            }
            cacheLock.unlock()
            
            // ๋‹ค์šด๋กœ๋“œ Operation ์ƒ์„ฑ
            let operation = ImageDownloadOperation(url: url)
            
            operation.completionBlock = { [weak self, weak operation] in
                guard let self = self,
                      let operation = operation,
                      !operation.isCancelled,
                      let image = operation.downloadedImage else { return }
                
                // ์บ์‹œ์— ์ €์žฅ
                self.cacheLock.lock()
                self.imageCache[url] = image
                self.cacheLock.unlock()
                
                downloadedImages.append(image)
                completedCount += 1
                
                DispatchQueue.main.async {
                    progress(completedCount, urls.count)
                    
                    if completedCount == urls.count {
                        completion(downloadedImages)
                    }
                }
            }
            
            operations.append(operation)
        }
        
        downloadQueue.addOperations(operations, waitUntilFinished: false)
    }
    
    func cancelAll() {
        downloadQueue.cancelAllOperations()
    }
    
    func clearCache() {
        cacheLock.lock()
        imageCache.removeAll()
        cacheLock.unlock()
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let downloader = OperationImageDownloader()
let imageURLs = [
    URL(string: "https://example.com/image1.jpg")!,
    URL(string: "https://example.com/image2.jpg")!,
    URL(string: "https://example.com/image3.jpg")!
]

downloader.downloadImages(urls: imageURLs) { completed, total in
    print("์ง„ํ–‰: \(completed)/\(total)")
} completion: { images in
    print("๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ! ์ด \(images.count)๊ฐœ")
}

// ํ•„์š”์‹œ ์ทจ์†Œ
// downloader.cancelAll()

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์€ ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ๋ฒ„๊ทธ์™€ ํฌ๋ž˜์‹œ์˜ ์›์ธ์ด ๋  ์ˆ˜ ์žˆ์–ด. ๊ผญ ์•Œ์•„์•ผ ํ•  ์ฃผ์˜์‚ฌํ•ญ๋“ค์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿšจ

๐Ÿ”ด ๋ฐ๋“œ๋ฝ (Deadlock) ํ”ผํ•˜๊ธฐ

๋ฐ๋“œ๋ฝ์ด๋ž€?

๋‘ ๊ฐœ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ์„œ๋กœ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋ฉด์„œ ์˜์›ํžˆ ๋ฉˆ์ถฐ๋ฒ„๋ฆฌ๋Š” ์ƒํ™ฉ์ด์•ผ. ๊ฐ€์žฅ ํ”ํ•œ ์‹ค์ˆ˜ ์ค‘ ํ•˜๋‚˜์ง€!

// โŒ ๋ฐ๋“œ๋ฝ ๋ฐœ์ƒ ์˜ˆ์‹œ
DispatchQueue.main.async {
    // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ ์ค‘
    DispatchQueue.main.sync {
        // ๋ฉ”์ธ ํ๊ฐ€ ์ž๊ธฐ ์ž์‹ ์„ ๊ธฐ๋‹ค๋ฆผ -> ๋ฐ๋“œ๋ฝ!
        print("์‹คํ–‰ ์•ˆ ๋จ")
    }
}

// โŒ ์ƒํ˜ธ ๋ฐ๋“œ๋ฝ
let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")

queue1.async {
    queue2.sync {
        // queue2๋ฅผ ๊ธฐ๋‹ค๋ฆผ
        queue1.sync {
            // queue1์„ ๊ธฐ๋‹ค๋ฆผ -> ๋ฐ๋“œ๋ฝ!
        }
    }
}

// โœ… ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•
DispatchQueue.main.async {
    // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ ์ค‘
    DispatchQueue.main.async {  // sync ๋Œ€์‹  async ์‚ฌ์šฉ
        print("์ •์ƒ ์‹คํ–‰!")
    }
}

// โœ… ๋‹ค๋ฅธ ํ ์‚ฌ์šฉ
DispatchQueue.main.async {
    DispatchQueue.global().async {  // ๋‹ค๋ฅธ ํ ์‚ฌ์šฉ
        print("์ •์ƒ ์‹คํ–‰!")
    }
}

๐Ÿƒ ๋ ˆ์ด์Šค ์ปจ๋””์…˜ (Race Condition) ๋ฐฉ์ง€

๋ ˆ์ด์Šค ์ปจ๋””์…˜์ด๋ž€?

์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•ด์„œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋Š” ์ƒํ™ฉ์ด์•ผ. ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ํ•ด์น  ์ˆ˜ ์žˆ์–ด!

// โŒ ๋ ˆ์ด์Šค ์ปจ๋””์…˜ ๋ฐœ์ƒ
class UnsafeCounter {
    var count = 0
    
    func increment() {
        count += 1  // ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™์‹œ ์ ‘๊ทผ -> ๋ฌธ์ œ!
    }
}

let counter = UnsafeCounter()
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    counter.increment()
}
print(counter.count)  // 1000์ด ์•„๋‹ ์ˆ˜ ์žˆ์Œ!

// โœ… Serial Queue๋กœ ํ•ด๊ฒฐ
class SafeCounter {
    private var count = 0
    private let queue = DispatchQueue(label: "com.myapp.counter")
    
    func increment() {
        queue.sync {
            count += 1
        }
    }
    
    func getCount() -> Int {
        return queue.sync { count }
    }
}

// โœ… Barrier๋กœ ํ•ด๊ฒฐ
class BarrierCounter {
    private var count = 0
    private let queue = DispatchQueue(label: "com.myapp.counter", 
                                      attributes: .concurrent)
    
    func increment() {
        queue.async(flags: .barrier) {
            self.count += 1
        }
    }
    
    func getCount() -> Int {
        return queue.sync { count }
    }
}

// โœ… NSLock์œผ๋กœ ํ•ด๊ฒฐ
class LockedCounter {
    private var count = 0
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        count += 1
        lock.unlock()
    }
    
    func getCount() -> Int {
        lock.lock()
        defer { lock.unlock() }
        return count
    }
}

๐ŸŽจ UI๋Š” ๋ฐ˜๋“œ์‹œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ!

๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ทœ์น™!

UIKit์€ ์Šค๋ ˆ๋“œ ์•ˆ์ „ํ•˜์ง€ ์•Š์•„. ๋ชจ๋“  UI ์—…๋ฐ์ดํŠธ๋Š” ๋ฐ˜๋“œ์‹œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ํ•ด์•ผ ํ•ด. ์ด๊ฑธ ์–ด๊ธฐ๋ฉด ํฌ๋ž˜์‹œ๋‚˜ ์ด์ƒํ•œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽจ

// โŒ ์ž˜๋ชป๋œ ์˜ˆ์‹œ
DispatchQueue.global().async {
    let data = self.fetchData()
    self.label.text = "์™„๋ฃŒ"  // ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ UI ์—…๋ฐ์ดํŠธ -> ์œ„ํ—˜!
    self.tableView.reloadData()  // ํฌ๋ž˜์‹œ ๊ฐ€๋Šฅ์„ฑ!
}

// โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ
DispatchQueue.global().async {
    let data = self.fetchData()
    
    DispatchQueue.main.async {
        self.label.text = "์™„๋ฃŒ"
        self.tableView.reloadData()
    }
}

// โœ… ํŽธ๋ฆฌํ•œ ํ—ฌํผ ํ•จ์ˆ˜
extension DispatchQueue {
    static func mainAsync(_ block: @escaping () -> Void) {
        if Thread.isMainThread {
            block()
        } else {
            DispatchQueue.main.async(execute: block)
        }
    }
}

// ์‚ฌ์šฉ
DispatchQueue.global().async {
    let data = self.fetchData()
    
    DispatchQueue.mainAsync {
        self.updateUI(with: data)
    }
}

๐Ÿ’พ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ์ฃผ์˜

// โŒ ๊ฐ•ํ•œ ์ฐธ์กฐ ์ˆœํ™˜ (Retain Cycle)
class ViewController: UIViewController {
    var data: [String] = []
    
    func loadData() {
        DispatchQueue.global().async {
            // self๋ฅผ ๊ฐ•ํ•˜๊ฒŒ ์บก์ฒ˜ -> ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜!
            let newData = self.fetchData()
            self.data = newData
        }
    }
}

// โœ… weak self ์‚ฌ์šฉ
class ViewController: UIViewController {
    var data: [String] = []
    
    func loadData() {
        DispatchQueue.global().async { [weak self] in
            guard let self = self else { return }
            let newData = self.fetchData()
            
            DispatchQueue.main.async { [weak self] in
                self?.data = newData
            }
        }
    }
}

// โœ… unowned ์‚ฌ์šฉ (self๊ฐ€ nil์ด ๋  ์ˆ˜ ์—†์„ ๋•Œ)
func processData() {
    DispatchQueue.global().async { [unowned self] in
        let result = self.heavyComputation()
        DispatchQueue.main.async { [unowned self] in
            self.displayResult(result)
        }
    }
}

// OperationQueue์—์„œ๋„ ๋™์ผ
let operation = BlockOperation { [weak self] in
    guard let self = self else { return }
    self.doSomething()
}

๐Ÿ”ง ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

๐Ÿ’ก ์ตœ์ ํ™” ๊ฐ€์ด๋“œ

1. ์ ์ ˆํ•œ QoS ์„ ํƒ: ์ž‘์—…์˜ ์ค‘์š”๋„์— ๋งž๋Š” QoS๋ฅผ ์‚ฌ์šฉํ•ด. ๋ชจ๋“  ์ž‘์—…์„ .userInteractive๋กœ ํ•˜๋ฉด ๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๋นจ๋ฆฌ ๋‹ณ์•„!

2. ๋™์‹œ ์‹คํ–‰ ๊ฐœ์ˆ˜ ์ œํ•œ: OperationQueue์˜ maxConcurrentOperationCount๋ฅผ ์ ์ ˆํžˆ ์„ค์ •ํ•ด. ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ์˜คํžˆ๋ ค ์„ฑ๋Šฅ์ด ๋–จ์–ด์ ธ.

3. ์ž‘์—… ํฌ๊ธฐ ์กฐ์ ˆ: ๋„ˆ๋ฌด ์ž‘์€ ์ž‘์—…์„ ๋งŽ์ด ๋งŒ๋“ค๋ฉด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์ปค์ ธ. ์ ๋‹นํ•œ ํฌ๊ธฐ๋กœ ๋ฌถ์–ด์„œ ์ฒ˜๋ฆฌํ•ด.

4. ์บ์‹ฑ ํ™œ์šฉ: ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์€ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•ด์„œ ์žฌ์‚ฌ์šฉํ•ด.

5. ์ทจ์†Œ ์ฒ˜๋ฆฌ: ๊ธด ์ž‘์—…์€ ์ค‘๊ฐ„์ค‘๊ฐ„ ์ทจ์†Œ ํ™•์ธ์„ ํ•ด์„œ ๋ถˆํ•„์š”ํ•œ ์ž‘์—…์„ ์ค„์—ฌ.

๐ŸŽ“ ๊ณ ๊ธ‰ ํŒจํ„ด๊ณผ ํ…Œํฌ๋‹‰

๊ธฐ๋ณธ์„ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๋ฉด, ์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ํŒจํ„ด๋“ค์„ ์•Œ์•„๋ณด์ž! ์‹ค๋ฌด์—์„œ ์ •๋ง ์œ ์šฉํ•œ ํ…Œํฌ๋‹‰๋“ค์ด์•ผ. ๐Ÿš€

๐Ÿ”„ Debounce์™€ Throttle ๊ตฌํ˜„

๊ฒ€์ƒ‰์ฐฝ์—์„œ ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋น„ํšจ์œจ์ ์ด์ง€? Debounce์™€ Throttle๋กœ ํ˜ธ์ถœ ํšŸ์ˆ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์–ด!

// Debounce: ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ ํ›„ ์ผ์ • ์‹œ๊ฐ„ ๋Œ€๊ธฐ
class Debouncer {
    private let delay: TimeInterval
    private var workItem: DispatchWorkItem?
    
    init(delay: TimeInterval) {
        self.delay = delay
    }
    
    func debounce(action: @escaping () -> Void) {
        workItem?.cancel()
        
        let newWorkItem = DispatchWorkItem(block: action)
        workItem = newWorkItem
        
        DispatchQueue.main.asyncAfter(deadline: .now() + delay, 
                                      execute: newWorkItem)
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
class SearchViewController: UIViewController {
    let debouncer = Debouncer(delay: 0.5)
    
    func searchBarTextDidChange(_ searchBar: UISearchBar) {
        debouncer.debounce { [weak self] in
            self?.performSearch(query: searchBar.text ?? "")
        }
    }
}

// Throttle: ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰
class Throttler {
    private let delay: TimeInterval
    private var lastExecutionTime: Date?
    private let queue = DispatchQueue(label: "com.myapp.throttler")
    
    init(delay: TimeInterval) {
        self.delay = delay
    }
    
    func throttle(action: @escaping () -> Void) {
        queue.async { [weak self] in
            guard let self = self else { return }
            
            let now = Date()
            if let lastTime = self.lastExecutionTime,
               now.timeIntervalSince(lastTime) < self.delay {
                return
            }
            
            self.lastExecutionTime = now
            DispatchQueue.main.async {
                action()
            }
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
class ScrollViewController: UIViewController {
    let throttler = Throttler(delay: 0.1)
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        throttler.throttle { [weak self] in
            self?.updateScrollIndicator()
        }
    }
}

๐ŸŽฏ ์ž‘์—… ์ฒด์ด๋‹ (Task Chaining)

์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์—ฐ๊ฒฐํ•ด์„œ ์‹คํ–‰ํ•˜๋Š” ํŒจํ„ด์ด์•ผ. Promise๋‚˜ Combine ์—†์ด๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด!

// GCD ๋ฒ„์ „
class TaskChain {
    private let queue = DispatchQueue(label: "com.myapp.taskchain")
    
    func execute<T>(_ tasks: [(T) -> T], initialValue: T, 
                   completion: @escaping (T) -> Void) {
        queue.async {
            var result = initialValue
            
            for task in tasks {
                result = task(result)
            }
            
            DispatchQueue.main.async {
                completion(result)
            }
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let chain = TaskChain()
let tasks: [(String) -> String] = [
    { text in
        print("1. ์†Œ๋ฌธ์ž๋กœ ๋ณ€ํ™˜")
        return text.lowercased()
    },
    { text in
        print("2. ๊ณต๋ฐฑ ์ œ๊ฑฐ")
        return text.replacingOccurrences(of: " ", with: "")
    },
    { text in
        print("3. ์—ญ์ˆœ์œผ๋กœ")
        return String(text.reversed())
    }
]

chain.execute(tasks, initialValue: "Hello World") { result in
    print("์ตœ์ข… ๊ฒฐ๊ณผ: \(result)")
}

// OperationQueue ๋ฒ„์ „
class ChainableOperation: Operation {
    var input: Any?
    var output: Any?
    
    override func main() {
        guard !isCancelled else { return }
        output = process(input: input)
    }
    
    func process(input: Any?) -> Any? {
        // ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„
        return input
    }
}

class LowercaseOperation: ChainableOperation {
    override func process(input: Any?) -> Any? {
        guard let text = input as? String else { return input }
        return text.lowercased()
    }
}

class ReverseOperation: ChainableOperation {
    override func process(input: Any?) -> Any? {
        guard let text = input as? String else { return input }
        return String(text.reversed())
    }
}

// ์‚ฌ์šฉ
let queue = OperationQueue()
let op1 = LowercaseOperation()
let op2 = ReverseOperation()

op1.input = "Hello World"
op2.addDependency(op1)

op2.completionBlock = {
    if let result = op2.output as? String {
        print("๊ฒฐ๊ณผ: \(result)")
    }
}

queue.addOperations([op1, op2], waitUntilFinished: false)

๐Ÿ” ์žฌ์‹œ๋„ ๋กœ์ง ๊ตฌํ˜„

๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•˜๋Š” ํŒจํ„ด์ด์•ผ. ์‹ค๋ฌด์—์„œ ์ •๋ง ๋งŽ์ด ์‚ฌ์šฉํ•ด!

class RetryableTask {
    private let maxRetries: Int
    private let retryDelay: TimeInterval
    private let queue: DispatchQueue
    
    init(maxRetries: Int = 3, 
         retryDelay: TimeInterval = 2.0,
         queue: DispatchQueue = .global()) {
        self.maxRetries = maxRetries
        self.retryDelay = retryDelay
        self.queue = queue
    }
    
    func execute<T>(_ task: @escaping () throws -> T,
                   completion: @escaping (Result<T, Error>) -> Void) {
        executeWithRetry(task, attemptsLeft: maxRetries, completion: completion)
    }
    
    private func executeWithRetry<T>(_ task: @escaping () throws -> T,
                                    attemptsLeft: Int,
                                    completion: @escaping (Result<T, Error>) -> Void) {
        queue.async {
            do {
                let result = try task()
                DispatchQueue.main.async {
                    completion(.success(result))
                }
            } catch {
                if attemptsLeft > 0 {
                    print("์‹คํŒจ! \(attemptsLeft)๋ฒˆ ์žฌ์‹œ๋„...")
                    
                    DispatchQueue.global().asyncAfter(deadline: .now() + self.retryDelay) {
                        self.executeWithRetry(task, 
                                            attemptsLeft: attemptsLeft - 1, 
                                            completion: completion)
                    }
                } else {
                    DispatchQueue.main.async {
                        completion(.failure(error))
                    }
                }
            }
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let retryTask = RetryableTask(maxRetries: 3, retryDelay: 1.0)

retryTask.execute({
    // ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์—…
    try self.fetchDataFromServer()
}) { result in
    switch result {
    case .success(let data):
        print("์„ฑ๊ณต: \(data)")
    case .failure(let error):
        print("์ตœ์ข… ์‹คํŒจ: \(error)")
    }
}

// OperationQueue ๋ฒ„์ „
class RetryableOperation: Operation {
    private let maxRetries: Int
    private var currentRetry = 0
    private let task: () throws -> Void
    
    init(maxRetries: Int = 3, task: @escaping () throws -> Void) {
        self.maxRetries = maxRetries
        self.task = task
        super.init()
    }
    
    override func main() {
        while currentRetry <= maxRetries && !isCancelled {
            do {
                try task()
                return
            } catch {
                currentRetry += 1
                if currentRetry <= maxRetries {
                    print("์žฌ์‹œ๋„ \(currentRetry)/\(maxRetries)")
                    sleep(2)
                } else {
                    print("์ตœ์ข… ์‹คํŒจ")
                }
            }
        }
    }
}

๐ŸŒ ์‹ค์ „ ์‹œ๋‚˜๋ฆฌ์˜ค: ๋ณตํ•ฉ ์˜ˆ์ œ

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋ชจ๋“  ๊ฑธ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ „ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž! SNS ์•ฑ์˜ ํ”ผ๋“œ ๋กœ๋”ฉ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด๋ณผ ๊ฑฐ์•ผ. ๐Ÿ“ฑ

// ํ”ผ๋“œ ์•„์ดํ…œ ๋ชจ๋ธ
struct FeedItem {
    let id: String
    let text: String
    let imageURL: URL?
    let authorName: String
    let timestamp: Date
}

// ํ”ผ๋“œ ๋กœ๋” (GCD + OperationQueue ํ˜ผํ•ฉ)
class FeedLoader {
    private let networkQueue = DispatchQueue(label: "com.myapp.network", 
                                             attributes: .concurrent)
    private let imageQueue = OperationQueue()
    private let processingQueue = DispatchQueue(label: "com.myapp.processing")
    
    init() {
        imageQueue.name = "com.myapp.imageQueue"
        imageQueue.maxConcurrentOperationCount = 4
    }
    
    func loadFeed(completion: @escaping (Result<[FeedItem], Error>) -> Void) {
        // 1๋‹จ๊ณ„: ํ”ผ๋“œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        networkQueue.async {
            do {
                let feedData = try self.fetchFeedData()
                
                // 2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ
                self.processingQueue.async {
                    let items = self.parseFeedData(feedData)
                    
                    // 3๋‹จ๊ณ„: ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ
                    self.downloadImages(for: items) { itemsWithImages in
                        DispatchQueue.main.async {
                            completion(.success(itemsWithImages))
                        }
                    }
                }
            } catch {
                DispatchQueue.main.async {
                    completion(.failure(error))
                }
            }
        }
    }
    
    private func fetchFeedData() throws -> Data {
        // ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
        sleep(1)
        return Data()
    }
    
    private func parseFeedData(_ data: Data) -> [FeedItem] {
        // ํŒŒ์‹ฑ ๋กœ์ง
        return []
    }
    
    private func downloadImages(for items: [FeedItem], 
                               completion: @escaping ([FeedItem]) -> Void) {
        let group = DispatchGroup()
        var processedItems = items
        
        for (index, item) in items.enumerated() {
            guard let imageURL = item.imageURL else { continue }
            
            group.enter()
            let operation = ImageDownloadOperation(url: imageURL)
            
            operation.completionBlock = { [weak operation] in
                if let image = operation?.downloadedImage {
                    // ์ด๋ฏธ์ง€ ์บ์‹ฑ ๋˜๋Š” ์ฒ˜๋ฆฌ
                    print("์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ: \(item.id)")
                }
                group.leave()
            }
            
            imageQueue.addOperation(operation)
        }
        
        group.notify(queue: .main) {
            completion(processedItems)
        }
    }
    
    func cancelAll() {
        imageQueue.cancelAllOperations()
    }
}

// ViewController์—์„œ ์‚ฌ์šฉ
class FeedViewController: UIViewController {
    private let feedLoader = FeedLoader()
    private var feedItems: [FeedItem] = []
    
    func refreshFeed() {
        showLoadingIndicator()
        
        feedLoader.loadFeed { [weak self] result in
            guard let self = self else { return }
            
            self.hideLoadingIndicator()
            
            switch result {
            case .success(let items):
                self.feedItems = items
                self.tableView.reloadData()
                print("โœ… ํ”ผ๋“œ ๋กœ๋“œ ์™„๋ฃŒ: \(items.count)๊ฐœ")
                
            case .failure(let error):
                self.showError(error)
                print("โŒ ํ”ผ๋“œ ๋กœ๋“œ ์‹คํŒจ: \(error)")
            }
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        feedLoader.cancelAll()
    }
    
    private func showLoadingIndicator() {
        // ๋กœ๋”ฉ ํ‘œ์‹œ
    }
    
    private func hideLoadingIndicator() {
        // ๋กœ๋”ฉ ์ˆจ๊น€
    }
    
    private func showError(_ error: Error) {
        // ์—๋Ÿฌ ํ‘œ์‹œ
    }
}

๐ŸŽ“ ์žฌ๋Šฅ๋„ท์—์„œ ๋” ๋ฐฐ์šฐ๊ธฐ

์—ฌ๊ธฐ๊นŒ์ง€ Grand Central Dispatch์™€ OperationQueue์— ๋Œ€ํ•ด ๊นŠ์ด ์žˆ๊ฒŒ ์•Œ์•„๋ดค์–ด! ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์€ ์ฒ˜์Œ์—” ์–ด๋ ต๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ณ„์† ์—ฐ์Šตํ•˜๋‹ค ๋ณด๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ต์ˆ™ํ•ด์งˆ ๊ฑฐ์•ผ. ๐Ÿ˜Š

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

๐Ÿ“š ํ•ต์‹ฌ ์ •๋ฆฌ

๐ŸŽฏ ๊ผญ ๊ธฐ์–ตํ•ด์•ผ ํ•  ๊ฒƒ๋“ค

1. GCD (Grand Central Dispatch)
โ€ข ๊ฐ€๋ณ๊ณ  ๋น ๋ฅธ ์ €์ˆ˜์ค€ API
โ€ข Serial Queue์™€ Concurrent Queue
โ€ข Main Queue์™€ Global Queue
โ€ข async/sync, DispatchGroup, Barrier

2. OperationQueue
โ€ข ๊ณ ์ˆ˜์ค€ API๋กœ ๋” ๋งŽ์€ ๊ธฐ๋Šฅ ์ œ๊ณต
โ€ข ์ž‘์—… ์ทจ์†Œ์™€ ์˜์กด์„ฑ ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์›€
โ€ข BlockOperation๊ณผ Custom Operation
โ€ข ์šฐ์„ ์ˆœ์œ„์™€ ๋™์‹œ ์‹คํ–‰ ๊ฐœ์ˆ˜ ์ œ์–ด

3. ์ฃผ์˜์‚ฌํ•ญ
โ€ข UI๋Š” ๋ฐ˜๋“œ์‹œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ
โ€ข ๋ฐ๋“œ๋ฝ๊ณผ ๋ ˆ์ด์Šค ์ปจ๋””์…˜ ์ฃผ์˜
โ€ข ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ (weak self ์‚ฌ์šฉ)
โ€ข ์ ์ ˆํ•œ QoS ์„ ํƒ

4. ์„ ํƒ ๊ธฐ์ค€
โ€ข ๊ฐ„๋‹จํ•œ ์ž‘์—… โ†’ GCD
โ€ข ๋ณต์žกํ•œ ์›Œํฌํ”Œ๋กœ์šฐ โ†’ OperationQueue
โ€ข ์„ฑ๋Šฅ ์ค‘์‹œ โ†’ GCD
โ€ข ์ทจ์†Œ/์˜์กด์„ฑ ํ•„์š” โ†’ OperationQueue

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

๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์€ ํ˜„๋Œ€ ์•ฑ ๊ฐœ๋ฐœ์—์„œ ํ•„์ˆ˜์ ์ธ ๊ธฐ์ˆ ์ด์•ผ. ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถ€๋“œ๋Ÿฌ์šด ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋ ค๋ฉด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” UI์—๋งŒ ์ง‘์ค‘ํ•˜๊ฒŒ ํ•ด์•ผ ํ•ด. ๐ŸŽจ

์ฒ˜์Œ์—” ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, ๊ธฐ๋ณธ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ณ  ํŒจํ„ด์„ ์ตํžˆ๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๊ฐ€ ๋ผ. ํŠนํžˆ GCD์™€ OperationQueue๋ฅผ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๋‹น์‹ ์€ ์ด๋ฏธ ์ค‘๊ธ‰ ์ด์ƒ์˜ iOS ๊ฐœ๋ฐœ์ž์•ผ! ๐Ÿ’ช

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

์ž, ์ด์ œ ๋‹น์‹ ์˜ ์•ฑ์— ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์„ ์ ์šฉํ•ด๋ณผ ์‹œ๊ฐ„์ด์•ผ. ํ™”์ดํŒ…! ๐Ÿš€โœจ

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

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

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

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