iOS Runtime هو بيئة تشغيل التطبيقات على نظام التشغيل Apple iOS، بما في ذلك Objective-C Runtime و Swift Runtime وأطر Cocoa Touch وآليات إدارة الذاكرة من خلال Automatic Reference Counting (ARC). iOS Runtime مسؤول عن الربط الديناميكي للطرق (message passing)، تحميل الفئات، إدارة الذاكرة والتفاعل مع الأجهزة من خلال أطر iOS. وفقاً لوثائق مطوري Apple، فإن فهم 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)، التي توفر واجهات برمجة التطبيقات للمطور.
فهم iOS Runtime يسمح للمطور بحل مشكلات معقدة: swizzling الطرق (Method Swizzling) لاختبارات A/B والتحليلات، التحميل الديناميكي للفئات، تحسين الذاكرة من خلال فهم ARC، تصحيح retain cycles وتسريبات الذاكرة، تحسين وقت تشغيل التطبيق من خلال 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 سلسلة الوراثة: يتحقق من ذاكرة التخزين المؤقت للفئة، ثم قائمة الطرق، ثم ينتقل إلى الفئة الفائقة. إذا لم يتم العثور على الطريقة، يتم تشغيل إعادة التوجيه: resolveInstanceMethod و forwardingTargetForSelector و forwardInvocation.
Method Swizzling هي تقنية لتبادل تطبيقات الطرق أثناء التنفيذ من خلال تبادل IMP (مؤشر التنفيذ) في 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 في جميع UIViewControllers في التطبيق. يضمن dispatch_once إجراء swizzling لمرة واحدة. يمنع class_addMethod الـ swizzling المزدوج والتعارض مع الفئات الفائقة. يُستخدم للتتبع التلقائي لعرض الشاشات في التحليلات دون تعديل الكود المصدري لوحدات التحكم.
في iOS الحديث (arm64)، قامت Apple بتحسين مؤشر isa:ليس مجرد عنوان فئة، بل حقل بتات (non-pointer isa) يحتوي على علامات إدارة الذاكرة ومعلومات الفئة. Tagged pointers هي تحسين آخر: قيم NSNumber و NSDate و NSString الصغيرة لا تُخزن ككائنات في الكومة، بل مباشرة في المؤشر، مما يلغي النفقات العامة لـ malloc و retain/release. يتم التعرف على tagged pointer من خلال البت الأقل أهمية في isa.
Swift Runtime يختلف جوهرياً عن Objective-C Runtime: Swift افتراضياً يستخدم التوزيع الثابت (static dispatch) من خلال vtable لطرق الفئة و direct call لـ value types وطرق الامتداد. التوزيع الديناميكي (dynamic dispatch) يُستخدم فقط للطرق الموسومة بـ @objc أو dynamic. هذا يوفر تحسناً في الأداء يصل إلى 40% مقارنة بـ Objective-C.
Value types (struct، enum) في Swift هي فرق رئيسي عن Objective-C. تُخزن على المكدس (stack) أو داخل كائن آخر، ولا تستخدم retain/release ولا تشارك في ARC لعد المراجع. Struct ليس لديه مؤشر isa ولا يمكن إرساله عبر objc_msgSend. Protocol witnesses هي نظير vtable للبروتوكولات، مما يسمح بالتوزيع الديناميكي لـ existential containers.
Swift Runtime يشمل أيضاً generics مع إعادة التجسيد (reified generics عبر mangled symbols) و COW (Copy-on-Write) لتحسين string و array و dictionary و set. عند نسخ مجموعة، يحدث النسخ الفعلي فقط عند تعديل إحدى النسخ. هذا يقلل النفقات العامة عند تمرير المجموعات بين الدوال.
import Foundation
// Swift: توزيع ثابت (vtable للفئة)
class Animal {
func makeSound() { print("...") } // vtable
}
class Dog: Animal {
override func makeSound() { print("Woof") } // vtable override
}
// @objc dynamic: توزيع Objective-C Runtime
class Cat: Animal {
@objc dynamic override func makeSound() {
print("Meow")
} // objc_msgSend
}
// Struct — لا توزيع runtime
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 للفئة (Dog)، objc_msgSend لـ @objc dynamic (Cat) و direct call للـ struct (Cow). protocol witnesses في existential containers ([SoundMaker]) تضيف overhead. عملياً، يختار Swift التوزيع الثابت حيثما أمكن، مما يوفر أداء قريباً من C.
Swift Runtime مصمم للتوافق الكامل مع Objective-C Runtime. أي فئة Swift ترث من NSObject تُسجل تلقائياً في Objective-C Runtime ويمكن استدعاؤها عبر objc_msgSend. السمة @objc تجعل أسلوب Swift متاحاً من Objective-C. جسر String: يتم تحويل Swift String تلقائياً إلى NSString عند تمريره إلى واجهة Objective-C API (toll-free bridging).
ARC (Automatic Reference Counting) هو نظام إدارة ذاكرة في iOS يعمل في وقت الترجمة. يقوم المترجم (Clang) بتحليل أعمار الكائنات ويدرج تلقائياً استدعاءات retain/release/autorelease. لا يحتاج المطور إلى استدعائها يدوياً — على عكس Manual Retain-Release (MRR) قبل iOS 5. يعمل ARC على مستوى كائنات Objective-C و Swift، ولكن ليس لـ value types (struct، enum).
كل كائن Objective-C وفئة Swift له عداد مراجع (retain count)، مخزن في حقل extra_rc داخل non-pointer isa. عند إنشاء كائن، retain count = 1. عند retain، يزداد العداد؛ عند release، ينقص. عندما يصل العداد إلى 0، يتم تحرير الكائن عبر dealloc (Objective-C) أو deinit (Swift). ARC آمن للخيوط: retain/release تستخدم عمليات ذرية (OSAtomicIncrement32/OSAtomicDecrement32).
Retain cycles هي المشكلة الرئيسية في ARC. إذا كان الكائن A يحمل مرجعاً قوياً إلى B، و B يحمل مرجعاً قوياً إلى 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() {
// 1. تشغيل Instruments > Leaks
// 2. تنفيذ إجراء ينشئ كائنات
// 3. التحقق من Leaks لوجود تسريبات
// 4. في 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: كلاهما يحمل مراجع قوية للآخر، لا يمكن لـ ARC تصفير العدادات. الحل هو weak parent في Child. يتم تصفير weak تلقائياً عند تحرير parent. unowned للحالات التي يكون فيها عمر parent مضموناً ليكون أطول من child (مثل viewController و view). استخدم Instruments > لاكتشاف retain cycles مبكراً.
Autorelease pool هو آلية تحرير مؤجل للكائنات المنشأة دون ملكية صريحة. @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().
وقت تشغيل التطبيق يعتمد بشكل حاسم على dyld: كلما زادت المكتبات الديناميكية وفئات Objective-C، زاد وقت pre-main. توصي Apple بتقليل عدد طرق +load (تُنفذ قبل main)، واستبدالها بـ +initialize (التهيئة البطيئة). منذ عام 2020، تستخدم Apple ذاكرة تخزين مؤقت dyld مسبقة البناء على 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. يظهر الإخراج الوقت الإجمالي، وقت تحميل dylib، وقت rebase/bind، وقت إعداد Objective-C ووقت التهيئة. القيم المستهدفة: الإجمالي < 400ms للتشغيل البارد، < 200ms للتشغيل الدافئ. التحسينات: دمج المكتبات، استبدال +load بـ +initialize، تقليل عدد فئات Objective-C (استخدم Swift)، الحد الأدنى من الأطر الديناميكية.
dyld shared cache هو ذاكرة تخزين مؤقت للمكتبات النظامية المرتبطة مسبقاً على iOS. جميع dylib النظامية (UIKit، Foundation، CoreGraphics) مجمعة في ملف واحد: /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64. هذا يلغي الحاجة لتحميل كل مكتبة نظامية بشكل منفصل — يصل dyld إلى الذاكرة المؤقتة، مما يسرع التشغيل بشكل كبير. التطبيقات التي تحتوي على 10+ أطر ديناميكية تعاني من أكبر تأخير، حيث أن 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 (المراجع القوية المتبادلة) باستخدام مراجع 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 (مؤشر التنفيذ) لطريقة أثناء التنفيذ عبر class_getInstanceMethod و method_exchangeImplementations من Objective-C Runtime. تُستخدم لاختبارات A/B والتحليلات (التتبع التلقائي للشاشات) والمراقبة. لا يُنصح بها في الإنتاج دون حاجة ماسة. في Swift، يتم استبدالها بـ @objc dynamic + Method Swizzling.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا