๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ ์„ฑ๋Šฅ ๋ถ„์„ ๋ฐ ๊ฐœ์„  ๊ณผ์ •

doyeon
  • #iOS
  • #SwiftUI
  • #TCA

์†Œ๊ฐœ

์•ˆ๋…•ํ•˜์„ธ์š”. ๋…ธ๋จธ์Šค ๋ชจ๋ฐ”์ผํŒ€์—์„œ iOS์•ฑ ๊ฐœ๋ฐœ์„ ๋งก๊ณ  ์žˆ๋Š” ์ด๋„์—ฐ์ž…๋‹ˆ๋‹ค.

fromm์˜ ๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ ์„œ๋น„์Šค๊ฐ€ ์ „๋ฉด ์˜คํ”ˆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

์ด๋ฅผ ์œ„ํ•ด ๋งŽ์€ ์•„ํ‹ฐ์ŠคํŠธ, ํŒฌ๋ถ„๋“ค๊ป˜ ๋” ์ข‹์€ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ณ ์ž ๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ์ž‘์—…์„ ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Xcode Instruments๋ฅผ ํ™œ์šฉํ•œ ์„ฑ๋Šฅ ๋ถ„์„ ๊ณผ์ •๊ณผ, ์–ด๋–ป๊ฒŒ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ–ˆ๋Š”์ง€์— ๋Œ€ํ•ด ๊ณต์œ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ ์„ฑ๋Šฅ ๋ถ„์„ ์กฐ๊ฑด

์„ฑ๋Šฅ์„ ๋ถ„์„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ์ž์˜ ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์„ ๊ณ ๋ คํ•  ํ•„์š”๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์กฐ๊ฑด์„ ๊ฐ€์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

1. ์ผ๋ฐ˜๊ธฐ๊ธฐ (iPhone 15 Plus)
2. ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ(iPhone 13 mini)
3. ์ €์ „๋ ฅ ๋ชจ๋“œ
4. ํƒ€ ์•ฑ์ด ๊ตฌ๋™๋˜๊ณ  ์žˆ๋Š” ํ™˜๊ฒฝ

์กฐ๊ฑด๋ณ„ Instruments ๋ถ„์„

Xcode Instruments์˜ Time Profiler๋ฅผ ์ด์šฉํ•˜์—ฌ ํ–‰์„ ๋ถ„์„ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ–‰์€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์˜ ์‘๋‹ต ์†๋„๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ถ„๋ฅ˜๋ฉ๋‹ˆ๋‹ค.

์‘๋‹ต ์‹œ๊ฐ„์„ค๋ช…ํ–‰ ๋ถ„๋ฅ˜ ๊ธฐ์ค€
100ms ๋ฏธ๋งŒ์ผ๋ฐ˜์ ์œผ๋กœ ์ฆ‰๊ฐ์ ์ธ ์†๋„๋กœ ๋Š๊ปด์ง€๋Š” ์ƒํƒœ์ž…๋‹ˆ๋‹ค.-
100ms ~ 250ms์ƒํ™ฉ์— ๋”ฐ๋ผ ์‚ด์ง ์ง€์—ฐ์ด ๋Š๊ปด์งˆ ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์ž…๋‹ˆ๋‹ค.-
250ms ์ด์ƒ ~ 500ms ๋ฏธ๋งŒ๋Œ€๋ถ€๋ถ„์˜ ํˆด์—์„œ โ€œMicrohangโ€์œผ๋กœ ๊ฐ„์ฃผํ•˜๋ฉฐ, ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ธฐ์ค€์ด ๋ฉ๋‹ˆ๋‹ค.Microhang
500ms ์ด์ƒ์‚ฌ์šฉ์ž๊ฐ€ ๋Š๊น€์„ ๋Š๋‚„ ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ๋กœ, ์ •์‹ ํ–‰(Full Hang)์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.์ •์‹ ํ–‰(Full Hang)

[ํ…Œ์ŠคํŠธ 1] ์ผ๋ฐ˜๊ธฐ๊ธฐ (iPhone 15 Plus)

Insturments ํ…Œ์ŠคํŠธ 1

  • ๋งˆ์ดํฌ๋กœํ–‰: 1๊ฐœ
  • ํ–‰: 0๊ฐœ

ํŠน๋ณ„ํžˆ ๋ฌธ์ œ๊ฐ€ ๋  ๋งŒํ•œ ํ–‰์€ ์žกํžˆ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

[ํ…Œ์ŠคํŠธ 2] ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ + ๊ฒŒ์ž„(3D)

Insturments ํ…Œ์ŠคํŠธ 2

  • ๋งˆ์ดํฌ๋กœํ–‰ 6๊ฐœ
  • ํ–‰: 1๊ฐœ

์•ฝ๊ฐ„ ์˜ํ–ฅ์ด ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ด๋‚˜ ์‹ฌ๊ฐํ•œ ์ˆ˜์ค€์€ ์•„๋‹™๋‹ˆ๋‹ค.

[ํ…Œ์ŠคํŠธ 3] ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ + ์ €์ „๋ ฅ ๋ชจ๋“œ

Insturments ํ…Œ์ŠคํŠธ 3

  • ๋งˆ์ดํฌ๋กœํ–‰: 36๊ฐœ
  • ํ–‰: 2๊ฐœ

ํ–‰์ด ๊ธ‰๊ฒฉํžˆ ์ฆ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

[ํ…Œ์ŠคํŠธ 4] ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ + ์ €์ „๋ ฅ ๋ชจ๋“œ + ๊ฒŒ์ž„ + ๋…นํ™”

Insturments ํ…Œ์ŠคํŠธ 4

  • ๋งˆ์ดํฌ๋กœํ–‰: 46๊ฐœ
  • ํ–‰: 2๊ฐœ

ํ…Œ์ŠคํŠธ 3๊ณผ ๋น„์Šทํ•œ ์–‘์ƒ์„ ๋ณด์˜€์Šต๋‹ˆ๋‹ค.

๋ถ„์„ ๊ฒฐ๊ณผ, ๊ฒŒ์ž„/๋…นํ™” ๋“ฑ ํƒ€ ์•ฑ ๋™์ž‘์˜ ์˜ํ–ฅ๋ณด๋‹จ ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ + ์ €์ „๋ ฅ ๋ชจ๋“œ ์˜ํ–ฅ์ด ํฌ๋‹ค๊ณ  ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ

ํ…Œ์ŠคํŠธ ์กฐ๊ฑด[ํ…Œ์ŠคํŠธ 1] ์ผ๋ฐ˜๊ธฐ๊ธฐ (iPhone 15 Plus)[ํ…Œ์ŠคํŠธ 2] ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ + ๊ฒŒ์ž„(3D)[ํ…Œ์ŠคํŠธ 3] ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ + ์ €์ „๋ ฅ ๋ชจ๋“œ[ํ…Œ์ŠคํŠธ 4] ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ + ์ €์ „๋ ฅ ๋ชจ๋“œ + ๊ฒŒ์ž„ + ๋…นํ™”
๋งˆ์ดํฌ๋กœ ํ–‰1๊ฐœ6๊ฐœ36๊ฐœ46๊ฐœ
ํ–‰0๊ฐœ1๊ฐœ2๊ฐœ2๊ฐœ

์ผ๋ฐ˜ ๊ธฐ๊ธฐ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ ๋งŒํ•œ ํ–‰์€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ, ํŠนํžˆ ์ €์ „๋ ฅ ๋ชจ๋“œ์—์„œ๋Š” ์‚ฌ์šฉ์„ฑ์— ์˜ํ–ฅ์„ ์ค„ ๋งŒํผ์˜ ํ–‰์ด ๋‚˜ํƒ€๋‚ฌ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ ๋‘๊ฐ€์ง€ ๊ฒฝ์šฐ๊ฐ€ ๋ฌธ์ œ๊ฐ€ ๋  ๋งŒ ํ–ˆ๋Š”๋ฐ์š”.

Insturments ๊ฒฐ๊ณผ

๋‘ ๊ฐœ์˜ 500ms ์ด์ƒ์˜ ํ–‰๊ณผ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‚˜ํƒ€๋‚˜๋Š” 500ms ์ดํ•˜์˜ ๋‹ค์ˆ˜์˜ ๋งˆ์ดํฌ๋กœ ํ–‰ ์ž…๋‹ˆ๋‹ค.

ํ–‰ ๋ถ„์„

500ms ์ด์ƒ์˜ ํ–‰ ๋ถ„์„

ํ–‰ ๋ถ„์„

๋ฌธ์ œ๊ฐ€ ๋œ ํ–‰์˜ Instruments ํƒ€์ž„๋ผ์ธ์„ ํ™•์ธํ•ด๋ณด๋ฉด, ํ•ด๋‹น ํ–‰์ด ๋‚˜ํƒ€๋‚˜๋Š” ์‹œ์ ์— ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ํ™œ๋ฐœํžˆ ๋™์ž‘ํ•˜๊ณ  ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ฒฝ์šฐ์— ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ๋ฐ”๋น ์ ธ ์‚ฌ์šฉ์ž์˜ ์•ก์…˜์— ๋ฐ˜์‘ํ•˜์ง€ ๋ชปํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

ํƒ€์ž„ ๋ผ์ธ์˜ ์„ธ ๋ฒˆ์งธ ์ค„์€ SwiftUI์˜ body์‹คํ–‰์„ ์ถ”์ ํ•˜๋Š” ํƒ€์ž„๋ผ์ธ์ž…๋‹ˆ๋‹ค.

์ด๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด LiveStreamingHostView์˜ body ์‹คํ–‰ ์‹œ๊ฐ„์ด ๋‹ค๋ฅธ ๋ทฐ์— ๋น„ํ•ด ํ˜„์ €ํžˆ ๊ธธ๋‹ค๋Š” ์ ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ๊ทผ๊ฑฐ๋กœ LiveStreamingHostView์˜ ๋ Œ๋”๋ง ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ธธ์–ด์ง€๋ฉฐ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์˜ ๊ณผ๋ถ€ํ•˜๋กœ ์ด์–ด์กŒ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

500ms ์ดํ•˜์˜ ๋งˆ์ดํฌ๋กœ ํ–‰ ๋ถ„์„

๋งˆ์ดํฌ๋กœ ํ–‰ ๋ถ„์„

ํ•˜๋‚˜์˜ ๋งˆ์ดํฌ๋กœ ํ–‰์„ ํ™•๋Œ€ํ•˜์—ฌ ๋ถ„์„ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

3๋ฒˆ์งธ ํƒ€์ž„๋ผ์ธ์—์„œ๋Š” LiveStreamingHostView๊ฐ€ ์žฌํ‰๊ฐ€๋˜๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํƒ€์ž„๋ผ์ธ ๋งˆ์ง€๋ง‰ ์ค„์—์„œ๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ํ™œ๋ฐœํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ณ  ์žˆ์Œ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด, ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ๋งˆ์ดํฌ๋กœ ํ–‰์˜ ์›์ธ์€ ์‹คํ–‰ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” LiveStreamingHostView๊ฐ€ ๋ฐ˜๋ณต์ ์œผ๋กœ ์žฌํ‰๊ฐ€๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๋Š” ์ ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉํ–ฅ ๊ฒฐ์ •

Instruments์˜ ํƒ€์ž„๋ผ์ธ ๋ถ„์„์„ ํ†ตํ•ด LiveStreamingHostView์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„๋ƒˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ์•ˆ์€ ๋‘ ๊ฐ€์ง€๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1. LiveStreamingHostView์˜ ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ค„์ธ๋‹ค.
2. LiveStreamingHostView์˜ ์žฌํ‰๊ฐ€ ํšŸ์ˆ˜๋ฅผ ์ค„์ธ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ 1๋ฒˆ ํ•ด๊ฒฐ์ฑ…์€ ์นด๋ฉ”๋ผ ํ™”๋ฉด ์†ก์ถœ๊ณผ ๊ฐ™์€ ๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ์˜ ํŠน์„ฑ์ƒ ๊ธฐ๋Šฅ์„ ์ถ•์†Œํ•˜๊ฑฐ๋‚˜ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์— ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ 2๋ฒˆ ํ•ด๊ฒฐ์ฑ…์„ ์ฑ„ํƒํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ์žฌํ‰๊ฐ€ ํšŸ์ˆ˜๋ฅผ ์ค„์ด๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ ‘๊ทผํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ ๋ฐœ์ƒ ์›์ธ

๋ทฐ์˜ ๋ถˆํ•„์š”ํ•œ ์žฌํ‰๊ฐ€๋ฅผ ์ค„์ด๋ ค๋ฉด ๋ทฐ๋ฅผ ์žฌํ‰๊ฐ€ํ•˜๋Š” ์š”์ธ์„ ์ฐพ์•„ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด์— ๋”ฐ๋ผ LiveStreamingHostView๊ฐ€ ์ž์ฃผ ์žฌํ‰๊ฐ€๋˜๋Š” ์›์ธ์„ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด SwiftUI์˜ ._printChanges() ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

public var body: some View {
    let _ = Self._printChanges()
    ZStack {
    // ...

[๊ฒฐ๊ณผ]

LiveStreamingHostView: @self, @identity, _viewStore, _keyboardResponder changed.
LiveStreamingHostView: \State.recorder changed. // ํ•˜์œ„ ๋ทฐ State
LiveStreamingHostView: @self, _viewStore changed.
LiveStreamingHostView: \State.recorder changed. // ํ•˜์œ„ ๋ทฐ State
LiveStreamingHostView: \ValueReference<LiveRecordState, InMemoryKey<LiveRecordState>>.<computed 0x00000001050f9174 (LiveRecordState)>, @self, _viewStore changed.

์ด๋ฅผ ํ†ตํ•ด ํ•˜์œ„ ๋ทฐ์—์„œ๋งŒ ์˜ํ–ฅ์„ ๋ฐ›์•„์•ผ ํ•  ์ƒํƒœ ๋ณ€๊ฒฝ์—๋„ ์ƒ์œ„ ๋ทฐ๊ฐ€ ์˜ํ–ฅ์„ ๋ฐ›๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

์›์ธ์€ TCA์˜ Scope

fromm์˜ ๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ ํŽ˜์ด์ง€๋Š” SwiftUI + TCA(The Composable Architecture)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

TCA๋Š” Point-Free์—์„œ ๊ฐœ๋ฐœํ•œ Swift ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์ƒํƒœ ๊ด€๋ฆฌ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋ชจ๋“ˆํ™”ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ ํ™”๋ฉด ๊ตฌ์กฐ

์ „์ฒด ํ™”๋ฉด์€ LiveStreamingHostView๋ผ๋Š” SwiftUI ๋ทฐ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  LiveStreamingHostView ๋‚ด๋ถ€์— ํ•˜์œ„ ๋ทฐ๋“ค์ด ์žˆ๊ณ  LiveStreamingHostFeature์—์„œ ๊ฐ ํ•˜์œ„๋ทฐ์˜ Reducer๋“ค์„ Scope๊ธฐ๋Šฅ์œผ๋กœ ์—ฐ๊ฒฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฆฌ๋“€์„œ ๊ตฌ์กฐ



ํ•˜์œ„ ๋ทฐ์˜ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ์ƒ์œ„ ๋ทฐ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ์›์ธ์€ TCA์˜ Scope ์‚ฌ์šฉ ๋ฐฉ์‹์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

Scope ๊ธฐ๋Šฅ์€ TCA์—์„œ ๋งค์šฐ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์œผ๋กœ ์ƒ์œ„ ๋ทฐ์™€ ํ•˜์œ„ ๋ทฐ ๊ฐ„์˜ ์ž์œ ๋กœ์šด ์†Œํ†ต์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋“ฑ ์—ฌ๋Ÿฌ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Scope ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•˜์œ„ ๋ทฐ์˜ State๋ฅผ ์ƒ์œ„ ๋ทฐ์—์„œ ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š”๋ฐ ์ด ๊ณผ์ •์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

fromm์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด scope ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

public struct LiveStreamingHostViewReducer: Reducer {

    // MARK: - State

    @ObservableState
    public struct State: Equatable {

        // MARK: - Sub Reducer

        public var chat: LiveChatViewReducer.State?
        public var control: LiveStreamingControxlViewReducer.State
        public var headerView: LiveStreamingHeaderViewReducer.State
        public var liveRoomState: LiveRoomStatReducer.State?
        public var recorder: LiveStreamingRecorderReducer.State
        public var waitingRoom: LiveStreamingWaitingRoomReducer.State?

์ด๋•Œ ํ•˜์œ„ ๋ทฐ์˜ State๊ฐ€ ์ƒ์œ„ ๋ทฐ์˜ State์˜ ์ผ๋ถ€๋กœ ํฌํ•จ๋˜๋ฉด์„œ, ํ•˜์œ„ ๋ทฐ์˜ ์ƒํƒœ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์ƒ์œ„ ๋ทฐ๋กœ ์ „ํŒŒ๋˜๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋™์ž‘์€ ํŠน์ • ์ƒํ™ฉ์—์„œ๋Š” ์œ ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ํ˜„์žฌ fromm์˜ ๋ผ์ด๋ธŒ ๊ธฐ๋Šฅ์—์„œ๋Š” ๋ถˆํ•„์š”ํ•œ ์ƒํƒœ ๋ณ€๊ฒฝ ์ „ํŒŒ๋ฅผ ์œ ๋ฐœํ•˜๋ฉฐ ์˜๋„์น˜ ์•Š์€ ๋™์ž‘์„ ์ดˆ๋ž˜ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

1. scope ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ

์ฒซ ๋ฒˆ์งธ ํ•ด๊ฒฐ์ฑ…์€ Scope๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๊ฐ ํ•˜์œ„ ๋ทฐ ์ปดํฌ๋„ŒํŠธ์—์„œ Reducer๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ ํ•˜์œ„ Reducer์—์„œ ์ƒ์œ„ ๋ฆฌ๋“€์„œ๋กœ action์„ ์ „๋‹ฌํ•  ๊ฒฝ๋กœ๋„ ์—†์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ฐ€์ ‘ํ•˜๊ฒŒ action์„ ์ฃผ๊ณ ๋ฐ›์•„์•ผํ•˜๋Š” ํ˜„์žฌ ์ƒํ™ฉ์—๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

2. scope ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉฐ ์ƒํƒœ๋ฅผ ์˜ต์ €๋น™ํ•˜์ง€ ์•Š๋Š” ๋ฐฉ๋ฒ• ์ฐพ๊ธฐ

Scope ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์ƒ์œ„๋ทฐ๋กœ ์ „ํŒŒํ•˜์ง€ ์•Š์„ ๋ฐฉ๋ฒ•์„ ์ฐพ๋˜ ์ค‘ TCA์—์„œ ์ œ๊ณตํ•˜๋Š” ๋งคํฌ๋กœ์—์„œ ๋ฐฉ๋ฒ•์„ ์ฐพ์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

TCA ๋งคํฌ๋กœ

TCA์˜ @ObservableState๋Š” ๊ฐ State ํ”„๋กœํผํ‹ฐ์— @ObservationStateTracked, @ObservationStateIgnored๋ผ๋Š” ์ถ”๊ฐ€์ ์ธ ๋งคํฌ๋กœ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋งคํฌ๋กœ๋“ค์€ ์–ด๋–ค ์—ญํ• ์„ ํ•˜๋Š”์ง€ swift-composable-architecture์˜ ComposableArchitectureMacros ์†Œ์Šค์ฝ”๋“œ์—์„œ ํ•ด๋‹น ๋งคํฌ๋กœ์˜ ๊ตฌํ˜„๋ถ€๋ฅผ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

@ObservationStateTracked์˜ ๊ตฌํ˜„๋ถ€๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋“ค์„ ์ƒ์„ฑํ•˜๋„๋ก ๋˜์–ด์žˆ๊ณ ,

public struct ObservationStateTrackedMacro: AccessorMacro {
  public static func expansion<
    Context: MacroExpansionContext,
    Declaration: DeclSyntaxProtocol
  >(
    of node: AttributeSyntax,
    providingAccessorsOf declaration: Declaration,
    in context: Context
  ) throws -> [AccessorDeclSyntax] {
        // ...
    let initAccessor: AccessorDeclSyntax =
      """
      @storageRestrictions(initializes: _\(identifier))
      init(initialValue) {
      _\(identifier) = initialValue
      }
      """

    let getAccessor: AccessorDeclSyntax =
      """
      get {
      \(raw: ObservableStateMacro.registrarVariableName).access(self, keyPath: \\.\(identifier))
      return _\(identifier)
      }
      """

    let setAccessor: AccessorDeclSyntax =
      """
      set {
      \(raw: ObservableStateMacro.registrarVariableName).mutate(self, keyPath: \\.\(identifier), &_\(identifier), newValue, _$isIdentityEqual)
      }
      """
    let modifyAccessor: AccessorDeclSyntax = """
      _modify {
        let oldValue = _$observationRegistrar.willModify(self, keyPath: \\.\(identifier), &_\(identifier))
        defer {
          _$observationRegistrar.didModify(self, keyPath: \\.\(identifier), &_\(identifier), oldValue, _$isIdentityEqual)
        }
        yield &_\(identifier)
      }
      """

    return [initAccessor, getAccessor, setAccessor, modifyAccessor]
  }
}

@ObservationStateIgnored๋Š” ์ถ”๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๋„๋ก ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

public struct ObservationStateIgnoredMacro: AccessorMacro {
  public static func expansion<
    Context: MacroExpansionContext,
    Declaration: DeclSyntaxProtocol
  >(
    of node: AttributeSyntax,
    providingAccessorsOf declaration: Declaration,
    in context: Context
  ) throws -> [AccessorDeclSyntax] {
    return []
  }
}

์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ, ์ด ๋งคํฌ๋กœ๋“ค์€ SwiftUI ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด ์ƒํƒœ๋ฅผ ์˜ต์ €๋น™ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  @ObservationStateIgnored๋Š” TCA ์˜ต์ €๋น™ ์‹œ์Šคํ…œ์— ์ƒํƒœ๋ฅผ ๋“ฑ๋กํ•˜์ง€ ์•Š์Œ์œผ๋กœ์จ ์ƒํƒœ ๋ณ€ํ™”์—๋„ SwiftUI์˜ body๊ฐ€ ์žฌํ‰๊ฐ€๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

@ObservableState๋Š” ์ด๋ฏธ ๋งคํฌ๋กœ๊ฐ€ ์ ์šฉ๋œ ํ”„๋กœํผํ‹ฐ์— ๋Œ€ํ•ด ์ถ”๊ฐ€์ ์ธ ๋งคํฌ๋กœ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ @ObservationStateIgnored๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด @ObservationStateTracked๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์•„ ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ๊ฐ€ ์˜ต์ €๋น™๋˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ์„ 

ํ˜„์žฌ ์ƒํ™ฉ์—์„œ๋Š” ์ƒ์œ„ Reducer๊ฐ€ ํ•˜์œ„ Reducer์˜ State๋ฅผ ์˜ต์ €๋น™ํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— @ObservationStateIgnored ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์œ„๋ทฐ๊ฐ€ ํ•˜์œ„๋ทฐ์˜ State์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

public struct LiveStreamingHostReducer: Reducer {
    @ObservableState
    public struct State: Equatable {
        // MARK: - Sub Reducers
        @ObservationStateIgnored
        public var chat: LiveChatReducer.State?
        @ObservationStateIgnored
        public var control: LiveStreamingControlReducer.State
        @ObservationStateIgnored
        public var headerView: LiveStreamingHeaderReducer.State
        @ObservationStateIgnored
        public var recorder: LiveStreamingRecorderFeature.State
        
        // ...

๊ฒฐ๊ณผ

๊ฐœ์„ ์„ ํ†ตํ•ด LiveStreamingHostView๊ฐ€ ํ•˜์œ„ ๋ทฐ ์ƒํƒœ๋กœ ์ธํ•ด ์žฌํ‰๊ฐ€๋˜๋Š” ํšŸ์ˆ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ์ƒ์œ„ ๋ทฐ์˜ ์žฌํ‰๊ฐ€๊ฐ€ ํ•˜์œ„ ๋ทฐ์˜ ์žฌํ‰๊ฐ€๋กœ ์ด์–ด์ง€์ง€ ์•Š๊ฒŒ ๋˜์–ด, LiveStreamingHostView์˜ ํ‰๊ท  body ์‹คํ–‰ ์‹œ๊ฐ„์ด 167ms์—์„œ 11ms๋กœ 93.41% ๊ฐ์†Œํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ฐœ์„  ์ „

๊ฐœ์„  ์ „

๊ฐœ์„  ํ›„

๊ฐœ์„  ํ›„

์ด์— ๋”ฐ๋ผ ํ–‰, ๋งˆ์ดํฌ๋กœ ํ–‰ ๋˜ํ•œ ๋ˆˆ์— ๋„๊ฒŒ ์ค„์–ด๋“  ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

๊ฐœ์„  ์ „

๊ฐœ์„  ์ „

๊ฐœ์„  ํ›„

๊ฐœ์„  ํ›„

๋งˆ๋ฌด๋ฆฌ

@ObservationStateIgnored ๋งคํฌ๋กœ๋Š” ์ด ์™ธ์—๋„ ๋‹ค์–‘ํ•œ ํ™œ์šฉ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์„ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด struct๋กœ ๊ตฌ์„ฑ๋œ Reducer ํŠน์„ฑ์ƒ State ์™ธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋ฐฉ๋ฒ•์ด ์ œํ•œ์ ์ธ๋ฐ์š”,

์ด๋Ÿด ๋•Œ @ObservationStateIgnored ๋งคํฌ๋กœ๋ฅผ ํ™œ์šฉํ•˜๋ฉด, ๋ทฐ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์œผ๋ฉด์„œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์ด ๊ธ€์ด TCA๋ฅผ ์ ์šฉ ์ค‘์ด์‹  ๊ฐœ๋ฐœ์ž๋ถ„๋“ค๊ป˜ ์กฐ๊ธˆ์ด๋‚˜๋งˆ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค ๐Ÿ™‡

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ