[iOS]底層原理五 : (面試題目整理)

底層原理一: (OC 本質(zhì)、KVC、KVO、Category、Block)
底層原理二: (Runtime、Runloop)
底層原理三 : (多線程、內(nèi)存管理)
底層原理四 : (性能優(yōu)化、架構(gòu))
底層原理五 : (面試題目整理)

1. 一個(gè)OC對(duì)象占用多少內(nèi)存

系統(tǒng)分配了16個(gè)字節(jié)給NSObject對(duì)象(通過(guò)malloc_size函數(shù)獲得)
但NSObject對(duì)象內(nèi)部只使用了8個(gè)字節(jié)的空間(64bit環(huán)境下,可以通過(guò)class_getInstanceSize函數(shù)獲得)

2. 對(duì)象的isa指針指向哪里?

instance對(duì)象的isa指向class對(duì)象
class對(duì)象的isa指向meta-class對(duì)象
meta-class對(duì)象的isa指向基類的meta-class對(duì)象

3.OC的類信息存放在哪里?

對(duì)象方法、屬性、成員變量、協(xié)議信息,存放在class對(duì)象中
類方法,存放在meta-class對(duì)象中
成員變量的具體值,存放在instance對(duì)象

4.iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?(KVO的本質(zhì)是什么?)

- 利用RuntimeAPI動(dòng)態(tài)生成一個(gè)子類,并且讓instance對(duì)象的isa指向這個(gè)全新的子類
- 當(dāng)修改instance對(duì)象的屬性時(shí),會(huì)調(diào)用Foundation的_NSSetXXXValueAndNotify函數(shù)
    willChangeValueForKey:
    父類原來(lái)的setter
    didChangeValueForKey:
- 內(nèi)部會(huì)觸發(fā)監(jiān)聽(tīng)器(Oberser)的監(jiān)聽(tīng)方法(observeValueForKeyPath:ofObject:change:context:)

5.如何手動(dòng)觸發(fā)KVO?

手動(dòng)調(diào)用willChangeValueForKey:和didChangeValueForKey:
//
- (void)viewDidLoad {
[super viewDidLoad];

    Person *person = [[Person alloc]init];;
    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    [p willChangeValueForKey:@"name"];
    [p didChangeValueForKey:@"name"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"被觀測(cè)對(duì)象:%@, 被觀測(cè)的屬性:%@, 值的改變: %@\n, 攜帶信息:%@", object, keyPath, change, context);
}

6.直接修改成員變量會(huì)觸發(fā)KVO么?

不會(huì)觸發(fā)KVO

7.通過(guò)KVC修改屬性會(huì)觸發(fā)KVO么?

會(huì)觸發(fā)KVO
KVC在賦值時(shí)候,內(nèi)部會(huì)觸發(fā)監(jiān)聽(tīng)器(Oberser)的監(jiān)聽(tīng)方法(observeValueForKeyPath:ofObject:change:context:) 發(fā)送通知

8.KVC的賦值和取值過(guò)程是怎樣的?原理是什么?

KVC的全稱是Key-Value Coding,俗稱“鍵值編碼”,可以通過(guò)一個(gè)key來(lái)訪問(wèn)某個(gè)屬性
調(diào)用 setValue:forKey:
setKey,_setKey  ->找到了則進(jìn)行賦值,未找到調(diào)用 accessInstanceVarlableDirctly 是否運(yùn)行 修改值,返回YES
調(diào)用_key, _isKey, key, isKey 進(jìn)行賦值

9.Category的使用場(chǎng)合是什么?

- 在不修改原有類代碼的情況下,為類添對(duì)象方法或者類方法
- 或者為類關(guān)聯(lián)新的屬性
- 分解龐大的類文件

使用場(chǎng)合:
- 添加實(shí)例方法
- 添加類方法
- 添加協(xié)議
- 添加屬性
- 關(guān)聯(lián)成員變量

10.Category的實(shí)現(xiàn)原理

Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲(chǔ)著分類的對(duì)象方法、類方法、屬性、協(xié)議信息
在程序運(yùn)行的時(shí)候,runtime會(huì)將Category的數(shù)據(jù),合并到類信息中(類對(duì)象、元類對(duì)象中)

11.Category和Class Extension的區(qū)別是什么?

Class Extension在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類信息中
Category是在運(yùn)行時(shí),才會(huì)將數(shù)據(jù)合并到類信息中

12.Category中有l(wèi)oad方法嗎?load方法是什么時(shí)候調(diào)用的?load 方法能繼承嗎?

- 有l(wèi)oad方法
- load方法在runtime加載類、分類的時(shí)候調(diào)用
- load方法可以繼承,但是一般情況下不會(huì)主動(dòng)去調(diào)用load方法,都是讓系統(tǒng)自動(dòng)調(diào)用

13. initialize方法如何調(diào)用,以及調(diào)用時(shí)機(jī)

- 當(dāng)類第一次收到消息的時(shí)候會(huì)調(diào)用類的initialize方法
- 是通過(guò) runtime 的消息機(jī)制 objc_msgSend(obj,@selector()) 進(jìn)行調(diào)用的
- 優(yōu)先調(diào)用分類的 initialize, 如果沒(méi)有分類會(huì)調(diào)用 子類的,如果子類未實(shí)現(xiàn)則調(diào)用 父類的

13. load、initialize方法的區(qū)別什么?它們?cè)赾ategory中的調(diào)用的順序?以及出現(xiàn)繼承時(shí)他們之間的調(diào)用過(guò)程?

- load 是類加載到內(nèi)存時(shí)候調(diào)用, 優(yōu)先父類->子類->分類
- initialize 是類第一次收到消息時(shí)候調(diào)用,優(yōu)先分類->子類->父類
- 同級(jí)別和編譯順序有關(guān)系
- load 方法是在 main 函數(shù)之前調(diào)用的

14. Category能否添加成員變量?如果可以,如何給Category添加成員變量?

不能直接給Category添加成員變量,但是可以間接實(shí)現(xiàn)Category有成員變量的效果
Category是發(fā)生在運(yùn)行時(shí),編譯完畢,類的內(nèi)存布局已經(jīng)確定,無(wú)法添加成員變量(Category的底層數(shù)據(jù)結(jié)構(gòu)也沒(méi)有成員變量的結(jié)構(gòu))
可以通過(guò) runtime 動(dòng)態(tài)的關(guān)聯(lián)屬性

15. block的原理是怎樣的?本質(zhì)是什么?

block 本質(zhì)其實(shí)是OC對(duì)象
block 內(nèi)部封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境

16. __block的作用是什么?有什么使用注意點(diǎn)?

如果需要在 block 內(nèi)部修改外部的 局部變量的值,就需要使用__block 修飾(全局變量和靜態(tài)變量不需要加__block 可以修改)

__block 修飾以后,局部變量的數(shù)據(jù)結(jié)構(gòu)就會(huì)發(fā)生改變,底層會(huì)變成一個(gè)結(jié)構(gòu)體的對(duì)象,結(jié)構(gòu)內(nèi)部會(huì)聲明 一個(gè) __block修飾變量的成員, 并且將 __block修飾變量的地址保存到堆內(nèi)存中. 后面如果修改 這個(gè)變量的值,可以通過(guò) isa 指針找到這個(gè)結(jié)構(gòu)體,進(jìn)來(lái)修改 這個(gè)變量的值;

可以在 block 內(nèi)部修改 變量的值

17. block的屬性修飾詞為什么是copy?使用block有哪些使用注意?

block 一旦沒(méi)有進(jìn)行copy操作,就不會(huì)在堆上
使用注意:循環(huán)引用問(wèn)題 (外部使用__weak 解決)

17. block在修改NSMutableArray,需不需要添加__block?

如果是操作 NSMutableArray 對(duì)象不需要,因?yàn)?block 內(nèi)部拷貝了 NSMutableArray對(duì)象的內(nèi)存地址,實(shí)際是通過(guò)內(nèi)存地址操作的
如果 NSMutableArray 對(duì)象要重新賦值,就需要加__block

18. Block 內(nèi)部為什么不能修改局部變量,需要加__block

通過(guò)查看Block 源碼,可以發(fā)現(xiàn), block 內(nèi)部如果單純使用 外部變量, 會(huì)在 block 內(nèi)部創(chuàng)建同樣的一個(gè)變量,并且將 外部變量的值引用過(guò)來(lái)..(只是將外部變量值拷貝到 block 內(nèi)部), 內(nèi)部這個(gè)變量和外部 實(shí)際已經(jīng)沒(méi)關(guān)系了

從另一方面分析,block 本質(zhì)也是一個(gè) 函數(shù)指針, 外部的變量也是一個(gè)局部變量,很有可能 block 在使用這個(gè)變量時(shí)候,外部變量已經(jīng)釋放了,會(huì)造成錯(cuò)誤

加了__block 以后, 會(huì)將外部變量的內(nèi)存拷貝到堆中, 內(nèi)存由 block 去管理.

19.講一下 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ā)

19.1 消息發(fā)送流程
當(dāng)我們的一個(gè) receiver(實(shí)例對(duì)象)收到消息的時(shí)候, 會(huì)通過(guò) isa 指針找到 他的類對(duì)象, 然后在類對(duì)象方法列表中查找 對(duì)應(yīng)的方法實(shí)現(xiàn),如果 未找到,則會(huì)通過(guò) superClass 指針找到其父類的類對(duì)象, 找到則返回,未找打則會(huì)一級(jí)一級(jí)往上查到,最終到NSObject 對(duì)象, 如果還是未找到就會(huì)進(jìn)行動(dòng)態(tài)方法解析

類方法調(diào)用同上,只不過(guò) isa 指針找到元類對(duì)象;

19.1 動(dòng)態(tài)方法解析機(jī)制
當(dāng)我們發(fā)送消息未找到方法實(shí)現(xiàn),就會(huì)進(jìn)入第二步,動(dòng)態(tài)方法解析: 代碼實(shí)現(xiàn)如下

//  動(dòng)態(tài)方法綁定- 實(shí)例法法調(diào)用
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(run)) {
        Method method = class_getInstanceMethod(self, @selector(test));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

// 類方法調(diào)用
+(BOOL) resolveClassMethod:(SEL)sel....

20.消息轉(zhuǎn)發(fā)機(jī)制流程

未找到動(dòng)態(tài)方法綁定,就會(huì)進(jìn)行消息轉(zhuǎn)發(fā)階段

// 快速消息轉(zhuǎn)發(fā)- 指定消息處理對(duì)象
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(run)) {
        return [Student new];
    }
    return  [super forwardingTargetForSelector:aSelector];
} 

// 標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)-消息簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(run))
    {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
   //內(nèi)部邏輯自己處理 
}

21.什么是Runtime?平時(shí)項(xiàng)目中有用過(guò)么?

Objective-C runtime是一個(gè)`運(yùn)行時(shí)`庫(kù),它為Objective-C語(yǔ)言的動(dòng)態(tài)特性提供支持,我們所寫的OC代碼在運(yùn)行時(shí)都轉(zhuǎn)成了runtime相關(guān)的代碼,類轉(zhuǎn)換成C語(yǔ)言對(duì)應(yīng)的結(jié)構(gòu)體,方法轉(zhuǎn)化為C語(yǔ)言對(duì)應(yīng)的函數(shù),發(fā)消息轉(zhuǎn)成了C語(yǔ)言對(duì)應(yīng)的函數(shù)調(diào)用。通過(guò)了解runtime以及源碼,可以更加深入的了解OC其特性和原理

OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行
OC的動(dòng)態(tài)性就是由Runtime來(lái)支撐和實(shí)現(xiàn)的,Runtime是一套C語(yǔ)言的API,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)
平時(shí)編寫的OC代碼,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用

22.runtime具體應(yīng)用

利用關(guān)聯(lián)對(duì)象(AssociatedObject)給分類添加屬性
遍歷類的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動(dòng)歸檔解檔)
交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問(wèn)題

23.打印結(jié)果分別是什么?

1.png
[self class] 和 [super class] 都是給當(dāng)前類返送消息,spuer 表示在父類中查找
[self superClass]  和 [super superclass] 也是也當(dāng)前類發(fā)消息,返回父類
第一個(gè)打印:
MJStudent / MJStudent/ MJerson / MJPerson

isKindOfClass 表示對(duì)象是否為當(dāng)前類或者子類的 類型
isMemberOfClass 表示是否為當(dāng)前類的的類型
isMemberOfClass 分為- 對(duì)象方法 和+ 類方法2中
- (bool)isMemberOfClass; 比較的是類對(duì)象
+ (bool)isMemberOfClass; 比較的是元類
第二個(gè)打印:
1 ,0, 0, 0

24.以下代碼能不能執(zhí)行成功?如果可以,打印結(jié)果是什么?

2.png
打印結(jié)果: <ViewController: 0x7f9396c16300>

25.講講 RunLoop,項(xiàng)目中有用到嗎?

runloop運(yùn)行循環(huán),保證程序一直運(yùn)行,主線程默認(rèn)開啟
用于處理線程上的各種事件,定時(shí)器等
可以提高程序性能,節(jié)約CPU資源,有事情做就做,沒(méi)事情做就讓線程休眠

應(yīng)用范疇:
定時(shí)器,事件響應(yīng),手勢(shì)識(shí)別,界面刷新,以及autoreleasePool 等等

26.runloop內(nèi)部實(shí)現(xiàn)邏輯?

3.png
實(shí)際上 RunLoop 就是這樣一個(gè)函數(shù),其內(nèi)部是一個(gè) do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)里;直到超時(shí)或被手動(dòng)停止,該函數(shù)才會(huì)返回。

27.runloop和線程的關(guān)系?

每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象
RunLoop保存在一個(gè)全局的Dictionary里,線程作為key,RunLoop作為value
線程剛創(chuàng)建時(shí)并沒(méi)有RunLoop對(duì)象,RunLoop會(huì)在第一次獲取它時(shí)創(chuàng)建
RunLoop會(huì)在線程結(jié)束時(shí)銷毀
主線程的RunLoop已經(jīng)自動(dòng)獲取(創(chuàng)建),子線程默認(rèn)沒(méi)有開啟RunLoop

28.timer 與 runloop 的關(guān)系?

timer 定時(shí)器,是基于 runloop 來(lái)實(shí)現(xiàn)的, runloop 在運(yùn)行循環(huán)當(dāng)中,監(jiān)聽(tīng)到了定制器 就會(huì)執(zhí)行;所以 timer 需要添加到 runloop 中去, 注意子線程的 runloop 默認(rèn)是不開啟的,如果在子線程執(zhí)行 timer 需要手動(dòng)開啟 runloop

29.程序中添加每3秒響應(yīng)一次的NSTimer,當(dāng)拖動(dòng)tableview時(shí)timer可能無(wú)法響應(yīng)要怎么解決?

將 timer 對(duì)象添加到 runloop 中,并修改 runloop 的運(yùn)行 mode

 NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:nil];
 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

30.runloop 是怎么響應(yīng)用戶操作的, 具體流程是什么樣的?

不明白問(wèn)題想問(wèn)什么?

31.說(shuō)說(shuō)runLoop的幾種狀態(tài)

添加Observer監(jiān)聽(tīng)RunLoop的所有狀態(tài)

4.png

32.runloop的mode作用是什么?

runloop 只能在一種 mode 下運(yùn)行, 做不同的事情,runloop 會(huì)切換到對(duì)應(yīng)的 model 下來(lái)執(zhí)行,默認(rèn)是  kCFRunLoopDefaultMode 如果視圖滑動(dòng)再回切換到  UITrackingRunLoopMode,如果需要在多種 mode 下運(yùn)行則需要手動(dòng)設(shè)置 kCFRunLoopCommonModes;

1. kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
2. UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
3. UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用,會(huì)切換到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
5. kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode 

33.你理解的多線程?

同一時(shí)間,CPU 只能處理理一條線程, 只有一條線程在?工作 多線程并發(fā)執(zhí)行,其實(shí)是 CPU 快速的在多條線程之間調(diào)度(切換) 如果 CPU 調(diào)度線程的時(shí)間?足夠快, 就造成了多線程并發(fā)執(zhí)?的假象

優(yōu)勢(shì)
充分發(fā)揮多核處理器的優(yōu)勢(shì),將不同線程任務(wù)分配給不同的處理器,真正進(jìn)入“?行 計(jì)算”狀態(tài)
弊端 
新線程會(huì)消耗內(nèi)存控件和cpu時(shí)間,線程太多會(huì)降低系統(tǒng)行性能。

34.iOS的多線程方案有哪幾種?你更傾向于哪一種?

5.png
傾向于GCD ,簡(jiǎn)單靈活,使用方便

35.你在項(xiàng)目中用過(guò) GCD 嗎?

使用過(guò)

GCD中有2個(gè)用來(lái)執(zhí)行任務(wù)的函數(shù)
用同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊(duì)列
block:任務(wù)

用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

36.GCD 的隊(duì)列類型

GCD的隊(duì)列可以分為2大類型
并發(fā)隊(duì)列(Concurrent Dispatch Queue)
可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

串行隊(duì)列(Serial Dispatch Queue)
讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))

37.說(shuō)一下 OperationQueue 和 GCD 的區(qū)別,以及各自的優(yōu)勢(shì)

1> GCD是純C語(yǔ)?言的API,NSOperationQueue是基于GCD的OC版本封裝
2> GCD只?支持FIFO的隊(duì)列列,NSOperationQueue可以很?方便便地調(diào)整執(zhí)?行行順 序、設(shè) 置最?大并發(fā)數(shù)量量
3> NSOperationQueue可以在輕松在Operation間設(shè)置依賴關(guān)系,?而GCD 需要寫很 多的代碼才能實(shí)現(xiàn)
4> NSOperationQueue?支持KVO,可以監(jiān)測(cè)operation是否正在執(zhí)?行行 (isExecuted)、 是否結(jié)束(isFinished),是否取消(isCanceld)
5> GCD的執(zhí)?行行速度?比NSOperationQueue快 任務(wù)之間不不太互相依賴:GCD 任務(wù)之間 有依賴\或者要監(jiān)聽(tīng)任務(wù)的執(zhí)?行行情況:NSOperationQueue

38.線程安全的處理手段有哪些?

1.加鎖
2.同步執(zhí)行

39.OC你了解的鎖有哪些?在你回答基礎(chǔ)上進(jìn)行二次提問(wèn);

os_unfair_lock  ios10 開始
OSSpinLock      ios10 廢棄
dispatch_semaphore   建議使用,性能也比較好
dispatch_mutex
dispatch_queue   串行
NSLock  對(duì) mutex 封裝
@synchronized 性能最差

40.追問(wèn)一:自旋和互斥對(duì)比?

什么情況使用自旋鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間很短
加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生
CPU資源不緊張
多核處理器

什么情況使用互斥鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng)
單核處理器
臨界區(qū)有IO操作
臨界區(qū)代碼復(fù)雜或者循環(huán)量大
臨界區(qū)競(jìng)爭(zhēng)非常激烈   

41.追問(wèn)二:使用以上鎖需要注意哪些?

注意死鎖
在串行隊(duì)列使用同步,容易造成死鎖

42.追問(wèn)三:用C/OC/C++,任選其一,實(shí)現(xiàn)自旋或互斥?口述即可!

兩種鎖的加鎖原理:

互斥鎖:線程會(huì)從sleep(加鎖)——>running(解鎖),過(guò)程中有上下文的切換,cpu的搶占,信號(hào)的發(fā)送等開銷。

自旋鎖:線程一直是running(加鎖——>解鎖),死循環(huán)檢測(cè)鎖的標(biāo)志位,

43.請(qǐng)問(wèn)下面代碼的打印結(jié)果是什么?

6.png
打印 1,3
performSelector after 是基于 timer 定制器,定時(shí)器又是基于 runloop 實(shí)現(xiàn)的
任務(wù)2在子線程中,子線程默認(rèn) runloop 是不開啟的,所以不執(zhí)行2

44.請(qǐng)問(wèn)下面代碼的打印結(jié)果是什么?

7.png
打印1
start 執(zhí)行完,線程就銷毀了.任務(wù) test 沒(méi)法執(zhí)行了

45.使用CADisplayLink、NSTimer有什么注意點(diǎn)?

CADisplayLink 保證調(diào)用頻率和刷幀頻率一直,60FPS, 不用設(shè)置時(shí)間間隔,每秒鐘60次
    可以使用 proxy 代理解決循環(huán)引用

    CADisplayLink、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用,如果target又對(duì)它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用

46.介紹下內(nèi)存的幾大區(qū)域

低地址-> 高地址
保留->代碼段->數(shù)據(jù)段(字符串常量,已初始化全局?jǐn)?shù)據(jù),未初始化數(shù)據(jù))>堆->棧內(nèi)存-> 內(nèi)核區(qū)域
代碼段: 編譯之后的代碼
數(shù)據(jù)段: 字符串常量,已經(jīng)初始化的全局變量,或者靜態(tài)變量,未初始化的全局變量,靜態(tài)變量
堆 (低>高)  通過(guò) alloc malloc calloc 動(dòng)態(tài)分配的內(nèi)存

棧 (高地址 從 低地址)  函數(shù)調(diào)用開銷()

47.講一下你對(duì) iOS 內(nèi)存管理的理解

在iOS中,使用引用計(jì)數(shù)來(lái)管理OC對(duì)象的內(nèi)存

一個(gè)新創(chuàng)建的OC對(duì)象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對(duì)象就會(huì)銷毀,釋放其占用的內(nèi)存空間

調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1,調(diào)用release會(huì)讓OC對(duì)象的引用計(jì)數(shù)-1

內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
當(dāng)調(diào)用alloc、new、copy、mutableCopy方法返回了一個(gè)對(duì)象,在不需要這個(gè)對(duì)象時(shí),要調(diào)用release或者autorelease來(lái)釋放它
想擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)+1;不想再擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)-1

可以通過(guò)以下私有函數(shù)來(lái)查看自動(dòng)釋放池的情況
extern void _objc_autoreleasePoolPrint(void);

48.ARC 都幫我們做了什么?

LLVM + Runtime 會(huì)為我們代碼自動(dòng)插入 retain 和 release 以及 autorelease等代碼,不需要我們手動(dòng)管理

49.weak指針的實(shí)現(xiàn)原理

Runtime維護(hù)了一個(gè)weak表,用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表,Key是所指對(duì)象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象的地址)數(shù)組。

runtime對(duì)注冊(cè)的類, 會(huì)進(jìn)行布局,對(duì)于weak對(duì)象會(huì)放入一個(gè)hash表中。 用weak指向的對(duì)象內(nèi)存地址作為key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì)dealloc,假如weak指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵, 在這個(gè)weak表中搜索,找到所有以a為鍵的weak對(duì)象,從而設(shè)置為nil。

50.autorelease對(duì)象在什么時(shí)機(jī)會(huì)被調(diào)用release

iOS在主線程的Runloop中注冊(cè)了2個(gè)Observer
-第1個(gè)Observer監(jiān)聽(tīng)了kCFRunLoopEntry事件,會(huì)調(diào)用objc_autoreleasePoolPush()
-第2個(gè)Observer
    監(jiān)聽(tīng)了kCFRunLoopBeforeWaiting事件,會(huì)調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
    監(jiān)聽(tīng)了kCFRunLoopBeforeExit事件,會(huì)調(diào)用objc_autoreleasePoolPop()

    objc_autoreleasePoolPop()調(diào)用時(shí)候回給 pool 中的對(duì)象發(fā)送一次 release 消息

51.方法里有局部對(duì)象, 出了方法后會(huì)立即釋放嗎

如果是普通的 局部對(duì)象 會(huì)立即釋放
如果是放在了 autoreleasePool 自動(dòng)釋放吃,則會(huì)等runloop 循環(huán),進(jìn)入休眠前釋放

52.思考以下2段代碼能發(fā)生什么事?有什么區(qū)別?

8.png
第一個(gè)內(nèi)存會(huì)暴漲,self.name 會(huì)不停的創(chuàng)建
第二個(gè)內(nèi)存固定,會(huì)使用 Tagged Pointer 將值存在地址中

53.你在項(xiàng)目中是怎么優(yōu)化內(nèi)存的?

內(nèi)存優(yōu)化可以從 內(nèi)存泄漏 和 內(nèi)存開銷 2方面入口

- 減少內(nèi)存泄露
  可以使用靜態(tài)分析以及instruments的leaks 分析
  注意 NStimer 以及 block ,delegate 等的使用,避免循環(huán)引用

- 降低內(nèi)存使用峰值
  1. 關(guān)于圖片加載占用內(nèi)存問(wèn)題:imageNamed: 方法會(huì)在內(nèi)存中緩存圖片,用于常用的圖片。
   imageWithContentsOfFile: 方法在視圖銷毀的時(shí)候會(huì)釋放圖片占用的內(nèi)存,適合不常用的大圖等。

  2.tableView cell 盡量使用重用機(jī)制,減少額外的開銷
  3.tableView 列表圖片展示盡量使用縮略圖
  4.延遲加載 對(duì)象,節(jié)約內(nèi)存開銷
  5.避免短時(shí)間大量創(chuàng)建對(duì)象,配合 autoreleasePool 減少內(nèi)存峰值
  6.重用大開銷對(duì)象,比如: NSDateFormatter和NSCalendar
  7.加載 html 盡量使用 wkwebView
  8.單例使用不易過(guò)多
  9.線程最大并發(fā)數(shù)

54.優(yōu)化你是從哪幾方面著手?

卡頓優(yōu)化
啟動(dòng)優(yōu)化
耗電量?jī)?yōu)化
app 瘦身

CPU 占用率、 內(nèi)存使用情況、網(wǎng)絡(luò)狀況監(jiān)控、啟動(dòng)時(shí)閃退、卡頓、FPS、使用時(shí)崩潰、耗電量監(jiān)控、流量監(jiān)控....

55.列表卡頓的原因可能有哪些?你平時(shí)是怎么優(yōu)化的?

1.最常用的就是cell的重用, 注冊(cè)重用標(biāo)識(shí)符
   如果不重用cell時(shí),每當(dāng)一個(gè)cell顯示到屏幕上時(shí),就會(huì)重新創(chuàng)建一個(gè)新的cell;
   如果有很多數(shù)據(jù)的時(shí)候,就會(huì)堆積很多cell。
   如果重用cell,為cell創(chuàng)建一個(gè)ID,每當(dāng)需要顯示cell 的時(shí)候,都會(huì)先去緩沖池中尋找可循環(huán)利用的cell,如果沒(méi)有再重新創(chuàng)建cell

2.避免cell的重新布局
   cell的布局填充等操作 比較耗時(shí),一般創(chuàng)建時(shí)就布局好
   如可以將cell單獨(dú)放到一個(gè)自定義類,初始化時(shí)就布局好

3.提前計(jì)算并緩存cell的屬性及內(nèi)容
    當(dāng)我們創(chuàng)建cell的數(shù)據(jù)源方法時(shí),編譯器并不是先創(chuàng)建cell 再定cell的高度
    而是先根據(jù)內(nèi)容一次確定每一個(gè)cell的高度,高度確定后,再創(chuàng)建要顯示的cell,滾動(dòng)時(shí),每當(dāng)cell進(jìn)入憑虛都會(huì)計(jì)算高度,提前估算高度告訴編譯器,編譯器知道高度后,緊接著就會(huì)創(chuàng)建cell,這時(shí)再調(diào)用高度的具體計(jì)算方法,這樣可以方式浪費(fèi)時(shí)間去計(jì)算顯示以外的cell

4.減少cell中控件的數(shù)量
   盡量使cell得布局大致相同,不同風(fēng)格的cell可以使用不用的重用標(biāo)識(shí)符,初始化時(shí)添加控件,
   不適用的可以先隱藏

5.不要使用ClearColor,無(wú)背景色,透明度也不要設(shè)置為0
   渲染耗時(shí)比較長(zhǎng)

6.使用局部更新
   如果只是更新某組的話,使用reloadSection進(jìn)行局部更新

7.加載網(wǎng)絡(luò)數(shù)據(jù),下載圖片,使用異步加載,并緩存

8.少使用addView 給cell動(dòng)態(tài)添加view

9.按需加載cell,cell滾動(dòng)很快時(shí),只加載范圍內(nèi)的cell

10.不要實(shí)現(xiàn)無(wú)用的代理方法,tableView只遵守兩個(gè)協(xié)議

11.緩存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時(shí)存在,這兩者同時(shí)存在才會(huì)出現(xiàn)“竄動(dòng)”的bug。所以我的建議是:只要是固定行高就寫預(yù)估行高來(lái)減少行高調(diào)用次數(shù)提升性能。如果是動(dòng)態(tài)行高就不要寫預(yù)估方法了,用一個(gè)行高的緩存字典來(lái)減少代碼的調(diào)用次數(shù)即可

12.不要做多余的繪制工作。在實(shí)現(xiàn)drawRect:的時(shí)候,它的rect參數(shù)就是需要繪制的區(qū)域,這個(gè)區(qū)域之外的不需要進(jìn)行繪制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判斷是否需要繪制image和text,然后再調(diào)用繪制方法。

13.預(yù)渲染圖像。當(dāng)新的圖像出現(xiàn)時(shí),仍然會(huì)有短暫的停頓現(xiàn)象。解決的辦法就是在bitmap context里先將其畫一遍,導(dǎo)出成UIImage對(duì)象,然后再繪制到屏幕;

14.使用正確的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)。

56.app 啟動(dòng)優(yōu)化

1\. pre-main 之前

    * 排查無(wú)用的動(dòng)態(tài)庫(kù)(定期清理)
    * 減少ObjC類(項(xiàng)目中不適用的的庫(kù),廢棄的代碼等)、方法(selector)、分類(category)的數(shù)量、無(wú)用的庫(kù)
    * 少在類的+load方法里做事情,盡量把這些事情推遲到+initiailize1.

2\. main 函數(shù)之后的 didFinishLaunchingWithOptions 加載完之前

    * 不影響用戶體驗(yàn)的操作,做延遲加載,不要全部放在  didFinishLaunchingWithOptions中去做
    * 版本更新,一些三方初始化,不需要在 didFinishLaunchingWithOptions 初始化的放到,界面展示完以后再初始化
    * 一些網(wǎng)絡(luò)請(qǐng)求延遲 請(qǐng)求..
    * 一些業(yè)務(wù)邏輯延遲 加載
    * 初始化第三方 SDK
    * 配置 APP 運(yùn)行需要的環(huán)境
    * 自己的一些工具類的初始化

57.app 耗電量?jī)?yōu)化

1.不要頻繁的刷新頁(yè)面,能刷新1行cell最好只刷新一行,盡量不要使用reloadData.
2.選擇正確的集合
    NSArray,使用index來(lái)查找很快(插入和刪除很慢)
    字典,使用鍵來(lái)查找很快
    NSSets,是無(wú)序的,用鍵查找很快,插入/刪除很快
3.少用運(yùn)算獲得圓角,必須要用圓角的話,不如把圖片本身就做成圓角
4.懶加載,不要一次性創(chuàng)建所有的subview,而是需要時(shí)才創(chuàng)建.
5.重用機(jī)制
6.圖片處理
    圖片與imageView相同大小,避免多余運(yùn)算
    可以使用整副的圖片,增加應(yīng)用體積,但是節(jié)省CPU
    可調(diào)大小的圖片,可以省去一些不必要的空間
    CALayer,CoreGraphics,甚至OpenGL來(lái)繪制,消耗CPU
7.cache,cache,cache(緩存所有需要的)
    服務(wù)器相應(yīng)結(jié)果的緩存(圖片)
    復(fù)雜計(jì)算結(jié)果的緩存(UITableView的行高)
8.盡量少用透明或半透明,會(huì)產(chǎn)生額外的運(yùn)算.

9.使用ARC減少內(nèi)存失誤,dealloc需要重寫并對(duì)屬性置為nil

10.避免龐大的xib,storyBoard,盡量使用純代碼開發(fā)

CPU層面

1.Timer的時(shí)間間隔不宜太短,滿足需求即可
2.線程適量,不宜過(guò)多,不要阻塞主線程
3.優(yōu)化算法,減少循環(huán)次數(shù)
4.定位和藍(lán)牙按需取用,定位之后要關(guān)閉或降低定位頻率
5.一些硬件的使用,不使用就關(guān)掉

58.app 的包瘦身

9.png

59.講講 MVC、MVVM、MVP,以及你在項(xiàng)目里具體是怎么寫的?

MVC  Model-view-controller 數(shù)據(jù)-視圖-控制器     
一般控制器用于管理數(shù)據(jù)和視圖, 數(shù)據(jù)和視圖交互都是通過(guò)控制器來(lái)進(jìn)行的.視圖和數(shù)據(jù)進(jìn)行了解耦, 但是我們?nèi)粘J褂媒?jīng)常會(huì)將模型綁定給視圖.模型封裝在視圖內(nèi)部,外部不用管理視圖內(nèi)部業(yè)務(wù)邏輯,這數(shù)據(jù) mvc 的變種, 控制器只給視圖模型數(shù)據(jù)就好了. 缺點(diǎn)是視圖和 模型有耦合;

MVVM Model-view-viewModel  模型-視圖-視圖模型
view 和 model 的交互通過(guò)viewmodel 來(lái)進(jìn)行交互,實(shí)現(xiàn)數(shù)據(jù)的雙向綁定

MVP  Model-view - Presenter  模型-視圖-主持人

view 和 model 的交互通過(guò)Presenter,controller通過(guò)Presenter來(lái)管理 model 和 View

60.你自己用過(guò)哪些設(shè)計(jì)模式?

結(jié)合自己項(xiàng)目來(lái)講吧

61.一般開始做一個(gè)項(xiàng)目,你的架構(gòu)是如何思考的?

根據(jù)模塊,使用 mvc 功能劃分..結(jié)合自己項(xiàng)目講比較容易
涉及到東西也比較多,比較雜,大到整個(gè)項(xiàng)目架構(gòu),小到一個(gè) view 的架構(gòu);沒(méi)具體的答案
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,621評(píng)論 1 32
  • 面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài) OC內(nèi)存管理 _strong 引用計(jì)數(shù)器來(lái)控制對(duì)象的生命周期。 _weak...
    運(yùn)氣不夠技術(shù)湊閱讀 1,221評(píng)論 0 10
  • 前言: 最近公司項(xiàng)目不怎么忙, 閑暇時(shí)間把iOS 在面試中可能會(huì)遇到的問(wèn)題整理了一番, 一部分題目是自己面試遇到...
    Leon_520閱讀 8,969評(píng)論 1 47
  • OC語(yǔ)法篇 面向?qū)ο?1. 一個(gè)NSObject對(duì)象占用多少內(nèi)存? 系統(tǒng)分配了16個(gè)字節(jié)給NSobject對(duì)象(通...
    內(nèi)心戲十足的偽胖子閱讀 601評(píng)論 1 8
  • 醜陋的中國(guó)人 《醜陋的中國(guó)人》是臺(tái)灣通俗歷史作家柏楊的著作。本書為柏楊在各種公開場(chǎng)合演講的講稿集結(jié)合成的。他在這書...
    黑貓子閱讀 205評(píng)論 0 0

友情鏈接更多精彩內(nèi)容