Property Wrapper: แนวคิดหลัก ไวยากรณ์ และตัวอย่างใน SwiftUI

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-19 เวลาอ่าน: 8 นาที

Property Wrapper เป็นกลไกของ Swift ที่เพิ่มชั้นของตรรกะในการเข้าถึงและแก้ไขคุณสมบัติโดยไม่ต้องทำซ้ำโค้ด ใน SwiftUI Property Wrappers กลายเป็นพื้นฐานของการจัดการสถานะของวิว: @State, @Binding, @ObservedObject, @StateObject และ @Environment ตาม เอกสารของ Swift (2025) wrapper คุณสมบัติลดโค้ดเทมเพลตในโปรเจกต์โดยเฉลี่ย 40% การเข้าใจ Property Wrapper เป็นสิ่งจำเป็นสำหรับนักพัฒนา iOS ทุกคนในการทำงานกับเฟรมเวิร์กอย่างมีประสิทธิภาพ

ประเด็นสำคัญ

  • Property Wrapper — โครงสร้างทางไวยากรณ์ของ Swift ที่เพิ่มตรรกะให้กับ getter และ setter ของคุณสมบัติ
  • @State — wrapper สำหรับสถานะเฉพาะที่ของวิวเดียว
  • @Binding — การเชื่อมต่อสองทิศทางกับแหล่งข้อมูลจากวิวแม่
  • @ObservedObject — การสังเกต ObservableObject ภายนอกโดยไม่เป็นเจ้าของ
  • @StateObject — การสร้างและเป็นเจ้าของ ObservableObject ในระดับวิวปัจจุบัน

Property Wrapper คืออะไร?

Property Wrapper — โครงสร้างภาษา Swift ที่เปิดตัวในเวอร์ชัน 5.1 ซึ่งอนุญาตให้ห่อหุ้มตรรกะการเข้าถึงคุณสมบัติในชนิดที่แยกต่างหาก แทนที่จะเขียน getter และ setter ซ้ำ ๆ ในทุกคลาส นักพัฒนาประกาศ wrapper หนึ่งครั้งและนำไปใช้ผ่านคำอธิบายประกอบ @ หน้าชนิด Swift จะห่อหุ้มคุณสมบัติในชนิดที่ระบุโดยอัตโนมัติ เรียกเมธอด wrappedValue และ projectedValue เมื่ออ่านและเขียน ตามข้อมูลของ Apple (WWDC 2019) Property Wrappers กลายเป็นนามธรรมหลักสำหรับ SwiftUI

wrapper คุณสมบัติคือโครงสร้างหรือคลาสที่มีแอตทริบิวต์ @propertyWrapper ภายใน ชนิดดังกล่าวต้อง implement คุณสมบัติ wrappedValue ซึ่งส่งคืนและตั้งค่าจริง คอมไพเลอร์ Swift แทนที่การเข้าถึงคุณสมบัติดั้งเดิมด้วยการเรียก wrappedValue ซ่อนการ implement จากโค้ดที่เรียกใช้โดยสมบูรณ์ นอกจากนี้ สามารถกำหนด projectedValue — การฉายภาพที่เข้าถึงได้ผ่านสัญลักษณ์ $

ข้อดีของ Property Wrappers อยู่ที่ การใช้ซ้ำ ของตรรกะ ตัวอย่างเช่น คุณสามารถสร้าง wrapper สำหรับการตรวจสอบอีเมล การแคชค่าหรือการซิงค์กับพื้นที่จัดเก็บ — และนำไปใช้กับคุณสมบัติใด ๆ ในโปรเจกต์ ใน SwiftUI แนวคิดนี้ถูกใช้ทุกที่: ทุกกลไกการจัดการสถานะถูก implement เป็น Property Wrapper แยกต่างหาก

Property Wrapper ทำงานอย่างไรใน Swift?

เมื่อประกาศคุณสมบัติด้วยคำอธิบายประกอบ @WrapperType var value: T คอมไพเลอร์ Swift จะแปลงโค้ด มันสร้าง instance ของ WrapperType และสร้างการเข้าถึงคุณสมบัติผ่าน wrappedValue โค้ดต้นฉบับ let x = value กลายเป็น let x = _value.wrappedValue และ value = newValue กลายเป็น _value.wrappedValue = newValue การแปลงนี้เกิดขึ้นในเวลาคอมไพล์ โดยไม่มีโอเวอร์เฮดรันไทม์

swift
@propertyWrapper
struct Capitalized {
    private var text: String

    var wrappedValue: String {
        get { text }
        set { text = newValue.capitalized }
    }

    init(initialValue: String) {
        text = initialValue.capitalized
    }
}

รายการแสดง wrapper Capitalized ซึ่งแปลงสตริงเป็นรูปแบบตัวพิมพ์ใหญ่โดยอัตโนมัติ เมื่อกำหนดค่า setter จะเรียก capitalized ก่อนบันทึก ตอนนี้คุณสมบัติใด ๆ ที่มีคำอธิบายประกอบ @Capitalized จะจัดเก็บเฉพาะข้อความที่จัดรูปแบบอย่างถูกต้อง วิธีการนี้ช่วยลดการทำซ้ำโค้ดการตรวจสอบและการจัดรูปแบบโดยสมบูรณ์

การฉายภาพ (projectedValue) — ช่องทางการสื่อสารเพิ่มเติมที่เข้าถึงได้ผ่านคำนำหน้า $ ใน SwiftUI คุณสมบัตินี้ถูกใช้ทุกที่: $state ให้ Binding และ $observedObject ส่งคืน Published.Publisher การฉายภาพช่วยให้ wrapper มีอินเทอร์เฟซแบบขยายโดยไม่เปลี่ยนการเข้าถึงพื้นฐานของค่า

Property Wrappers หลักใน SwiftUI

SwiftUI มี Property Wrappers ในตัวห้าตัวสำหรับการจัดการสถานะ: @State, @Binding, @ObservedObject, @StateObject และ @Environment แต่ละตัวแก้ไขงานเฉพาะและใช้ในสถานการณ์ต่างกัน @State สำหรับข้อมูลเฉพาะที่ง่าย, @Binding — สำหรับส่งการอ้างอิงข้อมูลไปยังวิวลูก, @ObservedObject และ @StateObject — สำหรับออบเจกต์ที่ซับซ้อน, @Environment — สำหรับค่าระบบจากลำดับชั้น

Wrapperวัตถุประสงค์ความเป็นเจ้าของ
@Stateสถานะเฉพาะที่ของวิวเดียววิวปัจจุบัน
@Bindingการเชื่อมต่อสองทิศทางกับแม่วิวแม่
@ObservedObjectการสังเกตออบเจกต์ภายนอกเจ้าของภายนอก
@StateObjectการสร้าง ObservableObjectวิวปัจจุบัน
@Environmentค่าระบบจากลำดับชั้นสภาพแวดล้อม SwiftUI

การเลือก Property Wrapper เฉพาะขึ้นอยู่กับแหล่งข้อมูลและวงจรชีวิตของมัน หากข้อมูลเป็นของวิวเดียวและไม่จำเป็นต้องใช้โดยคอมโพเนนต์ลูก — ใช้ @State หากวิวลูกต้องการแก้ไขข้อมูลของแม่ — ใช้ @Binding สำหรับออบเจกต์ที่ใช้ในหลายวิว @ObservedObject และ @StateObject เหมาะสม

@State: สถานะเฉพาะที่ของวิว

@State คือ Property Wrapper สำหรับจัดเก็บสถานะเฉพาะที่ภายในวิวเดียว SwiftUI จัดการหน่วยความจำสำหรับคุณสมบัติ @State โดยอัตโนมัติและวาดวิวใหม่ทุกครั้งที่มีการเปลี่ยนแปลง @State เหมาะสำหรับชนิดง่าย (String, Int, Bool, enum) และโครงสร้างที่เป็นของวิวปัจจุบันเท่านั้น เมื่อค่าเปลี่ยน SwiftUI จะรันคุณสมบัติ body อีกครั้ง

swift
struct CounterView: View {
    @State private var count: Int = 0

    var body: some View {
        VStack {
            Text("จำนวน: \(count)")
            Button("เพิ่ม") {
                count += 1
            }
        }
    }
}

ในตัวอย่าง คุณสมบัติ @State count จัดเก็บค่าปัจจุบันของตัวนับ SwiftUI สร้างพื้นที่จัดเก็บสำหรับคุณสมบัตินี้บนฮีปและเชื่อมโยงกับวงจรชีวิตของ CounterView เมื่อกดปุ่ม count เพิ่มขึ้น 1 SwiftUI ตรวจพบการเปลี่ยนแปลงและรัน body อีกครั้ง แสดงค่าใหม่ สำคัญ: @State ไม่ควรใช้สำหรับชนิดอ้างอิงที่ซับซ้อน — @StateObject และ @ObservedObject ถูกออกแบบมาสำหรับวัตถุประสงค์นั้น

@Binding: การเชื่อมต่อสองทิศทางระหว่างวิว

@Binding สร้างการอ้างอิงถึงแหล่งข้อมูลที่เป็นของวิวอื่น Binding ไม่ได้จัดเก็บค่าด้วยตัวเอง — มันอ่านและเขียนข้อมูลผ่าน @State, @StateObject หรือ Binding อื่นที่ส่งมาจากแม่ สิ่งนี้ช่วยให้คอมโพเนนต์ลูกแก้ไขสถานะของบรรพบุรุษโดยไม่ต้องเป็นเจ้าของข้อมูลโดยตรงและไม่ต้องใช้ callback

swift
struct ToggleSwitch: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

ในรายการ ToggleSwitch รับ @BindingBool จากวิวแม่ แม่สร้าง @State var isToggleOn = false และส่ง $isToggleOn ไปยังตัวเริ่มต้นของ ToggleSwitch เมื่อผู้ใช้สลับสวิตช์ภายในวิวลูก การเปลี่ยนแปลงจะสะท้อนทันทีใน @State ของแม่ กลไก Binding ช่วยขจัดความจำเป็นในการใช้ delegate หรือ closure เพื่อส่งการเปลี่ยนแปลงขึ้นไปในลำดับชั้นโดยสมบูรณ์

@ObservedObject และ @StateObject: ข้อมูลภายนอก

@ObservedObject คือ Property Wrapper สำหรับสังเกต instance ObservableObject ที่ส่งมาจากภายนอก วิวไม่ได้เป็นเจ้าของออบเจกต์นี้ — มันถูกสร้างในคอมโพเนนต์แม่หรือถูกฉีดผ่าน Environment เมื่อคุณสมบัติ @Published ใด ๆ ภายใน ObservableObject เปลี่ยน SwiftUI จะวาดวิวทั้งหมดที่สมัครผ่าน @ObservedObject ใหม่

@StateObject — wrapper สำหรับสร้างและเป็นเจ้าของ ObservableObject โดยตรงในวิว แตกต่างจาก @ObservedObject, @StateObject รับประกัน instance เดียวของออบเจกต์ตลอดวงจรชีวิตของวิว แม้ว่า SwiftUI จะสร้างโครงสร้างวิวใหม่ (ซึ่งเกิดขึ้นบ่อยครั้ง) @StateObject จะคงออบเจกต์ที่มีอยู่และไม่เรียกตัวเริ่มต้นอีกครั้ง

swift
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}

struct ProfileView: View {
    @StateObject var settings = UserSettings()

    var body: some View {
        ChildProfileView(settings: settings)
    }
}

struct ChildProfileView: View {
    @ObservedObject var settings: UserSettings

    var body: some View {
        Text("สวัสดี, \(settings.username)")
    }
}

ในตัวอย่าง ProfileView สร้าง UserSettings ผ่าน @StateObject กลายเป็นเจ้าของออบเจกต์ ChildProfileView รับ instance เดียวกันผ่าน @ObservedObject — สังเกตแต่ไม่จัดการวงจรชีวิต เมื่อ username เปลี่ยน วิวทั้งสองอัปเดต ถ้า ChildProfileView ใช้ @StateObject แทน @ObservedObject instance ใหม่พร้อมค่าเริ่มต้นจะถูกสร้างทุกครั้งที่เรนเดอร์

กฎสำคัญ: @StateObject ใช้ในวิวที่สร้างออบเจกต์ (แหล่งความจริง) ในขณะที่ @ObservedObject ใช้ในวิวที่รับออบเจกต์ที่สร้างแล้วจากแม่ การละเมิดกฎนี้นำไปสู่การสูญเสียสถานะหรือการสร้างข้อมูลใหม่โดยไม่คาดคิด

การสร้าง Property Wrapper แบบกำหนดเอง

Swift อนุญาตให้สร้าง Property Wrappers แบบกำหนดเองสำหรับตรรกะการเข้าถึงคุณสมบัติที่ทำซ้ำ เพียงประกาศโครงสร้างหรือคลาสด้วยแอตทริบิวต์ @propertyWrapper และ implement wrappedValue ด้านล่างคือ wrapper UserDefaultsWrapper ซึ่งซิงค์ค่าโดยอัตโนมัติกับ UserDefaults

swift
@propertyWrapper
struct UserDefaultsWrapper<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

struct AppConfig {
    @UserDefaultsWrapper(key: "theme", defaultValue: "light")
    var theme: String
}

Wrapper UserDefaultsWrapper ใช้ generic T เพื่อทำงานกับชนิดข้อมูลใด ๆ ที่ UserDefaults รองรับ Getter อ่านค่าตามคีย์ setter เขียน การใช้ @UserDefaultsWrapper(key:defaultValue:) กับคุณสมบัติ theme จะเชื่อมโยงกับพื้นที่จัดเก็บโดยอัตโนมัติ — ตรรกะ UserDefaults ทั้งหมดถูกซ่อนอยู่ภายใน wrapper นี่คือตัวอย่างทั่วไปของการลดโค้ดเทมเพลตด้วย Property Wrappers

เมื่อสร้าง wrapper แบบกำหนดเอง สิ่งสำคัญคือต้องพิจารณา ประสิทธิภาพ เนื่องจาก getter และ setter ถูกเรียกทุกครั้งที่เข้าถึงคุณสมบัติ ไม่ควรวางการดำเนินการ I/O ที่หนักใน wrappedValue สำหรับการจัดเก็บข้อมูลแบบอะซิงโครนัส ควรรวม Property Wrappers กับ ObservableObject และ @Published

คำถามที่พบบ่อย

ความแตกต่างระหว่าง @State และ @StateObject คืออะไร?

@State ออกแบบมาสำหรับชนิดง่าย (String, Int, Bool) และโครงสร้าง ในขณะที่ @StateObject สำหรับชนิดอ้างอิงที่ implement ObservableObject @State จัดเก็บค่าโดยตรงใน SwiftUI, @StateObject จัดการ instance คลาสบนฮีป

สามารถใช้ @Binding โดยไม่มี @State ได้หรือไม่?

ได้ @Binding สามารถสร้างจาก @StateObject, @ObservedObject หรือ Binding อื่นโดยใช้การฉายภาพ $ Binding ยังสามารถเริ่มต้นจาก ObservableObject ผ่าน $object.$publishedProperty หรือจาก InlineBinding ผ่าน Binding.constant(value)

ควรเลือก Property Wrapper ใดสำหรับข้อมูลที่ใช้ทั่วทั้งแอปพลิเคชัน?

สำหรับข้อมูลระดับโลก ใช้ @EnvironmentObject หรือฉีด ObservableObject ผ่าน EnvironmentValues @StateObject เหมาะสำหรับวิวรากโดยส่งต่อผ่าน @ObservedObject ไปยังคอมโพเนนต์ลูก

ทำไม @ObservedObject ถึงสามารถสร้างข้อมูลใหม่ได้?

@ObservedObject ไม่ได้เป็นเจ้าของออบเจกต์ — ถ้าวิวแม่ถูกสร้างใหม่และส่ง instance ใหม่ @ObservedObject จะสลับไปที่ instance นั้น เพื่อหลีกเลี่ยงการสูญเสียสถานะ วิวที่เป็นเจ้าของควรใช้ @StateObject

สามารถสร้าง Property Wrapper สำหรับการดำเนินการแบบอะซิงโครนัสได้หรือไม่?

ได้ แต่ใช้การรวมกันของ ObservableObject กับ @Published และฟังก์ชัน async ภายในคลาสได้ง่ายกว่า Property Wrapper เป็นแบบซิงโครนัสโดยธรรมชาติ — wrappedValue จะถูกคำนวณทุกครั้งที่เข้าถึง ซึ่งไม่เหมาะสำหรับการดำเนินการที่ใช้เวลานาน

สรุป

  • Property Wrapper — โครงสร้างทางไวยากรณ์ของ Swift 5.1 สำหรับห่อหุ้มตรรกะการเข้าถึงคุณสมบัติโดยไม่ต้องทำซ้ำโค้ด
  • @State — สถานะเฉพาะที่สำหรับชนิดง่าย วาดวิวใหม่โดยอัตโนมัติเมื่อเปลี่ยน
  • @Binding — การอ้างอิงข้อมูลวิวแม่โดยไม่เป็นเจ้าของ เข้าถึงได้ผ่านการฉายภาพ $
  • @StateObject — ความเป็นเจ้าของ ObservableObject พร้อมรับประกัน instance เดียวต่อวงจรชีวิต
  • @ObservedObject — การสังเกต ObservableObject ที่ส่งมาจากคอมโพเนนต์แม่
  • @Environment — การเข้าถึงออบเจกต์สภาพแวดล้อมระบบ SwiftUI
  • wrapper แบบกำหนดเอง ถูกสร้างผ่านแอตทริบิวต์ @propertyWrapper และการ implement wrappedValue

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม