iOS Runtime: nó là gì, môi trường thực thi ứng dụng trên iPhone

Tác giả: IT Sectr Đã đăng: 2026-05-17 Thời gian đọc: 12 phút

iOS Runtime là môi trường thực thi ứng dụng trên hệ điều hành Apple iOS, bao gồm Objective-C Runtime, Swift Runtime, các framework Cocoa Touch và cơ chế quản lý bộ nhớ thông qua Automatic Reference Counting (ARC). iOS Runtime chịu trách nhiệm về liên kết phương thức động (message passing), tải lớp, quản lý bộ nhớ và tương tác với phần cứng thông qua các framework iOS. Theo Tài liệu Nhà phát triển Apple, hiểu runtime là cần thiết để tối ưu hóa hiệu suất, gỡ lỗi và phát triển ứng dụng iOS ổn định.

Những điểm chính

  • iOS Runtime — môi trường thực thi ứng dụng bao gồm Objective-C Runtime, Swift Runtime và Cocoa Touch
  • Objective-C Runtime — liên kết phương thức động thông qua message passing (objc_msgSend)
  • Swift Runtime — điều phối tĩnh với tối ưu hóa thông qua value types và generics
  • ARC (Automatic Reference Counting) — quản lý bộ nhớ tự động tại thời điểm biên dịch
  • dyld — trình tải động tải các framework và thư viện khi khởi động ứng dụng

iOS Runtime là gì?

iOS Runtime là một tập hợp các thành phần hệ thống đảm bảo thực thi ứng dụng trên các thiết bị Apple chạy iOS. Nó bao gồm Objective-C Runtime (thư viện libobjc.A.dylib), Swift Runtime (libswiftCore.dylib), Core Foundation, các framework Cocoa Touch (UIKit, Foundation), trình tải động dyld và môi trường thực thi để quản lý bộ nhớ, luồng và giao tiếp liên tiến trình.

Về mặt kiến trúc, iOS Runtime hoạt động ở ba cấp độ. Ở cấp thấp nhất — định dạng nhị phân Mach-O và dyld, tải tệp thực thi và thư viện. Cấp trung — Objective-C Runtime và Swift Runtime, chịu trách nhiệm điều phối phương thức và quản lý đối tượng. Cấp cao nhất — các framework Cocoa Touch (UIKit, Foundation, Core Data, Metal), cung cấp API cho nhà phát triển.

Hiểu iOS Runtime cho phép nhà phát triển giải quyết các vấn đề phức tạp: swizzling phương thức (Method Swizzling) cho thử nghiệm A/B và phân tích, tải lớp động, tối ưu hóa bộ nhớ thông qua hiểu ARC, gỡ lỗi retain cycles và rò rỉ bộ nhớ, tối ưu hóa thời gian khởi chạy ứng dụng thông qua dyld. Nếu không có kiến thức runtime, việc lập hồ sơ và tối ưu hóa ở cấp hệ thống là không thể.

Các thành phần của iOS Runtime

Thành phầnThư việnMục đích
Objective-C Runtimelibobjc.A.dylibMessage passing, lớp động, swizzling
Swift RuntimelibswiftCore.dylibValue types, generics, protocol witnesses
Core FoundationCoreFoundation.frameworkCFType, toll-free bridging
dylddyld (usr/lib/dyld)Tải Mach-O, liên kết thư viện
libSystemlibSystem.B.dylibPOSIX threads, libc, libdispatch (GCD)

Định dạng Mach-O

Các ứng dụng cho iOS được biên dịch sang định dạng Mach-O (Mach Object). Tệp Mach-O chứa tiêu đề, lệnh tải và các phân đoạn: __TEXT (mã, hằng số), __DATA (biến toàn cục, siêu dữ liệu Objective-C), __LINKEDIT (ký hiệu, bảng di dời). dyld phân tích Mach-O và tải các phụ thuộc trước khi thực thi lệnh đầu tiên.

Objective-C Runtime: Message Passing và Điều phối Động

Objective-C Runtime là phần mạnh nhất của iOS Runtime. Không giống C++ với ràng buộc sớm (early binding), Objective-C sử dụng ràng buộc muộn (late binding) thông qua message passing. Một lệnh gọi phương thức [receiver message] không được biên dịch thành lệnh gọi hàm trực tiếp, mà thành objc_msgSend(receiver, @selector(message)), lệnh này tự động tìm cách triển khai phương thức trong lớp của đối tượng.

Mỗi đối tượng Objective-C lưu trữ một con trỏ isa trỏ đến lớp của nó. Lớp chứa danh sách phương thức, bộ nhớ đệm phương thức và con trỏ đến siêu lớp. objc_msgSend duyệt chuỗi kế thừa: kiểm tra bộ nhớ đệm của lớp, sau đó danh sách phương thức, sau đó chuyển đến siêu lớp. Nếu không tìm thấy phương thức, việc chuyển tiếp được kích hoạt: resolveInstanceMethod, forwardingTargetForSelector và forwardInvocation.

Method Swizzling là một kỹ thuật trao đổi cách triển khai phương thức một cách nhanh chóng thông qua trao đổi IMP (con trỏ triển khai) trong runtime. Nó được sử dụng cho thử nghiệm A/B, phân tích (theo dõi màn hình tự động) và giám sát. Không được khuyến nghị cho sản xuất nếu không có nhu cầu cấp thiết, vì nó có thể xung đột với các bản cập nhật hệ điều hành.

Ví dụ: Method Swizzling trong Objective-C

objective-c
// Method Swizzling để theo dõi viewDidLoad
#import 

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(swizzled_viewDidLoad);

        Method originalMethod = class_getInstanceMethod(
            class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(
            class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(
            class,
            originalSelector,
            method_getImplementation(swizzledMethod),
            method_getTypeEncoding(swizzledMethod)
        );

        if (didAddMethod) {
            class_replaceMethod(
                class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod)
            );
        } else {
            method_exchangeImplementations(
                originalMethod, swizzledMethod);
        }
    });
}

- (void)swizzled_viewDidLoad {
    // Theo dõi sự kiện
    NSLog(@"View Did Load: %@", self.class);
    // Gọi triển khai gốc
    [self swizzled_viewDidLoad];
}

@end

Thể loại UIViewController (Tracking) thay thế viewDidLoad bằng swizzled_viewDidLoad trong tất cả UIViewController trong ứng dụng. dispatch_once đảm bảo swizzling một lần. class_addMethod ngăn chặn swizzling kép và xung đột với các siêu lớp. Nó được sử dụng để theo dõi màn hình tự động trong phân tích mà không cần sửa đổi mã nguồn của bộ điều khiển.

Con trỏ isa và Tagged Pointers

Trong iOS hiện đại (arm64), Apple đã tối ưu hóa con trỏ isa: nó không chỉ là một địa chỉ lớp, mà là một trường bit (non-pointer isa) chứa cờ quản lý bộ nhớ và thông tin lớp. Tagged pointers là một tối ưu hóa khác: các giá trị NSNumber, NSDate và NSString nhỏ không được lưu trữ dưới dạng đối tượng trên heap, mà trực tiếp trong con trỏ, loại bỏ chi phí của malloc và retain/release. Một tagged pointer được nhận biết bằng bit ít quan trọng nhất của isa.

Swift Runtime: Điều phối Tĩnh và Tối ưu hóa

Swift Runtime khác cơ bản so với Objective-C Runtime: Swift theo mặc định sử dụng điều phối tĩnh (static dispatch) qua vtable cho các phương thức của lớp và direct call cho value types và các phương thức mở rộng. Điều phối động (dynamic dispatch) chỉ được sử dụng cho các phương thức được đánh dấu @objc hoặc dynamic. Điều này mang lại cải thiện hiệu suất lên đến 40% so với Objective-C.

Value types (struct, enum) trong Swift là một điểm khác biệt chính so với Objective-C. Chúng được lưu trữ trên ngăn xếp (stack) hoặc bên trong một đối tượng khác, không sử dụng retain/release và không tham gia ARC để đếm tham chiếu. Struct không có con trỏ isa và không thể được gửi qua objc_msgSend. Protocol witnesses là một dạng tương tự của vtable cho các giao thức, cho phép điều phối động cho existential containers.

Swift Runtime cũng bao gồm generics với sự tái hiện (reified generics thông qua mangled symbols) và COW (Copy-on-Write) để tối ưu hóa string, array, dictionary, set. Khi sao chép một bộ sưu tập, việc sao chép thực tế chỉ xảy ra khi một trong các bản sao được sửa đổi. Điều này giảm thiểu chi phí khi truyền bộ sưu tập giữa các hàm.

Điều phối Swift vs Objective-C

swift
import Foundation

// Swift: điều phối tĩnh (vtable cho class)
class Animal {
    func makeSound() { print("...") }  // vtable
}

class Dog: Animal {
    override func makeSound() { print("Woof") }  // vtable override
}

// @objc dynamic: điều phối Objective-C Runtime
class Cat: Animal {
    @objc dynamic override func makeSound() {
        print("Meow")
    }  // objc_msgSend
}

// Struct — không điều phối runtime
struct Cow {
    func makeSound() { print("Moo") }  // direct call
}

// Protocol with protocol witness
protocol SoundMaker {
    func makeSound()
}

struct Duck: SoundMaker {
    func makeSound() { print("Quack") }
}

// Sử dụng existential container
let soundMakers: [SoundMaker] = [Dog(), Cow(), Duck()]
for maker in soundMakers {
    maker.makeSound()  // protocol witness dispatch
}

// Kiểm thử hiệu suất
func testDispatch() {
    let dog = Dog()
    let cat = Cat()
    var cow = Cow()

    let start = CFAbsoluteTimeGetCurrent()
    for _ in 0..<1000000 {
        dog.makeSound()  // vtable: ~3ns
        cat.makeSound()  // objc_msgSend: ~15ns
        cow.makeSound()  // direct: ~1ns
    }
    let elapsed = CFAbsoluteTimeGetCurrent() - start
    print("Elapsed: (elapsed) sec")
}

Ví dụ minh họa ba loại điều phối trong Swift: vtable cho class (Dog), objc_msgSend cho @objc dynamic (Cat) và direct call cho struct (Cow). Protocol witnesses trong existential containers ([SoundMaker]) thêm chi phí. Trong thực tế, Swift chọn điều phối tĩnh bất cứ khi nào có thể, mang lại hiệu suất gần với C.

Swift Runtime và Cầu nối Objective-C

Swift Runtime được thiết kế để tương thích hoàn toàn với Objective-C Runtime. Bất kỳ lớp Swift nào kế thừa NSObject đều tự động được đăng ký trong Objective-C Runtime và có thể được gọi qua objc_msgSend. Thuộc tính @objc làm cho phương thức Swift có thể truy cập từ Objective-C. Cầu nối String: Swift String tự động được chuyển đổi sang NSString khi truyền đến API Objective-C (toll-free bridging).

ARC: Đếm Tham chiếu Tự động và Quản lý Bộ nhớ

ARC (Automatic Reference Counting) là một hệ thống quản lý bộ nhớ trong iOS hoạt động tại thời điểm biên dịch. Trình biên dịch (Clang) phân tích vòng đời của đối tượng và tự động chèn các lệnh gọi retain/release/autorelease. Nhà phát triển không cần gọi chúng thủ công — không giống Manual Retain-Release (MRR) trước iOS 5. ARC hoạt động ở cấp độ đối tượng Objective-C và Swift, nhưng không cho value types (struct, enum).

Mỗi đối tượng Objective-C và lớp Swift có một bộ đếm tham chiếu (retain count), được lưu trữ trong trường extra_rc bên trong non-pointer isa. Khi một đối tượng được tạo, retain count = 1. Khi retain, bộ đếm tăng; khi release, nó giảm. Khi bộ đếm đạt 0, đối tượng được giải phóng qua dealloc (Objective-C) hoặc deinit (Swift). ARC an toàn cho luồng: retain/release sử dụng các thao tác nguyên tử (OSAtomicIncrement32/OSAtomicDecrement32).

Retain cycles là vấn đề chính của ARC. Nếu đối tượng A giữ tham chiếu strong đến B và B giữ tham chiếu strong đến A, cả hai đối tượng sẽ không bao giờ được giải phóng vì bộ đếm tham chiếu của chúng sẽ không bao giờ đạt 0. Giải pháp là các tham chiếu weak (__weak trong Objective-C, weak trong Swift) hoặc tham chiếu unowned. Tham chiếu weak không làm tăng retain count và tự động được đặt về 0 (nil) khi đối tượng được giải phóng.

Gỡ lỗi Retain Cycles với Instruments

swift
import Foundation

// Ví dụ về retain cycle
class Parent {
    var child: Child?
    deinit { print("Parent deallocated") }
}

class Child {
    var parent: Parent?  // strong — tạo retain cycle!
    deinit { print("Child deallocated") }
}

var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent  // chu trình: Parent -> Child -> Parent
parent = nil
child = nil
// deinit KHÔNG được gọi — rò rỉ bộ nhớ!

// Sửa lỗi: weak
class WeakChild {
    weak var parent: Parent?  // weak — không tăng retain count
    deinit { print("WeakChild deallocated") }
}

// Sửa lỗi: unowned (cho vòng đời được đảm bảo)
class UnownedChild {
    unowned let parent: Parent
    init(parent: Parent) { self.parent = parent }
    deinit { print("UnownedChild deallocated") }
}

// Kiểm tra qua Instruments
func profileMemory() {
    // 1. Chạy Instruments > Leaks
    // 2. Thực hiện hành động tạo đối tượng
    // 3. Kiểm tra Leaks xem có rò rỉ không
    // 4. Trong Allocations tìm đối tượng không có dealloc
    for _ in 0..<1000 {
        let p = Parent()
        let c = WeakChild()
        p.child = c as? Child
        // c.parent = p — KHÔNG thêm, weak
    }
}

Ví dụ retain cycle giữa Parent và Child: cả hai giữ tham chiếu strong lẫn nhau, ARC không thể đặt bộ đếm về 0. Sửa lỗi là weak parent trong Child. weak tự động được đặt về 0 khi parent được giải phóng. unowned dành cho các trường hợp khi vòng đời của parent được đảm bảo dài hơn child (ví dụ: viewController và view). Sử dụng Instruments > Leaks để phát hiện retain cycles sớm.

Autorelease Pool

Autorelease pool là một cơ chế giải phóng trì hoãn cho các đối tượng được tạo mà không có quyền sở hữu rõ ràng. @autoreleasepool { } trong Swift và Objective-C tạo một pool được dọn sạch ở cuối khối, gửi release đến mỗi đối tượng trong pool. Quan trọng trong các vòng lặp (tạo hàng nghìn đối tượng tạm thời) và trên các luồng nền không có RunLoop. UIKit RunLoop tự động dọn sạch autorelease pool chính ở mỗi lần lặp.

dyld: Trình tải Động và Khởi chạy Ứng dụng

dyld (dynamic link editor) là trình tải hệ thống chịu trách nhiệm tải các tệp thực thi Mach-O và các thư viện động liên quan (dylib) khi khởi chạy ứng dụng iOS. dyld nằm tại /usr/lib/dyld và là một phần của libSystem. Quá trình tải bao gồm nhiều giai đoạn: phân tích Mach-O, tải phụ thuộc (Library Loader, LC_LOAD_DYLIB), di dời địa chỉ (ASLR), khởi tạo Objective-C Runtime và gọi main().

Thời gian khởi chạy ứng dụng phụ thuộc rất nhiều vào dyld: càng nhiều thư viện động và lớp Objective-C, thời gian pre-main càng dài. Apple khuyến nghị giảm thiểu số lượng phương thức +load (chúng thực thi trước main), thay thế chúng bằng +initialize (khởi tạo lười). Kể từ năm 2020, Apple sử dụng bộ nhớ đệm dyld được xây dựng sẵn trên iOS: các thư viện hệ thống được liên kết trước trong một bộ nhớ đệm duy nhất, tăng tốc tải.

Đo Pre-main Time

swift
import Foundation

// Đo thời gian khởi chạy qua DYLD_PRINT_STATISTICS
// Trong Xcode: Edit Scheme > Run > Arguments > Environment Variables
// DYLD_PRINT_STATISTICS = 1
// DYLD_PRINT_STATISTICS_DETAILS = 1

// Đo lường lập trình pre-main time
@main
struct AppMain {
    static func main() {
        let launchStart = CFAbsoluteTimeGetCurrent()

        // UIApplicationMain xảy ra ở đây
        AppDelegate.main()

        let launchEnd = CFAbsoluteTimeGetCurrent()
        let preMainTime = launchEnd - launchStart
        print("Pre-main time: (preMainTime) sec")
    }
}

// Tối ưu hóa: thay thế +load bằng +initialize
class OptimizedClass {
    // ❌ +load thực thi trước main
    // override class func load() { }

    // ✅ +initialize thực thi ở lần truy cập đầu tiên
    static let shared = OptimizedClass()
    private init() {
        // Khởi tạo ở đây
    }
}

// Tối ưu hóa số lượng dylib
// Hợp nhất thư viện tĩnh làm giảm số lượng LC_LOAD_DYLIB
// Sử dụng cờ -ObjC để liên kết chỉ các lớp Objective-C được sử dụng
// Xcode: Build Settings > Mach-O Type > Static Library

Để đo pre-main time, sử dụng DYLD_PRINT_STATISTICS trong lược đồ Xcode. Đầu ra hiển thị tổng thời gian, thời gian tải dylib, thời gian rebase/bind, thời gian thiết lập Objective-C và thời gian khởi tạo. Giá trị mục tiêu: tổng < 400ms cho khởi động nguội, < 200ms cho khởi động ấm. Tối ưu hóa: hợp nhất thư viện, thay thế +load bằng +initialize, giảm số lượng lớp Objective-C (sử dụng Swift), số lượng framework động tối thiểu.

dsc (dyld Shared Cache)

dyld shared cache là bộ nhớ đệm của các thư viện hệ thống được liên kết trước trên iOS. Tất cả dylib hệ thống (UIKit, Foundation, CoreGraphics) được kết hợp thành một tệp: /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64. Điều này loại bỏ nhu cầu tải từng thư viện hệ thống riêng lẻ — dyld truy cập bộ nhớ đệm, giúp tăng tốc đáng kể khởi động. Các ứng dụng có 10+ framework động trải qua độ trễ lớn nhất, vì dylib tùy chỉnh không được bao gồm trong dsc.

Câu hỏi thường gặp

iOS Runtime là gì và nó bao gồm những thành phần nào?

iOS Runtime là môi trường thực thi ứng dụng trên iOS, bao gồm Objective-C Runtime (libobjc.dylib), Swift Runtime (libswiftCore.dylib), các framework Cocoa Touch, dyld (trình tải động) và ARC (quản lý bộ nhớ). Nó cung cấp message passing cho Objective-C, điều phối tĩnh cho Swift, tải tệp Mach-O và quản lý bộ nhớ tự động.

Objective-C Runtime khác Swift Runtime như thế nào?

Objective-C Runtime sử dụng ràng buộc động thông qua objc_msgSend (message passing) với ràng buộc muộn. Swift Runtime sử dụng điều phối tĩnh (vtable cho lớp, direct call cho struct) để đạt hiệu suất. @objc dynamic kích hoạt Objective-C Runtime cho các lớp Swift. Swift struct không có con trỏ isa và không sử dụng retain/release.

ARC hoạt động như thế nào trong iOS?

ARC (Automatic Reference Counting) là quản lý bộ nhớ tại thời điểm biên dịch. Trình biên dịch Clang tự động chèn các lệnh gọi retain/release. Mỗi đối tượng có một bộ đếm tham chiếu; khi đạt 0, dealloc được gọi. Retain cycles (tham chiếu strong lẫn nhau) được ngăn chặn bằng tham chiếu weak/unowned. Sử dụng Instruments > Leaks để phát hiện rò rỉ.

dyld là gì và nó ảnh hưởng đến khởi chạy ứng dụng như thế nào?

dyld là trình tải động cho các tệp Mach-O. Nó tải tệp thực thi và tất cả dylib phụ thuộc, thực hiện di dời (ASLR), khởi tạo Objective-C Runtime và gọi main(). Pre-main time phụ thuộc vào số lượng dylib và phương thức +load. Sử dụng DYLD_PRINT_STATISTICS để đo. Tối ưu hóa: hợp nhất thư viện, thay thế +load bằng +initialize.

Method Swizzling là gì và khi nào nên sử dụng nó?

Method Swizzling là một kỹ thuật trao đổi IMP (con trỏ triển khai) của phương thức một cách nhanh chóng thông qua class_getInstanceMethod và method_exchangeImplementations của Objective-C Runtime. Nó được sử dụng cho thử nghiệm A/B, phân tích (theo dõi màn hình tự động) và giám sát. Không được khuyến nghị trong sản xuất nếu không có nhu cầu cấp thiết. Trong Swift, nó được thay thế bằng @objc dynamic + Method Swizzling.

Tóm tắt

  • iOS Runtime — môi trường thực thi ứng dụng iOS bao gồm Objective-C Runtime, Swift Runtime, dyld và ARC
  • Objective-C Runtime — message passing (objc_msgSend), con trỏ isa, swizzling, lớp động
  • Swift Runtime — điều phối tĩnh (vtable, direct call), value types, protocol witnesses
  • ARC (Automatic Reference Counting) — quản lý bộ nhớ tự động với retain/release tại thời điểm biên dịch
  • dyld — trình tải Mach-O động xác định tốc độ khởi chạy ứng dụng (pre-main time)
  • Retain cycles — được ngăn chặn bằng tham chiếu weak/unowned; gỡ lỗi qua Instruments Leaks
  • Tối ưu hóa — giảm thiểu +load, hợp nhất dylib, sử dụng Swift struct cho value types

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm