Automatic Reference Counting (ARC) คือระบบจัดการหน่วยความจำใน Swift และ Objective-C ที่นับจำนวนการอ้างอิงไปยังแต่ละออบเจ็กต์โดยอัตโนมัติและปล่อยหน่วยความจำเมื่อตัวนับถึงศูนย์ ตามข้อมูลจาก เอกสาร Apple Swift, 2026 ARC ถูกฝังอยู่ในคอมไพเลอร์และทำงานในขั้นตอนการคอมไพล์ โดยแทรกการเรียก retain/release ในตำแหน่งที่เหมาะสม แตกต่างจาก Garbage Collection ARC ไม่จำเป็นต้องมีเธรดตัวเก็บขยะแยกต่างหากและไม่สร้างการหยุดชั่วคราวระหว่างการทำงานของแอปพลิเคชัน
ประเด็นสำคัญ
ARC (Automatic Reference Counting) เป็นกลไกการจัดการหน่วยความจำที่ใช้คอมไพเลอร์ซึ่ง Apple เปิดตัวใน Xcode 4.2 (2011) สำหรับ Objective-C และ Swift สืบทอดต่อมา แตกต่างจากการจัดการหน่วยความจำด้วยตนเอง (Manual Retain-Release, MRR) ARC จะดำเนินการเรียก retain, release และ autorelease โดยอัตโนมัติโดยสมบูรณ์ โดยแทรกในขั้นตอนการคอมไพล์โดยไม่ต้องให้ผู้พัฒนาดำเนินการ
ARC ไม่ใช่ตัวเก็บขยะ มันคือการวิเคราะห์แบบคงที่พร้อมการแทรกโค้ดแบบไดนามิก: คอมไพเลอร์จะวิเคราะห์อายุของออบเจ็กต์และวาง retain/release ณ จุดที่ออบเจ็กต์ถูกสร้าง คัดลอก หรือออกจากขอบเขต ผลลัพธ์คือ การปล่อยหน่วยความจำแบบกำหนดได้: ออบเจ็กต์จะถูกลบออกทันทีเมื่อไม่มีการอ้างอิงชี้ไปยังมันอีกต่อไป โดยไม่มีความล่าช้าหรือการหยุดชั่วคราว
ตามข้อมูลจาก WWDC 2011 Session 323 การเปลี่ยนจาก MRR เป็น ARC ช่วยลดข้อบกพร่องที่เกี่ยวข้องกับหน่วยความจำในแอปพลิเคชันของ Apple ลง 70% ผู้พัฒนาหยุดการปรับสมดุล retain/release ด้วยตนเอง ซึ่งช่วยกำจัดข้อผิดพลาดประเภทการรั่วไหลและ double-free ทั้งหมด
แต่ละออบเจ็กต์ในหน่วยความจำมี ตัวนับการอ้างอิง (retain count) เมื่อสร้างออบเจ็กต์ ตัวนับจะถูกตั้งค่าเป็น 1 เมื่อการอ้างอิง strong ใหม่ชี้ไปที่ออบเจ็กต์ ตัวนับจะเพิ่มขึ้น (retain) เมื่อการอ้างอิง strong หายไป ตัวนับจะลดลง (release) เมื่อถึงศูนย์ ออบเจ็กต์จะถูกปล่อยทันที
คอมไพเลอร์ Swift ไม่ได้แทรก retain/release ทุกครั้งที่มีการกำหนดค่า — มันใช้การวิเคราะห์แบบคงที่เพื่อการปรับให้เหมาะสม ตัวอย่างเช่น ถ้ารับประกันว่าออบเจ็กต์จะไม่ถูกใช้หลังจากถูกส่งต่อ คอมไพเลอร์อาจข้าม release/retain ที่ไม่จำเป็น การปรับให้เหมาะสมนี้เรียกว่า ARC Optimization
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) initialized (retain count: 1)")
}
deinit {
print("\(name) deallocated")
}
}
func testARC() {
let p = Person(name: "Alice") // retain count = 1
let q = p // retain count = 2
// q ออกจากขอบเขต
// retain count = 1
// p ออกจากขอบเขต
// retain count = 0 → deinit
}
ตัวอย่างนี้แสดงให้เห็นว่า ARC จัดการตัวนับอย่างไร: เมื่อกำหนด q = p ตัวนับจะเพิ่มขึ้น เมื่อ q ออกจากขอบเขต ตัวนับจะลดลง เมื่อ การอ้างอิง strong สุดท้ายหายไป ดีอินิเชียไลเซอร์จะถูกเรียกทันที ไม่มีตัวเก็บขยะรอ — หน่วยความจำจะถูกปล่อยทันที
ARC และ Garbage Collection แก้ปัญหาเดียวกัน — การจัดการหน่วยความจำอัตโนมัติ — แต่ด้วยวิธีการที่แตกต่างกันโดยพื้นฐาน การเลือกระหว่างทั้งสองกำหนดสถาปัตยกรรมของภาษา: Swift (ARC) vs Java/Go (GC) มาดู ความแตกต่างหลัก กัน
| คุณลักษณะ | ARC (Swift/ObjC) | GC (Java/Go) |
|---|---|---|
| ช่วงเวลาปล่อย | กำหนดได้: ทันทีเมื่อตัวนับถึงศูนย์ | ไม่กำหนดได้: ในรอบการเก็บขยะถัดไป |
| การหยุดการทำงาน | ไม่มี (retain/release แทรกตอนคอมไพล์) | การหยุด Stop-The-World (2–200 มิลลิวินาที) |
| ค่าใช้จ่ายเพิ่ม | การเพิ่ม/ลดตัวนับทุกครั้งที่มีการอ้างอิง | การเดินกราฟออบเจ็กต์ การทำเครื่องหมาย การกวาด |
| ปัญหา | Retain Cycle (แก้ไขด้วยตนเอง) | การแตกของฮีป การรั่วไหลจากการอ้างอิงที่ถูกลืม |
| เธรดเพิ่มเติม | ไม่จำเป็น | จำเป็นต้องมีเธรดตัวเก็บขยะ |
การแลกเปลี่ยนสำคัญ: ARC ให้อายุของออบเจ็กต์ที่คาดการณ์ได้และไม่มีการหยุด แต่ผู้พัฒนาต้องเข้าใจ retain cycles และเลือก weak/unowned อย่างถูกต้อง GC ปลดปล่อยผู้พัฒนาจากข้อกังวลเหล่านี้ แต่ต้องแลกกับการหยุดที่ไม่กำหนดได้และเธรดเพิ่มเติม
ARC กำหนดตัวระบุการอ้างอิงสามประเภท แต่ละประเภทส่งผลต่อตัวนับและวงจรชีวิตของออบเจ็กต์แตกต่างกัน การเลือกตัวระบุที่ถูกต้องคือรากฐานของ การจัดการหน่วยความจำที่ปลอดภัย ใน Swift
Strong คือตัวระบุเริ่มต้น แต่ละการอ้างอิง strong จะเพิ่ม retain count ของออบเจ็กต์ขึ้น 1 ตราบใดที่ยังมีการอ้างอิง strong อยู่อย่างน้อยหนึ่งรายการ ออบเจ็กต์ก็ยังคงมีชีวิตอยู่ พรอพเพอร์ตี้ของคลาสและตัวแปรภายในทั้งหมดใน Swift เป็น strong โดยค่าเริ่มต้น การอ้างอิง strong สร้างความสัมพันธ์แบบเป็นเจ้าของ: ออบเจ็กต์ A เป็นเจ้าของออบเจ็กต์ B
Weak คือการอ้างอิงที่ไม่เพิ่ม retain count สามารถปล่อยออบเจ็กต์ได้แม้ว่าการอ้างอิง weak จะชี้ไปยังมัน หลังจากปล่อยแล้ว การอ้างอิง weak จะถูกตั้งค่าเป็น nil โดยอัตโนมัติ การอ้างอิง weak จะถูกประกาศเป็น var ด้วยชนิดที่เป็นทางเลือก (?) เสมอ ใช้เพื่อทำลาย retain cycle โดยเฉพาะในรูปแบบ delegate
Unowned คือการอ้างอิงที่ไม่เป็นเจ้าของซึ่งเหมือนกับ weak ตรงที่ไม่เพิ่ม retain count อย่างไรก็ตาม การอ้างอิง unowned จะไม่ถูกตั้งค่าเป็น nil หลังจากการปล่อย — การเข้าถึงออบเจ็กต์ที่ถูกปล่อยจะทำให้เกิดการขัดข้อง Unowned ใช้เมื่อรับประกันว่าออบเจ็กต์มีอายุอย่างน้อยเท่ากับออบเจ็กต์ที่อ้างอิง กรณีการใช้งานทั่วไปคือ คลอเชอร์ (closures) และความสัมพันธ์แบบพ่อแม่-ลูกที่มีอายุรับประกัน
class Customer {
let name: String
var card: CreditCard? // strong
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned — ไม่เป็นเจ้าของ
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card \(number) deallocated") }
}
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer และ CreditCard ถูกปล่อยทั้งคู่ — ไม่มี retain cycle
ที่นี่ CreditCard ใช้การอ้างอิง unowned ไปยัง Customer Customer เป็นเจ้าของบัตร (strong) และบัตรไม่ได้เป็นเจ้าของลูกค้า (unowned) เมื่อ Customer ถูกปล่อย ออบเจ็กต์ทั้งสองจะถูกปล่อย — ไม่มี retain cycle เกิดขึ้น ถ้า card.customer เป็น strong วงจรจะขัดขวางการปล่อย
แม้จะเป็นระบบอัตโนมัติ แต่ ARC ก็ไม่ใช่ทางออกที่ดีที่สุด ผู้พัฒนาพบปัญหาทั่วไปหลายประการที่ต้องอาศัยความเข้าใจเกี่ยวกับกลไกการจัดการหน่วยความจำภายใน
คลอเชอร์ (closures) ใน Swift จับตัวแปรภายนอกด้วยการอ้างอิง strong ถ้าคลอเชอร์ถูกกำหนดให้กับพรอพเพอร์ตี้ของคลาสและจับ self — จะเกิด retain cycle: คลาสถือคลอเชอร์ คลอเชอร์ถือ self วิธีแก้ไขคือรายการจับภาพ (capture list) ด้วย weak หรือ unowned
class NetworkManager {
var completionHandler: ((Data?) -> Void)?
var data: Data?
func fetchData() {
completionHandler = { [weak self] result in
guard let self else { return }
self.data = result
self.processResult()
}
}
func processResult() { }
}
รายการจับภาพ [weak self] สร้างการอ้างอิง weak ไปยัง self ภายในคลอเชอร์ ซึ่งทำลาย retain cycle ที่อาจเกิดขึ้น Guard let self รับประกันว่าออบเจ็กต์ยังมีชีวิตอยู่ก่อนดำเนินการโค้ด weak self เป็นแนวทางปฏิบัติมาตรฐานสำหรับคลอเชอร์แบบอะซิงโครนัสใน Swift
แม้ว่า retain/release จะเป็นการดำเนินการที่เบา แต่การเพิ่ม/ลดตัวนับบ่อยครั้งในลูปร้อนทำให้เกิดค่าใช้จ่ายเพิ่ม ใน Swift 5.9+ คอมไพเลอร์ใช้ การปรับให้เหมาะสม ซึ่งลบ retain/release ที่ซ้ำซ้อนถ้านักวิเคราะห์พิสูจน์ว่าปลอดภัย อย่างไรก็ตาม ใน Objective-C retain/release ยังคงเป็นจุดคอขวดในสถานการณ์ที่มีโหลดสูงด้วยการเรียกหลายล้านครั้งต่อวินาที
Autorelease Pool คือกลไกการปล่อยแบบเลื่อนเวลาที่ใช้ใน Objective-C และบางสถานการณ์ใน Swift ออบเจ็กต์จะถูกวางในพูลและได้รับการปล่อยเมื่อพูลถูกระบาย ในลูปที่มีออบเจ็กต์ชั่วคราวจำนวนมาก (เช่น การแยกวิเคราะห์ JSON) การสร้าง autoreleasepool แบบกำหนดเองจะช่วยลดการใช้ หน่วยความจำ สูงสุด
คำถามที่พบบ่อย
ในการจัดการด้วยตนเอง (MRR) ผู้พัฒนาเรียก retain, release และ autorelease อย่างชัดเจน ARC แทรกการเรียกเหล่านี้โดยอัตโนมัติในขั้นตอนการคอมไพล์ กำจัดความเสี่ยงของ double-free การรั่วไหลจากการลืม release และข้อผิดพลาดในการสมดุล retain/release
ARC จัดการเฉพาะออบเจ็กต์ Objective-C และคลาส Swift เท่านั้น สำหรับโครงสร้างและพอยน์เตอร์ C/C++ ARC ไม่สามารถใช้ได้ — ออบเจ็กต์เหล่านี้ถูกจัดการด้วยตนเองหรือผ่านสมาร์ทพอยน์เตอร์ C++ (shared_ptr, unique_ptr) ออบเจ็กต์ Core Foundation (CFString, CGColor) ก็ไม่อยู่ภายใต้ ARC เช่นกัน
weak — เมื่อออบเจ็กต์อาจถูกปล่อยก่อนออบเจ็กต์ที่อ้างอิง (ตัวแทน, คลอเชอร์แบบอะซิงโครนัส) unowned — เมื่อรับประกันว่าออบเจ็กต์มีอายุอย่างน้อยเท่ากับออบเจ็กต์ที่อ้างอิง (พ่อแม่-ลูกที่ลูกไม่สามารถอยู่ได้โดยไม่มีพ่อแม่) ถ้าไม่แน่ใจ ให้เลือก weak
ประเภทเอกซิสเทนเชียล (โปรโตคอลเป็นชนิด) ใน Swift ห่อหุ้มค่าในคอนเทนเนอร์พิเศษ (existential container) ซึ่งเพิ่มจำนวน retain/release ที่ขอบเขตของโปรโตคอล ใน Swift 5.7+ ชนิดผลลัพธ์ทึบแสง (opaque result types) และพารามิเตอร์ some ลดค่าใช้จ่ายโดยกำจัดคอนเทนเนอร์
ไม่มี API โดยตรงสำหรับอ่าน retain count ใน Swift — ถือเป็นรายละเอียดการนำไปใช้ สำหรับการวินิจฉัย ให้ใช้ Instruments (Allocations, Leaks) หรือตัวดีบักหน่วยความจำใน Xcode เครื่องมือเหล่านี้แสดงจำนวนอินสแตนซ์ของคลาสที่ยังมีชีวิตและห่วงโซ่การยึดถือ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม