Weak Reference (รีเฟอเรนซ์แบบอ่อน) คือรีเฟอเรนซ์ไปยังออบเจ็กต์ที่ไม่เพิ่มจำนวนการนับการคงอยู่ใน ARC ตาม Apple Swift Language Guide, 2026 รีเฟอเรนซ์แบบอ่อนถูกประกาศด้วยคีย์เวิร์ด weak และเป็น optional เสมอ เมื่อออบเจ็กต์ถูกปล่อย รีเฟอเรนซ์แบบอ่อนทั้งหมดไปยังออบเจ็กต์นั้นจะถูกตั้งค่าเป็น nil โดยอัตโนมัติ ซึ่งป้องกัน พอยน์เตอร์แขวน และทำให้รีเฟอเรนซ์แบบอ่อนเป็นกลไกที่ปลอดภัยสำหรับการแตกวงจรการคงอยู่
ประเด็นสำคัญ
weak ก่อน var ชนิดเป็น optional เสมอ (?)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
มาดูไวยากรณ์สำหรับการประกาศรีเฟอเรนซ์แบบอ่อนในทั้งสองภาษาของระบบนิเวศ Apple แม้ว่ารันไทม์จะใช้ร่วมกัน แต่ไวยากรณ์แตกต่างกัน แต่ ความหมายเหมือนกัน
ใน Swift รีเฟอเรนซ์แบบอ่อนถูกประกาศด้วยคีย์เวิร์ด weak ก่อน var ชนิดต้องเป็น optional เสมอ (Type?) เนื่องจากรีเฟอเรนซ์สามารถกลายเป็น nil ได้ทุกเมื่อ ค่าคงที่ (let) ไม่สามารถเป็น weak — มีเฉพาะตัวแปร
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 คุณสมบัติแบบอ่อนถูกประกาศโดยใช้แอตทริบิวต์ __weak หรือตัวปรับแต่ง weak ในการประกาศคุณสมบัติ:
// 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 ทุกที่นำไปสู่ ความซับซ้อนที่ไม่จำเป็น และทำให้อ่านยาก มาดูสถานการณ์การใช้งานที่ถูกต้องกัน
ตัวแทน — สถานการณ์หลักสำหรับ weak ออบเจ็กต์เจ้าของ (เช่น UITableView) ถือรีเฟอเรนซ์แบบเข้มไปยังตัวเอง ในขณะที่ตัวแทน (UIViewController) ไม่ควรเป็นเจ้าของตาราง Apple SDK รับประกันว่าตัวแทนและ dataSource ทั้งหมดเป็น weak สำหรับโปรโตคอลของคุณเอง ให้ใช้ weak var delegate เสมอ
เมื่อออบเจ็กต์ลูกจำเป็นต้องอ้างอิงถึงพ่อแม่ของมัน (เช่น ChildViewController เข้าถึงผู้ประสานงาน) ให้ใช้รีเฟอเรนซ์แบบอ่อน พ่อแม่ เป็นเจ้าของลูก (strong) ลูกเฝ้าดูพ่อแม่ (weak) — วงจรการคงอยู่ถูกกำจัด
Capture list [weak self] — วิธีมาตรฐานในการหลีกเลี่ยงวงจรการคงอยู่ใน closures ที่เก็บเป็นคุณสมบัติของคลาส ถ้า self อาจถูกดีอโลเคตก่อน closure เสร็จสมบูรณ์ weak self เป็นสิ่งจำเป็น
| สถานการณ์ | Weak | Strong |
|---|---|---|
| ตัวแทน | ✅ Weak เสมอ | ❌ วงจรการคงอยู่ |
| พ่อแม่ → ลูก | ❌ ไม่จำเป็น (พ่อแม่ควรเป็นเจ้าของ) | ✅ Strong |
| ลูก → พ่อแม่ | ✅ Weak | ❌ วงจรการคงอยู่ |
| Callback แบบอะซิงก์ | ✅ [weak self] | ❌ เสี่ยงวงจรการคงอยู่ |
| การผูกแน่น (owned) | ❌ unowned | ✅ Strong |
กฎทั่วไป: ถ้าออบเจ็กต์ A เป็นเจ้าของ B (A → B strong) ดังนั้น B → A ควรเป็น weak หรือ unowned ทิศทางของรีเฟอเรนซ์แบบเข้ม ควรเป็นจากเจ้าของไปยังผู้ใต้บังคับบัญชาเสมอ
ทั้ง weak และ unowned ไม่เพิ่ม retain count แต่แตกต่างกันในพฤติกรรมหลังการดีอโลเคชันของออบเจ็กต์ การเลือกระหว่างพวกมันเป็นเรื่องของ การรับประกันอายุ
Weak: กลายเป็น nil โดยอัตโนมัติ ชนิดเป็น optional เสมอ ต้องมีการแกะก่อนใช้งาน ปลอดภัย — การเข้าถึง nil ไม่ทำให้เกิดข้อขัดข้อง
Unowned: ไม่กลายเป็น nil ชนิดเป็น non-optional ถ้าออบเจ็กต์ถูกดีอโลเคต รีเฟอเรนซ์ unowned กลายเป็นพอยน์เตอร์แขวน — การเข้าถึงมันทำให้เกิดข้อขัดข้องรันไทม์ Unowned สันนิษฐานว่าออบเจ็กต์มีอายุอย่างน้อยเท่ากับฝั่งที่อ้างอิง
เลือก weak ถ้า: ออบเจ็กต์อาจถูกดีอโลเคตได้ทุกเมื่อ (ตัวแทนหลังปิดหน้าจอ) คุณไม่ได้ควบคุมอายุของออบเจ็กต์ หรือไม่แน่ใจเกี่ยวกับการรับประกัน Weak เป็นตัวเลือกที่ปลอดภัยสากล
เลือก unowned ถ้า: ออบเจ็กต์รับประกันว่าจะไม่ถูกดีอโลเคตก่อนออบเจ็กต์ที่อ้างอิง (เช่น Customer → CreditCard ที่บัตรไม่มีอยู่โดยไม่มีลูกค้า) Unowned ให้ API แบบ non-optional โดยไม่ต้องแกะ ซึ่งสะดวกกว่าในโค้ด
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 อาจเป็นคอขวด สำหรับสถานการณ์โหลดสูง ให้ใช้ strong และจัดโครงสร้างสถาปัตยกรรมใหม่
Struct, enum, tuple — ชนิดค่าที่ไม่มีส่วนร่วมใน ARC การพยายามประกาศ weak struct ทำให้เกิดข้อผิดพลาดในการคอมไพล์ ในการเก็บรีเฟอเรนซ์แบบอ่อนไปยังชนิดค่า ให้ใช้ตัวห่อในชนิดคลาสหรือ closure
Zeroing weak ปลอดภัยต่อเธรด: ถ้าออบเจ็กต์ถูกดีอโลเคตในเธรดหนึ่ง รีเฟอเรนซ์แบบอ่อนถูกทำให้เป็นศูนย์ในทุกเธรดแบบอะตอมมิก อย่างไรก็ตาม ช่องว่างระหว่างการอ่านรีเฟอเรนซ์แบบอ่อนและการดีรีเฟอเรนซ์สามารถนำไปสู่สภาวะการแข่งขัน — ออบเจ็กต์ถูกดีอโลเคตระหว่างการได้รับรีเฟอเรนซ์แบบอ่อนและการใช้งาน วิธีแก้ไข: การจับแบบเข้มของรีเฟอเรนซ์แบบอ่อนลงในตัวแปรเฉพาะที่
// สภาวะการแข่งขันกับ 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
IBOutlet ใน Interface Builder ควรเป็น weak เพราะลำดับชั้นของวิวถือรีเฟอเรนซ์แบบเข้มไปยังวิวย่อยอยู่แล้ว การทำสำเนารีเฟอเรนซ์แบบเข้มในคอนโทรลเลอร์ไม่สร้างวงจรการคงอยู่แต่ซ้ำซ้อน รีเฟอเรนซ์แบบอ่อนไปยัง outlet เป็นคำแนะนำของ Apple แม้ว่านักพัฒนาหลายคนใช้ strong เพื่อความเรียบง่ายของโค้ด
คำถามที่พบบ่อย
ไม่ weak สามารถชี้ไปยังออบเจ็กต์ที่มีอยู่หรือ nil เท่านั้น เมื่อสร้างออบเจ็กต์ใหม่ คุณจะได้รับรีเฟอเรนซ์แบบเข้มก่อน (ผ่านตัวเริ่มต้น) และหลังจากนั้นคุณจึงสามารถกำหนดรีเฟอเรนซ์แบบอ่อน weak nil ในตอนเริ่มต้นเป็นสถานะปกติ
Weak ขึ้นอยู่กับ ARC ซึ่งจัดการเฉพาะชนิดอ้างอิง (คลาส) ชนิดค่า (struct, enum) ถูกคัดลอกเมื่อกำหนดและไม่มี retain count สำหรับความสัมพันธ์แบบอ่อนกับชนิดค่า ให้ใช้ closures หรือตัวห่อในคลาสด้วยคุณสมบัติ weak
แต่ละการเข้าถึงรีเฟอเรนซ์แบบอ่อนทำการ ค้นหาในตารางรันไทม์ ในลูปที่มีการวนซ้ำหลายล้านครั้ง สิ่งนี้อาจช้ากว่ารีเฟอเรนซ์แบบเข้ม 2–5 เท่า สำหรับเส้นทางร้อน ให้คัดลอก weak ไปยังตัวแปรเข้มเฉพาะที่ก่อนลูป
เมื่อรีเฟอเรนซ์แบบเข้มทั้งหมดไปยังออบเจ็กต์หายไป — ที่ส่วนท้ายของขอบเขต เมื่อกำหนดคุณสมบัติใหม่ หรือเมื่อปิดหน้าจอ ใน สภาพแวดล้อมแบบมัลติเธรด สิ่งนี้สามารถเกิดขึ้นระหว่างโค้ดสองบรรทัด ตรวจสอบรีเฟอเรนซ์แบบอ่อนด้วย guard let หรือ if let เสมอ
ความหมายเหมือนกัน: ทั้งคู่ให้ zeroing weak ความแตกต่าง: Swift ต้องการชนิด optional และ var Objective-C ใช้ตัวปรับแต่งคุณสมบัติ Objective-C ยังรองรับ __unsafe_unretained — รีเฟอเรนซ์แบบอ่อนที่ไม่มีการ zeroing (ความเสี่ยงของพอยน์เตอร์แขวน)
สรุป
weak var + ชนิด optional; เฉพาะชนิด class และโปรโตคอล AnyObjectเราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม