iOS Runtime — körningsmiljön för applikationer på operativsystemet Apple iOS, som inkluderar Objective-C Runtime, Swift Runtime, Cocoa Touch-ramverk och minneshanteringsmekanismer via Automatic Reference Counting (ARC). iOS Runtime ansvarar för dynamisk metodbindning (message passing), laddning av klasser, minneshantering och interaktion med hårdvara via iOS-ramverk. Enligt Apple Developer Documentation är kunskap om runtime nödvändig för prestandaoptimering, felsökning och utveckling av stabila iOS-applikationer.
Huvudpunkter
iOS Runtime — samlingen av systemkomponenter som säkerställer exekvering av applikationer på Apple-enheter under iOS. Inkluderar Objective-C Runtime (biblioteket libobjc.A.dylib), Swift Runtime (libswiftCore.dylib), Core Foundation, Cocoa Touch-ramverk (UIKit, Foundation), den dynamiska laddaren dyld och en körningsmiljö för hantering av minne, trådar och interprocesskommunikation.
Arkitektoniskt fungerar iOS Runtime på tre nivåer. På den lägre nivån — Mach-O binärformatet och dyld, som laddar den körbara filen och bibliotek. Mellannivån — Objective-C Runtime och Swift Runtime, ansvariga för sändning av metoder och objekthantering. Den övre nivån — Cocoa Touch-ramverk (UIKit, Foundation, Core Data, Metal), som tillhandahåller API för utvecklaren.
Kunskap om iOS Runtime gör det möjligt för utvecklaren att lösa komplexa uppgifter: metod-swizzling (Method Swizzling) för A/B-testning och analys, dynamisk laddning av klasser, minnesoptimering genom förståelse av ARC, felsökning av retain cycles och minnesläckor, optimering av applikationens starttid via dyld. Utan runtime-kunskap är profilering och optimering på systemnivå omöjligt.
| Komponent | Bibliotek | Syfte |
|---|---|---|
| Objective-C Runtime | libobjc.A.dylib | Message passing, dynamiska klasser, swizzling |
| Swift Runtime | libswiftCore.dylib | Value types, generics, protocol witnesses |
| Core Foundation | CoreFoundation.framework | CFType, toll-free bridging |
| dyld | dyld (usr/lib/dyld) | Laddning av Mach-O, bindning av bibliotek |
| libSystem | libSystem.B.dylib | POSIX threads, libc, libdispatch (GCD) |
Applikationer för iOS kompileras till Mach-O-format (Mach Object). En Mach-O-fil innehåller header (header), laddningskommandon (load commands) och segment (segments): __TEXT (kod, konstanter), __DATA (globala variabler, Objective-C-metadata), __LINKEDIT (symboler, relokeringstabeller). dyld analyserar Mach-O och laddar beroenden innan den första instruktionen exekveras.
Objective-C Runtime — den mest kraftfulla delen av iOS Runtime. Till skillnad från C++ med tidig bindning (early binding) använder Objective-C sen bindning (late binding) via message passing. Metodanropet [receiver message] kompileras inte som ett direkt funktionsanrop, utan som objc_msgSend(receiver, @selector(message)), som dynamiskt hittar metodimplementeringen i objektets klass.
Varje Objective-C-objekt lagrar en isa-pekare till sin klass. Klassen innehåller en metodlista (method list), en metodcache (method cache) och en pekare till superklassen. objc_msgSend går igenom arvskedjan: kontrollerar klassens cache, sedan method list, därefter går den till superklassen. Om metoden inte hittas aktiveras forward: resolveInstanceMethod, forwardingTargetForSelector och forwardInvocation.
Method Swizzling — teknik för att byta ut metodimplementering på plats genom att byta IMP (implementation pointer) i runtime. Används för A/B-testning, analys (automatisk spårning av skärmar) och övervakning. Rekommenderas inte i produktion utan yttersta nödvändighet, eftersom det kan komma i konflikt med uppdateringar av operativsystemet.
// Method Swizzling för att spåra 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 {
// Spårning av händelse
NSLog(@"View Did Load: %@", self.class);
// Anrop av ursprunglig implementering
[self swizzled_viewDidLoad];
}
@end Kategorin UIViewController (Tracking) ersätter viewDidLoad med swizzled_viewDidLoad i alla UIViewController i applikationen. dispatch_once garanterar engångs-swizzling. class_addMethod förhindrar dubbel swizzling och konflikter med superklasser. Används för automatisk spårning av skärmvisning i analys utan att ändra källkoden för kontrollerna.
I modern iOS (arm64) har Apple optimerat isa-pekaren: det är inte bara en klassadress, utan ett bitfält (non-pointer isa) som innehåller flaggor för minneshantering och information om klassen. Tagged pointers — en annan optimering: små NSNumber-, NSDate- och NSString-värden lagras inte som objekt på heapen, utan direkt i pekaren, vilket eliminerar overhead från malloc och retain/release. tagged pointer känns igen av den minst signifikanta biten i isa.
Swift Runtime skiljer sig fundamentalt från Objective-C Runtime: Swift använder som standard statisk sändning (static dispatch) via vtable för klassmetoder och direct call för value types och extension methods. Dynamisk sändning (dynamic dispatch) används endast för metoder markerade med @objc eller dynamic. Detta ger en prestandaökning på upp till 40 % jämfört med Objective-C.
Value types (struct, enum) i Swift — den viktigaste skillnaden från Objective-C. De lagras på stacken (stack) eller inuti ett annat objekt, använder inte retain/release och deltar inte i ARC för referensräknaren. Struct har ingen isa-pekare och kan inte skickas via objc_msgSend. Protocol witnesses — motsvarigheten till vtable för protokoll, som möjliggör dynamisk sändning för existential container.
Swift Runtime inkluderar också generics med reifiering (reified generics via mangled symbols) och COW (Copy-on-Write) för optimering av string, array, dictionary, set. Vid kopiering av en samling sker den faktiska kopieringen endast när en av kopiorna ändras. Detta minimerar overhead vid överföring av samlingar mellan funktioner.
import Foundation
// Swift: statisk sändning (vtable för 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") }
}
// Användning av existential container
let soundMakers: [SoundMaker] = [Dog(), Cow(), Duck()]
for maker in soundMakers {
maker.makeSound() // protocol witness dispatch
}
// Prestandatestning
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")
}Exemplet visar tre typer av sändning i Swift: vtable för class (Dog), objc_msgSend för @objc dynamic (Cat) och direct call för struct (Cow). Protocol witnesses i existential container ([SoundMaker]) lägger till overhead. I praktiken väljer Swift statisk sändning där det är möjligt och ger prestanda nära C.
Swift Runtime är designat för full kompatibilitet med Objective-C Runtime. Varje Swift-klass som ärver NSObject registreras automatiskt i Objective-C Runtime och kan anropas via objc_msgSend. Attributet @objc gör en Swift-metod tillgänglig från Objective-C. String-brygga: Swift String bryggas automatiskt till NSString vid överföring till Objective-C API (toll-free bridging).
ARC (Automatic Reference Counting) — minneshanteringssystem i iOS, som fungerar vid kompileringsstadiet. Kompilatorn (Clang) analyserar objektens livscykel och infogar automatiskt anrop till retain/release/autorelease. Utvecklaren behöver inte anropa dem manuellt — till skillnad från Manual Retain-Release (MRR) före iOS 5. ARC fungerar på nivån för Objective-C-objekt och Swift class, men inte för value types (struct, enum).
Varje Objective-C-objekt och Swift-klass har en referensräknare (retain count), lagrad i fältet extra_rc inuti non-pointer isa. Vid skapande av objekt är retain count = 1. Vid retain ökar räknaren, vid release minskar den. När räknaren når 0 frigörs objektet via dealloc (Objective-C) eller deinit (Swift). ARC är trådsäkert: retain/release använder atomära operationer (OSAtomicIncrement32/OSAtomicDecrement32).
Retain cycles — det största problemet med ARC. Om objekt A lagrar en strong referens till B, och B — en strong referens till A, kommer båda objekten aldrig att frigöras eftersom deras referensräknare inte kommer att nollställas. Lösning — svaga referenser (__weak i Objective-C, weak i Swift) eller unowned. Svaga referenser ökar inte retain count och nollställs automatiskt (nil) vid frigöring av objektet.
import Foundation
// Exempel på retain cycle
class Parent {
var child: Child?
deinit { print("Parent deallocated") }
}
class Child {
var parent: Parent? // strong — skapar retain cycle!
deinit { print("Child deallocated") }
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent // cykel: Parent -> Child -> Parent
parent = nil
child = nil
// deinit anropas INTE — minnesläcka!
// Åtgärd: weak
class WeakChild {
weak var parent: Parent? // weak — ökar inte retain count
deinit { print("WeakChild deallocated") }
}
// Åtgärd: unowned (för garanterad livslängd)
class UnownedChild {
unowned let parent: Parent
init(parent: Parent) { self.parent = parent }
deinit { print("UnownedChild deallocated") }
}
// Kontroll via Instruments
func profileMemory() {
// 1. Starta Instruments > Leaks
// 2. Utför åtgärd som skapar objekt
// 3. Kontrollera Leaks för läckor
// 4. I Allocations hitta objekt utan dealloc
for _ in 0..<1000 {
let p = Parent()
let c = WeakChild()
p.child = c as? Child
// c.parent = p — lägger INTE till, weak
}
}Exempel på retain cycle mellan Parent och Child: båda har strong referenser till varandra, ARC kan inte nollställa räknarna. Åtgärd — weak parent i Child. weak nollställs automatiskt vid frigöring av parent. unowned — för fall där parentens livslängd garanterat är längre än childs (t.ex. viewController och view). Använd Instruments > Leaks för att upptäcka retain cycles i ett tidigt skede.
Autorelease pool — mekanism för fördröjd release för objekt som skapats utan explicit ägande. @autoreleasepool { } i Swift och Objective-C skapar en pool som töms i slutet av bloccket och skickar release till varje objekt i poolen. Kritisk i loopar (skapande av tusentals temporära objekt) och på bakgrundstrådar utan RunLoop. UIKit RunLoop tömmer automatiskt den huvudsakliga autorelease-poolen vid varje iteration.
dyld (dynamic link editor) — systemladdaren som ansvarar för att ladda Mach-O körbara filer och tillhörande dynamiska bibliotek (dylib) vid start av iOS-applikation. dyld finns på sökvägen /usr/lib/dyld och är en del av libSystem. Laddningsprocessen omfattar flera steg: tolkning av Mach-O, laddning av beroenden (Library Loader, LC_LOAD_DYLIB), relokering av adresser (ASLR), initiering av Objective-C Runtime och anrop av main().
Applikationens starttid (launch time) är kritiskt beroende av dyld: ju fler dynamiska bibliotek och Objective-C-klasser, desto längre pre-main time. Apple rekommenderar att minimera antalet +load-metoder (de exekveras före main) och ersätta dem med +initialize (lat initiering). Sedan 2020 använder Apple förbyggd dyld cache på iOS: systembibliotek är förlänkade i en enda cache, vilket snabbar upp laddningen.
import Foundation
// Mätning av starttid via DYLD_PRINT_STATISTICS
// I Xcode: Edit Scheme > Run > Arguments > Environment Variables
// DYLD_PRINT_STATISTICS = 1
// DYLD_PRINT_STATISTICS_DETAILS = 1
// Programmatisk mätning av pre-main time
@main
struct AppMain {
static func main() {
let launchStart = CFAbsoluteTimeGetCurrent()
// UIApplicationMain sker här
AppDelegate.main()
let launchEnd = CFAbsoluteTimeGetCurrent()
let preMainTime = launchEnd - launchStart
print("Pre-main time: (preMainTime) sec")
}
}
// Optimering: ersättning av +load med +initialize
class OptimizedClass {
// ❌ +load exekveras före main
// override class func load() { }
// ✅ +initialize exekveras vid första användning
static let shared = OptimizedClass()
private init() {
// Initiering här
}
}
// Optimering av antal dylib
// Sammanslagning av statiska bibliotek minskar antalet LC_LOAD_DYLIB
// Använd flaggan -ObjC för att länka endast använda Objective-C-klasser
// Xcode: Build Settings > Mach-O Type > Static LibraryFör att mäta pre-main time använd DYLD_PRINT_STATISTICS i Xcode-schemat. Utdata visar total time, dylib loading time, rebase/bind time, Objective-C setup time och initializer time. Målvärden: total < 400 ms för kallstart, < 200 ms för varmstart. Optimeringar: sammanslagning av bibliotek, ersättning av +load med +initialize, minskning av antalet Objective-C-klasser (använd Swift), minimalt antal dynamiska ramverk.
dyld shared cache — cachen för förlänkade systembibliotek på iOS. Alla system-dylib (UIKit, Foundation, CoreGraphics) är sammanslagna i en fil: /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64. Detta eliminerar behovet av att ladda varje systembibliotek separat — dyld får åtkomst till cachen, vilket avsevärt snabbar upp starten. Applikationer med 10+ dynamiska ramverk upplever den största fördröjningen eftersom anpassade dylib inte ingår i dsc.
Vanliga frågor
iOS Runtime — körningsmiljön för applikationer på iOS, inklusive Objective-C Runtime (libobjc.dylib), Swift Runtime (libswiftCore.dylib), Cocoa Touch-ramverk, dyld (dynamisk laddare) och ARC (minneshantering). Tillhandahåller message passing för Objective-C, statisk sändning för Swift, laddning av Mach-O-filer och automatisk minneshantering.
Objective-C Runtime använder dynamisk bindning via objc_msgSend (message passing) med sen bindning. Swift Runtime använder statisk sändning (vtable för klasser, direct call för struct) för prestanda. @objc dynamic aktiverar Objective-C Runtime för Swift-klasser. Swift struct har ingen isa-pekare och använder inte retain/release.
ARC (Automatic Reference Counting) — minneshantering vid kompileringsstadiet. Clang-kompilatorn infogar automatiskt anrop till retain/release. Varje objekt har en referensräknare; vid noll anropas dealloc. Retain cycles (ömsesidiga strong-referenser) förhindras med weak/unowned-referenser. Använd Instruments > Leaks för att upptäcka läckor.
dyld — dynamisk laddare av Mach-O-filer. Laddar den körbara filen och alla beroende dylib, utför relokering (ASLR), initierar Objective-C Runtime och anropar main(). Pre-main time beror på antalet dylib och +load-metoder. Använd DYLD_PRINT_STATISTICS för mätning. Optimering: sammanslagning av bibliotek, ersättning av +load med +initialize.
Method Swizzling — teknik för att byta ut en metods IMP (implementation pointer) på plats via Objective-C Runtime class_getInstanceMethod och method_exchangeImplementations. Används för A/B-testning, analys (automatisk skärmspårning) och övervakning. Rekommenderas inte i produktion utan yttersta nödvändighet. I Swift ersätts det med @objc dynamic + Method Swizzling.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också