本大綱針對(duì)資深 iOS 工程師,聚焦Runtime核心原理、底層實(shí)現(xiàn)、實(shí)戰(zhàn)應(yīng)用及面試高頻考點(diǎn),兼顧技術(shù)深度與面試適配性,涵蓋“基礎(chǔ)原理-底層結(jié)構(gòu)-核心機(jī)制-實(shí)戰(zhàn)場(chǎng)景-面試真題”五大模塊,助力系統(tǒng)復(fù)習(xí)與面試突破。
一、Runtime核心基礎(chǔ)(面試基礎(chǔ)必問(wèn))
1.1 Runtime本質(zhì)與作用
核心定義:Runtime是Objective-C的運(yùn)行時(shí)系統(tǒng),包含一套C語(yǔ)言API,負(fù)責(zé)OC語(yǔ)言的動(dòng)態(tài)特性實(shí)現(xiàn)(動(dòng)態(tài)類型、動(dòng)態(tài)綁定、動(dòng)態(tài)加載),是OC語(yǔ)言“編譯時(shí)確定部分,運(yùn)行時(shí)確定部分”的核心支撐
核心價(jià)值:
- 使OC具備動(dòng)態(tài)特性:運(yùn)行時(shí)確定對(duì)象類型、綁定方法實(shí)現(xiàn)、修改類結(jié)構(gòu)
- 為上層框架提供底層支撐:KVO、Category、Block、屬性自動(dòng)合成等功能的底層實(shí)現(xiàn)依賴Runtime
- 支持跨語(yǔ)言交互:OC與C/C++、Swift的混編底層銜接
Runtime源碼關(guān)聯(lián):熟悉objc4源碼結(jié)構(gòu)(開(kāi)源地址:opensource.apple.com),核心文件:objc.h、objc-runtime.h、runtime.h
面試考點(diǎn):Runtime的核心作用?OC為什么是動(dòng)態(tài)語(yǔ)言?Runtime與編譯期的區(qū)別?
1.2 編譯期與運(yùn)行時(shí)的區(qū)別
編譯期:完成詞法/語(yǔ)法分析、類型檢查、生成IR、編譯為Mach-O二進(jìn)制文件,確定靜態(tài)可知信息(如類的聲明、方法聲明)
運(yùn)行時(shí):由Runtime系統(tǒng)主導(dǎo),完成類的初始化、對(duì)象創(chuàng)建、方法查找與調(diào)用、動(dòng)態(tài)修改類結(jié)構(gòu)等動(dòng)態(tài)操作
典型案例對(duì)比:
- 編譯期:`int a = 10;` 確定變量類型與初始值
- 運(yùn)行時(shí):`id obj = [NSObject new];` 對(duì)象類型僅在運(yùn)行時(shí)確定;`[obj performSelector:@selector(test)]` 方法調(diào)用在運(yùn)行時(shí)綁定實(shí)現(xiàn)
- 面試考點(diǎn):舉一個(gè)編譯期與運(yùn)行時(shí)差異的例子?Runtime如何彌補(bǔ)編譯期的局限性?
二、Runtime底層核心結(jié)構(gòu)(深度核心,面試高頻)
2.1 objc_class結(jié)構(gòu)體(Runtime核心)
-
最新objc_class結(jié)構(gòu)(objc4-818.2版本):
`struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; // 指向元類
if !OBJC2
Class _Nullable super_class OBJC2_UNAVAILABLE; // 父類
const char * _Nonnull name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; // 實(shí)例變量列表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; // 方法列表(可變)
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; // 協(xié)議列表
endif
} OBJC2_UNAVAILABLE;`
- 核心成員深度解析:
- isa指針:
- 作用:關(guān)聯(lián)類與實(shí)例,實(shí)例的isa指向類,類的isa指向元類(Meta Class),元類的isa指向根元類,根元類的isa指向自身
- isa_mask:64位系統(tǒng)下isa包含更多信息(如引用計(jì)數(shù)、是否被弱引用),通過(guò)掩碼提取類信息(OBJC_ISA_MASK = 0x00007ffffffffff8ULL)
- 面試考點(diǎn):isa指針的指向鏈條?元類的作用?如何通過(guò)isa獲取類信息?
- cache_t(方法緩存):
- 結(jié)構(gòu):哈希表實(shí)現(xiàn),存儲(chǔ)最近調(diào)用的方法(SEL-IMP映射),目的是提升方法查找效率
- 緩存策略:LRU淘汰機(jī)制,當(dāng)緩存滿時(shí)清理不常用方法;方法調(diào)用時(shí)優(yōu)先查緩存,未命中再查方法列表
- 面試考點(diǎn):cache_t的實(shí)現(xiàn)原理?為什么要設(shè)計(jì)方法緩存?緩存的淘汰策略是什么?
- methodLists:可變數(shù)組,存儲(chǔ)類的實(shí)例方法/類方法,Category的方法會(huì)被附加到該列表(編譯期不合并,運(yùn)行時(shí)合并)
- ivars:實(shí)例變量列表,存儲(chǔ)成員變量的類型、名稱、偏移量,屬性的實(shí)例變量由編譯器自動(dòng)合成并加入該列表
2.2 元類(Meta Class)深度理解
核心定義:元類是“類的類”,類對(duì)象的isa指向元類,元類中存儲(chǔ)類方法(+方法)的實(shí)現(xiàn)
元類的繼承鏈條:
- 子類元類的父類是父類的元類
- 根類(NSObject)的元類的父類是根類本身
- 根元類的isa指向自身
核心作用:統(tǒng)一方法調(diào)用機(jī)制(實(shí)例調(diào)用-方法查類,類調(diào)用-方法查元類),使類方法的調(diào)用邏輯與實(shí)例方法一致
面試考點(diǎn):元類存在的意義?類方法存儲(chǔ)在哪里?如何調(diào)用元類中的方法?
2.3 核心數(shù)據(jù)結(jié)構(gòu)(SEL、IMP、Method、Ivar、Property)
-
SEL(選擇器):
本質(zhì):字符串的哈希值,唯一標(biāo)識(shí)方法名(相同方法名的SEL相同,與參數(shù)類型無(wú)關(guān))
創(chuàng)建與獲取:
@selector(test)編譯期創(chuàng)建、NSSelectorFromString(@"test")運(yùn)行時(shí)創(chuàng)建、sel_getName(SEL)獲取方法名字符串注意:SEL僅區(qū)分方法名,不區(qū)分參數(shù)和返回值(如
- (void)test和- (int)test的SEL相同,會(huì)導(dǎo)致方法覆蓋)
IMP(方法實(shí)現(xiàn)指針):
- 本質(zhì):函數(shù)指針,指向方法的具體實(shí)現(xiàn)代碼,格式為`id (*IMP)(id, SEL, ...)`(第一個(gè)參數(shù)是self,第二個(gè)是SEL,后續(xù)是方法參數(shù))
- 核心價(jià)值:通過(guò)IMP可直接調(diào)用方法,繞過(guò)Runtime的方法查找流程,提升性能
- Method(方法結(jié)構(gòu)體):
- 結(jié)構(gòu):包含SEL(方法名)、IMP(方法實(shí)現(xiàn))、types(方法簽名,描述參數(shù)和返回值類型)
- 核心API:`class_getInstanceMethod`(獲取實(shí)例方法)、`class_getClassMethod`(獲取類方法)、`method_getImplementation`(獲取IMP)
- Ivar(實(shí)例變量):
- 結(jié)構(gòu):存儲(chǔ)成員變量的名稱、類型、偏移量(offset),通過(guò)偏移量可直接訪問(wèn)成員變量的值
- 核心API:`class_copyIvarList`(獲取類的所有實(shí)例變量)、`ivar_getName`(獲取變量名)、`object_getIvar`(獲取變量值)
- Property(屬性):
- 與Ivar的區(qū)別:Property是封裝后的成員變量,包含setter/getter方法,編譯器自動(dòng)合成實(shí)例變量(_屬性名)
- 核心API:`class_copyPropertyList`(獲取類的所有屬性)、`property_getName`(獲取屬性名)、`property_getAttributes`(獲取屬性特性,如nonatomic、strong)
- 面試考點(diǎn):SEL與IMP的關(guān)系?Method結(jié)構(gòu)體包含哪些核心信息?如何通過(guò)Runtime獲取類的屬性和成員變量?
三、Runtime核心機(jī)制(深度難點(diǎn),面試核心)
3.1 消息發(fā)送機(jī)制(objc_msgSend)
- 核心流程(從調(diào)用[obj test]到方法執(zhí)行):
1. 快速查找:通過(guò)obj的isa找到類,先查類的cache_t緩存,若命中直接獲取IMP執(zhí)行
2. 慢速查找:緩存未命中時(shí),遍歷類的methodLists查找方法,未找到則沿父類鏈向上查找(直到NSObject)
3. 動(dòng)態(tài)方法解析:若未找到方法,調(diào)用`+ (BOOL)resolveInstanceMethod:(SEL)sel`(實(shí)例方法)/`+ (BOOL)resolveClassMethod:(SEL)sel`(類方法),允許動(dòng)態(tài)添加方法實(shí)現(xiàn)
4. 消息轉(zhuǎn)發(fā):解析失敗后,進(jìn)入消息轉(zhuǎn)發(fā)流程:
- 第一步:`- (id)forwardingTargetForSelector:(SEL)aSelector`,返回可處理該消息的對(duì)象(快速轉(zhuǎn)發(fā))
- 第二步:`- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector`,返回方法簽名;再調(diào)用`- (void)forwardInvocation:(NSInvocation *)anInvocation`,手動(dòng)轉(zhuǎn)發(fā)消息(完整轉(zhuǎn)發(fā))
5. 消息轉(zhuǎn)發(fā)失?。赫{(diào)用`doesNotRecognizeSelector:`,拋出異常崩潰
- 深度細(xì)節(jié):
- objc_msgSend的優(yōu)化:匯編實(shí)現(xiàn)快速查找流程,減少函數(shù)調(diào)用開(kāi)銷;緩存命中時(shí)直接跳轉(zhuǎn)IMP執(zhí)行
- 父類鏈查找順序:子類→父類→...→NSObject→nil
- 類方法的查找:類對(duì)象的isa指向元類,查找流程與實(shí)例方法一致,只是查找的是元類及其父類鏈
- 面試考點(diǎn):詳細(xì)描述objc_msgSend的完整流程?動(dòng)態(tài)方法解析與消息轉(zhuǎn)發(fā)的區(qū)別?如何通過(guò)消息轉(zhuǎn)發(fā)避免“unrecognized selector sent to instance”崩潰?
3.2 動(dòng)態(tài)方法解析與消息轉(zhuǎn)發(fā)實(shí)戰(zhàn)
-
動(dòng)態(tài)方法解析案例:
`+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 動(dòng)態(tài)添加方法實(shí)現(xiàn)
class_addMethod(self, sel, (IMP)dynamicTest, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicTest(id self, SEL _cmd) {
NSLog(@"動(dòng)態(tài)解析的test方法");
}`
消息轉(zhuǎn)發(fā)案例(快速轉(zhuǎn)發(fā)):
- (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(test)) { return [[OtherClass alloc] init]; // 由OtherClass處理test方法 } return [super forwardingTargetForSelector:aSelector]; }消息轉(zhuǎn)發(fā)案例(完整轉(zhuǎn)發(fā)):
`- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}(void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
OtherClass *other = [[OtherClass alloc] init];
if ([other respondsToSelector:sel]) {
[anInvocation invokeWithTarget:other];
} else {
[super forwardInvocation:anInvocation];
}
}`面試考點(diǎn):動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)的應(yīng)用場(chǎng)景?快速轉(zhuǎn)發(fā)和完整轉(zhuǎn)發(fā)的區(qū)別?如何實(shí)現(xiàn)一個(gè)通用的消息轉(zhuǎn)發(fā)機(jī)制避免崩潰?
3.3 Category底層實(shí)現(xiàn)與加載機(jī)制
底層結(jié)構(gòu)(category_t):
struct category_t { const char *name; // 所屬類名 classref_t cls; // 所屬類(運(yùn)行時(shí)綁定) struct method_list_t *instanceMethods; // 實(shí)例方法列表 struct method_list_t *classMethods; // 類方法列表 struct protocol_list_t *protocols; // 協(xié)議列表 struct property_list_t *instanceProperties; // 實(shí)例屬性列表 };加載機(jī)制(編譯期→運(yùn)行期):
1. 編譯期:Category被編譯為category_t結(jié)構(gòu)體,存儲(chǔ)在Mach-O的__DATA段,此時(shí)未與所屬類關(guān)聯(lián)
2. 運(yùn)行期(dyld加載階段):
- 通過(guò)runtime的`_objc_init`函數(shù)初始化,遍歷所有category_t
- 將Category的方法列表、協(xié)議列表、屬性列表合并到所屬類的對(duì)應(yīng)列表中
- 合并規(guī)則:Category的方法排在類原有方法之后,多個(gè)Category的合并順序由編譯順序決定(最后編譯的Category方法優(yōu)先)
- Category與Class Extension的區(qū)別:
- Class Extension(類擴(kuò)展):編譯期合并到類中,可添加私有屬性、方法聲明,屬于類的一部分
- Category:運(yùn)行期合并到類中,不能添加實(shí)例變量(可通過(guò)關(guān)聯(lián)對(duì)象間接添加),主要用于擴(kuò)展方法
- 面試考點(diǎn):Category的底層實(shí)現(xiàn)?Category的方法為什么會(huì)覆蓋類的原有方法?如何解決多個(gè)Category方法同名的優(yōu)先級(jí)問(wèn)題?Category能否添加實(shí)例變量?為什么?
3.4 關(guān)聯(lián)對(duì)象(Associated Objects)
- 核心原理:
- Category不能直接添加實(shí)例變量,關(guān)聯(lián)對(duì)象通過(guò)Runtime API在運(yùn)行時(shí)為對(duì)象動(dòng)態(tài)附加“屬性”,本質(zhì)是將對(duì)象與關(guān)聯(lián)值存儲(chǔ)在全局哈希表中
- 底層結(jié)構(gòu):系統(tǒng)維護(hù)一個(gè)全局哈希表,key是對(duì)象的指針地址,value是另一個(gè)哈希表(存儲(chǔ)關(guān)聯(lián)的key-value對(duì))
-
核心API:`// 設(shè)置關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
// 獲取關(guān)聯(lián)對(duì)象
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
// 移除所有關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id _Nonnull object);`
- 關(guān)聯(lián)策略(objc_AssociationPolicy):
- ASSIGN:弱引用,不持有value,value釋放后變?yōu)橐爸羔?
- RETAIN_NONATOMIC:強(qiáng)引用,非原子性
- COPY_NONATOMIC:拷貝,非原子性
- RETAIN:強(qiáng)引用,原子性
- COPY:拷貝,原子性
- 注意事項(xiàng):
- key的選擇:推薦使用`static const void *key = &key;`(唯一且不占用內(nèi)存),避免使用字符串(可能存在哈希沖突)
- 內(nèi)存管理:關(guān)聯(lián)對(duì)象的生命周期與被關(guān)聯(lián)對(duì)象一致,被關(guān)聯(lián)對(duì)象釋放時(shí),關(guān)聯(lián)對(duì)象會(huì)根據(jù)策略自動(dòng)釋放(強(qiáng)引用策略會(huì)釋放value)
- 面試考點(diǎn):關(guān)聯(lián)對(duì)象的底層實(shí)現(xiàn)原理?關(guān)聯(lián)策略與@property屬性修飾符的對(duì)應(yīng)關(guān)系?關(guān)聯(lián)對(duì)象是否會(huì)導(dǎo)致內(nèi)存泄漏?如何避免?
3.5 Method Swizzling(方法交換)
核心原理:通過(guò)Runtime API交換兩個(gè)Method的IMP指針,實(shí)現(xiàn)“鉤子”效果,在不修改原有代碼的情況下攔截方法調(diào)用
-
標(biāo)準(zhǔn)實(shí)現(xiàn)(線程安全):
`+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [self class];
SEL originalSel = @selector(test);
SEL swizzledSel = @selector(swizzledTest);Method originalMethod = class_getInstanceMethod(cls, originalSel); Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel); // 先嘗試添加方法,避免原有方法不存在(如父類的方法) BOOL didAddMethod = class_addMethod(cls, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(cls, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); }});
}` 關(guān)鍵注意事項(xiàng):
- 線程安全:在+load方法中執(zhí)行,且用dispatch_once保證唯一執(zhí)行(+load在dyld加載時(shí)執(zhí)行,單線程)
- 避免遞歸:交換后的方法中若調(diào)用原方法名,會(huì)觸發(fā)交換后的方法,需通過(guò)`class_getInstanceMethod`獲取原IMP直接調(diào)用避免遞歸
- 父類與子類:Method Swizzling僅影響當(dāng)前類,若子類未重寫方法,交換子類的方法會(huì)影響父類方法的調(diào)用
應(yīng)用場(chǎng)景:埋點(diǎn)統(tǒng)計(jì)、崩潰監(jiān)控、性能監(jiān)控、方法攔截與替換(如攔截UIButton的點(diǎn)擊事件)
面試考點(diǎn):Method Swizzling的底層原理?為什么要在+load方法中實(shí)現(xiàn)?如何保證線程安全?如何避免方法交換后的遞歸調(diào)用?
四、Runtime與上層框架的關(guān)聯(lián)(資深工程師必備)
4.1 KVO底層實(shí)現(xiàn)(基于Runtime)
- 核心原理:
- 當(dāng)對(duì)象被添加KVO監(jiān)聽(tīng)時(shí),Runtime動(dòng)態(tài)創(chuàng)建該對(duì)象所屬類的子類(NSKVONotifying_XXX)
- 重寫子類的setter方法,在setter中觸發(fā)`willChangeValueForKey:`和`didChangeValueForKey:`,通知觀察者
- 將對(duì)象的isa指針指向動(dòng)態(tài)子類,使對(duì)象變?yōu)樽宇惖膶?shí)例
與Runtime的關(guān)聯(lián):動(dòng)態(tài)創(chuàng)建類(
objc_allocateClassPair)、重寫方法(class_addMethod)、修改isa指針面試考點(diǎn):KVO的底層實(shí)現(xiàn)原理?為什么KVO只能監(jiān)聽(tīng)屬性的setter方法?如何手動(dòng)觸發(fā)KVO?
4.2 Block底層與Runtime的關(guān)聯(lián)
底層結(jié)構(gòu):Block本質(zhì)是一個(gè)包含isa指針的結(jié)構(gòu)體,屬于對(duì)象類型,isa指向_NSConcreteStackBlock/_NSConcreteMallocBlock/_NSConcreteGlobalBlock
與Runtime的關(guān)聯(lián):
- Block的copy操作:棧Block拷貝到堆時(shí),通過(guò)Runtime的`_Block_copy`函數(shù)處理,自動(dòng)retain捕獲的對(duì)象
- Block的銷毀:堆Block釋放時(shí),通過(guò)`_Block_dispose`函數(shù)釋放捕獲的對(duì)象
- Block的調(diào)用:本質(zhì)是調(diào)用Block結(jié)構(gòu)體中的IMP指針,遵循objc_msgSend的調(diào)用邏輯
- 面試考點(diǎn):Block的底層結(jié)構(gòu)?Block捕獲self時(shí)為什么會(huì)導(dǎo)致循環(huán)引用?__block修飾符的作用(底層原理)?
4.3 屬性自動(dòng)合成與Runtime
自動(dòng)合成流程:編譯器在編譯期為@property自動(dòng)合成實(shí)例變量(_屬性名)和setter/getter方法,運(yùn)行時(shí)通過(guò)Runtime將實(shí)例變量添加到類的ivars列表,setter/getter方法添加到methodLists
手動(dòng)實(shí)現(xiàn)setter/getter的影響:若手動(dòng)實(shí)現(xiàn)setter和getter,編譯器不會(huì)自動(dòng)合成實(shí)例變量,需手動(dòng)聲明或通過(guò)
@synthesize指定面試考點(diǎn):@property的自動(dòng)合成原理?@synthesize和@dynamic的區(qū)別?如何手動(dòng)實(shí)現(xiàn)一個(gè)atomic的setter/getter方法?
五、面試高頻真題與解答思路
5.1 基礎(chǔ)類真題
真題1:OC的動(dòng)態(tài)特性有哪些?Runtime如何支撐這些特性?
解答思路:先列出動(dòng)態(tài)特性(動(dòng)態(tài)類型、動(dòng)態(tài)綁定、動(dòng)態(tài)加載),再分別說(shuō)明Runtime的支撐機(jī)制(動(dòng)態(tài)類型通過(guò)isa判斷類、動(dòng)態(tài)綁定通過(guò)objc_msgSend運(yùn)行時(shí)綁定方法、動(dòng)態(tài)加載通過(guò)Runtime加載類/分類)真題2:SEL、IMP、Method三者的關(guān)系?
解答思路:SEL是方法名標(biāo)識(shí),IMP是方法實(shí)現(xiàn)指針,Method是包含SEL和IMP的結(jié)構(gòu)體;通過(guò)SEL可找到對(duì)應(yīng)的Method,再?gòu)腗ethod中獲取IMP執(zhí)行方法
5.2 核心機(jī)制真題
真題1:詳細(xì)描述objc_msgSend的完整流程,若方法未找到會(huì)如何處理?
解答思路:按“快速查找→慢速查找→動(dòng)態(tài)方法解析→消息轉(zhuǎn)發(fā)→崩潰”的順序展開(kāi),重點(diǎn)說(shuō)明每個(gè)階段的核心操作和API,體現(xiàn)對(duì)底層流程的理解真題2:Method Swizzling的實(shí)現(xiàn)步驟和注意事項(xiàng)?如何避免線程安全問(wèn)題?
解答思路:先說(shuō)明核心原理(交換IMP),再列出標(biāo)準(zhǔn)實(shí)現(xiàn)代碼,重點(diǎn)解釋+load方法和dispatch_once的作用,分析線程安全、避免遞歸、父類子類影響等注意事項(xiàng)真題3:關(guān)聯(lián)對(duì)象的底層實(shí)現(xiàn)?為什么Category不能直接添加實(shí)例變量?
解答思路:關(guān)聯(lián)對(duì)象通過(guò)全局哈希表存儲(chǔ);Category不能添加實(shí)例變量是因?yàn)榫幾g期類的ivars列表已固定,運(yùn)行時(shí)無(wú)法修改類的內(nèi)存布局,而關(guān)聯(lián)對(duì)象是額外的哈希表存儲(chǔ),不影響類的原有結(jié)構(gòu)
5.3 實(shí)戰(zhàn)應(yīng)用真題
真題1:如何通過(guò)Runtime實(shí)現(xiàn)一個(gè)通用的崩潰監(jiān)控工具(攔截unrecognized selector崩潰)?
解答思路:利用消息轉(zhuǎn)發(fā)機(jī)制,通過(guò)Category為NSObject添加forwardingTargetForSelector:或methodSignatureForSelector:/forwardInvocation:方法,攔截未識(shí)別的SEL,記錄崩潰信息并返回默認(rèn)處理對(duì)象,避免崩潰真題2:如何通過(guò)Runtime獲取一個(gè)類的所有屬性和成員變量,并修改私有成員變量的值?
解答思路:使用class_copyPropertyList獲取屬性,class_copyIvarList獲取成員變量;通過(guò)ivar_getOffset獲取私有成員變量的偏移量,再通過(guò)object_getIvar/object_setIvar修改值真題3:KVO的底層實(shí)現(xiàn)與Runtime的關(guān)系?如何自定義KVO?
解答思路:說(shuō)明KVO動(dòng)態(tài)創(chuàng)建子類、重寫setter、修改isa指針的流程,關(guān)聯(lián)Runtime的動(dòng)態(tài)類創(chuàng)建、方法添加API;自定義KVO可通過(guò)Runtime手動(dòng)創(chuàng)建子類、重寫setter、添加監(jiān)聽(tīng)邏輯實(shí)現(xiàn)
復(fù)習(xí)建議:1. 結(jié)合objc4源碼閱讀核心結(jié)構(gòu)(objc_class、category_t)和核心函數(shù)(objc_msgSend、method_exchangeImplementations),加深底層理解;2. 動(dòng)手實(shí)現(xiàn)關(guān)鍵機(jī)制(Method Swizzling、消息轉(zhuǎn)發(fā)、關(guān)聯(lián)對(duì)象),驗(yàn)證原理;3. 梳理“底層原理→上層應(yīng)用→面試考點(diǎn)”的關(guān)聯(lián)邏輯,形成知識(shí)體系。
(注:文檔部分內(nèi)容可能由 AI 生成)