總結(jié)一些iOS的底層面試題。鞏固一下iOS的相關(guān)基礎(chǔ)知識(shí)。
如有出入,還望各位大神指出。
OC對(duì)象
1. NSObject對(duì)象的本質(zhì)是什么?
- NSObject對(duì)象的本質(zhì)就是結(jié)構(gòu)體
2. 一個(gè)NSObject對(duì)象占用多少內(nèi)存?
NSObject對(duì)象創(chuàng)建實(shí)例對(duì)象的時(shí)候系統(tǒng)分配了16個(gè)內(nèi)存(通過malloc_size函數(shù)可獲得)
但是 NSObject只使用了8個(gè)字節(jié) 使用(class_getinstanceSize可獲得)
3. 對(duì)象的isa指針指向哪里?
instance對(duì)象的isa指針指向class對(duì)象
class對(duì)象的isa指針指向 meta-class對(duì)象
meta-class對(duì)象的isa指針基類的meta-class對(duì)象
-
isa的優(yōu)化
isa在arm64構(gòu)架之前 isa的值就類對(duì)象的地址值。
-
isa在arm64構(gòu)架開始的時(shí)候 采用了 isa優(yōu)化的策略, 使用了共用體的技術(shù)。將64位的內(nèi)存地址存儲(chǔ)了很多東西,其中33位存儲(chǔ)的是isa具體的地址值的。因?yàn)楣灿皿w中 前三位有存儲(chǔ)的東西(),所以在&isa_mask出來的類對(duì)象地址值的二進(jìn)制后面三位永遠(yuǎn)都是000, 十六進(jìn)制就是8 或者0結(jié)尾的地址值
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; ///> typedef unsigned long struct { ///> 0 代表普通指針,存儲(chǔ)著Class、MetaClass對(duì)象的內(nèi)存地址 ///> 1 代表優(yōu)化過,使用位域存儲(chǔ)更多信息 uintptr_t nonpointer : 1; ///> 是否有設(shè)置過關(guān)聯(lián)對(duì)象,如果沒有,釋放時(shí)會(huì)更快 uintptr_t has_assoc : 1; ///> 是否有C++的析構(gòu)函數(shù)(.cxx_destruct),如果沒有,釋放會(huì)更快 uintptr_t has_cxx_dtor : 1; ///> 存儲(chǔ)著Class、MetaClass的內(nèi)存地址 uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ///> 用于在調(diào)試時(shí)分辨率是否未完成初始化 uintptr_t magic : 6; ///> 是否被弱指針指向過? 如果沒有,釋放會(huì)更快 uintptr_t weakly_referenced : 1; ///> 對(duì)象是否正在釋放 uintptr_t deallocating : 1; ///> 引用計(jì)數(shù)器是否過大?無法存儲(chǔ)在isa中 ///> 如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫 side table的類屬性中 uintptr_t has_sidetable_rc : 1; ///> 里面存儲(chǔ)的值是引用計(jì)數(shù)器減1 uintptr_t extra_rc : 19; }; ... }
4. OC類的信息存儲(chǔ)在哪里?
- meta-class存儲(chǔ):類方法
- class對(duì)象存儲(chǔ): 對(duì)象方法,屬性,成員變量,協(xié)議信息
- instance存儲(chǔ): 成員變量具體的值
5. 說說你對(duì)函數(shù)調(diào)用的理解吧。
- 函數(shù)調(diào)用 實(shí)際實(shí)際上就是 給對(duì)象發(fā)送一條消息
- objc_msgSend(對(duì)象, @selectir(對(duì)象方法))
- 尋找順序(對(duì)象方法) instance的isa指針找到類對(duì)象 在類對(duì)象中尋找方法,若沒有向superClass中查找。
- 尋找順序(類方法) instance的isa指針找到類對(duì)象 --> 類對(duì)象的isa找到meta-calss --> 在meta-class對(duì)象中尋找類方法,若沒有向superClass中查找。
KVO
1. iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?(KVO的本質(zhì)是什么?)
利用Runtime API 動(dòng)態(tài)生成了一個(gè)新的類, 并且instance對(duì)象的isa指針指向這個(gè)生成的新子類。
-
當(dāng)修改instance對(duì)象的屬性時(shí),會(huì)調(diào)用Foundation的_NSSetXXXValueForKey函數(shù)
- willChangeValueForKey
- 父類原來的set方法
- didChangeValueForKey
- didChangeValueForKey 內(nèi)部會(huì)觸發(fā)observerValueForKeyPath方法 實(shí)現(xiàn)監(jiān)聽。
輕量級(jí)KVO框架:GitHub - facebook/KVOController
KVC
1. 使用KVC會(huì)不會(huì)調(diào)用KVO?
- 會(huì)調(diào)用KVO, 因?yàn)樗膬?nèi)部使用了:
- willChangeValueForKey
- 直接去_方式去更改
- didChangeValueForKey
- didChangeValueForKey 內(nèi)部會(huì)觸發(fā)
2. KVC的賦值和取值過程是怎么樣的?原理是什么?
- 賦值
- setKey、_setKey 順序查找方法
- 有: 傳遞參數(shù)調(diào)用防范
- 沒有: 查看accessInstanceVariablesDirectly 方法返回值 yes 繼續(xù)查找
- _key、_iskey、key、iskey 順序查找
- setKey、_setKey 順序查找方法
Category:
1. Category的使用場合是什么?
2. Category中的屬性是否也存在類對(duì)象中?如果存在是怎么生成和存在的?如果不存在,它存在的位置在哪里?
- 一個(gè)類永遠(yuǎn)只有一個(gè)類對(duì)象
- 在運(yùn)行起來之后 最重都會(huì)合并在 類對(duì)象中去。
3. Category的使用原理是什么?實(shí)現(xiàn)過程
-
原理:底層結(jié)構(gòu)是結(jié)構(gòu)體 categoty_t 創(chuàng)建好分類之后分兩個(gè)階段:
-
編譯階段:
將每一個(gè)分類都生成所對(duì)應(yīng)的 category_t結(jié)構(gòu)體, 結(jié)構(gòu)體中存放 分類的所屬類name、class、對(duì)象方法列表、類方法列表、協(xié)議列表、屬性列表。
-
Runtime運(yùn)行時(shí)階段:
將生成的分類數(shù)據(jù)合并到原始的類中去,某個(gè)類的分類數(shù)據(jù)會(huì)在合并到一個(gè)大的數(shù)組當(dāng)中(后參與編譯的分類會(huì)在數(shù)組的前面),分類的方法列表,屬性列表,協(xié)議列表等都放在二維數(shù)組當(dāng)中,然后重新組織類中的方法,將每一個(gè)分類對(duì)應(yīng)的列表的合并到原始類的列表中。(合并前會(huì)根據(jù)二維數(shù)組的數(shù)量擴(kuò)充原始類的列表,然后將分類的列表放入前面)
-
-
調(diào)用順序
-
為什么Category的中的方法會(huì)優(yōu)先調(diào)用?
如上所述, 在擴(kuò)充數(shù)組的時(shí)候 會(huì)將原始類中擁有的方法列表移動(dòng)到后面, 將分類的方法列表數(shù)據(jù)放在前面,所以分類的數(shù)據(jù)會(huì)優(yōu)先調(diào)用
-
延伸問題 - 如果多個(gè)分類中都實(shí)現(xiàn)了同一個(gè)方法,那么在調(diào)用該方法的時(shí)候會(huì)優(yōu)先調(diào)用哪一個(gè)方法?
在多個(gè)分類中擁有相同的方法的時(shí)候, 會(huì)根據(jù)編譯的先后順序 來添加分類方法列表, 后編譯的分類方法在最前面,所以要看 Build Phases --> compile Sources中的順序。 后參加編譯的在前面。
-
4. Category和Extension的區(qū)別是什么?
- Category 在運(yùn)行的時(shí)候才將數(shù)據(jù)合并到類信息中
- Extension 在編譯的時(shí)候就將數(shù)據(jù)包含在類信息中了 @interface Xxxx() 也叫做匿名分類
5. Category中有l(wèi)oad方法嗎?load是什么時(shí)候調(diào)用的?
- 有l(wèi)oad方法
- load什么時(shí)候調(diào)用
- load方法在runtime加載類、分類的時(shí)候調(diào)用
6. load、initialize方法的區(qū)別是什么?它們?cè)贑ategory中的調(diào)用順序?以及出現(xiàn)繼承時(shí)他們之間的調(diào)用過程?當(dāng)一個(gè)類有分類的時(shí)候?yàn)槭裁?load能多次調(diào)用兒initialize值調(diào)用了一次?
-
調(diào)用方式:
- load 根據(jù)函數(shù)地址直接調(diào)用
- initialize 是通過 objc_msgSend調(diào)用
-
調(diào)用時(shí)刻:
- load是runtime加載類、分類的時(shí)候調(diào)用(只會(huì)調(diào)用1次)
- initialize 是類第一次接收到消息的時(shí)候調(diào)用objc_msgsend()方法 如alloc、每一個(gè)類只會(huì)調(diào)用1次(但是父類的initialize方法可能會(huì)調(diào)用多次) 有些子類沒有initialize方法所以調(diào)用父類的。
-
調(diào)用順序:
- load:
- 先調(diào)用類的+load方法
- 按照編譯的先后順序調(diào)用(先編譯、先調(diào)用)
- 調(diào)用子類的+load方法之前會(huì)先調(diào)用父類的+load
- 再調(diào)用分類的+load方法
- 按照編譯的先后順序調(diào)用(先編譯、先調(diào)用)
- 先調(diào)用類的+load方法
- initialize
- 初始化父類
- 在初始化子類(可能最終調(diào)用的是父類的initialize方法)
- load:
-
load方法可以繼承 我們?cè)谧宇悰]有實(shí)現(xiàn)的時(shí)候可以調(diào)用,但是一般都是類自動(dòng)去調(diào)用,我們不會(huì)主動(dòng)調(diào)用,當(dāng)子類沒有實(shí)現(xiàn)+load方法的時(shí)候不會(huì)不會(huì)自動(dòng)調(diào)用了就
<img src="/Users/yuangonmg/Library/Application Support/typora-user-images/image-20191112191237086.png" alt="image-20191112191237086" style="zoom:25%;" />
-
當(dāng)一個(gè)類有分類的時(shí)候?yàn)槭裁?load能多次調(diào)用兒initialize值調(diào)用了一次?
- 根據(jù)源碼看出來,+load 直接通過函數(shù)指針指向函數(shù),拿到函數(shù)地址,找到函數(shù)代碼,直接調(diào)用 分開來直接調(diào)用的 不是通過objc_msgsend調(diào)用的
- 而 initialize是通過消息發(fā)送機(jī)制,isa找到類對(duì)象找到方法調(diào)用的 所以只調(diào)用一次
7. Category能否添加成員變量?如果可以,如何給Category添加成員變量?
-
不能直接給Category添加成員變量,但是可以間接添加。
- 使用一個(gè)全局的字典 (缺點(diǎn): 每一個(gè)屬相都需要一套相同的代碼)
///> DLPerson+Test.h @interface DLPerson (Test) ///> 如果直接使用 @property 只會(huì)生成方法的聲名 不會(huì)生成成員變量和set、get方法的實(shí)現(xiàn)。 @property (nonatomic, assign) int weigjt; @end ///> DLPerson+Test.m #import "DLPerson+Test.h" @implemention DLPerson (Test) NSMutableDictionary weights_; + (void)load{ weights_ = [NSMutableDictionary alloc]init]; } - (void)setWeight:(int)weight{ NSString *key = [NSString stringWithFormat:@"%p",self]; weights_[key] = @(weight); } - (int)weight{ NSString *key = [NSString stringWithFormat:@"%p",self]; return [weights_[key] intValue] } @end 使用runtime機(jī)制給分類添加屬性
```objc
#import<objc/runtime.h>
const void *DLNameKey = &DLNameKey
///> 添加關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(
id object, ///> 給哪一個(gè)對(duì)象添加關(guān)聯(lián)對(duì)象
const void * key, ///> 指針(賦值取值的key) &DLNameKey
id value, ///> 關(guān)聯(lián)的值
objc_AssociationPolicy policy ///> 關(guān)聯(lián)策略 下方表格
)
eg : objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY_NONATOMIC);
///> 獲得關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject(
id object, ///> 哪一個(gè)對(duì)象的關(guān)聯(lián)對(duì)象
const void * key ///> 指針(賦值取值的key)
)
eg:
objc_getAssociatedObject(self,@selector(name));
/// _cmd == @selector(name);
objc_getAssociatedObject(self,_cmd);
///> 移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(
id object ///>
)
- objc_AssociationPolicy(關(guān)聯(lián)策略)
|objc_AssociationPolicy(關(guān)聯(lián)策略) |對(duì)應(yīng)的修飾符|
|:---|:---|:---|:---|
|OBJC_ASSOCIATION_ASSIGN | assign |
|OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
|OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
|OBJC_ASSOCIATION_RETAIN | strong, atomic |
|OBJC_ASSOCIATION_COPY | copy, atomic |
Block
1. block的原理是怎樣的?本質(zhì)是什么
- block的本質(zhì)就是一個(gè)oc對(duì)象 內(nèi)部也有isa指針, 封裝了函數(shù)及調(diào)用環(huán)境的OC對(duì)象,
2. 看代碼解釋原因
int main(int argc, const char *argv[]){
@autoreleasepool{
int age = 10;
void (^block)(void) = ^{
NSLog(@" age is %d ",age);
};
age = 20;
block();
}
}
/*
輸出結(jié)果為? 為什么?
輸出結(jié)果是: 10
如果沒有修飾符 默認(rèn)是auto
為了能訪問外部的變量
block有一個(gè)變量捕獲的機(jī)制
因?yàn)樗蔷植孔兞?并且沒有用static修飾
所以它被捕獲到block中是 一個(gè)值,外部再次改變時(shí) block中的age不會(huì)改變。
*/
| 變量類型 | 捕獲到Block內(nèi)部 | 訪問方式 | |
|---|---|---|---|
| 局部變量 | auto | ? | 值傳遞 |
| 局部變量 | static | ? | 指針傳遞 |
| 全局變量 | ? | 直接訪問 |
int main(int argc, const char *argv[]){
@autoreleasepool{
int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@" age is %d, height is %d",age, height);
};
age = 20;
height = 20;
block();
}
}
/*
輸出結(jié)果為? 為什么?
age is 10, height is 20
局部變量用static 修飾之后 捕獲到block中的是 height的指針,
因此修改通過指針修改變量之后 外部的變量也被修改了
*/
int age = 10;
static int height = 10;
int main(int argc, const char *argv[]){
@autoreleasepool{
void (^block)(void) = ^{
NSLog(@" age is %d, height is %d",age, height);
};
age = 20;
height = 20;
block();
}
}
/*
輸出結(jié)果為? 為什么?
age is 20, height is 20
因?yàn)?age 和 height是全局變量不需要捕獲直接就可以修改
全局變量 對(duì)應(yīng)該就可以訪問,
局部變量 需要跨函數(shù)訪問,所以需要捕獲
因此修改通過指針修改變量之后 外部的變量也被修改了
*/
int main(int argc, const char *argv[]){
@autoreleasepool{
void (^block)(void) = ^{
NSLog(@" self %p",self);
};
block();
}
}
/*
self 會(huì)不會(huì)被捕獲?
因?yàn)楹瘮?shù)默認(rèn)會(huì)有兩個(gè)參數(shù) void test(DLPerson *self, SEL _cmd)
所以self 也是一個(gè)局部變量
訪問@property(nonatmic, copy) NSString *name;
因?yàn)樗浅蓡T變量, 訪問的時(shí)候 用self.name 或者 self->_name 訪問 所以 block在內(nèi)部會(huì)捕獲self。
*/
3. 既然block是一個(gè)OC對(duì)象,那么block的對(duì)象類型是什么?
- ios內(nèi)存分為5發(fā)區(qū)域
- 堆: 動(dòng)態(tài)分配內(nèi)存,需要程序員申請(qǐng),也需要程序員自己管理(alloc、malloc等...)
- 棧: 放一些局部變量,臨時(shí)變量 系統(tǒng)自己管理
- 靜態(tài)區(qū)(全局區(qū)): 存放全局的靜態(tài)對(duì)象。(編譯時(shí)分配,APP結(jié)束由系統(tǒng)釋放)
- 常量區(qū): 常量。(編譯時(shí)分配,APP結(jié)束由系統(tǒng)釋放)
- 代碼區(qū): 程序區(qū)
-
block有三種類型,最終都繼承自NSBlock類型
- superClass
NSGlobalBlock : __NSGlobalBlock : NSBlock : NSObject
block類型 環(huán)境 存放位置 NSGlobalBlock 沒有訪問auto變量 靜態(tài)區(qū) NSStackBlock 訪問了auto變量 棧 NSMallocBlock NSStackBlock調(diào)用了copy 堆 - superClass
-
NSStackBlock調(diào)用了copy 代碼實(shí)例
void (^block)(void); void (^block1)(void); void test2(){ int age = 10; block = ^{ NSLog("age is %d",age); /* 因?yàn)?block訪問了 auto變量 所以目前block的類型為NSStackBlock類型, 存放的位置在 棧 上 在 main訪問是 這個(gè)block已經(jīng)被釋放了。 */ }; [block1 = ^{ NSLog("age is %d",age); /* 因?yàn)?block訪問了 auto變量 并且 進(jìn)行了copy操作 所以目前block的類型為 NSMallocBlock 類型 存放的位置在 堆 上 在 main訪問是 這個(gè)block是可以被訪問的。 */ } copy]; } int main(int argc, const char *argv[]){ @autoreleasepool{ test2(); block(); /// 輸出的值為 很大不是想要的值 block1();/// 輸出的值是10 } } -
每一種類型的block調(diào)用了copy之后結(jié)果如下所示
block的類型 副本源的配置存儲(chǔ)域 復(fù)制后的區(qū)域 NSGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么都不做 NSStackBlock 棧 從棧復(fù)制到堆 NSMallocBlock 堆 引用計(jì)數(shù)器+1
4. 在什么情況下 ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上?
block作為函數(shù)的返回值的時(shí)候
將block賦值給__strong指針時(shí)
-
block作為 Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
NSArray *arr = @[]; /// 遍歷數(shù)組中包含 usingBlock方法的參數(shù) [arr enumerateObjectUsingBlock:^(id _Nonnullobj, NSUInteger idx, Bool _Nonnull stop){ }] ; -
block作為GCD屬性的建議寫法
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ }); disPatch_after(disPatch_time(IDSPATCH_TIME_NOW, (int64_t)(delayInSecounds *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ }); -
MRC下block屬性建議寫法
- @property (copy, nonatomic) void (^block)(void);
-
ARC下block屬性建議寫法
- @property (strong, nonatomic) void (^block)(void);
- @property (copy, nonatomic) void (^block)(void);
5. __weak的作用是什么?有什么使用注意點(diǎn)?
- __weak 是一個(gè)修飾符
- 當(dāng)block內(nèi)部訪問的對(duì)象類型的auto變量
- 如果block是在棧上,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
- 如果Block被拷貝到堆上
- 會(huì)調(diào)用block內(nèi)部的copy函數(shù)
- copy函數(shù)會(huì)調(diào)用源碼中的_Block_object_assign函數(shù)
- _Block_object_assign函數(shù)會(huì)根據(jù)修飾 auto 變量的修飾符(__strong、__weak 、__unsafe_unretained)來決定作出相應(yīng)的操作,形成強(qiáng)引用或者弱引用
- block從對(duì)上移除
- 會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
- dispose函數(shù)會(huì)調(diào)用源碼中的 _Block_object_dispose函數(shù)
- _Block_object_dispose函數(shù)會(huì)自動(dòng)釋放auto變量(release)
6. __block的作用是什么?有什么使用注意點(diǎn)?
-
block為什么不能修改外部變量的值?
- 因?yàn)閎lock也是一個(gè)函數(shù)調(diào)用(可以說他們是兩個(gè)函數(shù), block調(diào)用另一個(gè)函數(shù)的變量是不能調(diào)的)
- 在C++內(nèi)部 block是調(diào)用的另一個(gè)函數(shù) 實(shí)現(xiàn)。
__block修飾之后會(huì)將變量包裝成一個(gè)對(duì)象 可以解決block內(nèi)部無法修改auto變量的問題
包裝秤對(duì)象之后就可以通過指針修改 外部的變量了
使用注意點(diǎn): 在MRC環(huán)境下不會(huì)對(duì)指向的對(duì)象產(chǎn)生強(qiáng)引用的
7. __block的屬性修飾詞是什么?為什么?使用block有哪些注意點(diǎn)?
- 修飾詞是copy
- block 如果沒有進(jìn)行copy操作就不會(huì)再堆上, 在堆上才能控制它的生命周期
- 注意循環(huán)引用的問題
- 在ARC環(huán)境下 使用呢strong和copy都可以沒有區(qū)別 在MRC環(huán)境下有區(qū)別
- block是一個(gè)對(duì)象, 所以block理論上是可以retain/release的. 但是block在創(chuàng)建的時(shí)候它的內(nèi)存是默認(rèn)是分配在棧(stack)上, 而不是堆(heap)上的. 所以它的作用域僅限創(chuàng)建時(shí)候的當(dāng)前上下文(函數(shù), 方法...), 當(dāng)你在該作用域外調(diào)用該block時(shí), 程序就會(huì)崩潰.
8. block在修飾NSMutableArray,需不需要添加__block?
-
不需要
NSMutableArray *array = [[NSMutableView alloc]init] void (^block)(void) = ^{ array = nil; ///> 這樣操作是需要__block的。 ///> ///> 下面這個(gè)是不需要 __block修飾的,因?yàn)檫@個(gè)只是使用它的指針而不是修改它的值 [array addObject:@"aa"]; [array addObject:@"aa"]; }
- 在修改NSMutableArray的數(shù)組的時(shí)候 并不是在修改這個(gè)數(shù)據(jù) 而是在使用這個(gè)指針去
Runtime
22. 講一下OC的消息機(jī)制
- OC中的方法調(diào)用最后都是 objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
- objc_msgSend底層有三大模塊
- 消息發(fā)送(當(dāng)前類、父類中查找)、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)
23. 消息轉(zhuǎn)發(fā)機(jī)制流程
? 在objc_msgSend有三大階段
- 消息發(fā)送階段
- 給當(dāng)前類發(fā)送一條消息,
- 會(huì)先從當(dāng)前的類中的緩存查找
- 如果沒有去遍歷 class_rw_t 方法列表查找
- 如果沒有再去父類的緩存查找
- 如果沒有在去父類的class_rw_t方法列表中查找
- 循環(huán)父類 如果找到調(diào)用方法, 并且將方法緩存到 方法調(diào)用者的方法緩存中
- 如果一直沒有到下一個(gè)階段 動(dòng)態(tài)解析階段
- 動(dòng)態(tài)解析階段
- 動(dòng)態(tài)解析會(huì)調(diào)用-resolveInstanceMethod \ +resolveClassMethod 方法 在方法中手動(dòng)添加class_addMethod方法的調(diào)用。
- 只會(huì)解析一次 會(huì)將是否解析過的參數(shù)置位YES
- 然后在次 調(diào)用消息發(fā)送的階段
- 如果我們實(shí)現(xiàn)了 方法的添加 則在消息發(fā)送階段可以找到這個(gè)方法
- 調(diào)用方法并 將方法緩存到 方法調(diào)用者的緩存中
- 如果沒有實(shí)現(xiàn), 在第二次走到動(dòng)態(tài)解析階段,不會(huì)進(jìn)入動(dòng)態(tài)解析,因?yàn)樯弦淮我呀?jīng)解析過了
- 我們將動(dòng)態(tài)解析過的參數(shù)設(shè)置為YES,所以會(huì)走到下一個(gè)階段 消息轉(zhuǎn)發(fā)階段
- 消息轉(zhuǎn)發(fā)階段
- 第一種: 實(shí)現(xiàn)了forwardingTargetForSelector方法
- 調(diào)用forwardingTargetForSelector 方法(返回一個(gè)類對(duì)象), 直接使用我們?cè)O(shè)置的類去發(fā)送消息。
- 第二種: 沒有實(shí)現(xiàn)forwardingTargetForSelector
- 回去調(diào)用 methodSignatureForSelector 方法,在這個(gè)方法添加方法簽名
- 之后會(huì)調(diào)用forwardInvocation 方法, 在這個(gè)方法中我們 [anInvocation invokeWithTarget:類對(duì)象];
- 或者其他操作都可以 這里沒有什么限制。
- 第一種: 實(shí)現(xiàn)了forwardingTargetForSelector方法
24. 什么是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í)寫的代碼 底層都是轉(zhuǎn)換成了 runtime的API進(jìn)行調(diào)用的
- 具體應(yīng)用
- 關(guān)聯(lián)對(duì)象,給分類添加屬性,set和get的實(shí)現(xiàn)
- 遍歷類的成員變量 歸檔解檔、字典轉(zhuǎn)模型
- 交換方法(系統(tǒng)的交換方法)
26. iskindOfClass 和 isMemberOfClass的區(qū)別?
-
isMemberOfClass源碼:
/// 返回的直接是 是否是當(dāng)前的類, 當(dāng) 象 - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; } /// 返回的直接是 是否是當(dāng)前的類, /// 當(dāng)前元類對(duì)象 + (BOOL)isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } -
iskindOfClass源碼:
/// for循環(huán)查找 , 會(huì)根據(jù)當(dāng)前類和 當(dāng)前類的父類去逐級(jí)查找 , - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } /// for循環(huán)查找 , 會(huì)根據(jù)當(dāng)前類和 當(dāng)前類的額父類去逐級(jí)查找 , /// 當(dāng)前元類對(duì)象 + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } -
相關(guān)面試題:
// NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]); // NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]); // NSLog(@"%d", [[MJPerson class] isKindOfClass:[MJPerson class]]); // NSLog(@"%d", [[MJPerson class] isMemberOfClass:[MJPerson class]]); /// 上面的寫法 與 下面的寫法 相同 // 這句代碼的方法調(diào)用者不管是哪個(gè)類(只要是NSObject體系下的、繼承于NSObject),都返回YES NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1 NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0 NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0 NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0 // 輸出的結(jié)果是什么?
Runloop
1. 講講Runloop片在項(xiàng)目中的應(yīng)用
Runloop
多線程
1. iOS中常見的多線程方案
| 技術(shù)方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
|---|---|---|---|---|
| pthread | 1. 一套通用的多線程API 2. 適用于Unix/Linux/Windows等系統(tǒng) 3. 跨平臺(tái)、可移植 4. 使用難度大 |
C | 程序員管理 | 幾乎不用 |
| NSThread | 1. 使用更加面向?qū)ο? 2. 簡單易用,可直接操作線程對(duì)象 |
OC | 程序員管理 | 偶爾使用 |
| GCD | 1. 旨在代替NSThread等線程技術(shù) 2. 充分利用設(shè)備的多核 |
C | 自動(dòng)管理 | 經(jīng)常使用 |
| NSOperation | 1. 基于GCD(底層是GCD) 2. 比GCD多了一些簡單使用的功能 3. 使用更加面向?qū)ο?/td> | OC | 自動(dòng)管理 | 經(jīng)常使用 |
2. GCD的常用函數(shù)
- GCD中有2個(gè)用來執(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);
- 用同步的方式執(zhí)行任務(wù)
3. GCD的隊(duì)列
- GCD的隊(duì)列可以分為2大類型
- 并發(fā)隊(duì)列
- 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
- 并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
- 串行隊(duì)列
- 讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))
- 并發(fā)隊(duì)列
4. 組合隊(duì)列執(zhí)行表
| 并發(fā)隊(duì)列 | 手動(dòng)創(chuàng)建串行隊(duì)列 | 主隊(duì)列 | |
|---|---|---|---|
| 同步 | 沒有開啟新線程 串行執(zhí)行任務(wù) |
沒有開啟新線程 串行執(zhí)行任務(wù) |
沒有開啟新線程 串行執(zhí)行任務(wù) |
| 異步 | 開啟新線程 并行執(zhí)行任務(wù) |
開啟新線程 串行執(zhí)行任務(wù) |
沒有開啟新線程 串行執(zhí)行任務(wù) |
5. GCD的線程鎖
- (void)interview01{
///> 會(huì)發(fā)生死鎖,
NSLog(@"任務(wù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"任務(wù)2");
});
NSLog(@"任務(wù)3");
/// dispatch_sync 需要立馬在當(dāng)前線程 同步執(zhí)行任務(wù) 當(dāng)前在主線程中
/// 而主隊(duì)列需要等 主線程的東西執(zhí)行完之后才會(huì)執(zhí)行。 所以造成了死鎖
}
- (void)interview02{
///> 不會(huì)發(fā)生死鎖
NSLog(@"任務(wù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"任務(wù)2");
});
NSLog(@"任務(wù)3");
// dispatch_sync 不需要需要立馬在當(dāng)前線程 同步執(zhí)行任務(wù) 所以等待主線程執(zhí)行結(jié)束之后才執(zhí)行的
}
- (void)interview03{
///> 會(huì)產(chǎn)生死鎖
NSLog(@"任務(wù)1");
dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); /// 同步;
dispatch_async(queue, ^{
NSLog(@"任務(wù)2");
dispatch_sync(queue, ^{ //死鎖
NSLog(@"任務(wù)3");
});
NSLog(@"任務(wù)4");
});
NSLog(@"任務(wù)5");
}
- (void)interview04{
///> 不會(huì)產(chǎn)生死鎖
NSLog(@"任務(wù)1");
dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); /// 串行;
// dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 并發(fā);
dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_SERIAL); /// 串行; 也不會(huì)
dispatch_async(queue, ^{
NSLog(@"任務(wù)2");
dispatch_sync(queue2, ^{ //死鎖
NSLog(@"任務(wù)3");
});
NSLog(@"任務(wù)4");
});
NSLog(@"任務(wù)5");
//不會(huì)產(chǎn)生死鎖 因?yàn)閮蓚€(gè)任務(wù)不在同一個(gè)隊(duì)列之中, 所以不存在互相等待的問題。
}
- (void)interview05{
///> 不會(huì)產(chǎn)生死鎖
NSLog(@"任務(wù)1 thread:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 并發(fā);
dispatch_async(queue, ^{
NSLog(@"任務(wù)2 thread:%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"任務(wù)3 thread:%@",[NSThread currentThread]);
});
NSLog(@"任務(wù)4 thread:%@",[NSThread currentThread]);
});
NSLog(@"任務(wù)5 thread:%@",[NSThread currentThread]);
//不會(huì)產(chǎn)生死鎖 因?yàn)閮蓚€(gè)任務(wù)不在同一個(gè)隊(duì)列之中, 所以不存在互相等待的問題。
}
6. GCD的線程鎖-- runloop有關(guān)的鎖
- (void)test{
NSLog(@"2");
}
- (void)touchesBegan03{
// NSThread *thread = [[NSThread alloc]initWithBlock:^{
// NSLog(@"1");
//
// }];
// [thread start];
// [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
// 運(yùn)行后會(huì)崩潰 因?yàn)樽泳€程 performSelector方法 沒有開啟runloop, 當(dāng)執(zhí)行test的時(shí)候這個(gè)線程已經(jīng)沒有了。
NSThread *thread = [[NSThread alloc]initWithBlock:^{
NSLog(@"1");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
/// r添加開啟runloop后 在線程中有runloop存在線程就不會(huì)死掉, 之后調(diào)用performSelect就沒有問題了
}
- (void)touchesBegan02{
/// 創(chuàng)建全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
//[self performSelector:@selector(test) withObject:nil];/// 打印結(jié)果 1 2 3 等價(jià)于[self test]
/// 這句代碼點(diǎn)進(jìn)去發(fā)現(xiàn)是在Runloop中的方法
/// 本質(zhì)就是向Runloop中添加了一個(gè)定時(shí)器。 子線程默認(rèn)是沒有啟動(dòng) Runloop的
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; /// 打印結(jié)果 1 3
NSLog(@"3");
/// 啟動(dòng)runloop
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
- (void)touchesBegan01{
/// 創(chuàng)建全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// [self performSelector:@selector(test) withObject:nil];/// 打印結(jié)果 1 2 3 等價(jià)于[self test]
/// 這句代碼點(diǎn)進(jìn)去發(fā)現(xiàn)是在Runloop中的方法
// 本質(zhì)就是向Runloop中添加了一個(gè)定時(shí)器。 子線程默認(rèn)是沒有啟動(dòng) Runloop的
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; /// 打印結(jié)果 1 3
NSLog(@"3");
});
}
7. GCD組隊(duì)列的使用
- 異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2
- 等任務(wù)1、任務(wù)2都執(zhí)行完畢后,再回到主線程執(zhí)行任務(wù)3
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_CONCURRENT);// 并發(fā)隊(duì)列
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)1 thread --- %@",[NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)2 thread --- %@",[NSThread currentThread]);
}
});
///> 回到主線程執(zhí)行 任務(wù)3
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務(wù)3 thread --- %@",[NSThread currentThread]);
// }
// });
///> 執(zhí)行完任務(wù)1、2之后再執(zhí)行任務(wù)3、4
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)3 thread --- %@",[NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)4 thread --- %@",[NSThread currentThread]);
}
});
}
8. 多線程安全隱患的解決方案
- 隱患造成, 多個(gè)線程同時(shí)訪問一個(gè)數(shù)據(jù)然后對(duì)數(shù)據(jù)進(jìn)行操作
- 解決方案:使用線程同步技術(shù),
- 常見線程同步技術(shù): 加鎖
- iOS線程同步方案:
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
內(nèi)存管理
性能優(yōu)化
1. 什么是CPU和GPU
- CPU (Central Processing Unit,中央處理器 )
- 對(duì)象的創(chuàng)建和銷毀、對(duì)象屬性的調(diào)整、布局計(jì)算、文本的計(jì)算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制 (Core Graphics)
- GPU (Graphics Processing Unit,圖形處理器 )
-
紋理的渲染
-
2. 卡頓原因
-
cpu處理后GPU處理 若垂直同步信號(hào)早于GPU處理的速度那么會(huì)形成掉幀問題
- 在 iOS中有雙緩存機(jī)制,有前幀緩存、后幀緩存
2. 卡頓優(yōu)化 - CPU
- 盡量使用輕量級(jí)的對(duì)象,比如用不到事件處理的地方,可以考慮使用CALayer 替代 UIView
- UIView和CALayer的區(qū)別?
- CALayer是UIView的一個(gè)成員
- CALayer是專門用來顯示東西的
- UIView是用來負(fù)責(zé)監(jiān)聽點(diǎn)擊事件等
- UIView和CALayer的區(qū)別?
- 不要頻繁的調(diào)用UIView的相關(guān)屬性,比如frame、bounds、transform等屬性,盡量減少不必要的修改。
- 盡量提前計(jì)算好布局,在有需要時(shí)一次性調(diào)整好對(duì)應(yīng)的屬性,不要多次修改屬性。
- Autolayout會(huì)比直接設(shè)置frame消耗更多的CPU資源
- 圖片的size最好跟UIImageView的size保持一致
- 因?yàn)槿绻龌蛘遀IImageView會(huì)對(duì)圖片進(jìn)行伸縮的處理
- 控制線程的最大并發(fā)數(shù)量
- 盡量把一些耗時(shí)的操作放到子線程
- 文本處理(儲(chǔ)存計(jì)算和繪制)
- 圖片處理(解碼、繪制)
3. 卡頓優(yōu)化 - GPU
- 盡量減少視圖數(shù)量和層次
- 盡量避免短時(shí)間內(nèi)大量圖片的顯示,盡可能將多張圖片合成一張進(jìn)行顯示
- GPU能處理的最大紋理尺寸是4096x4096,一旦超過這個(gè)尺寸,機(jī)會(huì)占用CPU資源進(jìn)行處理,所以紋理盡量不要超過這個(gè)尺寸。
- 減少透明視圖,不透明的就設(shè)置opaque為YES
- 盡量避免離屏渲染
4. 離屏渲染
-
在OpenGL中,GPU有2中渲染方式
- On-Screen Rendering: 當(dāng)前屏幕渲染,在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行渲染操作。
- Off-Screen Rendering: 離屏渲染,在當(dāng)前屏幕緩沖區(qū)以外新開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作
-
離屏渲染消耗性能原因:
- 需要?jiǎng)?chuàng)建新的緩沖區(qū)
- 離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境,先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上,有需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕。
-
那些操作會(huì)出發(fā)離屏渲染?
光柵化 layer.shouldRasterize = YES;
遮罩,layer.mask =
-
圓角,同事設(shè)置layer.maskToBounds = YES、layer.cornerRadius大于0
- 考慮通過CoreGraphics繪制圓角,或者美工直接提供。
- 第一種方法:通過設(shè)置layer的屬性
imageView.layer.masksToBounds = YES; [self.view addSubview:imageView];- 第二種方法:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個(gè)圓角
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; imageView.image = [UIImage imageNamed:@"1"]; //開始對(duì)imageView進(jìn)行畫圖 UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0); //使用貝塞爾曲線畫出一個(gè)圓形圖 [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip]; [imageView drawRect:imageView.bounds]; imageView.image = UIGraphicsGetImageFromCurrentImageContext(); //結(jié)束畫圖 UIGraphicsEndImageContext(); [self.view addSubview:imageView];- 第三種方法:使用CAShapeLayer和UIBezierPath設(shè)置圓角
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; imageView.image = [UIImage imageNamed:@"1"]; UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size]; CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init]; //設(shè)置大小 maskLayer.frame = imageView.bounds; //設(shè)置圖形樣子 maskLayer.path = maskPath.CGPath; imageView.layer.mask = maskLayer; [self.view addSubview:imageView]; }- 推薦三種方式,對(duì)內(nèi)存的消耗最少啊,渲染速度快
-
陰影, layer.shadowXXX
- 如果設(shè)置了layer.shadowPath就不會(huì)產(chǎn)生
4. 卡頓檢測
平時(shí)所說的“卡頓”主要是因?yàn)樵谥骶€程執(zhí)行了比較耗時(shí)的操作
可以添加Observer到主線程RunLoop中,通過監(jiān)聽RunLoop狀態(tài)切換的耗時(shí),以達(dá)到監(jiān)控卡頓的目的
5. 耗電優(yōu)化
- 耗電來源
- CPU處理 Processing
- 網(wǎng)絡(luò) Networking
- 定位 Location
- 圖像 Graphice
- 盡可能降低CPU、GPU的功耗
- 少用定時(shí)器
- 優(yōu)化I/O操作 文件讀寫
- 盡量不要頻繁的寫入小數(shù)據(jù),最好批量一次性寫入
- 讀寫大量重要的數(shù)據(jù)的時(shí)候,考慮使用dispatch_io,其提供了基于GCD的異步操作文件I/O的API。用dispatch_io系統(tǒng)會(huì)優(yōu)化磁盤訪問
- 數(shù)據(jù)量比較大的,建議使用數(shù)據(jù)庫(比如SQList、CoreData)
- 網(wǎng)絡(luò)優(yōu)化
- 減少,壓縮網(wǎng)絡(luò)數(shù)據(jù)
- 多次請(qǐng)求結(jié)果相同,盡量使用緩存
- 使用斷點(diǎn)續(xù)傳,否則網(wǎng)絡(luò)不穩(wěn)定時(shí)可能多次傳輸相同的內(nèi)容
- 網(wǎng)絡(luò)不可用時(shí)盡量不要嘗試執(zhí)行網(wǎng)絡(luò)請(qǐng)求
- 讓用戶可以取消長時(shí)間運(yùn)行或者網(wǎng)絡(luò)速度很慢的網(wǎng)絡(luò)操作,設(shè)置合理的超時(shí)時(shí)間
- 批量傳輸,比如:下載視頻流時(shí),不要傳輸很小的數(shù)據(jù)包,直接下載整個(gè)文件或者一大塊一大塊的下載,如果下載廣告,一次性多下載一些,然后慢慢展示。如果下載電子郵件,一次下載多封不要,一封一封的下載。
- 定位優(yōu)化
- 如果只需要快速確定用戶位置,最好用CLLocationManager的requestLocation方法。定位完成之后,會(huì)自動(dòng)讓定位硬件斷電。
- 如果不是導(dǎo)航應(yīng)用,盡量不要實(shí)時(shí)更新位置,定位完畢之后就關(guān)掉定位服務(wù)
- 盡量降低定位的精準(zhǔn)度,如果沒有需求的話盡量使用低精準(zhǔn)度的定位。隨軟件自身要求
- 如果需要后臺(tái)定位,盡量設(shè)置pausesLocationUpdatasAutomatically為YES,如果用戶不太可能移動(dòng)的時(shí)候系統(tǒng)會(huì)自動(dòng)暫停位置更新
- App啟動(dòng)
- App啟動(dòng)分為兩種
- 冷啟動(dòng)(Cold Launch):從零開始啟動(dòng)App
- 熱啟動(dòng)(Warm Launch):App已經(jīng)在內(nèi)存中,在后臺(tái)存活,再次點(diǎn)擊圖標(biāo)啟動(dòng)App
- App啟動(dòng)時(shí)間的優(yōu)化,主要針對(duì)于冷啟動(dòng)
- 通過添加環(huán)境變量可以打印app啟動(dòng)的時(shí)間分析(Edit scheme -> Run -> Arguments)
- DYLD_PRINT_STATISTICS設(shè)置為1
- 如果想要看更詳細(xì)的內(nèi)容,那就將DYLD_PRINTP_STATISTICS_DETAILS設(shè)置為1
- 通過添加環(huán)境變量可以打印app啟動(dòng)的時(shí)間分析(Edit scheme -> Run -> Arguments)
- 冷起訂分為三大階段
- dyld(dynamic link editor),Apple的動(dòng)態(tài)連接器,可用來裝在Mach-O文件(可執(zhí)行文件,動(dòng)態(tài)庫等)
- 啟動(dòng)時(shí)做的事情
- 裝載App的可執(zhí)行文件,同時(shí)會(huì)遞歸加載所有依賴的動(dòng)態(tài)庫
- 當(dāng)dyld把所有的可執(zhí)行文件和動(dòng)態(tài)庫都裝載完畢之后,會(huì)通知runtime進(jìn)行下一步處理
- 啟動(dòng)時(shí)做的事情
- runtime
- 啟動(dòng)時(shí)runtime所做的事情
- 調(diào)用map_images進(jìn)行可執(zhí)行文件內(nèi)容的解析和處理
- 在load_images中調(diào)用call_load_methods,調(diào)用所有Class和Catrgory的+load方法
- 進(jìn)行各種Objc結(jié)構(gòu)的初始化,(注冊(cè)O(shè)bjc類、初始化類對(duì)象等等)
- 調(diào)用C++靜態(tài)初始化器和attribute((constructor))修飾的函數(shù)
- 到此為止可執(zhí)行文件的動(dòng)態(tài)庫和所有的符號(hào)(Class、protocols、Selector、IMP...)都已經(jīng)按格式成功加載到內(nèi)存中,被runtime所管理
- 啟動(dòng)時(shí)runtime所做的事情
- main
- 總結(jié)一下
- APP的啟動(dòng)有dyld主導(dǎo),將可執(zhí)行文件加載到內(nèi)存、順便加載所有依賴的動(dòng)態(tài)庫
- 并有Runtime負(fù)責(zé)加載成objc定義的結(jié)構(gòu)
- 所有初始化工作結(jié)束后,dyld就會(huì)調(diào)用main函數(shù)
-
接下來就是UIApplicationMain函數(shù),AppDelegate的Application:didFinishLaunchingWithOptions:方法
image
- 總結(jié)一下
- dyld(dynamic link editor),Apple的動(dòng)態(tài)連接器,可用來裝在Mach-O文件(可執(zhí)行文件,動(dòng)態(tài)庫等)
- App啟動(dòng)分為兩種
- 如何優(yōu)化啟動(dòng)時(shí)間
- dyld
- 減少動(dòng)態(tài)庫、合并一些動(dòng)態(tài)庫(定期清理不必要的動(dòng)態(tài)庫)
- 減少Objc類、分類的數(shù)量、減少Selector數(shù)量(定期清理不必要的類、分類)
- 減少C++虛函數(shù)數(shù)量
- swift盡量使用struct
- runtime
- 用+initialize方法和dispatch_once取代所有的attribute((constructor))、C++靜態(tài)構(gòu)造器、Objc的+load
- main
- 在不影響用戶體驗(yàn)的前提下,盡可能將一些操作延遲,不要全部都放在finishLaunching方法中
- 按需求加載
- dyld
6. 安裝包瘦身
安裝包(IPA)主要由可執(zhí)行文件、資源組成
-
資源(圖片、音頻、視頻等)
- 采用無損壓縮
- 去除沒用到的資源文件(GitHub -查詢多余的資源文件_下載鏈接)
-
可執(zhí)行文件瘦身
-
編譯器優(yōu)化
- Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default設(shè)置為YES
- 去掉異常支持,Enable C++ Exceptions、Enable Objective-C Exceptions設(shè)置為NO,Other C Flags添加-fno-exceptions
-
利用AppCode(AppCode_下載鏈接)檢測未使用的代碼:
- 菜單欄 -> Code -> inspect Code
編寫LLVM插件檢測出重復(fù)代碼、未被調(diào)用的代碼
-
生成LinkMap文件,可以查看可執(zhí)行文件的具體組成
image- 可借助第三方工具解析LinkMap文件 GitHub -檢查每個(gè)類占用空間大小工具_(dá)下載鏈接
-
構(gòu)架設(shè)計(jì)
1. 設(shè)計(jì)模式
-
設(shè)計(jì)模式(Design Pattern)
- 是一套被反復(fù)使用、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)
- 使用設(shè)計(jì)模式的好處是:可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性
- 一般與編程語言無關(guān),是一套比較成熟的編程思想
-
設(shè)計(jì)模式可以分為三大類
- 創(chuàng)建型模式:對(duì)象實(shí)例化的模式,用于解耦對(duì)象的實(shí)例化過程
- 單例模式、工廠方法模式,等等
- 結(jié)構(gòu)型模式:把類或?qū)ο蠼Y(jié)合在一起形成一個(gè)更大的結(jié)構(gòu)
- 代理模式、適配器模式、組合模式、裝飾模式,等等
- 行為型模式:類或?qū)ο笾g如何交互,及劃分責(zé)任和算法
- 觀察者模式、命令模式、責(zé)任鏈模式,等等
- 創(chuàng)建型模式:對(duì)象實(shí)例化的模式,用于解耦對(duì)象的實(shí)例化過程