
我們經(jīng)常會看一些面試題,但是好多面試題我們都是知其然不知其所以然,你如果認(rèn)真的看了我上面總結(jié)的幾十篇文章,那么你也會知其所以然。
OC對象本質(zhì)
1、一個(gè)NSObject對象占用多少內(nèi)存?
系統(tǒng)分配了16個(gè)字節(jié)給NSObject對象(通過malloc_size函數(shù)獲得),但NSObject對象內(nèi)部只使用了8個(gè)字節(jié)的空間(64bit環(huán)境下,可以通過class_getInstanceSize函數(shù)獲得)
2、對象的isa指針指向哪里?
- instance對象的isa指向class對象
- class對象的isa指向meta-class對象
- meta-class對象的isa指向基類的meta-class對象
3、OC的類信息存放在哪里?
- 對象方法、屬性、成員變量、協(xié)議信息,存放在class對象中
- 類方法,存放在meta-class對象中
- 成員變量的具體值,存放在instance對象
KVO
1、iOS用什么方式實(shí)現(xiàn)對一個(gè)對象的KVO?(KVO的本質(zhì)是什么?)
- 利用RuntimeAPI動(dòng)態(tài)生成一個(gè)子類,并且讓instance對象的isa指向這個(gè)全新的子類
- 當(dāng)修改instance對象的屬性時(shí),會調(diào)用Foundation的
_NSSetXXXValueAndNotify函數(shù)- 1、調(diào)用
willChangeValueForKey方法 - 2、調(diào)用
setAge方法 - 3、調(diào)用
didChangeValueForKey方法 - 4、
didChangeValueForKey方法內(nèi)部調(diào)用oberser的observeValueForKeyPath:ofObject:change:context:方法
- 1、調(diào)用
2、如何手動(dòng)觸發(fā)KVO?
手動(dòng)調(diào)用willChangeValueForKey:和didChangeValueForKey:
3、直接修改成員變量會觸發(fā)KVO么?
不會觸發(fā)KVO
KVC
1、通過KVC修改屬性會觸發(fā)KVO么?
會觸發(fā)KVO,因?yàn)镵VC是調(diào)用set方法,KVO就是監(jiān)聽set方法
2、KVC的賦值和取值過程是怎樣的?原理是什么?
KVO的setValue:forKey原理

- 1、按照setKey,_setKey的順序查找成員方法,如果找到方法,傳遞參數(shù),調(diào)用方法
- 2、如果沒有找到,查看accessInstanceVariablesDirectly的返回值(accessInstanceVariablesDirectly的返回值默認(rèn)是YES),
- 返回值為YES,按照_Key,_isKey,Key,isKey的順序查找成員變量, 如果找到,直接賦值,如果沒有找到,調(diào)用setValue:forUndefinedKey:,拋出異常
- 返回NO,直接調(diào)用setValue:forUndefinedKey:,拋出異常
KVO的ValueforKey原理

- 1、按照getKey,key,isKey,_key的順序查找成員方法,如果找到直接調(diào)用取值
- 2、如果沒有找到,查看accessInstanceVariablesDirectly的返回值
- 返回值為YES,按照_Key,_isKey,Key,isKey的順序查找成員變量,如果找到,直接取值,如果沒有找到,調(diào)用setValue:forUndefinedKey:,拋出異常
- 返回NO,直接調(diào)用setValue:forUndefinedKey:,拋出異常
Category
1、Category的實(shí)現(xiàn)原理
- Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲著分類的對象方法、類方法、屬性、協(xié)議信息
- 在程序運(yùn)行的時(shí)候,runtime會將Category的數(shù)據(jù),合并到類信息中(類對象、元類對象中)
2、Category和Class Extension的區(qū)別是什么?
- Class Extension在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類信息中
- Category是在運(yùn)行時(shí),才會將數(shù)據(jù)合并到類信息中
3、load、initialize方法的區(qū)別什么?
-
1.調(diào)用方式
- 1> load是根據(jù)函數(shù)地址直接調(diào)用
- 2> initialize是通過objc_msgSend調(diào)用
-
2.調(diào)用時(shí)刻
- 1> load是runtime加載類、分類的時(shí)候調(diào)用(只會調(diào)用1次 )
- 2> initialize是類第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類只會initialize一次(父類的initialize方法可能會被調(diào)用多次)
4、load、initialize的調(diào)用順序
1.load
- 1> 先調(diào)用類的load
- a) 先編譯的類,優(yōu)先調(diào)用load
- b) 調(diào)用子類的load之前,會先調(diào)用父類的load
- 2> 再調(diào)用分類的load
- a) 先編譯的分類,優(yōu)先調(diào)用load
2.initialize
- 1> 先初始化父類
- 2> 再初始化子類(可能最終調(diào)用的是父類的initialize方法)
5、如何實(shí)現(xiàn)給分類“添加成員變量”?
默認(rèn)情況下,因?yàn)榉诸惖讓咏Y(jié)構(gòu)的限制,不能添加成員變量到分類中。但可以通過關(guān)聯(lián)對象來間接實(shí)現(xiàn)
關(guān)聯(lián)對象提供了以下API
添加關(guān)聯(lián)對象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
獲得關(guān)聯(lián)對象
id objc_getAssociatedObject(id object, const void * key)
移除所有的關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id object)
Block
1、block的原理是怎樣的?本質(zhì)是什么?
- block本質(zhì)上也是一個(gè)OC對象,它內(nèi)部也有個(gè)isa指針
- block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象

2、block的(capture)

為了保證block內(nèi)部能夠正常訪問外部的變量,block有個(gè)變量捕獲機(jī)制
3、Block類型有哪幾種 block有3種類型,可以通過調(diào)用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
- 1、NSGlobalBlock ( _NSConcreteGlobalBlock
- 2、NSStackBlock ( _NSConcreteStackBlock )
- 3、NSMallocBlock ( _NSConcreteMallocBlock )

4、block的copy
在ARC環(huán)境下,編譯器會根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況
- 1、block作為函數(shù)返回值時(shí)
- 2、將block賦值給__strong指針時(shí)
- 3、block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
- 4、block作為GCD API的方法參數(shù)時(shí)
MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);
ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
5、__block修飾符
__block可以用于解決block內(nèi)部無法修改auto變量值的問題__block不能修飾全局變量、靜態(tài)變量(static)編譯器會將
__block變量包裝成一個(gè)對象當(dāng)__block變量在棧上時(shí),不會對指向的對象產(chǎn)生強(qiáng)引用
-
當(dāng)__block變量被copy到堆時(shí)
- 會調(diào)用__block變量內(nèi)部的copy函數(shù)
- copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
- _Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃╛_strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于ARC時(shí)會retain,MRC時(shí)不會retain)
-
如果__block變量從堆上移除
- 會調(diào)用__block變量內(nèi)部的dispose函數(shù)
- dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
- _Block_object_dispose函數(shù)會自動(dòng)釋放指向的對象(release)
6、循環(huán)引用
- 用__weak、__unsafe_unretained解決
__unsafe_unretained typeof(self) weakSelf = self;
self.block = ^{
print(@"%p", weakSelf);
}
__weak typeof(self) weakSelf = self;
self.block = ^{
print(@"%p", weakSelf);
}
- 用__block解決(必須要調(diào)用block)
__block id weakSelf = self;
self.block = ^{
weakSelf = nil;
}
self.block();
RunTime
1、講一下 OC 的消息機(jī)制
- OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
- objc_msgSend底層有3大階段:消息發(fā)送(當(dāng)前類、父類中查找)、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)
2、消息轉(zhuǎn)發(fā)機(jī)制流程
- 1、消息發(fā)送
- 2、動(dòng)態(tài)方法解析
- 3、消息轉(zhuǎn)發(fā)
消息發(fā)送階段
消息發(fā)送流程是我們平時(shí)最經(jīng)常使用的流程,其他的像動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)其實(shí)是補(bǔ)救措施。具體流程如下

- 1、首先判斷消息接受者receiver是否為nil,如果為nil直接退出消息發(fā)送
- 2、如果存在消息接受者receiverClass,首先在消息接受者receiverClass的cache中查找方法,如果找到方法,直接調(diào)用。如果找不到,往下進(jìn)行
- 3、沒有在消息接受者receiverClass的cache中找到方法,則從receiverClass的class_rw_t中查找方法,如果找到方法,執(zhí)行方法,并把該方法緩存到receiverClass的cache中;如果沒有找到,往下進(jìn)行
- 4、沒有在receiverClass中找到方法,則通過superClass指針找到superClass,也是現(xiàn)在緩存中查找,如果找到,執(zhí)行方法,并把該方法緩存到receiverClass的cache中;如果沒有找到,往下進(jìn)行
- 5、沒有在消息接受者superClass的cache中找到方法,則從superClass的class_rw_t中查找方法,如果找到方法,執(zhí)行方法,并把該方法緩存到receiverClass的cache中;如果沒有找到,重復(fù)4、5步驟。如果找不到了superClass了,往下進(jìn)行
- 6、如果在最底層的superClass也找不到該方法,則要轉(zhuǎn)到動(dòng)態(tài)方法解析
動(dòng)態(tài)方法解析

開發(fā)者可以實(shí)現(xiàn)以下方法,來動(dòng)態(tài)添加方法實(shí)現(xiàn)
+resolveInstanceMethod:-
+resolveClassMethod:動(dòng)態(tài)解析過后,會重新走“消息發(fā)送”的流程,從receiverClass的cache中查找方法這一步開始執(zhí)行
消息轉(zhuǎn)發(fā)
如果方法一個(gè)方法在消息發(fā)送階段沒有找到相關(guān)方法,也沒有進(jìn)行動(dòng)態(tài)方法解析,這個(gè)時(shí)候就會走到消息轉(zhuǎn)發(fā)階段了。

- 調(diào)用
forwardingTargetForSelector,返回值不為nil時(shí),會調(diào)用objc_msgSend(返回值, SEL) - 調(diào)用
methodSignatureForSelector,返回值不為nil,調(diào)用forwardInvocation:方法;返回值為nil時(shí),調(diào)用doesNotRecognizeSelector:方法 - 開發(fā)者可以在
forwardInvocation:方法中自定義任何邏輯 - 以上方法都有對象方法、類方法2個(gè)版本(前面可以是加號+,也可以是減號-)
3、什么是Runtime?平時(shí)項(xiàng)目中有用過么?
- OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語言,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行
- OC的動(dòng)態(tài)性就是由Runtime來支撐和實(shí)現(xiàn)的,Runtime是一套C語言的API,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)
- 平時(shí)編寫的OC代碼,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用
具體應(yīng)用
- 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
- 遍歷類的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動(dòng)歸檔解檔)
- 交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
- 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題
4、super的本質(zhì)
- super調(diào)用,底層會轉(zhuǎn)換為objc_msgSendSuper2函數(shù)的調(diào)用,接收2個(gè)參數(shù)
struct objc_super2SEL
- receiver是消息接收者
- current_class是receiver的Class對象
RunLoop
1、講講 RunLoop,項(xiàng)目中有用到嗎?
- 定時(shí)器切換的時(shí)候,為了保證定時(shí)器的準(zhǔn)確性,需要添加runLoop 2、在聊天界面,我們需要持續(xù)的把聊天信息存到數(shù)據(jù)庫中,這個(gè)時(shí)候需要開啟一個(gè)保活線程,在這個(gè)線程中處理
2、runloop內(nèi)部實(shí)現(xiàn)邏輯
每次運(yùn)行RunLoop,線程的RunLoop會自動(dòng)處理之前未處理的消息,并通知相關(guān)的觀察者。具體順序
- 1、通知觀察者(observers)RunLoop即將啟動(dòng)
- 2、通知觀察者(observers)任何即將要開始的定時(shí)器
- 3、通知觀察者(observers)即將處理source0事件
- 4、處理source0
- 5、如果有source1,跳到第9步
- 6、通知觀察者(observers)線程即將進(jìn)入休眠
- 7、將線程置于休眠知道任一下面的事件發(fā)生
- 1、source0事件觸發(fā)
- 2、定時(shí)器啟動(dòng)
- 3、外部手動(dòng)喚醒
- 8、通知觀察者(observers)線程即將喚醒
- 9、處理喚醒時(shí)收到的時(shí)間,之后跳回2
- 1、如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器事件
- 2、如果source0啟動(dòng),傳遞相應(yīng)的消息
- 10、通知觀察者RunLoop結(jié)束

3、RunLoop與線程
- 每條線程都有唯一的一個(gè)與之對應(yīng)的RunLoop對象
- RunLoop保存在一個(gè)全局的Dictionary里,線程作為key,RunLoop作為value
- 線程剛創(chuàng)建時(shí)并沒有RunLoop對象,RunLoop會在第一次獲取它時(shí)創(chuàng)建
- RunLoop會在線程結(jié)束時(shí)銷毀
- 主線程的RunLoop已經(jīng)自動(dòng)獲?。▌?chuàng)建),子線程默認(rèn)沒有開啟RunLoop
4、timer 與 runloop 的關(guān)系?
- 一個(gè)RunLoop包含若干個(gè)Mode,每個(gè)Mode又包含若干個(gè)Source0/Source1/Timer/Observer
- RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode
- 如果需要切換Mode,只能退出當(dāng)前Loop,再重新選擇一個(gè)Mode進(jìn)入
- 不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
- 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
解決定時(shí)器在滾動(dòng)視圖上面失效問題NSTimer添加到兩種RunLoop中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
5、RunLoop有幾種狀態(tài)
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
**6、RunLoop的mode的作用 **
RunLoop的mode的作用 系統(tǒng)注冊了5中mode
kCFRunLoopDefaultMode //App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode //界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
UIInitializationRunLoopMode // 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
GSEventReceiveRunLoopMode // 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
kCFRunLoopCommonModes //這是一個(gè)占位用的Mode,不是一種真正的Mode
但是我們只能使用兩種mode
kCFRunLoopDefaultMode //App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode //界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
多線程
1、你理解的多線程?
2、iOS的多線程方案有哪幾種?你更傾向于哪一種?
3、你在項(xiàng)目中用過 GCD 嗎?
4、GCD 的隊(duì)列類型
5、說一下 OperationQueue 和 GCD 的區(qū)別,以及各自的優(yōu)勢
6、線程安全的處理手段有哪些? 使用線程鎖
- 1、OSSpinLock
- 2、os_unfair_lock
- 3、pthread_mutex
- 4、dispatch_semaphore
- 5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
- 6、NSLock
- 7、NSRecursiveLock
- 8、NSCondition
- 9、NSConditionLock
- 10、@synchronized
- 11、pthread_rwlock
- 12、dispatch_barrier_async
- 13、atomic
7、線程通訊 線程間通信的體現(xiàn)
- 1、一個(gè)線程傳遞數(shù)據(jù)給另一個(gè)線程
- 2、在一個(gè)線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另一個(gè)線程繼續(xù)執(zhí)行任務(wù)
1、NSThread 可以先將自己的當(dāng)前線程對象注冊到某個(gè)全局的對象中去,這樣相互之間就可以獲取對方的線程對象,然后就可以使用下面的方法進(jìn)行線程間的通信了,由于主線程比較特殊,所以框架直接提供了在主線程執(zhí)行的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
2、GCD
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
內(nèi)存管理
1、使用CADisplayLink、NSTimer有什么注意點(diǎn)?
CADisplayLink、NSTimer會對target產(chǎn)生強(qiáng)引用,如果target又對它們產(chǎn)生強(qiáng)引用,那么就會引發(fā)循環(huán)引用
2、介紹下內(nèi)存的幾大區(qū)域
- 代碼段:編譯之后的代碼
- 數(shù)據(jù)段
- 字符串常量:比如NSString *str = @"123"
- 已初始化數(shù)據(jù):已初始化的全局變量、靜態(tài)變量等
- 未初始化數(shù)據(jù):未初始化的全局變量、靜態(tài)變量等
- 棧:函數(shù)調(diào)用開銷,比如局部變量。分配的內(nèi)存空間地址越來越小
- 堆:通過alloc、malloc、calloc等動(dòng)態(tài)分配的空間,分配的內(nèi)存空間地址越來越大
3、講一下你對 iOS 內(nèi)存管理的理解 在iOS中,使用引用計(jì)數(shù)來管理OC對象的內(nèi)存
- 一個(gè)新創(chuàng)建的OC對象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對象就會銷毀,釋放其占用的內(nèi)存空間
- 調(diào)用retain會讓OC對象的引用計(jì)數(shù)+1,調(diào)用release會讓OC對象的引用計(jì)數(shù)-1
內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
- 當(dāng)調(diào)用alloc、new、copy、mutableCopy方法返回了一個(gè)對象,在不需要這個(gè)對象時(shí),要調(diào)用release或者autorelease來釋放它
- 想擁有某個(gè)對象,就讓它的引用計(jì)數(shù)+1;不想再擁有某個(gè)對象,就讓它的引用計(jì)數(shù)-1
可以通過以下私有函數(shù)來查看自動(dòng)釋放池的情況 extern void _objc_autoreleasePoolPrint(void);
4、ARC 都幫我們做了什么 LLVM + Runtime
- LVVM生成release代碼
- RunTime負(fù)責(zé)執(zhí)行
5、weak指針的實(shí)現(xiàn)原理 runtime維護(hù)了一個(gè)weak表,用于存儲指向某個(gè)對象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表,key是所指對象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對象指針的地址)數(shù)組
- 1、初始化時(shí):runtime會調(diào)用objc_initWeak函數(shù),初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂?/li>
- 2、添加引用時(shí):objc_initWeak函數(shù)會調(diào)用 storeWeak() 函數(shù), storeWeak() 的作用是更新指針指向,創(chuàng)建對應(yīng)的弱引用表
- 3、釋放時(shí),調(diào)用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除,最后清理對象的記錄
6、autorelease對象在什么時(shí)機(jī)會被調(diào)用release
- 1、iOS在主線程的Runloop中注冊了2個(gè)Observer
- 2、第1個(gè)Observer監(jiān)聽了kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush()
- 3、第2個(gè)Observer監(jiān)聽了kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush() 監(jiān)聽了kCFRunLoopBeforeExit事件,會調(diào)用objc_autoreleasePoolPop()
autoreleased 對象是在 runloop 的即將進(jìn)入休眠時(shí)進(jìn)行釋放的
7、方法里有局部對象, 出了方法后會立即釋放嗎 在ARC情況下會立即釋放 在MRC情況下,對象是在 runloop 的即將進(jìn)入休眠時(shí)進(jìn)行釋放的