Weak Reference — คืออะไร ไวยากรณ์ และการใช้ในการพัฒนาแอปมือถือ

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

Weak Reference (รีเฟอเรนซ์แบบอ่อน) คือรีเฟอเรนซ์ไปยังออบเจ็กต์ที่ไม่เพิ่มจำนวนการนับการคงอยู่ใน ARC ตาม Apple Swift Language Guide, 2026 รีเฟอเรนซ์แบบอ่อนถูกประกาศด้วยคีย์เวิร์ด weak และเป็น optional เสมอ เมื่อออบเจ็กต์ถูกปล่อย รีเฟอเรนซ์แบบอ่อนทั้งหมดไปยังออบเจ็กต์นั้นจะถูกตั้งค่าเป็น nil โดยอัตโนมัติ ซึ่งป้องกัน พอยน์เตอร์แขวน และทำให้รีเฟอเรนซ์แบบอ่อนเป็นกลไกที่ปลอดภัยสำหรับการแตกวงจรการคงอยู่

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

  • Weak Reference — รีเฟอเรนซ์ที่ไม่ส่งผลต่อ retain count ของออบเจ็กต์ กลายเป็น nil เมื่อออบเจ็กต์ถูกปล่อย
  • การประกาศ — คีย์เวิร์ด weak ก่อน var ชนิดเป็น optional เสมอ (?)
  • การใช้งาน — ตัวแทน, closure, ความสัมพันธ์พ่อ-แม่เพื่อแตกวงจรการคงอยู่
  • ความปลอดภัย — การตั้งค่าเป็น nil โดยอัตโนมัติหลังออบเจ็กต์ถูกดีอโลเคต (zeroing weak)
  • ความแตกต่างจาก unowned — weak กลายเป็น nil และปลอดภัย unowned ไม่กลายเป็น nil และต้องการการรับประกันอายุ

Weak Reference คืออะไร?

Weak Reference คือรีเฟอเรนซ์แบบไม่เป็นเจ้าของไปยังออบเจ็กต์ใน ARC (Automatic Reference Counting) ต่างจากรีเฟอเรนซ์แบบเข้มที่เพิ่ม retain count ของออบเจ็กต์และรับประกันอายุของมัน รีเฟอเรนซ์แบบอ่อนอนุญาตให้ออบเจ็กต์ถูกปล่อยได้แม้ว่าจะยังถูกอ้างอิงอยู่ หลังจากดีอโลเคชัน รีเฟอเรนซ์แบบอ่อนจะถูกตั้งค่าเป็น nil โดยอัตโนมัติ — สิ่งนี้เรียกว่า zeroing weak

Zeroing weak เป็นคุณสมบัติสำคัญของรันไทม์ Swift และ Objective-C เมื่อจำนวนการนับรีเฟอเรนซ์ของออบเจ็กต์ถึงศูนย์และออบเจ็กต์ถูกดีอโลเคต รันไทม์จะวนผ่านรีเฟอเรนซ์แบบอ่อนทั้งหมดไปยังออบเจ็กต์นี้ (ที่เก็บในตารางอ่อนพิเศษ) และตั้งค่าเป็น nil สิ่งนี้รับประกันว่าการเข้าถึงหน่วยความจำที่ถูกปล่อย (use-after-free) เป็นไปไม่ได้ผ่านรีเฟอเรนซ์แบบอ่อน — การอ่านใด ๆ จะคืนค่า nil

ตาม Apple WWDC 2012 Session 406 รีเฟอเรนซ์แบบอ่อน zeroing ได้กำจัดบั๊กที่ทำให้เกิดข้อขัดข้องทั้งคลาสที่เกี่ยวข้องกับพอยน์เตอร์แขวน (dangling pointers) ซึ่งพบได้ทั่วไปในการจัดการหน่วยความจำด้วยตนเอง (MRR) ใน MRR รีเฟอเรนซ์แบบอ่อนมีอยู่เพียงในรูปแบบ __unsafe_unretained — พวกมันไม่ถูกทำให้เป็นศูนย์ และการเข้าถึงออบเจ็กต์ที่ถูกดีอโลเคตทำให้เกิด EXC_BAD_ACCESS

ไวยากรณ์ weak ใน Swift และ Objective-C

มาดูไวยากรณ์สำหรับการประกาศรีเฟอเรนซ์แบบอ่อนในทั้งสองภาษาของระบบนิเวศ Apple แม้ว่ารันไทม์จะใช้ร่วมกัน แต่ไวยากรณ์แตกต่างกัน แต่ ความหมายเหมือนกัน

Swift

ใน Swift รีเฟอเรนซ์แบบอ่อนถูกประกาศด้วยคีย์เวิร์ด weak ก่อน var ชนิดต้องเป็น optional เสมอ (Type?) เนื่องจากรีเฟอเรนซ์สามารถกลายเป็น nil ได้ทุกเมื่อ ค่าคงที่ (let) ไม่สามารถเป็น weak — มีเฉพาะตัวแปร

swift
class ViewController: UIViewController {
    // weak properties: only var, only optional
    weak var delegate: ViewControllerDelegate?
    weak var parentView: UIView?

    weak var completionHandler: ((Bool) -> Void)?  // ⚠️ closures ไม่เก็บ weak
    // ⬆️ ข้อผิดพลาด: weak ใช้ได้เฉพาะกับชนิด class ไม่ใช่ closures
}

สำคัญ: weak ใช้ได้เฉพาะกับอินสแตนซ์ของคลาส (ชนิด class), AnyObject และโปรโตคอลที่สืบทอดจาก AnyObject Struct, enum และ closure ไม่สามารถเป็น weak — เป็นชนิดค่าและไม่มีส่วนร่วมใน ARC

Objective-C

ใน Objective-C คุณสมบัติแบบอ่อนถูกประกาศโดยใช้แอตทริบิวต์ __weak หรือตัวปรับแต่ง weak ในการประกาศคุณสมบัติ:

objective-c
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end

// ตัวแปร weak เฉพาะที่
__weak MyObject *weakRef = someStrongObject;

รันไทม์ Objective-C ยังให้ zeroing weak แต่เพิ่มเติมบล็อกการใช้ weak กับโครงสร้าง C และออบเจ็กต์ Core Foundation บางส่วน สำหรับสิ่งเหล่านี้ ใช้ __unsafe_unretained — โดยไม่มีการ zeroing

เมื่อใดควรใช้รีเฟอเรนซ์แบบอ่อน

รีเฟอเรนซ์แบบอ่อนไม่ใช่วิธีแก้แบบสากล แต่เป็นเครื่องมือสำหรับสถานการณ์เฉพาะ การใช้ weak ทุกที่นำไปสู่ ความซับซ้อนที่ไม่จำเป็น และทำให้อ่านยาก มาดูสถานการณ์การใช้งานที่ถูกต้องกัน

ตัวแทน (รูปแบบ Delegate)

ตัวแทน — สถานการณ์หลักสำหรับ weak ออบเจ็กต์เจ้าของ (เช่น UITableView) ถือรีเฟอเรนซ์แบบเข้มไปยังตัวเอง ในขณะที่ตัวแทน (UIViewController) ไม่ควรเป็นเจ้าของตาราง Apple SDK รับประกันว่าตัวแทนและ dataSource ทั้งหมดเป็น weak สำหรับโปรโตคอลของคุณเอง ให้ใช้ weak var delegate เสมอ

พ่อ-แม่ที่มีการอ้างอิงกลับ

เมื่อออบเจ็กต์ลูกจำเป็นต้องอ้างอิงถึงพ่อแม่ของมัน (เช่น ChildViewController เข้าถึงผู้ประสานงาน) ให้ใช้รีเฟอเรนซ์แบบอ่อน พ่อแม่ เป็นเจ้าของลูก (strong) ลูกเฝ้าดูพ่อแม่ (weak) — วงจรการคงอยู่ถูกกำจัด

Closure แบบอะซิงโครนัส

Capture list [weak self] — วิธีมาตรฐานในการหลีกเลี่ยงวงจรการคงอยู่ใน closures ที่เก็บเป็นคุณสมบัติของคลาส ถ้า self อาจถูกดีอโลเคตก่อน closure เสร็จสมบูรณ์ weak self เป็นสิ่งจำเป็น

สถานการณ์WeakStrong
ตัวแทน✅ Weak เสมอ❌ วงจรการคงอยู่
พ่อแม่ → ลูก❌ ไม่จำเป็น (พ่อแม่ควรเป็นเจ้าของ)✅ Strong
ลูก → พ่อแม่✅ Weak❌ วงจรการคงอยู่
Callback แบบอะซิงก์✅ [weak self]❌ เสี่ยงวงจรการคงอยู่
การผูกแน่น (owned)❌ unowned✅ Strong

กฎทั่วไป: ถ้าออบเจ็กต์ A เป็นเจ้าของ B (A → B strong) ดังนั้น B → A ควรเป็น weak หรือ unowned ทิศทางของรีเฟอเรนซ์แบบเข้ม ควรเป็นจากเจ้าของไปยังผู้ใต้บังคับบัญชาเสมอ

Weak vs Unowned: การเปรียบเทียบและสถานการณ์

ทั้ง weak และ unowned ไม่เพิ่ม retain count แต่แตกต่างกันในพฤติกรรมหลังการดีอโลเคชันของออบเจ็กต์ การเลือกระหว่างพวกมันเป็นเรื่องของ การรับประกันอายุ

ความแตกต่าง

Weak: กลายเป็น nil โดยอัตโนมัติ ชนิดเป็น optional เสมอ ต้องมีการแกะก่อนใช้งาน ปลอดภัย — การเข้าถึง nil ไม่ทำให้เกิดข้อขัดข้อง

Unowned: ไม่กลายเป็น nil ชนิดเป็น non-optional ถ้าออบเจ็กต์ถูกดีอโลเคต รีเฟอเรนซ์ unowned กลายเป็นพอยน์เตอร์แขวน — การเข้าถึงมันทำให้เกิดข้อขัดข้องรันไทม์ Unowned สันนิษฐานว่าออบเจ็กต์มีอายุอย่างน้อยเท่ากับฝั่งที่อ้างอิง

เมื่อใดเลือก weak

เลือก weak ถ้า: ออบเจ็กต์อาจถูกดีอโลเคตได้ทุกเมื่อ (ตัวแทนหลังปิดหน้าจอ) คุณไม่ได้ควบคุมอายุของออบเจ็กต์ หรือไม่แน่ใจเกี่ยวกับการรับประกัน Weak เป็นตัวเลือกที่ปลอดภัยสากล

เมื่อใดเลือก unowned

เลือก unowned ถ้า: ออบเจ็กต์รับประกันว่าจะไม่ถูกดีอโลเคตก่อนออบเจ็กต์ที่อ้างอิง (เช่น Customer → CreditCard ที่บัตรไม่มีอยู่โดยไม่มีลูกค้า) Unowned ให้ API แบบ non-optional โดยไม่ต้องแกะ ซึ่งสะดวกกว่าในโค้ด

swift
class Order {
    let id: Int
    var items: [Item] = []

    init(id: Int) { self.id = id }

    // ความสัมพันธ์แบบเข้ม: Order เป็นเจ้าของ Item
    func addItem(name: String) {
        let item = Item(name: name, order: self)
        items.append(item)
    }
}

class Item {
    let name: String
    unowned let order: Order          // ✅ unowned — Item ไม่มีชีวิตอยู่โดยไม่มี Order

    init(name: String, order: Order) {
        self.name = name
        self.order = order
    }
}

// ตัวอย่างกับ weak: ตัวแทนที่ไม่มีการรับประกันอายุ
protocol NetworkServiceDelegate: AnyObject {
    func didReceiveResponse(data: Data)
}

class NetworkService {
    weak var delegate: NetworkServiceDelegate?  // ✅ weak — ตัวแทนอาจหายไป
}

ในตัวอย่าง Item ใช้ unowned เพราะรายการคำสั่งซื้อไม่สามารถมีอยู่ได้โดยไม่มีคำสั่งซื้อเอง — การรับประกันอายุแข็งแกร่ง NetworkService ใช้ weak เพราะตัวแทน (เช่น ViewController) อาจถูกปิดและดีอโลเคตได้ทุกเมื่อ

ข้อจำกัดของรีเฟอเรนซ์แบบอ่อนและข้อควรระวัง

รีเฟอเรนซ์แบบอ่อนเป็นเครื่องมือที่มีประสิทธิภาพ แต่มีข้อจำกัดที่สำคัญต้องเข้าใจเพื่อการใช้งานที่ถูกต้องในการพัฒนา iOS

ประสิทธิภาพของ weak

รีเฟอเรนซ์แบบอ่อนช้ากว่าแบบเข้ม: ในทุกการเข้าถึง รันไทม์ตรวจสอบว่าออบเจ็กต์ถูกดีอโลเคตหรือไม่ (ค้นหาในตารางอ่อน) ในสถานการณ์ส่วนใหญ่ ความแตกต่างไม่รู้สึกได้ แต่ในลูปร้อนที่มีการเข้าถึงหลายล้านครั้ง weak อาจเป็นคอขวด สำหรับสถานการณ์โหลดสูง ให้ใช้ strong และจัดโครงสร้างสถาปัตยกรรมใหม่

Weak ใช้ไม่ได้กับชนิดค่า

Struct, enum, tuple — ชนิดค่าที่ไม่มีส่วนร่วมใน ARC การพยายามประกาศ weak struct ทำให้เกิดข้อผิดพลาดในการคอมไพล์ ในการเก็บรีเฟอเรนซ์แบบอ่อนไปยังชนิดค่า ให้ใช้ตัวห่อในชนิดคลาสหรือ closure

Weak ในมัลติเธรดดิ้ง

Zeroing weak ปลอดภัยต่อเธรด: ถ้าออบเจ็กต์ถูกดีอโลเคตในเธรดหนึ่ง รีเฟอเรนซ์แบบอ่อนถูกทำให้เป็นศูนย์ในทุกเธรดแบบอะตอมมิก อย่างไรก็ตาม ช่องว่างระหว่างการอ่านรีเฟอเรนซ์แบบอ่อนและการดีรีเฟอเรนซ์สามารถนำไปสู่สภาวะการแข่งขัน — ออบเจ็กต์ถูกดีอโลเคตระหว่างการได้รับรีเฟอเรนซ์แบบอ่อนและการใช้งาน วิธีแก้ไข: การจับแบบเข้มของรีเฟอเรนซ์แบบอ่อนลงในตัวแปรเฉพาะที่

swift
// สภาวะการแข่งขันกับ weak ในมัลติเธรดดิ้ง
func performAsync() {
    weak var weakSelf = self
    queue.async {
        // ⚠️ weakSelf อาจเป็น nil ระหว่างการตรวจสอบและการใช้งาน
        if weakSelf != nil {
            weakSelf!.doSomething()  // CRASH ถ้ากลายเป็น nil
        }
    }
}

// ✅ การแก้ไข: การจับแบบเข้มระหว่างการใช้งาน
func performAsyncSafe() {
    queue.async { [weak self] in
        guard let strongSelf = self else { return }
        strongSelf.doSomething()  // strongSelf — รีเฟอเรนซ์เข้มเฉพาะที่
    }
}

ในเวอร์ชันที่ปลอดภัย weak self ถูกจับ จากนั้นแกะทันทีเป็นตัวแปรเข้มเฉพาะที่ strongSelf ถ้า self ยังมีชีวิต มันจะคงอยู่ตลอดระยะเวลาของบล็อก ถ้าไม่ guard จะทำงานและโค้ดจะไม่ถูกดำเนินการ สำนวนนี้เป็นรูปแบบมาตรฐานสำหรับ closure แบบอะซิงโครนัสใน Swift

UIView และ Weak Outlet

IBOutlet ใน Interface Builder ควรเป็น weak เพราะลำดับชั้นของวิวถือรีเฟอเรนซ์แบบเข้มไปยังวิวย่อยอยู่แล้ว การทำสำเนารีเฟอเรนซ์แบบเข้มในคอนโทรลเลอร์ไม่สร้างวงจรการคงอยู่แต่ซ้ำซ้อน รีเฟอเรนซ์แบบอ่อนไปยัง outlet เป็นคำแนะนำของ Apple แม้ว่านักพัฒนาหลายคนใช้ strong เพื่อความเรียบง่ายของโค้ด

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

รีเฟอเรนซ์แบบอ่อนสามารถชี้ไปยังออบเจ็กต์ที่ยังไม่ได้ถูกสร้างได้หรือไม่?

ไม่ weak สามารถชี้ไปยังออบเจ็กต์ที่มีอยู่หรือ nil เท่านั้น เมื่อสร้างออบเจ็กต์ใหม่ คุณจะได้รับรีเฟอเรนซ์แบบเข้มก่อน (ผ่านตัวเริ่มต้น) และหลังจากนั้นคุณจึงสามารถกำหนดรีเฟอเรนซ์แบบอ่อน weak nil ในตอนเริ่มต้นเป็นสถานะปกติ

ทำไม weak ทำงานเฉพาะกับชนิด class?

Weak ขึ้นอยู่กับ ARC ซึ่งจัดการเฉพาะชนิดอ้างอิง (คลาส) ชนิดค่า (struct, enum) ถูกคัดลอกเมื่อกำหนดและไม่มี retain count สำหรับความสัมพันธ์แบบอ่อนกับชนิดค่า ให้ใช้ closures หรือตัวห่อในคลาสด้วยคุณสมบัติ weak

Weak ส่งผลต่อประสิทธิภาพในลูปอย่างไร?

แต่ละการเข้าถึงรีเฟอเรนซ์แบบอ่อนทำการ ค้นหาในตารางรันไทม์ ในลูปที่มีการวนซ้ำหลายล้านครั้ง สิ่งนี้อาจช้ากว่ารีเฟอเรนซ์แบบเข้ม 2–5 เท่า สำหรับเส้นทางร้อน ให้คัดลอก weak ไปยังตัวแปรเข้มเฉพาะที่ก่อนลูป

เมื่อใดที่รีเฟอเรนซ์แบบอ่อนสามารถกลายเป็น nil โดยไม่คาดคิด?

เมื่อรีเฟอเรนซ์แบบเข้มทั้งหมดไปยังออบเจ็กต์หายไป — ที่ส่วนท้ายของขอบเขต เมื่อกำหนดคุณสมบัติใหม่ หรือเมื่อปิดหน้าจอ ใน สภาพแวดล้อมแบบมัลติเธรด สิ่งนี้สามารถเกิดขึ้นระหว่างโค้ดสองบรรทัด ตรวจสอบรีเฟอเรนซ์แบบอ่อนด้วย guard let หรือ if let เสมอ

Weak แตกต่างจาก __weak ใน Objective-C อย่างไร?

ความหมายเหมือนกัน: ทั้งคู่ให้ zeroing weak ความแตกต่าง: Swift ต้องการชนิด optional และ var Objective-C ใช้ตัวปรับแต่งคุณสมบัติ Objective-C ยังรองรับ __unsafe_unretained — รีเฟอเรนซ์แบบอ่อนที่ไม่มีการ zeroing (ความเสี่ยงของพอยน์เตอร์แขวน)

สรุป

  • Weak Reference — รีเฟอเรนซ์แบบไม่เป็นเจ้าของที่ไม่เพิ่ม retain count และถูกทำให้เป็นศูนย์โดยอัตโนมัติเมื่อดีอโลเคต
  • ไวยากรณ์weak var + ชนิด optional; เฉพาะชนิด class และโปรโตคอล AnyObject
  • Zeroing weak — รันไทม์ทำให้รีเฟอเรนซ์แบบอ่อนทั้งหมดไปยังออบเจ็กต์ที่ถูกดีอโลเคตเป็นศูนย์ ป้องกันพอยน์เตอร์แขวน
  • สถานการณ์ — ตัวแทน, พ่อ-แม่ที่มีการอ้างอิงกลับ, closure แบบอะซิงโครนัส ([weak self])
  • Weak vs Unowned — weak กลายเป็น nil (ปลอดภัย), unowned ไม่กลายเป็น nil (เสี่ยงข้อขัดข้อง แต่ไม่ใช่ optional)
  • ประสิทธิภาพ — weak ช้ากว่า strong เนื่องจากการค้นหาในตารางรันไทม์; สำหรับเส้นทางร้อน ให้คัดลอกไปยัง strong
  • คำแนะนำ — หากไม่แน่ใจเกี่ยวกับการรับประกันอายุ ให้เลือก weak

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

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

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

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