ARC: คืออะไร หลักการทำงานของ Automatic Reference Counting ใน iOS

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

Automatic Reference Counting (ARC) คือระบบจัดการหน่วยความจำใน Swift และ Objective-C ที่นับจำนวนการอ้างอิงไปยังแต่ละออบเจ็กต์โดยอัตโนมัติและปล่อยหน่วยความจำเมื่อตัวนับถึงศูนย์ ตามข้อมูลจาก เอกสาร Apple Swift, 2026 ARC ถูกฝังอยู่ในคอมไพเลอร์และทำงานในขั้นตอนการคอมไพล์ โดยแทรกการเรียก retain/release ในตำแหน่งที่เหมาะสม แตกต่างจาก Garbage Collection ARC ไม่จำเป็นต้องมีเธรดตัวเก็บขยะแยกต่างหากและไม่สร้างการหยุดชั่วคราวระหว่างการทำงานของแอปพลิเคชัน

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

  • ARC — Automatic Reference Counting ระบบจัดการหน่วยความจำที่ใช้คอมไพเลอร์ใน Swift และ Objective-C
  • หลักการทำงาน — แต่ละออบเจ็กต์มีตัวนับการอ้างอิง (retain count) เมื่อถึงศูนย์ ออบเจ็กต์จะถูกปล่อยทันที
  • ตัวระบุ — strong, weak และ unowned กำหนดว่าการอ้างอิงส่งผลต่อตัวนับและวงจรชีวิตของออบเจ็กต์อย่างไร
  • ความแตกต่างจาก GC — ARC ทำงานแบบกำหนดได้ในขั้นตอนการคอมไพล์ โดยไม่มีการหยุด Stop-The-World หรือเธรดตัวเก็บขยะพื้นหลัง
  • Retain Cycle — ปัญหาหลักของ ARC: ถ้าออบเจ็กต์สองตัวอ้างอิงซึ่งกันและกันผ่าน strong ตัวนับของพวกมันจะไม่มีวันถึงศูนย์

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 ทั้งหมด

Automatic Reference Counting ทำงานอย่างไร

แต่ละออบเจ็กต์ในหน่วยความจำมี ตัวนับการอ้างอิง (retain count) เมื่อสร้างออบเจ็กต์ ตัวนับจะถูกตั้งค่าเป็น 1 เมื่อการอ้างอิง strong ใหม่ชี้ไปที่ออบเจ็กต์ ตัวนับจะเพิ่มขึ้น (retain) เมื่อการอ้างอิง strong หายไป ตัวนับจะลดลง (release) เมื่อถึงศูนย์ ออบเจ็กต์จะถูกปล่อยทันที

คอมไพเลอร์ Swift ไม่ได้แทรก retain/release ทุกครั้งที่มีการกำหนดค่า — มันใช้การวิเคราะห์แบบคงที่เพื่อการปรับให้เหมาะสม ตัวอย่างเช่น ถ้ารับประกันว่าออบเจ็กต์จะไม่ถูกใช้หลังจากถูกส่งต่อ คอมไพเลอร์อาจข้าม release/retain ที่ไม่จำเป็น การปรับให้เหมาะสมนี้เรียกว่า ARC Optimization

swift
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 vs Garbage Collection: ความแตกต่างหลัก

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 ปลดปล่อยผู้พัฒนาจากข้อกังวลเหล่านี้ แต่ต้องแลกกับการหยุดที่ไม่กำหนดได้และเธรดเพิ่มเติม

Strong, Weak และ Unowned: ตัวระบุการอ้างอิงใน ARC

ARC กำหนดตัวระบุการอ้างอิงสามประเภท แต่ละประเภทส่งผลต่อตัวนับและวงจรชีวิตของออบเจ็กต์แตกต่างกัน การเลือกตัวระบุที่ถูกต้องคือรากฐานของ การจัดการหน่วยความจำที่ปลอดภัย ใน Swift

Strong

Strong คือตัวระบุเริ่มต้น แต่ละการอ้างอิง strong จะเพิ่ม retain count ของออบเจ็กต์ขึ้น 1 ตราบใดที่ยังมีการอ้างอิง strong อยู่อย่างน้อยหนึ่งรายการ ออบเจ็กต์ก็ยังคงมีชีวิตอยู่ พรอพเพอร์ตี้ของคลาสและตัวแปรภายในทั้งหมดใน Swift เป็น strong โดยค่าเริ่มต้น การอ้างอิง strong สร้างความสัมพันธ์แบบเป็นเจ้าของ: ออบเจ็กต์ A เป็นเจ้าของออบเจ็กต์ B

Weak

Weak คือการอ้างอิงที่ไม่เพิ่ม retain count สามารถปล่อยออบเจ็กต์ได้แม้ว่าการอ้างอิง weak จะชี้ไปยังมัน หลังจากปล่อยแล้ว การอ้างอิง weak จะถูกตั้งค่าเป็น nil โดยอัตโนมัติ การอ้างอิง weak จะถูกประกาศเป็น var ด้วยชนิดที่เป็นทางเลือก (?) เสมอ ใช้เพื่อทำลาย retain cycle โดยเฉพาะในรูปแบบ delegate

Unowned

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

swift
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 และวิธีแก้ไข

แม้จะเป็นระบบอัตโนมัติ แต่ ARC ก็ไม่ใช่ทางออกที่ดีที่สุด ผู้พัฒนาพบปัญหาทั่วไปหลายประการที่ต้องอาศัยความเข้าใจเกี่ยวกับกลไกการจัดการหน่วยความจำภายใน

Retain Cycle ในคลอเชอร์

คลอเชอร์ (closures) ใน Swift จับตัวแปรภายนอกด้วยการอ้างอิง strong ถ้าคลอเชอร์ถูกกำหนดให้กับพรอพเพอร์ตี้ของคลาสและจับ self — จะเกิด retain cycle: คลาสถือคลอเชอร์ คลอเชอร์ถือ self วิธีแก้ไขคือรายการจับภาพ (capture list) ด้วย weak หรือ unowned

swift
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

แม้ว่า retain/release จะเป็นการดำเนินการที่เบา แต่การเพิ่ม/ลดตัวนับบ่อยครั้งในลูปร้อนทำให้เกิดค่าใช้จ่ายเพิ่ม ใน Swift 5.9+ คอมไพเลอร์ใช้ การปรับให้เหมาะสม ซึ่งลบ retain/release ที่ซ้ำซ้อนถ้านักวิเคราะห์พิสูจน์ว่าปลอดภัย อย่างไรก็ตาม ใน Objective-C retain/release ยังคงเป็นจุดคอขวดในสถานการณ์ที่มีโหลดสูงด้วยการเรียกหลายล้านครั้งต่อวินาที

Autorelease Pool

Autorelease Pool คือกลไกการปล่อยแบบเลื่อนเวลาที่ใช้ใน Objective-C และบางสถานการณ์ใน Swift ออบเจ็กต์จะถูกวางในพูลและได้รับการปล่อยเมื่อพูลถูกระบาย ในลูปที่มีออบเจ็กต์ชั่วคราวจำนวนมาก (เช่น การแยกวิเคราะห์ JSON) การสร้าง autoreleasepool แบบกำหนดเองจะช่วยลดการใช้ หน่วยความจำ สูงสุด

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

ARC แตกต่างจากการจัดการหน่วยความจำด้วยตนเอง (MRR) อย่างไร?

ในการจัดการด้วยตนเอง (MRR) ผู้พัฒนาเรียก retain, release และ autorelease อย่างชัดเจน ARC แทรกการเรียกเหล่านี้โดยอัตโนมัติในขั้นตอนการคอมไพล์ กำจัดความเสี่ยงของ double-free การรั่วไหลจากการลืม release และข้อผิดพลาดในการสมดุล retain/release

ARC สามารถทำงานกับโค้ด C/C++ ได้หรือไม่?

ARC จัดการเฉพาะออบเจ็กต์ Objective-C และคลาส Swift เท่านั้น สำหรับโครงสร้างและพอยน์เตอร์ C/C++ ARC ไม่สามารถใช้ได้ — ออบเจ็กต์เหล่านี้ถูกจัดการด้วยตนเองหรือผ่านสมาร์ทพอยน์เตอร์ C++ (shared_ptr, unique_ptr) ออบเจ็กต์ Core Foundation (CFString, CGColor) ก็ไม่อยู่ภายใต้ ARC เช่นกัน

เมื่อใดควรใช้ weak และเมื่อใดควรใช้ unowned?

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

ประเภทเอกซิสเทนเชียล (Existential Types) คืออะไรและส่งผลต่อ ARC อย่างไร?

ประเภทเอกซิสเทนเชียล (โปรโตคอลเป็นชนิด) ใน Swift ห่อหุ้มค่าในคอนเทนเนอร์พิเศษ (existential container) ซึ่งเพิ่มจำนวน retain/release ที่ขอบเขตของโปรโตคอล ใน Swift 5.7+ ชนิดผลลัพธ์ทึบแสง (opaque result types) และพารามิเตอร์ some ลดค่าใช้จ่ายโดยกำจัดคอนเทนเนอร์

จะตรวจสอบ retain count ใน Swift ได้อย่างไร?

ไม่มี API โดยตรงสำหรับอ่าน retain count ใน Swift — ถือเป็นรายละเอียดการนำไปใช้ สำหรับการวินิจฉัย ให้ใช้ Instruments (Allocations, Leaks) หรือตัวดีบักหน่วยความจำใน Xcode เครื่องมือเหล่านี้แสดงจำนวนอินสแตนซ์ของคลาสที่ยังมีชีวิตและห่วงโซ่การยึดถือ

สรุป

  • ARC — ระบบจัดการหน่วยความจำที่ใช้คอมไพเลอร์สำหรับ Swift และ Objective-C ที่ทำงานผ่านการนับการอ้างอิง
  • หลักการ — แต่ละออบเจ็กต์มี retain count เมื่อถึงศูนย์ ออบเจ็กต์จะถูกปล่อยทันทีและแบบกำหนดได้
  • ความแตกต่างจาก GC — ARC ทำงานโดยไม่มีเธรดพื้นหลังหรือการหยุด Stop-The-World แต่ต้องควบคุม retain cycles
  • Strong — เพิ่มตัวนับ weak และ unowned ไม่เพิ่ม แต่ unowned ไม่เป็น nil เมื่อปล่อย
  • คลอเชอร์ — สาเหตุหลักของ retain cycles ใน Swift รายการจับภาพ [weak self] เป็นวิธีแก้ปัญหามาตรฐาน
  • Autorelease Pool — กลไกการปล่อยแบบเลื่อนเวลาสำหรับออบเจ็กต์ชั่วคราวในลูปและสถานการณ์กำหนดเอง
  • การวินิจฉัย — Xcode Memory Debugger, Instruments และ LeakCanary (ผ่านบริดจ์ ObjC) สำหรับค้นหาปัญหา

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

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

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

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