Ver3.0 ๐ Grand Central Dispatch์ OperationQueue๋ก ์ ๋ณตํ๋ Swift ๋ฉํฐ์ค๋ ๋ฉ์ ์ธ๊ณ

๐ Grand Central Dispatch์ OperationQueue๋ก ์ ๋ณตํ๋ Swift ๋ฉํฐ์ค๋ ๋ฉ์ ์ธ๊ณ
๋์์ฑ ํ๋ก๊ทธ๋๋ฐ, ์ด์ ์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ์ดํดํด๋ณด์!
์๋ ! ๐ ์ค๋์ Swift ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ ๋ฉํฐ์ค๋ ๋ฉ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ ๊ฑฐ์ผ. ํน์ ์ฑ์ด ๋ฒ๋ฒ ๊ฑฐ๋ฆฌ๊ฑฐ๋ ํ๋ฉด์ด ๋ฉ์ถฐ์ ์ฌ์ฉ์๊ฐ ๋ต๋ตํดํ๋ ๊ฒฝํ ์์ง? ๊ทธ๊ฑด ๋ฐ๋ก ๋ฉ์ธ ์ค๋ ๋์์ ๋ฌด๊ฑฐ์ด ์์ ์ ์ฒ๋ฆฌํ๊ณ ์๊ธฐ ๋๋ฌธ์ด์ผ. ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ํต์ฌ ๊ธฐ์ ์ด ๋ฐ๋ก Grand Central Dispatch(GCD)์ OperationQueue์ผ!
์ด ๋ ๊ฐ์ง ๊ธฐ์ ์ iOS์ macOS ๊ฐ๋ฐ์์ ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ์ ๊ตฌํํ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ธ๋ฐ, ๊ฐ๊ฐ์ ํน์ง๊ณผ ์ฌ์ฉ๋ฒ์ ์ ๋๋ก ์๋ฉด ์ฑ์ ์ฑ๋ฅ์ ๊ทน์ ์ผ๋ก ํฅ์์ํฌ ์ ์์ด. ์ฌ๋ฅ๋ท์์๋ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ์ด๋ฐ ๊ธฐ์ ์ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํ๊ณ ์๋๋ฐ, ์ค๋์ ๋ด๊ฐ ์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ์ค๋ช ํด์ค๊ฒ! ๐ฏ
๐ญ ๋ฉํฐ์ค๋ ๋ฉ์ด ๋ญ๊ธธ๋?
๋จผ์ ๊ธฐ๋ณธ ๊ฐ๋ ๋ถํฐ ์ก๊ณ ๊ฐ์! ์ค๋ ๋(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๋ ์์คํ ์ด ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋ ํ๋ค์ ์ ๊ณตํด. ์ด๊ฑธ ์ ํ์ฉํ๋ฉด ๋ฐ๋ก ํ๋ฅผ ๋ง๋ค ํ์ ์์ด ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ด!
๋ฉ์ธ ์ค๋ ๋์์ ์คํ๋๋ Serial Queue์ผ. UI ์ ๋ฐ์ดํธ๋ ๋ฐ๋์ ์ฌ๊ธฐ์ ํด์ผ ํด! ์ด๊ฑด ์ ๋ง ์ค์ํ ๊ท์น์ด์ผ. ๐จ
// ๋ฉ์ธ ํ์์ UI ์
๋ฐ์ดํธ
DispatchQueue.main.async {
self.label.text = "์์
์๋ฃ!"
self.activityIndicator.stopAnimating()
self.tableView.reloadData()
}
// ๋๊ธฐ์ ์ผ๋ก ์คํ (์ฃผ์: ๋ฉ์ธ ์ค๋ ๋์์ ํธ์ถํ๋ฉด ๋ฐ๋๋ฝ!)
DispatchQueue.main.sync {
// ์ด ์ฝ๋๋ ์ํํด! ๋ฉ์ธ ์ค๋ ๋์์ ํธ์ถํ๋ฉด ์ฑ์ด ๋ฉ์ถฐ๋ฒ๋ ค
}
์์คํ ์ด ๊ด๋ฆฌํ๋ 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๋ฅผ ์ฌ์ฉํด:
โข ๊ฐ๋จํ ๋น๋๊ธฐ ์์
(๋คํธ์ํฌ ์์ฒญ, ์ด๋ฏธ์ง ๋ก๋ฉ)
โข ์ฑ๋ฅ์ด ๋งค์ฐ ์ค์ํ ๊ฒฝ์ฐ
โข ๋น ๋ฅด๊ฒ ๊ตฌํํ๊ณ ์ถ์ ๋
โข ์์
์ทจ์๋ ์์กด์ฑ์ด ํ์ ์์ ๋
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
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.