iOS Runtime — běhové prostředí aplikací na operačním systému Apple iOS, zahrnující Objective-C Runtime, Swift Runtime, frameworky Cocoa Touch a mechanismy správy paměti prostřednictvím Automatic Reference Counting (ARC). iOS Runtime se stará o dynamické vázání metod (message passing), načítání tříd, správu paměti a interakci s hardwarem prostřednictvím iOS frameworků. Podle Apple Developer Documentation je porozumění runtime nezbytné pro optimalizaci výkonu, ladění a vývoj stabilních iOS aplikací.
Hlavní body
iOS Runtime — soubor systémových komponent zajišťujících provádění aplikací na zařízeních Apple pod iOS. Zahrnuje Objective-C Runtime (knihovna libobjc.A.dylib), Swift Runtime (libswiftCore.dylib), Core Foundation, frameworky Cocoa Touch (UIKit, Foundation), dynamický zavaděč dyld a runtime prostředí pro správu paměti, vláken a meziprocesové komunikace.
Architektonicky iOS Runtime pracuje na třech úrovních. Na spodní úrovni — binární formát Mach-O a dyld, které načítá spustitelný soubor a knihovny. Střední úroveň — Objective-C Runtime a Swift Runtime, odpovědné za odesílání metod a správu objektů. Horní úroveň — frameworky Cocoa Touch (UIKit, Foundation, Core Data, Metal), poskytující API pro vývojáře.
Porozumění iOS Runtime umožňuje vývojáři řešit složité úkoly: swizzling metod (Method Swizzling) pro A/B testování a analytiku, dynamické načítání tříd, optimalizaci paměti prostřednictvím porozumění ARC, ladění retain cycles a úniků paměti, optimalizaci doby spouštění aplikace prostřednictvím dyld. Bez znalosti runtime není profilování a optimalizace na úrovni systému možné.
| Komponenta | Knihovna | Účel |
|---|---|---|
| Objective-C Runtime | libobjc.A.dylib | Message passing, dynamické třídy, swizzling |
| Swift Runtime | libswiftCore.dylib | Value types, generics, protocol witnesses |
| Core Foundation | CoreFoundation.framework | CFType, toll-free bridging |
| dyld | dyld (usr/lib/dyld) | Načítání Mach-O, vázání knihoven |
| libSystem | libSystem.B.dylib | POSIX threads, libc, libdispatch (GCD) |
Aplikace pro iOS jsou kompilovány do formátu Mach-O (Mach Object). Soubor Mach-O obsahuje hlavičku (header), příkazy načítání (load commands) a segmenty (segments): __TEXT (kód, konstanty), __DATA (globální proměnné, metadata Objective-C), __LINKEDIT (symboly, tabulky relokace). dyld analyzuje Mach-O a načítá závislosti před provedením první instrukce.
Objective-C Runtime — nejvýkonnější část iOS Runtime. Na rozdíl od C++ s časným vázáním (early binding), Objective-C používá pozdní vázání (late binding) prostřednictvím message passing. Volání metody [receiver message] není kompilováno jako přímé volání funkce, ale jako objc_msgSend(receiver, @selector(message)), který dynamicky najde implementaci metody ve třídě objektu.
Každý objekt Objective-C ukládá ukazatel isa na svou třídu. Třída obsahuje seznam metod (method list), mezipaměť metod (method cache) a ukazatel na nadtřídu. objc_msgSend prochází řetězec dědičnosti: kontroluje mezipaměť třídy, poté method list, pak přechází k nadtřídě. Pokud metoda není nalezena, spustí se forward: resolveInstanceMethod, forwardingTargetForSelector a forwardInvocation.
Method Swizzling — technika výměny implementace metody za běhu prostřednictvím výměny IMP (implementation pointer) v runtime. Používá se pro A/B testování, analytiku (automatické sledování obrazovek) a monitorování. Nedoporučuje se v produkci bez krajní nutnosti, protože může kolidovat s aktualizacemi operačního systému.
// Method Swizzling pro sledování 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 {
// Sledování události
NSLog(@"View Did Load: %@", self.class);
// Volání původní implementace
[self swizzled_viewDidLoad];
}
@end Kategorie UIViewController (Tracking) nahrazuje viewDidLoad za swizzled_viewDidLoad ve všech UIViewController v aplikaci. dispatch_once zaručuje jednorázové swizzling. class_addMethod zabraňuje dvojitému swizzlingu a konfliktům s nadtřídami. Používá se pro automatické sledování zobrazení obrazovek v analytice bez změny zdrojového kódu ovladačů.
V moderním iOS (arm64) Apple optimalizoval ukazatel isa: není to jen adresa třídy, ale bitové pole (non-pointer isa) obsahující příznaky správy paměti a informace o třídě. Tagged pointers — další optimalizace: hodnoty NSNumber, NSDate a NSString malé velikosti se neukládají jako objekty na haldě, ale přímo v ukazateli, čímž se eliminuje režie malloc a retain/release. tagged pointer je rozpoznán podle nejméně významného bitu isa.
Swift Runtime se zásadně liší od Objective-C Runtime: Swift ve výchozím nastavení používá statické odesílání (static dispatch) prostřednictvím vtable pro metody tříd a direct call pro value types a extension methods. Dynamické odesílání (dynamic dispatch) se používá pouze pro metody označené @objc nebo dynamic. To poskytuje zvýšení výkonu až o 40 % ve srovnání s Objective-C.
Value types (struct, enum) ve Swiftu — klíčový rozdíl oproti Objective-C. Ukládají se na zásobníku (stack) nebo uvnitř jiného objektu, nepoužívají retain/release a neúčastní se ARC pro počítadlo odkazů. Struct nemá ukazatel isa a nelze jej odeslat prostřednictvím objc_msgSend. Protocol witnesses — obdoba vtable pro protokoly, umožňující dynamické odesílání pro existential container.
Swift Runtime také zahrnuje generics s reifikací (reified generics prostřednictvím mangled symbols) a COW (Copy-on-Write) pro optimalizaci string, array, dictionary, set. Při kopírování kolekcí dochází ke skutečnému kopírování pouze při úpravě jedné z kopií. To minimalizuje režii při předávání kolekcí mezi funkcemi.
import Foundation
// Swift: statické odesílání (vtable pro 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") }
}
// Použití existential container
let soundMakers: [SoundMaker] = [Dog(), Cow(), Duck()]
for maker in soundMakers {
maker.makeSound() // protocol witness dispatch
}
// Testování výkonu
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")
}Příklad demonstruje tři typy odesílání v Swift: vtable pro class (Dog), objc_msgSend pro @objc dynamic (Cat) a direct call pro struct (Cow). Protocol witnesses v existential container ([SoundMaker]) přidávají režii. V praxi Swift volí statické odesílání všude, kde je to možné, a poskytuje výkon blízký C.
Swift Runtime je navržen pro plnou kompatibilitu s Objective-C Runtime. Každá třída Swift dědící NSObject se automaticky zaregistruje v Objective-C Runtime a může být volána prostřednictvím objc_msgSend. Atribut @objc zpřístupňuje metodu Swift z Objective-C. Most String: Swift String je automaticky přemostěn na NSString při předávání do Objective-C API (toll-free bridging).
ARC (Automatic Reference Counting) — systém správy paměti v iOS, který pracuje ve fázi kompilace. Kompilátor (Clang) analyzuje životní cyklus objektů a automaticky vkládá volání retain/release/autorelease. Vývojář je nemusí volat ručně — na rozdíl od Manual Retain-Release (MRR) před iOS 5. ARC pracuje na úrovni objektů Objective-C a Swift class, ale ne pro value types (struct, enum).
Každý objekt Objective-C a třída Swift má počítadlo odkazů (retain count), uložené v poli extra_rc uvnitř non-pointer isa. Při vytvoření objektu je retain count = 1. Při retain se počítadlo zvyšuje, při release snižuje. Když počítadlo dosáhne 0, objekt je dealokován prostřednictvím dealloc (Objective-C) nebo deinit (Swift). ARC je thread-safe: retain/release používají atomické operace (OSAtomicIncrement32/OSAtomicDecrement32).
Retain cycles — hlavní problém ARC. Pokud objekt A ukládá strong odkaz na B a B — strong odkaz na A, oba objekty nebudou nikdy dealokovány, protože jejich počítadla odkazů se nevynulují. Řešení — slabé odkazy (__weak v Objective-C, weak ve Swift) nebo unowned. Slabé odkazy nezvyšují retain count a automaticky se nulují (nil) při dealokaci objektu.
import Foundation
// Příklad retain cycle
class Parent {
var child: Child?
deinit { print("Parent deallocated") }
}
class Child {
var parent: Parent? // strong — vytváří retain cycle!
deinit { print("Child deallocated") }
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent // cyklus: Parent -> Child -> Parent
parent = nil
child = nil
// deinit se NEVOLÁ — únik paměti!
// Oprava: weak
class WeakChild {
weak var parent: Parent? // weak — nezvyšuje retain count
deinit { print("WeakChild deallocated") }
}
// Oprava: unowned (pro zaručenou životnost)
class UnownedChild {
unowned let parent: Parent
init(parent: Parent) { self.parent = parent }
deinit { print("UnownedChild deallocated") }
}
// Kontrola pomocí Instruments
func profileMemory() {
// 1. Spusťte Instruments > Leaks
// 2. Proveďte akci vytvářející objekty
// 3. Zkontrolujte Leaks na úniky
// 4. V Allocations najděte objekty bez dealloc
for _ in 0..<1000 {
let p = Parent()
let c = WeakChild()
p.child = c as? Child
// c.parent = p — NEPŘIDÁVÁME, weak
}
}Příklad retain cycle mezi Parent a Child: oba mají strong odkazy na sebe navzájem, ARC nemůže vynulovat počítadla. Oprava — weak parent v Child. weak se automaticky vynuluje při dealokaci parent. unowned — pro případy, kdy je životnost parent zaručeně delší než child (např. viewController a view). Použijte Instruments > Leaks k detekci retain cycles v rané fázi.
Autorelease pool — mechanismus odloženého release pro objekty vytvořené bez explicitního vlastnictví. @autoreleasepool { } ve Swift a Objective-C vytváří fond, který je na konci bloku vyprázdněn a odesílá release každému objektu ve fondu. Kriticky důležité ve smyčkách (vytváření tisíců dočasných objektů) a na vláknech na pozadí bez RunLoop. UIKit RunLoop automaticky vyprazdňuje hlavní autorelease pool při každé iteraci.
dyld (dynamic link editor) — systémový zavaděč zodpovědný za načítání spustitelných souborů Mach-O a souvisejících dynamických knihoven (dylib) při spouštění iOS aplikace. dyld se nachází na cestě /usr/lib/dyld a je součástí libSystem. Proces načítání zahrnuje několik fází: parsování Mach-O, načítání závislostí (Library Loader, LC_LOAD_DYLIB), relokaci adres (ASLR), inicializaci Objective-C Runtime a volání main().
Doba spouštění aplikace (launch time) kriticky závisí na dyld: čím více dynamických knihoven a tříd Objective-C, tím delší je pre-main time. Apple doporučuje minimalizovat počet metod +load (provádějí se před main), nahrazovat je +initialize (líná inicializace). Od roku 2020 Apple používá předem sestavenou dyld cache na iOS: systémové knihovny jsou předem propojeny do jediné mezipaměti, což urychluje načítání.
import Foundation
// Měření doby spouštění pomocí DYLD_PRINT_STATISTICS
// V Xcode: Edit Scheme > Run > Arguments > Environment Variables
// DYLD_PRINT_STATISTICS = 1
// DYLD_PRINT_STATISTICS_DETAILS = 1
// Programové měření pre-main time
@main
struct AppMain {
static func main() {
let launchStart = CFAbsoluteTimeGetCurrent()
// UIApplicationMain nastává zde
AppDelegate.main()
let launchEnd = CFAbsoluteTimeGetCurrent()
let preMainTime = launchEnd - launchStart
print("Pre-main time: (preMainTime) sec")
}
}
// Optimalizace: nahrazení +load za +initialize
class OptimizedClass {
// ❌ +load se provádí před main
// override class func load() { }
// ✅ +initialize se provádí při prvním použití
static let shared = OptimizedClass()
private init() {
// Inicializace zde
}
}
// Optimalizace počtu dylib
// Slučování statických knihoven snižuje počet LC_LOAD_DYLIB
// Použijte přepínač -ObjC pro linkování pouze používaných tříd Objective-C
// Xcode: Build Settings > Mach-O Type > Static LibraryPro měření pre-main time použijte DYLD_PRINT_STATISTICS ve schématu Xcode. Výstup zobrazí total time, dylib loading time, rebase/bind time, Objective-C setup time a initializer time. Cílové hodnoty: total < 400 ms pro studený start, < 200 ms pro teplý start. Optimalizace: slučování knihoven, nahrazení +load za +initialize, snížení počtu tříd Objective-C (použijte Swift), minimální počet dynamických frameworků.
dyld shared cache — mezipaměť předem propojených systémových knihoven na iOS. Všechny systémové dylib (UIKit, Foundation, CoreGraphics) jsou sloučeny do jednoho souboru: /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64. Tím odpadá nutnost načítat každou systémovou knihovnu samostatně — dyld přistupuje k mezipaměti, což výrazně urychluje spouštění. Aplikace s 10+ dynamickými frameworky zaznamenávají největší zpoždění, protože vlastní dylib nejsou součástí dsc.
Často kladené otázky
iOS Runtime — běhové prostředí aplikací na iOS, zahrnující Objective-C Runtime (libobjc.dylib), Swift Runtime (libswiftCore.dylib), frameworky Cocoa Touch, dyld (dynamický zavaděč) a ARC (správa paměti). Poskytuje message passing pro Objective-C, statické odesílání pro Swift, načítání souborů Mach-O a automatickou správu paměti.
Objective-C Runtime používá dynamické vázání prostřednictvím objc_msgSend (message passing) s pozdním vázáním. Swift Runtime používá statické odesílání (vtable pro třídy, direct call pro struct) pro výkon. @objc dynamic aktivuje Objective-C Runtime pro třídy Swift. Swift struct nemá ukazatel isa a nepoužívá retain/release.
ARC (Automatic Reference Counting) — správa paměti ve fázi kompilace. Kompilátor Clang automaticky vkládá volání retain/release. Každý objekt má počítadlo odkazů, při jehož vynulování je volán dealloc. Retain cycles (vzájemné strong odkazy) se předchází weak/unowned odkazy. Pro detekci úniků použijte Instruments > Leaks.
dyld — dynamický zavaděč souborů Mach-O. Načítá spustitelný soubor a všechny závislé dylib, provádí relokaci (ASLR), inicializuje Objective-C Runtime a volá main(). Pre-main time závisí na počtu dylib a metod +load. Pro měření použijte DYLD_PRINT_STATISTICS. Optimalizace: slučování knihoven, nahrazení +load za +initialize.
Method Swizzling — technika výměny IMP (implementation pointer) metody za běhu prostřednictvím Objective-C Runtime class_getInstanceMethod a method_exchangeImplementations. Používá se pro A/B testování, analytiku (automatické sledování obrazovek) a monitorování. Nedoporučuje se v produkci bez krajní nutnosti. Ve Swiftu je nahrazeno @objc dynamic + Method Swizzling.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také