iOS Runtime — de runtime-omgeving voor applicaties op het besturingssysteem Apple iOS, inclusief Objective-C Runtime, Swift Runtime, Cocoa Touch frameworks en mechanismen voor geheugenbeheer via Automatic Reference Counting (ARC). iOS Runtime is verantwoordelijk voor dynamische methodebinding (message passing), het laden van klassen, geheugenbeheer en interactie met hardware via iOS-frameworks. Volgens Apple Developer Documentation is kennis van runtime noodzakelijk voor prestatieoptimalisatie, debugging en het ontwikkelen van stabiele iOS-applicaties.
Belangrijkste punten
iOS Runtime — de verzameling systeemcomponenten die de uitvoering van applicaties op Apple-apparaten met iOS mogelijk maakt. Het omvat Objective-C Runtime (bibliotheek libobjc.A.dylib), Swift Runtime (libswiftCore.dylib), Core Foundation, Cocoa Touch frameworks (UIKit, Foundation), de dynamische lader dyld en een runtime-omgeving voor geheugen-, thread- en interprocescommunicatiebeheer.
Architectonisch werkt iOS Runtime op drie niveaus. Op het onderste niveau — het Mach-O binaire formaat en dyld, die het uitvoerbare bestand en bibliotheken laadt. Het middelste niveau — Objective-C Runtime en Swift Runtime, verantwoordelijk voor methode-dispatch en objectbeheer. Het bovenste niveau — Cocoa Touch frameworks (UIKit, Foundation, Core Data, Metal), die API's bieden voor de ontwikkelaar.
Kennis van iOS Runtime stelt de ontwikkelaar in staat complexe taken op te lossen: method swizzling (Method Swizzling) voor A/B-testen en analyses, dynamisch laden van klassen, geheugenoptimalisatie door ARC te begrijpen, debuggen van retain cycles en geheugenlekken, optimalisatie van de opstarttijd van de applicatie via dyld. Zonder runtime-kennis zijn profilering en optimalisatie op systeemniveau onmogelijk.
| Component | Bibliotheek | Doel |
|---|---|---|
| Objective-C Runtime | libobjc.A.dylib | Message passing, dynamische klassen, 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 laden, bibliotheken koppelen |
| libSystem | libSystem.B.dylib | POSIX threads, libc, libdispatch (GCD) |
iOS-applicaties worden gecompileerd naar Mach-O formaat (Mach Object). Een Mach-O-bestand bevat een header (header), laadopdrachten (load commands) en segmenten (segments): __TEXT (code, constanten), __DATA (globale variabelen, Objective-C metadata), __LINKEDIT (symbolen, relocatietabellen). dyld analyseert het Mach-O-bestand en laadt afhankelijkheden voordat de eerste instructie wordt uitgevoerd.
Objective-C Runtime — het krachtigste deel van iOS Runtime. In tegenstelling tot C++ met vroege binding (early binding), gebruikt Objective-C late binding via message passing. De methodeaanroep [receiver message] wordt niet als directe functieaanroep gecompileerd, maar als objc_msgSend(receiver, @selector(message)), die dynamisch de implementatie van de methode in de klasse van het object vindt.
Elk Objective-C-object slaat een isa-pointer naar zijn klasse op. De klasse bevat een methodelijst (method list), een methodecache (method cache) en een pointer naar de superklasse. objc_msgSend doorloopt de overervingsketen: controleert de cache van de klasse, vervolgens de method list, dan gaat het naar de superklasse. Als de methode niet wordt gevonden, wordt forward geactiveerd: resolveInstanceMethod, forwardingTargetForSelector en forwardInvocation.
Method Swizzling — techniek om de implementatie van een methode ter plekke te vervangen door het uitwisselen van IMP (implementation pointer) in de runtime. Gebruikt voor A/B-testen, analyses (automatisch volgen van schermen) en monitoring. Niet aanbevolen in productie zonder uiterste noodzaak, omdat het kan conflicteren met updates van het besturingssysteem.
// Method Swizzling voor het volgen van 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 {
// Gebeurtenis volgen
NSLog(@"View Did Load: %@", self.class);
// Aanroepen van originele implementatie
[self swizzled_viewDidLoad];
}
@end De categorie UIViewController (Tracking) vervangt viewDidLoad door swizzled_viewDidLoad in alle UIViewController's in de applicatie. dispatch_once garandeert eenmalige swizzling. class_addMethod voorkomt dubbele swizzling en conflicten met superklassen. Gebruikt voor automatisch volgen van schermweergave in analyses zonder de broncode van controllers te wijzigen.
In modern iOS (arm64) heeft Apple de isa-pointer geoptimaliseerd: dit is niet zomaar een klasse-adres, maar een bitveld (non-pointer isa) met vlaggen voor geheugenbeheer en informatie over de klasse. Tagged pointers — een andere optimalisatie: kleine NSNumber-, NSDate- en NSString-waarden worden niet als objecten op de heap opgeslagen, maar direct in de pointer, waardoor de overhead van malloc en retain/release wordt geëlimineerd. tagged pointer wordt herkend aan de minst significante bit van isa.
Swift Runtime verschilt fundamenteel van Objective-C Runtime: Swift gebruikt standaard statische dispatch via vtable voor klassemethoden en direct call voor value types en extension methods. Dynamische dispatch wordt alleen gebruikt voor methoden gemarkeerd met @objc of dynamic. Dit levert een prestatieverbetering tot 40% op in vergelijking met Objective-C.
Value types (struct, enum) in Swift — het belangrijkste verschil met Objective-C. Ze worden op de stack (stack) of binnen een ander object opgeslagen, gebruiken geen retain/release en nemen niet deel aan ARC voor de referentieteller. Struct heeft geen isa-pointer en kan niet via objc_msgSend worden verzonden. Protocol witnesses — het equivalent van vtable voor protocollen, waardoor dynamische dispatch mogelijk is voor existential container.
Swift Runtime omvat ook generics met reïficatie (reified generics via mangled symbols) en COW (Copy-on-Write) voor optimalisatie van string, array, dictionary, set. Bij het kopiëren van een collectie vindt daadwerkelijke kopiëring alleen plaats wanneer een van de kopieën wordt gewijzigd. Dit minimaliseert de overhead bij het doorgeven van collecties tussen functies.
import Foundation
// Swift: statische dispatch (vtable voor 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") }
}
// Gebruik van existential container
let soundMakers: [SoundMaker] = [Dog(), Cow(), Duck()]
for maker in soundMakers {
maker.makeSound() // protocol witness dispatch
}
// Prestatietesten
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")
}Het voorbeeld toont drie soorten dispatch in Swift: vtable voor class (Dog), objc_msgSend voor @objc dynamic (Cat) en direct call voor struct (Cow). Protocol witnesses in existential container ([SoundMaker]) voegen overhead toe. In de praktijk kiest Swift waar mogelijk voor statische dispatch en levert het prestaties dicht bij C.
Swift Runtime is ontworpen voor volledige compatibiliteit met Objective-C Runtime. Elke Swift-klasse die NSObject overerft, wordt automatisch geregistreerd in Objective-C Runtime en kan via objc_msgSend worden aangeroepen. Het @objc-attribuut maakt een Swift-methode toegankelijk vanuit Objective-C. String-brug: Swift String wordt automatisch gebridged naar NSString bij overdracht naar Objective-C API (toll-free bridging).
ARC (Automatic Reference Counting) — geheugenbeheersysteem in iOS, werkend tijdens de compilatiefase. De compiler (Clang) analyseert de levensduur van objecten en voegt automatisch retain/release/autorelease-aanroepen in. De ontwikkelaar hoeft ze niet handmatig aan te roepen — in tegenstelling tot Manual Retain-Release (MRR) vóór iOS 5. ARC werkt op het niveau van Objective-C-objecten en Swift class, maar niet voor value types (struct, enum).
Elk Objective-C-object en Swift class heeft een referentieteller (retain count), opgeslagen in het extra_rc-veld binnen non-pointer isa. Bij het maken van een object is retain count = 1. Bij retain neemt de teller toe, bij release neemt deze af. Wanneer de teller 0 bereikt, wordt het object gedealloceerd via dealloc (Objective-C) of deinit (Swift). ARC is thread-safe: retain/release gebruiken atomaire bewerkingen (OSAtomicIncrement32/OSAtomicDecrement32).
Retain cycles — het belangrijkste probleem van ARC. Als object A een strong referentie naar B heeft en B een strong referentie naar A, zullen beide objecten nooit worden gedealloceerd omdat hun referentietellers niet op nul komen. Oplossing — zwakke referenties (__weak in Objective-C, weak in Swift) of unowned. Zwakke referenties verhogen retain count niet en worden automatisch op nul gezet (nil) bij deallocatie van het object.
import Foundation
// Voorbeeld retain cycle
class Parent {
var child: Child?
deinit { print("Parent deallocated") }
}
class Child {
var parent: Parent? // strong — creëert retain cycle!
deinit { print("Child deallocated") }
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent // cyclus: Parent -> Child -> Parent
parent = nil
child = nil
// deinit wordt NIET aangeroepen — geheugenlek!
// Oplossing: weak
class WeakChild {
weak var parent: Parent? // weak — verhoogt retain count niet
deinit { print("WeakChild deallocated") }
}
// Oplossing: unowned (voor gegarandeerde levensduur)
class UnownedChild {
unowned let parent: Parent
init(parent: Parent) { self.parent = parent }
deinit { print("UnownedChild deallocated") }
}
// Controle via Instruments
func profileMemory() {
// 1. Start Instruments > Leaks
// 2. Voer actie uit die objecten creëert
// 3. Controleer Leaks op lekken
// 4. Vind in Allocations objecten zonder dealloc
for _ in 0..<1000 {
let p = Parent()
let c = WeakChild()
p.child = c as? Child
// c.parent = p — voegen NIET toe, weak
}
}Voorbeeld van een retain cycle tussen Parent en Child: beide hebben strong referenties naar elkaar, ARC kan de tellers niet op nul zetten. Oplossing — weak parent in Child. weak wordt automatisch op nul gezet bij deallocatie van parent. unowned — voor gevallen waarin de levensduur van parent gegarandeerd langer is dan die van child (bijv. viewController en view). Gebruik Instruments > Leaks voor het detecteren van retain cycles in een vroeg stadium.
Autorelease pool — mechanisme voor uitgestelde release van objecten die zonder expliciet eigendom zijn gemaakt. @autoreleasepool { } in Swift en Objective-C creëert een pool die aan het einde van het blok wordt geleegd, waarbij release naar elk object in de pool wordt gestuurd. Cruciaal in lussen (creëren van duizenden tijdelijke objecten) en op achtergrondthreads zonder RunLoop. UIKit RunLoop leegt automatisch de main autorelease pool bij elke iteratie.
dyld (dynamic link editor) — de systeemlader verantwoordelijk voor het laden van Mach-O uitvoerbare bestanden en bijbehorende dynamische bibliotheken (dylib) bij het starten van een iOS-applicatie. dyld bevindt zich op /usr/lib/dyld en maakt deel uit van libSystem. Het laadproces omvat verschillende fasen: parsen van Mach-O, laden van afhankelijkheden (Library Loader, LC_LOAD_DYLIB), relocatie van adressen (ASLR), initialisatie van Objective-C Runtime en aanroep van main().
De opstarttijd van de applicatie (launch time) is kritisch afhankelijk van dyld: hoe meer dynamische bibliotheken en Objective-C-klassen, hoe langer de pre-main time. Apple raadt aan het aantal +load-methoden (die vóór main worden uitgevoerd) te minimaliseren en te vervangen door +initialize (lazy initialisatie). Sinds 2020 gebruikt Apple prebuilt dyld cache op iOS: systeembibliotheken zijn vooraf gekoppeld in één cache, wat het laden versnelt.
import Foundation
// Meten van opstarttijd via DYLD_PRINT_STATISTICS
// In Xcode: Edit Scheme > Run > Arguments > Environment Variables
// DYLD_PRINT_STATISTICS = 1
// DYLD_PRINT_STATISTICS_DETAILS = 1
// Programmatisch meten van pre-main time
@main
struct AppMain {
static func main() {
let launchStart = CFAbsoluteTimeGetCurrent()
// UIApplicationMain vindt hier plaats
AppDelegate.main()
let launchEnd = CFAbsoluteTimeGetCurrent()
let preMainTime = launchEnd - launchStart
print("Pre-main time: (preMainTime) sec")
}
}
// Optimalisatie: vervangen van +load door +initialize
class OptimizedClass {
// ❌ +load wordt vóór main uitgevoerd
// override class func load() { }
// ✅ +initialize wordt bij eerste gebruik uitgevoerd
static let shared = OptimizedClass()
private init() {
// Initialisatie hier
}
}
// Optimalisatie van aantal dylibs
// Samenvoegen van statische bibliotheken vermindert aantal LC_LOAD_DYLIB
// Gebruik -ObjC-vlag voor het linken van alleen gebruikte Objective-C-klassen
// Xcode: Build Settings > Mach-O Type > Static LibraryGebruik DYLD_PRINT_STATISTICS in het Xcode-schema om pre-main time te meten. De uitvoer toont total time, dylib loading time, rebase/bind time, Objective-C setup time en initializer time. Doelwaarden: total < 400ms voor koude start, < 200ms voor warme start. Optimalisaties: samenvoegen van bibliotheken, vervangen van +load door +initialize, verminderen van het aantal Objective-C-klassen (gebruik Swift), minimaal aantal dynamische frameworks.
dyld shared cache — de cache van vooraf gekoppelde systeembibliotheken op iOS. Alle systeem-dylibs (UIKit, Foundation, CoreGraphics) zijn samengevoegd in één bestand: /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64. Dit elimineert de noodzaak om elke systeembibliotheek afzonderlijk te laden — dyld benadert de cache, wat het starten aanzienlijk versnelt. Applicaties met 10+ dynamische frameworks ondervinden de grootste vertraging omdat aangepaste dylibs geen deel uitmaken van de dsc.
Veelgestelde vragen
iOS Runtime — de runtime-omgeving voor applicaties op iOS, inclusief Objective-C Runtime (libobjc.dylib), Swift Runtime (libswiftCore.dylib), Cocoa Touch frameworks, dyld (dynamische lader) en ARC (geheugenbeheer). Het zorgt voor message passing voor Objective-C, statische dispatch voor Swift, laden van Mach-O-bestanden en automatisch geheugenbeheer.
Objective-C Runtime gebruikt dynamische binding via objc_msgSend (message passing) met late binding. Swift Runtime gebruikt statische dispatch (vtable voor klassen, direct call voor struct) voor prestaties. @objc dynamic schakelt Objective-C Runtime in voor Swift-klassen. Swift struct heeft geen isa-pointer en gebruikt geen retain/release.
ARC (Automatic Reference Counting) — geheugenbeheer tijdens de compilatiefase. De Clang-compiler voegt automatisch retain/release-aanroepen in. Elk object heeft een referentieteller; bij nul wordt dealloc aangeroepen. Retain cycles (wederzijdse strong referenties) worden voorkomen met weak/unowned referenties. Gebruik Instruments > Leaks voor het detecteren van lekken.
dyld — de dynamische lader van Mach-O-bestanden. Het laadt het uitvoerbare bestand en alle afhankelijke dylibs, voert relocatie (ASLR) uit, initialiseert Objective-C Runtime en roept main() aan. Pre-main time hangt af van het aantal dylibs en +load-methoden. Gebruik DYLD_PRINT_STATISTICS voor meting. Optimalisatie: samenvoegen van bibliotheken, vervangen van +load door +initialize.
Method Swizzling — techniek om de IMP (implementation pointer) van een methode ter plekke te vervangen via Objective-C Runtime class_getInstanceMethod en method_exchangeImplementations. Gebruikt voor A/B-testen, analyses (automatisch volgen van schermen) en monitoring. Niet aanbevolen in productie zonder uiterste noodzaak. In Swift vervangen door @objc dynamic + Method Swizzling.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook