Result Builder คือแอตทริบิวต์ของ Swift ที่ใช้งานผ่านโปรโตคอล @resultBuilder ซึ่งแปลงลำดับของนิพจน์ให้เป็นค่าผสม คอมไพเลอร์จะแปลงบล็อกโค้ดที่มีโครงสร้างควบคุม if, for, switch เป็นการเรียกเมธอดสแตติกของ builder — buildBlock, buildEither, buildArray ตามข้อเสนอ Swift Evolution SE-0289 (2022) result builders ช่วยให้สร้าง DSL แบบประกาศภายใน Swift ได้โดยไม่ต้องใช้ตัวแยกวิเคราะห์ภายนอก ตัวอย่างที่รู้จักกันดีคือ @ViewBuilder ใน SwiftUI ซึ่งบอดีของ view ถูกสร้างจากองค์ประกอบแบบมีเงื่อนไขและแบบวนซ้ำในสไตล์การประกาศ
ประเด็นสำคัญ
TupleViewif/else, switch, for-in ผ่านเมธอด buildOptional, buildEither, buildArrayResult Builder (เดิมรู้จักในชื่อ function builders) เป็นกลไกของ Swift ที่ช่วยให้เปลี่ยนลำดับของนิพจน์ที่คั่นด้วยการขึ้นบรรทัดใหม่เป็นค่าเดียว ถูกประกาศโดยใช้แอตทริบิวต์ @resultBuilder ที่ใช้กับโครงสร้างซึ่ง implement เมธอดการแปลงแบบสแตติก
ก่อนมี result builders ไวยากรณ์แบบประกาศของ SwiftUI body เป็นไปไม่ได้ แทนที่จะเป็นรายการ view ที่กะทัดรัด นักพัฒนาจะต้องเขียนการเรียก TupleView ด้วยตนเอง Result Builder จะห่อหุ้มแต่ละนิพจน์โดยอัตโนมัติ รองรับการแยกสาขาและลูป ซ่อนความซับซ้อนของการประกอบจากนักพัฒนา
ตาม Swift Evolution SE-0289 ที่ได้รับการยอมรับในปี 2022 result builder คือวิวัฒนาการของแนวคิด function builders (SE-0258, Swift 5.1) การเปลี่ยนแปลงหลัก: การเปลี่ยนชื่อจาก @_functionBuilder เป็น @resultBuilder และขยายไปยังพารามิเตอร์ของฟังก์ชัน ทำให้สามารถใช้ builders สำหรับอาร์กิวเมนต์ closure ใด ๆ ไม่ใช่แค่บอดีของ view
ใช้ result builders เมื่อคุณต้องการให้ผู้ใช้ไลบรารีของคุณมีไวยากรณ์แบบประกาศสำหรับสร้างโครงสร้างที่ซับซ้อน — การกำหนดค่า, คำสั่งค้นหา, ส่วนประกอบ UI — โดยไม่ต้องเขียนโค้ดประกอบแบบ'imperative
คอมไพเลอร์ Swift แปลงแต่ละบล็อกโค้ดที่ทำเครื่องหมายด้วย @resultBuilder เป็นลำดับของการเรียกเมธอดสแตติกของ builder มาดู builder ง่าย ๆ ที่เชื่อมสตริง:
@resultBuilder
struct StringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: " ")
}
}
การใช้ builder นี้ — แต่ละสตริงในบรรทัดแยกกันจะถูกเชื่อมด้วยช่องว่าง:
@StringBuilder
func greeting() -> String {
"Hello"
"World"
"from"
"Swift"
}
// คอมไพเลอร์แปลงค่านี้เป็น:
// StringBuilder.buildBlock("Hello", "World", "from", "Swift")
// ผลลัพธ์: "Hello World from Swift"
คอมไพเลอร์จัดกลุ่มนิพจน์ที่ต่อเนื่องกันและส่งเป็นพารามิเตอร์ variadic ไปยัง buildBlock หากมี if ปรากฏระหว่างนิพจน์ คอมไพเลอร์จะเรียก buildOptional หรือ buildEither สำหรับการแยกสาขา สำหรับลูป for-in มันจะเรียก buildArray ดังนั้น โค้ด Swift ปกติจะถูกแปลงเป็นห่วงโซ่การเรียกที่สร้างค่าสุดท้าย
แต่ละ result builder กำหนดชุดเมธอดสแตติกที่คอมไพเลอร์เรียกระหว่างการแปลง เมธอดหลัก:
| เมธอด | วัตถุประสงค์ | เมื่อถูกเรียก |
|---|---|---|
| buildBlock | รวมลำดับของนิพจน์ | สำหรับแต่ละบล็อกที่ไม่มีการแยกสาขา |
| buildOptional | จัดการ if ที่ไม่มี else | เมื่อมี if โดยไม่มี else |
| buildEither(first:) | สาขาแรกของ if-else | เมื่อมี if กับ else |
| buildEither(second:) | สาขาที่สองของ if-else | เมื่อมี if กับ else |
| buildArray | จัดการลูป for-in | เมื่อมี for-in |
| buildExpression | แปลงนิพจน์แต่ละตัว | สำหรับแต่ละนิพจน์ก่อนส่งไปยัง buildBlock |
| buildFinalResult | การแปลงสุดท้าย | ก่อนกลับจาก closure |
การ implement ขั้นต่ำต้องการเพียง buildBlock ที่มีพารามิเตอร์ variadic — เพียงพอสำหรับบล็อกที่ไม่มีการแยกสาขา การเพิ่ม buildOptional และ buildEither รวมถึงการรองรับโครงสร้างแบบมีเงื่อนไข และ buildArray สำหรับลูป ตามเอกสาร Swift (2025) แนะนำให้ implement ทุกเมธอดเพื่อความยืดหยุ่นสูงสุดของ DSL
buildExpression อนุญาตให้รับนิพจน์ประเภทต่าง ๆ และแปลงเป็นประเภท builder เดียว ตัวอย่างเช่น ใน @ViewBuilder buildExpression รับ Text, Image, Button และแปลงเป็นประเภท View ทั่วไป
มาดูการสร้าง builder สำหรับสร้างสตริง HTML กัน DSL นี้จะช่วยให้เขียน HTML แบบประกาศได้โดยตรงใน Swift:
@resultBuilder
enum HTMLBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined()
}
static func buildOptional(_ component: String?) -> String {
component ?? ""
}
static func buildEither(first component: String) -> String {
component
}
static func buildEither(second component: String) -> String {
component
}
static func buildArray(_ components: [String]) -> String {
components.joined()
}
}
การใช้ builder ที่กำหนดเองเพื่อสร้าง HTML:
func div(@HTMLBuilder _ content: () -> String) -> String {
"<div>\(content())</div>"
}
func p(_ text: String) -> String {
"<p>\(text)</p>"
}
let page = div {
p("Hello")
p("World")
if showFooter {
p("ส่วนท้าย")
}
}
// ผลลัพธ์: <div><p>Hello</p><p>World</p><p>Footer</p></div>
ตามบทความ “Building Custom Result Builders in Swift” จาก Swift.org (2025) builders ที่กำหนดเองถูกใช้ในไลบรารีเพื่อสร้างไฟล์การกำหนดค่า ส่วนประกอบ UI การแมปข้อมูล และแม้แต่คำสั่งค้นหาฐานข้อมูล — ทุกที่ที่ต้องการไวยากรณ์แบบประกาศพร้อมการรองรับการแยกสาขา
@ViewBuilder คือ result builder ที่สร้างใน SwiftUI ซึ่งใช้กับพารามิเตอร์ content ของ view คอนเทนเนอร์ส่วนใหญ่: VStack, HStack, ZStack, Group, List และคุณสมบัติ body เอง มันช่วยให้เขียนหลาย view ในบรรทัดแยกกันโดยไม่ต้องใช้เครื่องหมายจุลภาคหรือตัวห่อหุ้ม
@ViewBuilder implement ทุกเมธอดของ result builder รวมถึงการรองรับ if-else, switch และ for-in เมื่อเงื่อนไขเป็นจริง buildEither(first:) จะคืนค่า view หนึ่ง เมื่อเป็นเท็จ buildEither(second:) จะคืนค่าอีก view หนึ่ง ทั้งสองสาขาต้องคืนค่าประเภทเดียวกัน แต่ SwiftUI ใช้ AnyView ภายในหรือการลบประเภทผ่าน ConditionalContent
struct GreetingView: View {
let isLoggedIn: Bool
var body: some View {
VStack {
Image(systemName: "person.circle")
Text("โปรไฟล์")
.font(.title)
if isLoggedIn {
Text("ยินดีต้อนรับกลับ!")
.foregroundColor(.green)
} else {
Button("เข้าสู่ระบบ") { }
}
}
}
}
หากไม่มี @ViewBuilder โค้ดเดียวกันจะต้องใช้ Group สำหรับแต่ละส่วนแบบมีเงื่อนไขหรือใช้ AnyView ซึ่งทำให้ประสิทธิภาพลดลง @ViewBuilder จะเลือกการแสดงผลที่มีประสิทธิภาพที่สุดโดยอัตโนมัติ — ConditionalContent หรือ TupleView — สำหรับแต่ละชุดค่าผสม
ข้อจำกัดแรกคือ จำนวนนิพจน์สูงสุด ใน buildBlock ไลบรารีมาตรฐาน Swift กำหนดโอเวอร์โหลดของ buildBlock สำหรับ 2–10 นิพจน์ หากบล็อกมีมากกว่า 10 นิพจน์ คอมไพเลอร์จะแสดงข้อผิดพลาด วิธีแก้ไขคือการจัดกลุ่มผ่าน Group หรือ VStack เพื่อแบ่งเป็นบล็อกย่อย
ข้อจำกัดที่สองคือ การขาดการรองรับ ตัวแปรและการกำหนดค่าภายในบล็อก builder ไม่สามารถประกาศ let x = 5 ภายใน @ViewBuilder นิพจน์ทั้งหมดต้องเป็นนิพจน์ที่คืนค่าประเภทของ builder สำหรับการคำนวณระหว่างกลาง ให้ใช้การคำนวณนอก builder หรือ buildExpression ที่รองรับประเภทต่าง ๆ
ข้อจำกัดที่สามคือ ความซับซ้อนในการดีบัก ข้อผิดพลาดการคอมไพล์ภายใน result builder มักสร้างข้อความที่สับสน โดยเฉพาะเมื่อประเภทไม่ตรงกันในสาขา if/else ใช้ประเภทส่งคืนที่ชัดเจนและ AnyView สำหรับการดีบัก แม้ว่าอย่างหลังจะลดประสิทธิภาพ ตาม Hacking with Swift (2025) คำแนะนำเชิงปฏิบัติ — เริ่มต้นด้วย builder ง่าย ๆ โดยไม่มีการแยกสาขาและค่อย ๆ เพิ่มการรองรับโครงสร้างแบบมีเงื่อนไข
คำถามที่พบบ่อย
Result Builder คือแอตทริบิวต์ Swift ที่แปลงลำดับของนิพจน์เป็นค่าผลลัพธ์ผ่านเมธอดสแตติก มันช่วยให้สร้าง DSL แบบประกาศได้ ตัวอย่างที่รู้จักกันดีคือ @ViewBuilder ใน SwiftUI สำหรับสร้างลำดับชั้น view โดยไม่ต้องใช้โค้ดแบบ imperative
ประกาศโครงสร้างด้วยแอตทริบิวต์ @resultBuilder และ implement อย่างน้อยเมธอด buildBlock เพื่อรองรับเงื่อนไข เพิ่ม buildOptional และ buildEither สำหรับลูป เพิ่ม buildArray ใช้แอตทริบิวต์ builder ก่อนพารามิเตอร์ closure ในฟังก์ชัน
เฉพาะ buildBlock เท่านั้นที่จำเป็น เมธอดอื่น ๆ ทั้งหมด — buildOptional, buildEither, buildArray, buildExpression, buildFinalResult — เป็นทางเลือกและเพิ่มการรองรับโครงสร้างที่เกี่ยวข้อง ยิ่ง implement เมธอดมากเท่าไหร่ DSL ก็ยิ่งยืดหยุ่นมากขึ้นเท่านั้น
@ViewBuilder คือการ implement ที่เป็นรูปธรรมของ result builder สำหรับโปรโตคอล View มันถูกกำหนดใน SwiftUI เป็นโครงสร้างที่มีแอตทริบิวต์ @resultBuilder ซึ่งให้เมธอด buildBlock สำหรับจำนวน view ที่แตกต่างกัน (TupleView), buildEither สำหรับ ConditionalContent และ buildArray สำหรับ ForEach
ใช่ โอเวอร์โหลด buildBlock มาตรฐานรองรับสูงสุด 10 นิพจน์ เมื่อเกินนี้ ให้ใช้คอนเทนเนอร์แบบซ้อน (Group, VStack) เพื่อแบ่งเป็นบล็อกย่อย builder ที่กำหนดเองสามารถกำหนด buildBlock แบบ variadic โดยไม่มีข้อจำกัด
สรุป
buildBlock, buildEither, buildOptional, buildArray ขึ้นอยู่กับโครงสร้างควบคุมเราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ