๐Ÿค“ย ํ•™์Šต๋ฐฐ๊ฒฝ


SwiftUI๋กœ ๋ทฐ๋ฅผ ๋งŒ๋“ค๋‹ค๋ณด๋ฉด ViewBuilder๋ผ๋Š” ํ”„๋กœํผํ‹ฐ ๋ž˜ํผ๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋œ๋‹ค. ์ด ์นœ๊ตฌ์˜ ์—ญํ• ์€ ๋ญ๊ณ  ์–ด๋–ป๊ฒŒ ๋‹ค์–‘ํ•œ ๋ทฐ๋“ค์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด์ฃผ๋Š” ๊ฑธ๊นŒ๋ผ๋Š” ์˜๋ฌธ์ด ์ƒ๊ฒผ๋‹ค. ๊ทธ๋ž˜์„œ ํ•ด๋‹น ๋‚ด์šฉ์— ๋Œ€ํ•ด ์ข€ ๋” ๊ณต๋ถ€ํ•ด๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค.

๐Ÿ˜Žย ํ•™์Šต๋‚ด์šฉ


resultBuilder

ViewBuilder์— ๋Œ€ํ•ด ์ฐพ์•„๋ณด๋‹ค๋ณด๋‹ˆ resultBuilder์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. @resuiltBuilder๋Š” ๋ฌด์—‡์ผ๊นŒ? ๋จผ์ € ์˜ˆ์‹œ ์ฝ”๋“œ๋ฅผ ๋ณด์ž

@resultBuilder
struct AddEvenNumbers {
    static func buildBlock(_ components: Int...) -> Int {
        var final: Int = 0
        
        for part in components {
            final += part % 2 == 0 ? part : 0
        }
        
        return final
    }
}

@AddEvenNumbers
var result: Int {
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
}

print(result)//30

๋‹ค์Œ ์ฝ”๋“œ๋Š” ์ง์ˆ˜๋งŒ ๊ณจ๋ผ์„œ ๊ทธ ํ•ฉ์„ ๋”ํ•ด์ฃผ๋Š” @resuiltBuilder์ด๋‹ค. ๋จผ์ € @resuiltBuilder๋ฅผ ์ ์šฉํ•œ ๊ตฌ์กฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์„œ ํ”„๋กœํผํ‹ฐ ๋ž˜ํผ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ  ๋‚ด๋ถ€์—์„œ ์ƒ์„ธ ๋กœ์ง์„ ๊ตฌํ˜„ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ƒ์„ฑํ•œ ํ”„๋กœํผํ‹ฐ ๋ž˜ํผ๋ฅผ ์ ์šฉํ•˜๋ฉด result ๋ณ€์ˆ˜์ฒ˜๋Ÿผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์š”์†Œ๋“ค์„ ๋‚˜์—ดํ•˜๊ณ  ํ•ด๋‹น ์š”์†Œ๋“ค์„ ์ •ํ•ด์ง„ ๊ทœ์น™์— ๋”ฐ๋ผ ๊ฒฐํ•ฉ, ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ @resuiltBuilder์ด๋‹ค. @resuiltBuilder๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๋ฉด buildBlock์™€ ๊ฐ™์€ ํ•„์ˆ˜ ๋ฉ”์„œ๋“œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ if๋ฌธ, Optional์ผ ๊ฒฝ์šฐ ๊ฐ’๋“ค์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์ด ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค. ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋“ค์„ ํ™œ์šฉํ•ด์„œ ์š”์†Œ๋“ค์„ ๋‹ค์–‘ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

result ๋ณ€์ˆ˜์˜ ํ˜•ํƒœ๋ฅผ ๋ณด๋ฉด ๋น„์Šทํ•œ ์–ด๋–ค๊ฒŒ ๋– ์˜ค๋ฅด์ง€ ์•Š๋‚˜โ€ฆ? ๋งž๋‹ค ๋ฐ”๋กœ SwiftUI์˜ View ์„ ์–ธ ๋ฐฉ์‹์ด๋‹ค. ๋‘ ๋ฐฉ์‹์ด ๋น„์Šทํ•ด ๋ณด์ด๋Š” ์ด์œ ๋Š” SwiftUI์˜ ViewBuilder๋„ @resuiltBuilder๋ฅผ ์ฑ„ํƒํ•ด์„œ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ViewBuilder

ViewBuilder๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ž์‹ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํด๋กœ์ € ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ ์†์„ฑ์œผ๋กœ ์‚ฌ์šฉ๋˜๋ฉฐ, ์ด๋Ÿฌํ•œ ํด๋กœ์ €๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž์‹ ๋ทฐ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

ViewBuilder๋Š” ํ•˜๋‚˜์˜ ํด๋กœ์ €๋กœ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž์‹ ๋ทฐ๋ฅผ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ VStack๊ณผ ๊ฐ™์€ ์Šคํƒ ๋‚ด๋ถ€์— ์—ฌ๋Ÿฌ ์ž์‹ ๋ทฐ๋“ค์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‰ฝ๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋„ VStack ๋‚ด๋ถ€๋ฅผ ๋ณด๋ฉด ํด๋กœ์ € ๋งค๊ฐœ๋ณ€์ˆ˜์— ViewBuilder ์†์„ฑ์ด ์ ์šฉ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด๋ ‡๋“ฏ ์šฐ๋ฆฌ๋Š” ViewBuilder๋ฅผ ํ™œ์šฉํ•ด์„œ ์—ฌ๋Ÿฌ ์ž์‹ ๋ทฐ๋“ค์„ ํ•œ ๋ฒˆ์— ์ •์˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ViewBuilder ํ˜•์‹์œผ๋กœ ๋ทฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ๊ฐ€๋…์„ฑ๋„ ์ข‹๋‹ค!

image.png

ViewBuilder๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค.

private func ImageCellView(model: ImageDTO) -> some View {
    if let image = imageDict[model.id] {
        thumbnailImageView(image: image)
            .onAppear{
                debugPrint("original appeared \\(model.id)")
            }
    } else {
        waitingFetchingView(model: model)
    }
}

๋‹ค์Œ ์ฝ”๋“œ๋Š” ๋”•์…”๋„ˆ๋ฆฌ์— ์ด๋ฏธ์ง€๊ฐ€ ์—†๋‹ค๋ฉด PlaceHolder(waitingFetchingView)๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ์žˆ๋‹ค๋ฉด thumbnailImageVIew๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. ๊ฐ€๋งŒ ๋ณด๋ฉด ๋กœ์ง์ƒ ๋ฌธ์ œ๊ฐ€ ์—†์–ด๋ณด์ด์ง€๋งŒ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๊ฐ€ ๋‚œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋ฆฌํ„ด ํƒ€์ž…์ด some View์ด๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ ๊ณผ์ •์—์„œ ํ•˜๋‚˜์˜ ํƒ€์ž…์œผ๋กœ ์ถ”๋ก ์ด ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋Š”๋ฐ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” ๋‘ ๊ฐ€์ง€ ํƒ€์ž…์˜ ๋ทฐ๊ฐ€ ๋™์‹œ์— ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๊ทธ๋Ÿด ๋•Œ ์ €๊ธฐ์— ViewBuilder ์†์„ฑ์„ ๋”ํ•ด์ฃผ๋ฉด ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

@ViewBuilder
private func ImageCellView(model: ImageDTO) -> some View { ... }