iOS Runtime — محیط اجرای برنامهها در سیستم عامل Apple iOS، شامل Objective-C Runtime، Swift Runtime، فریمورکهای Cocoa Touch و مکانیسمهای مدیریت حافظه از طریق Automatic Reference Counting (ARC). iOS Runtime مسئول اتصال پویای متدها (message passing)، بارگذاری کلاسها، مدیریت حافظه و تعامل با سختافزار از طریق فریمورکهای iOS است. به گفته Apple Developer Documentation، درک runtime برای بهینهسازی عملکرد، اشکالزدایی و توسعه برنامههای پایدار iOS ضروری است.
نکات اصلی
iOS Runtime — مجموعهای از مؤلفههای سیستمی است که اجرای برنامهها را در دستگاههای Apple تحت iOS فراهم میکند. شامل Objective-C Runtime (کتابخانه libobjc.A.dylib)، Swift Runtime (libswiftCore.dylib)، Core Foundation، فریمورکهای Cocoa Touch (UIKit, Foundation)، بارگذار پویا dyld و محیط زمان اجرا برای مدیریت حافظه، رشتهها و ارتباطات بین فرآیندی است.
از نظر معماری، iOS Runtime در سه سطح کار میکند. در سطح پایین — فرمت باینری Mach-O و dyld که فایل اجرایی و کتابخانهها را بارگذاری میکند. سطح میانی — Objective-C Runtime و Swift Runtime که مسئول توزیع متدها و مدیریت اشیا هستند. سطح بالا — فریمورکهای Cocoa Touch (UIKit, Foundation, Core Data, Metal) که API را برای توسعهدهنده فراهم میکنند.
درک iOS Runtime به توسعهدهنده اجازه میدهد مسائل پیچیده را حل کند: جابجایی متدها (Method Swizzling) برای تست A/B و تحلیل، بارگذاری پویای کلاسها، بهینهسازی حافظه از طریق درک ARC، اشکالزدایی retain cycleها و نشت حافظه، بهینهسازی زمان راهاندازی برنامه از طریق dyld. بدون دانش runtime، نمایهسازی و بهینهسازی در سطح سیستم غیرممکن است.
| مؤلفه | کتابخانه | هدف |
|---|---|---|
| Objective-C Runtime | libobjc.A.dylib | Message passing، کلاسهای پویا، swizzling |
| Swift Runtime | libswiftCore.dylib | Value types، generics، protocol witnesses |
| Core Foundation | CoreFoundation.framework | CFType، toll-free bridging |
| dyld | dyld (usr/lib/dyld) | بارگذاری Mach-O، پیوند کتابخانهها |
| libSystem | libSystem.B.dylib | POSIX threads، libc، libdispatch (GCD) |
برنامههای iOS به فرمت Mach-O (Mach Object) کامپایل میشوند. فایل Mach-O شامل هدر (header)، دستورات بارگذاری (load commands) و بخشها (segments) است: __TEXT (کد، ثابتها)، __DATA (متغیرهای سراسری، متادیتای Objective-C)، __LINKEDIT (نمادها، جدولهای جایگذاری). dyld Mach-O را تحلیل میکند و قبل از اجرای اولین دستور، وابستگیها را بارگذاری میکند.
Objective-C Runtime — قدرتمندترین بخش iOS Runtime. برخلاف C++ با پیوند زودهنگام (early binding)، Objective-C از پیوند دیرهنگام (late binding) از طریق message passing استفاده میکند. فراخوانی متد [receiver message] نه بهعنوان فراخوانی مستقیم تابع، بلکه بهصورت objc_msgSend(receiver, @selector(message)) کامپایل میشود که پیادهسازی متد را در کلاس شی بهصورت پویا پیدا میکند.
هر شی Objective-C یک اشارهگر isa به کلاس خود ذخیره میکند. کلاس شامل لیست متدها (method list)، حافظه پنهان متدها (method cache) و اشارهگر به ابرکلاس است. objc_msgSend زنجیره وراثت را طی میکند: حافظه پنهان کلاس را بررسی میکند، سپس method list، سپس به ابرکلاس میرود. اگر متد پیدا نشود، forward فعال میشود: resolveInstanceMethod، forwardingTargetForSelector و forwardInvocation.
Method Swizzling — تکنیک جایگزینی پیادهسازی متد در لحظه از طریق تعویض IMP (implementation pointer) در runtime. برای تست A/B، تحلیل (ردیابی خودکار صفحهها) و نظارت استفاده میشود. بدون ضرورت مطلق در محیط تولید توصیه نمیشود، زیرا ممکن است با بهروزرسانیهای سیستم عامل تداخل داشته باشد.
// Method Swizzling برای ردیابی 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 {
// ردیابی رویداد
NSLog(@"View Did Load: %@", self.class);
// فراخوانی پیادهسازی اصلی
[self swizzled_viewDidLoad];
}
@end دسته UIViewController (Tracking) viewDidLoad را با swizzled_viewDidLoad در تمام UIViewControllerهای برنامه جایگزین میکند. dispatch_once یکبار swizzling را تضمین میکند. class_addMethod از swizzling مضاعف و تداخل با ابرکلاسها جلوگیری میکند. برای ردیابی خودکار نمایش صفحهها در تحلیل بدون تغییر کد منبع کنترلرها استفاده میشود.
در iOS مدرن (arm64)، Apple اشارهگر isa را بهینه کرده است: این فقط یک آدرس کلاس نیست، بلکه یک فیلد بیتی (non-pointer isa) حاوی پرچمهای مدیریت حافظه و اطلاعات درباره کلاس است. Tagged pointers — بهینهسازی دیگر: مقادیر NSNumber، NSDate و NSString کوچک نه بهعنوان اشیا در heap، بلکه مستقیماً در اشارهگر ذخیره میشوند و سربار malloc و retain/release را حذف میکنند. tagged pointer با کمترین بیت isa شناسایی میشود.
Swift Runtime اساساً با Objective-C Runtime تفاوت دارد: Swift بهطور پیشفرض از توزیع ایستا (static dispatch) از طریق vtable برای متدهای کلاس و direct call برای value types و extension methods استفاده میکند. توزیع پویا (dynamic dispatch) فقط برای متدهای مشخص شده با @objc یا dynamic استفاده میشود. این کار تا ۴۰٪ افزایش عملکرد نسبت به Objective-C میدهد.
Value types (struct, enum) در Swift — تفاوت اصلی با Objective-C. آنها روی پشته (stack) یا داخل یک شی دیگر ذخیره میشوند، از retain/release استفاده نمیکنند و در ARC برای شمارنده مراجع شرکت نمیکنند. Struct اشارهگر isa ندارد و نمیتواند از طریق objc_msgSend ارسال شود. Protocol witnesses — معادل vtable برای پروتکلها که توزیع پویا را برای existential container امکانپذیر میکند.
Swift Runtime همچنین شامل generics با بازنمونهسازی (reified generics از طریق mangled symbols) و COW (Copy-on-Write) برای بهینهسازی string، array، dictionary، set است. هنگام کپی کردن مجموعه، کپی واقعی فقط هنگام تغییر یکی از کپیها رخ میدهد. این کار سربار هنگام ارسال مجموعهها بین توابع را به حداقل میرساند.
import Foundation
// Swift: توزیع ایستا (vtable برای class)
class Animal {
func makeSound() { print("...") } // vtable
}
class Dog: Animal {
override func makeSound() { print("Woof") } // vtable override
}
// @objc dynamic: Objective-C Runtime dispatch
class Cat: Animal {
@objc dynamic override func makeSound() {
print("Meow")
} // objc_msgSend
}
// Struct — no runtime dispatch
struct Cow {
func makeSound() { print("Moo") } // direct call
}
// Protocol with protocol witness
protocol SoundMaker {
func makeSound()
}
struct Duck: SoundMaker {
func makeSound() { print("Quack") }
}
// استفاده از existential container
let soundMakers: [SoundMaker] = [Dog(), Cow(), Duck()]
for maker in soundMakers {
maker.makeSound() // protocol witness dispatch
}
// تست عملکرد
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")
}مثال سه نوع توزیع را در Swift نشان میدهد: vtable برای class (Dog)، objc_msgSend برای @objc dynamic (Cat) و direct call برای struct (Cow). Protocol witnesses در existential container ([SoundMaker]) سربار اضافه میکنند. در عمل، Swift در هرجا که ممکن باشد توزیع ایستا را انتخاب میکند و عملکرد نزدیک به C را فراهم میکند.
Swift Runtime برای سازگاری کامل با Objective-C Runtime طراحی شده است. هر کلاس Swift که از NSObject ارثبری میکند، بهطور خودکار در Objective-C Runtime ثبت میشود و میتواند از طریق objc_msgSend فراخوانی شود. ویژگی @objc متد Swift را از Objective-C قابل دسترسی میکند. پل String: Swift String هنگام ارسال به API Objective-C بهطور خودکار به NSString پل میشود (toll-free bridging).
ARC (Automatic Reference Counting) — سیستم مدیریت حافظه در iOS که در مرحله کامپایل کار میکند. کامپایلر (Clang) چرخه عمر اشیا را تحلیل میکند و بهطور خودکار فراخوانیهای retain/release/autorelease را درج میکند. توسعهدهنده نیازی به فراخوانی دستی آنها ندارد — برخلاف Manual Retain-Release (MRR) قبل از iOS 5. ARC در سطح اشیای Objective-C و Swift class کار میکند، اما برای value types (struct, enum) نه.
هر شی Objective-C و Swift class یک شمارنده مرجع (retain count) دارد که در فیلد extra_rc داخل non-pointer isa ذخیره میشود. هنگام ایجاد شی، retain count = 1. هنگام retain شمارنده افزایش مییابد، هنگام release کاهش مییابد. وقتی شمارنده به ۰ میرسد، شی از طریق dealloc (Objective-C) یا deinit (Swift) آزاد میشود. ARC thread-safe است: retain/release از عملیات اتمی (OSAtomicIncrement32/OSAtomicDecrement32) استفاده میکنند.
Retain cycles — مشکل اصلی ARC. اگر شی A یک مرجع strong به B داشته باشد و B یک مرجع strong به A، هر دو شی هرگز آزاد نخواهند شد، زیرا شمارندههای مرجع آنها صفر نخواهد شد. راهحل — مراجع ضعیف (__weak در Objective-C، weak در Swift) یا unowned. مراجع ضعیف retain count را افزایش نمیدهند و هنگام آزادسازی شی بهطور خودکار صفر میشوند (nil).
import Foundation
// مثال retain cycle
class Parent {
var child: Child?
deinit { print("Parent deallocated") }
}
class Child {
var parent: Parent? // strong — retain cycle ایجاد میکند!
deinit { print("Child deallocated") }
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent // چرخه: Parent -> Child -> Parent
parent = nil
child = nil
// deinit فراخوانی نمیشود — نشت حافظه!
// رفع: weak
class WeakChild {
weak var parent: Parent? // weak — retain count را افزایش نمیدهد
deinit { print("WeakChild deallocated") }
}
// رفع: unowned (برای طول عمر تضمینی)
class UnownedChild {
unowned let parent: Parent
init(parent: Parent) { self.parent = parent }
deinit { print("UnownedChild deallocated") }
}
// بررسی از طریق Instruments
func profileMemory() {
// ۱. Instruments > Leaks را اجرا کنید
// ۲. عملی ایجاد کننده اشیا را انجام دهید
// ۳. Leaks را برای نشت بررسی کنید
// ۴. در Allocations اشیای بدون dealloc را پیدا کنید
for _ in 0..<1000 {
let p = Parent()
let c = WeakChild()
p.child = c as? Child
// c.parent = p — اضافه نمیکنیم، weak
}
}مثال retain cycle بین Parent و Child: هر دو به یکدیگر مراجع strong دارند، ARC نمیتواند شمارندهها را صفر کند. رفع — weak parent در Child. weak بهطور خودکار هنگام آزادسازی parent صفر میشود. unowned — برای مواردی که طول عمر parent تضمیناً بیشتر از child است (مثلاً viewController و view). برای تشخیص retain cycles در مراحل اولیه از Instruments > Leaks استفاده کنید.
Autorelease pool — مکانیزم release تأخیری برای اشیایی که بدون مالکیت صریح ایجاد شدهاند. @autoreleasepool { } در Swift و Objective-C یک pool ایجاد میکند که در انتهای بلوک تخلیه میشود و release را به هر شی درون pool ارسال میکند. در حلقهها (ایجاد هزاران شی موقت) و در رشتههای پسزمینه بدون RunLoop حیاتی است. UIKit RunLoop بهطور خودکار autorelease pool اصلی را در هر تکرار تخلیه میکند.
dyld (dynamic link editor) — بارگذار سیستمی مسئول بارگذاری فایلهای اجرایی Mach-O و کتابخانههای پویای مرتبط (dylib) هنگام راهاندازی برنامه iOS. dyld در مسیر /usr/lib/dyld قرار دارد و بخشی از libSystem است. فرآیند بارگذاری شامل چند مرحله است: تجزیه Mach-O، بارگذاری وابستگیها (Library Loader, LC_LOAD_DYLIB)، جایگذاری آدرسها (ASLR)، مقداردهی اولیه Objective-C Runtime و فراخوانی main().
زمان راهاندازی برنامه (launch time) بهطور بحرانی به dyld بستگی دارد: هرچه کتابخانههای پویا و کلاسهای Objective-C بیشتر باشد، pre-main time طولانیتر است. Apple توصیه میکند تعداد متدهای +load را به حداقل برسانید (آنها قبل از main اجرا میشوند)، آنها را با +initialize (مقداردهی تنبل) جایگزین کنید. از سال ۲۰۲۰، Apple از prebuilt dyld cache در iOS استفاده میکند: کتابخانههای سیستمی از پیش در یک حافظه پنهان واحد پیوند داده شدهاند که بارگذاری را加速 میکند.
import Foundation
// اندازهگیری زمان راهاندازی از طریق DYLD_PRINT_STATISTICS
// در Xcode: Edit Scheme > Run > Arguments > Environment Variables
// DYLD_PRINT_STATISTICS = 1
// DYLD_PRINT_STATISTICS_DETAILS = 1
// اندازهگیری برنامهای pre-main time
@main
struct AppMain {
static func main() {
let launchStart = CFAbsoluteTimeGetCurrent()
// UIApplicationMain اینجا رخ میدهد
AppDelegate.main()
let launchEnd = CFAbsoluteTimeGetCurrent()
let preMainTime = launchEnd - launchStart
print("Pre-main time: (preMainTime) sec")
}
}
// بهینهسازی: جایگزینی +load با +initialize
class OptimizedClass {
// ❌ +load قبل از main اجرا میشود
// override class func load() { }
// ✅ +initialize در اولین مراجعه اجرا میشود
static let shared = OptimizedClass()
private init() {
// مقداردهی اولیه اینجا
}
}
// بهینهسازی تعداد dylib
// ادغام کتابخانههای ایستا تعداد LC_LOAD_DYLIB را کاهش میدهد
// از پرچم -ObjC برای پیوند فقط کلاسهای Objective-C استفاده شده استفاده کنید
// Xcode: Build Settings > Mach-O Type > Static Libraryبرای اندازهگیری pre-main time از DYLD_PRINT_STATISTICS در طرح Xcode استفاده کنید. خروجی total time، dylib loading time، rebase/bind time، Objective-C setup time و initializer time را نشان میدهد. مقادیر هدف: total < ۴۰۰ms برای راهاندازی سرد، < ۲۰۰ms برای گرم. بهینهسازیها: ادغام کتابخانهها، جایگزینی +load با +initialize، کاهش تعداد کلاسهای Objective-C (از Swift استفاده کنید)، حداقل تعداد فریمورکهای پویا.
dyld shared cache — حافظه پنهان کتابخانههای سیستمی از پیش پیوند داده شده در iOS. تمام dylibهای سیستمی (UIKit, Foundation, CoreGraphics) در یک فایل ادغام شدهاند: /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64. این کار نیاز به بارگذاری هر کتابخانه سیستمی جداگانه را از بین میبرد — dyld به حافظه پنهان مراجعه میکند که راهاندازی را بهطور قابل توجهی سرعت میبخشد. برنامههای با ۱۰+ فریمورک پویا بیشترین تأخیر را تجربه میکنند، زیرا dylibهای سفارشی در dsc نیستند.
سؤالات متداول
iOS Runtime — محیط اجرای برنامهها در iOS، شامل Objective-C Runtime (libobjc.dylib)، Swift Runtime (libswiftCore.dylib)، فریمورکهای Cocoa Touch، dyld (بارگذار پویا) و ARC (مدیریت حافظه). message passing را برای Objective-C، توزیع ایستا برای Swift، بارگذاری فایلهای Mach-O و مدیریت خودکار حافظه فراهم میکند.
Objective-C Runtime از پیوند پویا از طریق objc_msgSend (message passing) با پیوند دیرهنگام استفاده میکند. Swift Runtime از توزیع ایستا (vtable برای کلاسها، direct call برای struct) برای عملکرد استفاده میکند. @objc dynamic Objective-C Runtime را برای کلاسهای Swift فعال میکند. Swift struct اشارهگر isa ندارد و از retain/release استفاده نمیکند.
ARC (Automatic Reference Counting) — مدیریت حافظه در مرحله کامپایل. کامپایلر Clang بهطور خودکار فراخوانیهای retain/release را درج میکند. هر شی یک شمارنده مرجع دارد که هنگام صفر شدن، dealloc فراخوانی میشود. Retain cycles (مراجع strong متقابل) با مراجع weak/unowned جلوگیری میشوند. برای تشخیص نشت از Instruments > Leaks استفاده کنید.
dyld — بارگذار پویای فایلهای Mach-O. فایل اجرایی و تمام dylibهای وابسته را بارگذاری میکند، جایگذاری (ASLR) را انجام میدهد، Objective-C Runtime را مقداردهی اولیه میکند و main() را فراخوانی میکند. Pre-main time به تعداد dylibها و متدهای +load بستگی دارد. برای اندازهگیری از DYLD_PRINT_STATISTICS استفاده کنید. بهینهسازی: ادغام کتابخانهها، جایگزینی +load با +initialize.
Method Swizzling — تکنیک جایگزینی IMP (implementation pointer) متد در لحظه از طریق Objective-C Runtime class_getInstanceMethod و method_exchangeImplementations. برای تست A/B، تحلیل (ردیابی خودکار صفحهها) و نظارت استفاده میشود. بدون ضرورت مطلق در محیط تولید توصیه نمیشود. در Swift با @objc dynamic + Method Swizzling جایگزین میشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید