目錄:
1.Runtime
2.NSNotification相關(guān)
3.RunLoop
4.多線程相關(guān)
5.KVO
6.Block相關(guān)
7.視圖與圖像相關(guān)
8.數(shù)據(jù)結(jié)構(gòu)與算法
9.架構(gòu)設(shè)計(jì)
10.系統(tǒng)基礎(chǔ)知識(shí)
11.性能優(yōu)化相關(guān)
一跷敬、Runtime
1.介紹下runtime的內(nèi)存模型(isa、對(duì)象肴掷、類艾杏、metaclass讯壶、結(jié)構(gòu)體的存儲(chǔ)信息等)?
/** p/x &(obj->isa): 0x0000600001f2c780
* p obj: 0x0000600001f2c780
* 實(shí)例對(duì)象,isa指針地址為實(shí)例對(duì)象obj的地址,但isa指向的是類對(duì)象的地址
*/
NSObject *obj = [[NSObject alloc] init];
/** 類對(duì)象盆佣,一個(gè)類對(duì)象在整個(gè)程序運(yùn)行過(guò)程中只會(huì)存在一個(gè),不管你調(diào)用多少次class方法械荷,同一個(gè)類返回的類對(duì)象地址始終是一樣的類對(duì)象結(jié)構(gòu)體
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;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
*/
Class cls = [NSObject class];
//元類對(duì)象共耍,元類對(duì)象需要通過(guò)object_getClass這個(gè)runtime函數(shù)獲取,在整個(gè)程序運(yùn)行過(guò)程中也只存在一個(gè)該類的元類對(duì)象
Class metaCls = object_getClass(cls);
NSLog(@"obj->%p",obj);
NSLog(@"cls->%p",cls);
NSLog(@"metaCls->%p",metaCls);
- instance中存放著isa和所有對(duì)象的值吨瞎,instance的isa指向的是class痹兜。
- class存放著isa、superclass以及NSObject的屬性信息和對(duì)象方法颤诀。superclass指向其父類字旭,NSObject(Root class)的類對(duì)象的superclass指向nil。isa指向的是meta崖叫。通過(guò)對(duì)象調(diào)用對(duì)象方法的時(shí)候遗淳,首先會(huì)通過(guò)instance的isa找到其class,然后在class中找到對(duì)象方法并調(diào)用,沒(méi)有找到則會(huì)通過(guò)superclass一層一層往上找心傀,最終還是找不到的時(shí)候就會(huì)報(bào)錯(cuò)屈暗。
- meta和class類似,只不過(guò)它存儲(chǔ)的不是對(duì)象方法,而是類方法恐锦。并且元類的 isa 全部指向NSObject的元類對(duì)象往果。元類的superclass指向其父類,但是NSObject的元類對(duì)象的superclass指向的是其類對(duì)象一铅。通過(guò)類對(duì)象調(diào)用類方法的時(shí)候陕贮,首先會(huì)通過(guò)class的isa找到其meta,然后在meta中找到類方法并調(diào)用潘飘,沒(méi)有找到則會(huì)通過(guò)superclass一層一層往上找肮之,最終還是找不到的時(shí)候就會(huì)報(bào)錯(cuò)。
艾歐艾斯之手寫了一個(gè)不錯(cuò)的方法來(lái)驗(yàn)證卜录,其實(shí)原理是消息轉(zhuǎn)發(fā)的一部分:
@interface NSObject (DVObject)
@end
@implementation NSObject (DVObject)
- (void)eat {
NSLog(@"吃飯");
}
@end
@interface Person : NSObject
+ (void)eat;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person eat];
}
return 0;
}
運(yùn)行這段代碼戈擒,我們發(fā)現(xiàn)程序并沒(méi)有奔潰,而是打印了”吃飯“艰毒,這是為什么呢筐高?其實(shí)離不開(kāi)oc的運(yùn)行時(shí)特性。
這里Person調(diào)用eat類方法丑瞧,首先會(huì)通過(guò)isa找到自己的元類對(duì)象柑土,發(fā)現(xiàn)沒(méi)有eat方法,然后通過(guò)superclass找到NSObject的元類對(duì)象繼續(xù)查找eat方法绊汹,依然沒(méi)有稽屏,再通過(guò)superclass,注意西乖,此時(shí)的superclass指向的是NSObject的類對(duì)象狐榔,而通過(guò)代碼可以發(fā)現(xiàn)NSObject的類對(duì)象中是存在eat方法的,并且調(diào)用成功获雕。
SEL (@selector(someMethod)) 有以下的特性:
- Objective-C 為我們維護(hù)了一個(gè)巨大的選擇子表
- 在使用 @selector() 時(shí)會(huì)從這個(gè)選擇子表中根據(jù)選擇子的名字查找對(duì)應(yīng)的 SEL薄腻。如果沒(méi)有找到,則會(huì)生成一個(gè) SEL 并添加到表中
- 在編譯期間會(huì)掃描全部的頭文件和實(shí)現(xiàn)文件將其中的方法以及使用 @selector() 生成的選擇子加入到選擇子表中
3.category如何被加載的典鸡,兩個(gè)category的load方法的加載順序? 兩個(gè)category的同名方法的加載順序被廓?
在runtime時(shí),首先把category的實(shí)例方法萝玷、協(xié)議以及屬性添加到類上嫁乘,其次
把category的類方法和協(xié)議添加到類的metaclass上;
附加category到類的工作會(huì)先于+load方法的執(zhí)行球碉,+load的執(zhí)行順序是先類蜓斧,后category,而category的+load執(zhí)行順序是根據(jù)編譯順序決定的睁冬;
我們已經(jīng)知道category其實(shí)并不是完全替換掉原來(lái)類的同名方法挎春,只是category在方法列表的前面而已看疙,對(duì)于category同名方法的加載順序,顯然是按照后編譯的方法先執(zhí)行直奋;
對(duì)于調(diào)用到原來(lái)類中被category覆蓋掉的方法能庆,我們只要順著方法列表找到最后一個(gè)對(duì)應(yīng)名字的方法,就可以調(diào)用原來(lái)類的方法脚线。
點(diǎn)擊這里查看詳細(xì)介紹
4.category & extension區(qū)別搁胆,能給NSObject添加Extension嗎,結(jié)果如何邮绿?
category:分類
給類添加新的方法
不能給類添加成員變量
通過(guò)@property定義的變量渠旁,只能生成對(duì)應(yīng)的getter和setter的方法聲明,但是不能實(shí)現(xiàn)getter和setter方法船逮,同時(shí)也不能生成帶下劃線的成員屬性顾腊;
注意:為什么不能添加屬性,原因就是category是運(yùn)行期決定的杂靶,在運(yùn)行期類的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量會(huì)破壞類的內(nèi)存布局冠骄,會(huì)產(chǎn)生意想不到的錯(cuò)誤伪煤。
extension:擴(kuò)展
可以給類添加成員變量,但是是私有的凛辣,可以給類添加方法,但是是私有的
添加的屬性和方法是類的一部分职烧,在編譯期就決定的扁誓。
在編譯器和頭文件的@interface和實(shí)現(xiàn)文件里的@implement一起形成了一個(gè)完整的類。伴隨著類的產(chǎn)生而產(chǎn)生蚀之,也隨著類的消失而消失,必須有類的源碼才可以給類添加extension蝗敢,所以對(duì)于系統(tǒng)一些類,如nsstring足删,就無(wú)法添加類擴(kuò)展
不能給NSObject添加Extension寿谴,因?yàn)樵趀xtension中添加的方法或?qū)傩员仨氃谠搭惖奈募?m文件中實(shí)現(xiàn)才可以,即:你必須有一個(gè)類的源碼才能添加一個(gè)類的extension失受。
擴(kuò)展是在編譯期決定讶泰,而分類是在運(yùn)行期。
5.IMP拂到、SEL痪署、Method的區(qū)別和使用場(chǎng)景?
IMP:是方法的實(shí)現(xiàn)兄旬,即:一段c函數(shù)
SEL:是方法名
Method:是objc_method類型指針狼犯,它是一個(gè)結(jié)構(gòu)體,如下:
struct objc_method {
SEL _Nonnull method_name ///方法的名稱
char * _Nullable method_types ///方法的類型
IMP _Nonnull method_imp ///方法的具體實(shí)現(xiàn),由 IMP 指針指向
}
6.load悯森、initialize方法的區(qū)別什么宋舷?在繼承關(guān)系中他們有什么區(qū)別?
+load方法在這個(gè)文件被程序裝載時(shí)調(diào)用瓢姻。只要是在Compile Sources中出現(xiàn)的文件總是會(huì)被裝載肥缔,這與這個(gè)類是否被用到無(wú)關(guān),因此+load方法總是在main函數(shù)之前調(diào)用汹来。調(diào)用方式并不是采用runtime的objc_msgSend方式調(diào)用的续膳,而是直接采用函數(shù)的內(nèi)存地址直接調(diào)用的; 多個(gè)類的load調(diào)用順序,是依賴于compile sources中的文件順序決定的收班,根據(jù)文件從上到下的順序調(diào)用子類和父類同時(shí)實(shí)現(xiàn)load的方法時(shí)坟岔,父類的方法先被調(diào)用本類與category的調(diào)用順序是,優(yōu)先調(diào)用本類的(注意:category是在最后被裝載的);load是被動(dòng)調(diào)用的摔桦,在類裝載時(shí)調(diào)用的社付,不需要手動(dòng)觸發(fā)調(diào)用
initialize:當(dāng)類或子類第一次收到消息時(shí)被調(diào)用(即:靜態(tài)方法或?qū)嵗椒ǖ谝淮伪徽{(diào)用,也就是這個(gè)類第一次被用到的時(shí)候),方式是通過(guò)runtime的objc_msgSend的方式調(diào)用的邻耕,此時(shí)所有的類都已經(jīng)裝載完畢鸥咖;子類和父類同時(shí)實(shí)現(xiàn)initialize,父類的先被調(diào)用兄世,然后調(diào)用子類的啼辣;本類與category同時(shí)實(shí)現(xiàn)initialize,category會(huì)覆蓋本類的方法御滩,只調(diào)用category的initialize一次(這也說(shuō)明initialize的調(diào)用方式采用objc_msgSend的方式調(diào)用的)鸥拧;initialize是主動(dòng)調(diào)用的,只有當(dāng)類第一次被用到的時(shí)候才會(huì)觸發(fā)削解。
可參考詳細(xì)解釋
7.weak的實(shí)現(xiàn)原理富弦?SideTable的結(jié)構(gòu)是什么樣的?
weak表其實(shí)是一個(gè)hash表氛驮,Key是所指對(duì)象的地址腕柜,Value是weak指針的地址數(shù)組,weak是弱引用矫废,所引用對(duì)象的計(jì)數(shù)器不會(huì)+1,并在引用對(duì)象被釋放的時(shí)候自動(dòng)被設(shè)置為nil盏缤。通常用于解決循環(huán)引用問(wèn)題。
1磷脯、初始化時(shí):runtime會(huì)調(diào)用objc_initWeak函數(shù)蛾找,初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂贰?br> 2、添加引用時(shí):objc_initWeak函數(shù)會(huì)調(diào)用 objc_storeWeak() 函數(shù)赵誓, objc_storeWeak() 的作用是更新指針指向打毛,創(chuàng)建對(duì)應(yīng)的弱引用表柿赊。
3、釋放時(shí)幻枉,調(diào)用clearDeallocating函數(shù)碰声。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil熬甫,最后把這個(gè)entry從weak表中刪除胰挑,最后清理對(duì)象的記錄。
對(duì)象釋放后:
1椿肩、調(diào)用objc_release
2瞻颂、因?yàn)閷?duì)象的引用計(jì)數(shù)為0,所以執(zhí)行dealloc
3郑象、在dealloc中贡这,調(diào)用了_objc_rootDealloc函數(shù)
4、在_objc_rootDealloc中厂榛,調(diào)用了object_dispose函數(shù)
5盖矫、調(diào)用objc_destructInstance
6、最后調(diào)用objc_clear_deallocating,詳細(xì)過(guò)程如下:
a. 從weak表中獲取廢棄對(duì)象的地址為鍵值的記錄
b. 將包含在記錄中的所有附有 weak修飾符變量的地址击奶,賦值為nil
c. 將weak表中該記錄刪除
d. 從引用計(jì)數(shù)表中刪除廢棄對(duì)象的地址為鍵值的記錄
8.系統(tǒng)如何實(shí)現(xiàn)關(guān)聯(lián)對(duì)象的辈双?關(guān)聯(lián)對(duì)象如何進(jìn)行內(nèi)存管理的?關(guān)聯(lián)對(duì)象的應(yīng)用柜砾?
可以在不改變類的源碼的情況下湃望,為類添加實(shí)例變量。需要注意的是局义,這里指的實(shí)例變量喜爷,并不是真正的屬于類的實(shí)例變量,而是一個(gè)關(guān)聯(lián)值變量萄唇。
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
結(jié)構(gòu)圖:- 關(guān)聯(lián)對(duì)象的值實(shí)際上是通過(guò)AssociationsManager對(duì)象負(fù)責(zé)管理的。在AssociationsManager中有一個(gè)spinlock類型的自旋鎖lock术幔。保證每次只有一個(gè)線程對(duì)AssociationsManager進(jìn)行操作另萤,保證線程安全。
- 關(guān)聯(lián)哈希表AssociationsHashMap中的key為object地址的指針诅挑,value為對(duì)象關(guān)聯(lián)表ObjcAssociationMap的首地址四敞,在ObjcAssociationMap表中,key值是set方法里面?zhèn)鬟^(guò)來(lái)的形參const void *key拔妥,value值是ObjcAssociation對(duì)象忿危。 ObjcAssociation對(duì)象中存儲(chǔ)了set方法最后兩個(gè)參數(shù),policy和value
- 先初始化一個(gè) AssociationsManager没龙,獲取唯一的保存關(guān)聯(lián)對(duì)象的哈希表 AssociationsHashMap铺厨,然后在AssociationsHashMap里面去查找object地址的指針缎玫。如果找到,就找到了第二張表ObjectAssociationMap解滓。在這張表里繼續(xù)查找key赃磨。如果在第二張表ObjectAssociationMap找到對(duì)應(yīng)的ObjcAssociation對(duì)象,那就更新它的值洼裤。如果沒(méi)有找到邻辉,就新建一個(gè)ObjcAssociation對(duì)象,放入第二張表ObjectAssociationMap中腮鞍。
關(guān)聯(lián)對(duì)象注意的點(diǎn):
- 由于不改變?cè)惖膶?shí)現(xiàn)值骇,所以可以給原生類或者是打包的庫(kù)進(jìn)行擴(kuò)展,一般配合Category實(shí)現(xiàn)完整的功能移国。
- ObjC類定義的變量吱瘩,由于runtime的特性,都會(huì)暴露到外部桥狡,使用關(guān)聯(lián)對(duì)象可以隱藏關(guān)鍵變量搅裙,保證安全。
- 可以用于KVO裹芝,使用關(guān)聯(lián)對(duì)象作為觀察者部逮,可以避免觀察自身導(dǎo)致循環(huán)。
- 這里需要注意的是標(biāo)記成OBJC_ASSOCIATION_ASSIGN的關(guān)聯(lián)對(duì)象和@property (weak) 是不一樣的嫂易,上面表格中等價(jià)定義寫的是 @property (unsafe_unretained)兄朋,對(duì)象被銷毀時(shí),屬性值仍然還在怜械。如果之后再次使用該對(duì)象就會(huì)導(dǎo)致程序閃退颅和。所以我們?cè)谑褂肙BJC_ASSOCIATION_ASSIGN時(shí),要格外注意
- 關(guān)于關(guān)聯(lián)對(duì)象還有一點(diǎn)需要說(shuō)明的是objc_removeAssociatedObjects缕允。這個(gè)方法是移除源對(duì)象中所有的關(guān)聯(lián)對(duì)象峡扩,并不是其中之一。所以其方法參數(shù)中也沒(méi)有傳入指定的key障本。要?jiǎng)h除指定的關(guān)聯(lián)對(duì)象教届,使用 objc_setAssociatedObject 方法將 key 對(duì)應(yīng)的value設(shè)置成 nil 即可。
objc_setAssociatedObject(self, associatedKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
- 關(guān)聯(lián)對(duì)象的釋放問(wèn)題:關(guān)聯(lián)對(duì)象要比本對(duì)象釋放晚一些驾霜。
調(diào)用 object_dispose()
為 C++ 的實(shí)例變量們(iVars)調(diào)用 destructors
為 ARC 狀態(tài)下的 實(shí)例變量們(iVars) 調(diào)用 -release
解除所有使用 runtime Associate方法關(guān)聯(lián)的對(duì)象
解除所有 __weak 引用
9.ARC的實(shí)現(xiàn)原理案训?AutoreleasePool原理
詳細(xì)原理可參照這篇文章 點(diǎn)擊查看
ARC下可能導(dǎo)致內(nèi)存泄漏:
block中的循環(huán)引用
NSTimer的循環(huán)引用
addObserver的循環(huán)引用
delegate的強(qiáng)引用
大次數(shù)循環(huán)內(nèi)存爆漲
非OC對(duì)象的內(nèi)存處理(需手動(dòng)釋放)
10.class、objc_getClass粪糙、object_getclass 方法有什么區(qū)別?
objc_getClass參數(shù)是類名的字符串强霎,返回的就是這個(gè)類的類對(duì)象;object_getClass參數(shù)是id類型蓉冈,它返回的是這個(gè)id的isa指針?biāo)赶虻腃lass城舞,如果傳參是Class轩触,isa指針指向的是元類對(duì)象,即返回該Class的metaClass椿争。
11.Method Swizzle注意事項(xiàng)怕膛?
- 第一個(gè)風(fēng)險(xiǎn)是,需要在 +load 方法中進(jìn)行方法交換秦踪。因?yàn)槿绻谄渌麜r(shí)候進(jìn)行方法交換褐捻,難以保證另外一個(gè)線程中不會(huì)同時(shí)調(diào)用被交換的方法,從而導(dǎo)致程序不能按預(yù)期執(zhí)行椅邓。
- 第二個(gè)風(fēng)險(xiǎn)是柠逞,被交換的方法必須是當(dāng)前類的方法,不能是父類的方法景馁,直接把父類的實(shí)現(xiàn)拷貝過(guò)來(lái)不會(huì)起作用板壮。父類的方法必須在調(diào)用的時(shí)候使用,而不是方法交換時(shí)使用合住。
- 第三個(gè)風(fēng)險(xiǎn)是绰精,交換的方法如果依賴了 cmd,那么交換后透葛,如果 cmd 發(fā)生了變化笨使,就會(huì)出現(xiàn)各種奇怪問(wèn)題,而且這些問(wèn)題還很難排查僚害。特別是交換了系統(tǒng)方法硫椰,你無(wú)法保證系統(tǒng)方法內(nèi)部是否依賴了 cmd。
- 第四個(gè)風(fēng)險(xiǎn)是萨蚕,方法交換命名沖突靶草。如果出現(xiàn)沖突,可能會(huì)導(dǎo)致方法交換失敗
12.iOS是怎么管理內(nèi)存的岳遥?
iOS采用自動(dòng)應(yīng)用計(jì)數(shù)來(lái)進(jìn)行內(nèi)存管理(ARC)奕翔。一個(gè)對(duì)象被創(chuàng)建出來(lái)后,被自己持有浩蓉,此時(shí)引用計(jì)數(shù)為1糠悯,當(dāng)其他變量持有該對(duì)象時(shí),引用計(jì)數(shù)加1妻往。當(dāng)持有者調(diào)用release 時(shí),引用計(jì)數(shù)減1试和。當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí)讯泣,內(nèi)存就會(huì)被釋放回收。
在最開(kāi)始的時(shí)候阅悍,程序是直接訪問(wèn)物理內(nèi)存好渠,但后來(lái)有了多程序多任務(wù)同時(shí)運(yùn)行昨稼,就出現(xiàn)了很多問(wèn)題。比如拳锚,同時(shí)運(yùn)行的程序占用的總內(nèi)存必須要小于實(shí)際物理內(nèi)存大小假栓。再比如,程序能夠直接訪問(wèn)和修改物理內(nèi)存霍掺,也就能夠直接訪問(wèn)和修改其他程序所使用的物理內(nèi)存匾荆,程序運(yùn)行時(shí)的安全就無(wú)法保障
二、NSNotification相關(guān)
1.通知存儲(chǔ)是以
name
和object
為維度的杆烁,即判定是不是同一個(gè)通知要從name
和object
區(qū)分牙丽,如果他們都相同則認(rèn)為是同一個(gè)通知,后面包括查找邏輯兔魂、刪除邏輯都是以這兩個(gè)為維度的烤芦;
2.理解數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)是整個(gè)通知機(jī)制的核心,其他功能只是在此基礎(chǔ)上擴(kuò)展了一些邏輯析校;
3.存儲(chǔ)過(guò)程并沒(méi)有做去重操作构罗,這也解釋了為什么同一個(gè)通知注冊(cè)多次則響應(yīng)多次;
4.NSNotificationCenter都是同步發(fā)送的智玻,關(guān)于NSNotificationQueue的異步發(fā)送遂唧,從線程的角度看并不是真正的異步發(fā)送,或可稱為延時(shí)發(fā)送尚困,它是利用了runloop的時(shí)機(jī)來(lái)觸發(fā)蠢箩。
查看更多介紹
三、Runloop
1.App是如何響應(yīng)觸摸事件的?
1.APP進(jìn)程的mach port接收來(lái)自SpringBoard的觸摸事件事甜,主線程的runloop被喚醒谬泌,觸發(fā)source1回調(diào)。
2.source1回調(diào)又觸發(fā)了一個(gè)source0回調(diào)逻谦,將接收到的IOHIDEvent對(duì)象封裝成UIEvent對(duì)象掌实,此時(shí)APP將正式開(kāi)始對(duì)于觸摸事件的響應(yīng)。
3.source0回調(diào)將觸摸事件添加到UIApplication的事件隊(duì)列邦马,當(dāng)觸摸事件出隊(duì)后UIApplication為觸摸事件尋找最佳響應(yīng)者贱鼻。
4.尋找到最佳響應(yīng)者之后,接下來(lái)的事情便是事件在響應(yīng)鏈中傳遞和響應(yīng)滋将。
更加詳細(xì)介紹邻悬,請(qǐng)點(diǎn)擊這里
或者參考我之前寫的文章 iOS基礎(chǔ)篇-事件處理
2.runloop與線程關(guān)系,為什么只在主線程刷新UI随闽?
runloop與線程是一一對(duì)應(yīng)的父丰,一個(gè)runloop對(duì)應(yīng)一個(gè)核心的線程,為什么說(shuō)是核心的掘宪,是因?yàn)閞unloop是可以嵌套的蛾扇,但是核心的只能有一個(gè)攘烛,他們的關(guān)系保存在一個(gè)全局的字典里。
runloop在第一次獲取時(shí)被創(chuàng)建镀首,在線程結(jié)束時(shí)被銷毀坟漱。
對(duì)于主線程來(lái)說(shuō),runloop在程序一啟動(dòng)就默認(rèn)創(chuàng)建好了更哄。
對(duì)于子線程來(lái)說(shuō)芋齿,runloop是懶加載的,只有當(dāng)我們使用的時(shí)候才會(huì)創(chuàng)建竖瘾,所以在子線程用定時(shí)器要注意:確保子線程的runloop被創(chuàng)建沟突,不然定時(shí)器不會(huì)回調(diào)
主線程刷新UI是因?yàn)槎嗑€程技術(shù)有坑,所以 UIKit 干脆就做成了線程不安全捕传,只能在主線程上操作
3.如何使線程被菔茫活?PerformSelector 的實(shí)現(xiàn)原理庸论?
使用mach_port線程敝案ǎ活?線程之間的通訊是靠mach_port聂示?
NSMachPort (mach_port) 并在外部線程通過(guò)這個(gè) port 發(fā)送消息到 loop 內(nèi)域携;但此處添加 port 只是為了讓 RunLoop 不退出
當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中鱼喉。所以如果當(dāng)前線程沒(méi)有 RunLoop秀鞭,則這個(gè)方法會(huì)失效。
當(dāng)調(diào)用 performSelector:onThread: 時(shí)扛禽,實(shí)際上其會(huì)創(chuàng)建一個(gè) Timer 加到對(duì)應(yīng)的線程去锋边,同樣的,如果對(duì)應(yīng)線程沒(méi)有 RunLoop 該方法也會(huì)失效编曼。
四豆巨、多線程相關(guān)
關(guān)于多線程基礎(chǔ)介紹,看這篇文章就夠了掐场,點(diǎn)擊查看
//OperationQueue 創(chuàng)建的自定義隊(duì)列同時(shí)具有串行往扔、并發(fā)功能
//NSInvocationOperation在swift中廢棄
let ope1 = BlockOperation {
for _ in 0..<2 {
//模擬耗時(shí)操作
Thread.sleep(forTimeInterval: 2)
print("1--->",Thread.current)
}
}
let ope2 = BlockOperation {
for _ in 0..<2 {
//模擬耗時(shí)
Thread.sleep(forTimeInterval: 2)
print("2--->",Thread.current)
}
}
let ope3 = BlockOperation {
for _ in 0..<2 {
//模擬耗時(shí)操作
Thread.sleep(forTimeInterval: 2)
print("3--->",Thread.current)
}
}
let queue = OperationQueue.init()
queue.addOperation(ope1)
queue.addOperation(ope2)
queue.addOperation(ope3)
//當(dāng)maxConcurrentOperationCount=-1時(shí)(默認(rèn)值)并行
********************************************************************
2---> <NSThread: 0x6000036a9e80>{number = 3, name = (null)}
1---> <NSThread: 0x6000036ae200>{number = 5, name = (null)}
3---> <NSThread: 0x60000368a900>{number = 7, name = (null)}
1---> <NSThread: 0x6000036ae200>{number = 5, name = (null)}
3---> <NSThread: 0x60000368a900>{number = 7, name = (null)}
2---> <NSThread: 0x6000036a9e80>{number = 3, name = (null)}
//當(dāng)設(shè)置maxConcurrentOperationCount=1時(shí),變?yōu)榇嘘?duì)列
********************************************************************
1---> <NSThread: 0x6000003be380>{number = 4, name = (null)}
1---> <NSThread: 0x6000003be380>{number = 4, name = (null)}
2---> <NSThread: 0x6000003be380>{number = 4, name = (null)}
2---> <NSThread: 0x6000003be380>{number = 4, name = (null)}
3---> <NSThread: 0x60000035d8c0>{number = 7, name = (null)}
3---> <NSThread: 0x60000035d8c0>{number = 7, name = (null)}
1.多線程并發(fā)帶來(lái)的內(nèi)存問(wèn)題熊户?
我們知道萍膛,創(chuàng)建線程的過(guò)程,需要用到物理內(nèi)存嚷堡,CPU 也會(huì)消耗時(shí)間卦羡。而且,新建一個(gè)線程,系統(tǒng)還需要為這個(gè)進(jìn)程空間分配一定的內(nèi)存作為線程堆棧绿饵。堆棧大小是 4KB 的倍數(shù)。在 iOS 開(kāi)發(fā)中瓶颠,主線程堆棧大小是 1MB拟赊,新創(chuàng)建的子線程堆棧大小是 512KB。
除了內(nèi)存開(kāi)銷外粹淋,線程創(chuàng)建得多了吸祟,CPU 在切換線程上下文時(shí),還會(huì)更新寄存器桃移,更新寄存器的時(shí)候需要尋址舌剂,而尋址的過(guò)程還會(huì)有較大的 CPU 消耗庸诱。所以,線程過(guò)多時(shí)內(nèi)存和 CPU 都會(huì)有大量的消耗,從而導(dǎo)致 App 整體性能降低恕沫,使得用戶體驗(yàn)變成差。CPU 和內(nèi)存的使用超出系統(tǒng)限制時(shí)再榄,甚至?xí)斐上到y(tǒng)強(qiáng)殺蝙叛。這種情況對(duì)用戶和 App 的傷害就更大了。
2.iOS有哪些類型的線程鎖绞惦,分別介紹下作用和使用場(chǎng)景?
一共可以分為以下幾類:
- 使用NSLock(普通鎖逼纸;已實(shí)現(xiàn)NSLocking協(xié)議)實(shí)現(xiàn)鎖
- 使用Synchronized指令實(shí)現(xiàn)鎖
- 使用C語(yǔ)言的pthread_mutex_t實(shí)現(xiàn)鎖
- 使用GCD的dispatch_semaphore_t(信號(hào)量)實(shí)現(xiàn)鎖
- 使用NSRecursiveLock(遞歸鎖;已實(shí)現(xiàn)NSLocking協(xié)議)實(shí)現(xiàn)鎖济蝉;可以在遞歸場(chǎng)景中使用杰刽。如果使用NSLock,會(huì)出現(xiàn)死鎖
- 使用NSConditionLock(條件鎖王滤;已實(shí)現(xiàn)NSLocking協(xié)議)實(shí)現(xiàn)鎖贺嫂;可以在需要符合條件才進(jìn)行鎖操作的場(chǎng)景中使用
- 使用NSDistributedLock(分布式鎖;區(qū)別其他鎖類型淑仆,它沒(méi)有實(shí)現(xiàn)NSLocking協(xié)議)實(shí)現(xiàn)鎖涝婉;它基于文件系統(tǒng),會(huì)自動(dòng)創(chuàng)建用于標(biāo)識(shí)的臨時(shí)文件或文件夾蔗怠,執(zhí)行完后自動(dòng)清除臨時(shí)文件或文件夾墩弯;可以在多個(gè)進(jìn)程或多個(gè)程序之間需要構(gòu)建互斥的場(chǎng)景中使用
具體用法可以查看這里
3.dispatch_once實(shí)現(xiàn)原理?
首次調(diào)用dispatch_once時(shí)寞射,因?yàn)橥獠總魅氲膁ispatch_once_t變量值為nil渔工,故vval會(huì)為NULL,故if判斷成立桥温。然后調(diào)用_dispatch_client_callout執(zhí)行block引矩,然后在block執(zhí)行完成之后將vval的值更新成DISPATCH_ONCE_DONE表示任務(wù)已完成。最后遍歷鏈表的節(jié)點(diǎn)并調(diào)用_dispatch_thread_semaphore_signal來(lái)喚醒等待中的信號(hào)量;
當(dāng)其他線程同時(shí)也調(diào)用dispatch_once時(shí)旺韭,因?yàn)閕f判斷是原子性操作氛谜,故只有一個(gè)線程進(jìn)入到if分支中,其他線程會(huì)進(jìn)入else分支区端。在else分支中會(huì)判斷block是否已完成值漫,如果已完成則跳出循環(huán);否則就是更新鏈表并調(diào)用_dispatch_thread_semaphore_wait阻塞線程织盼,等待if分支中的block完成后再喚醒當(dāng)前等待的線程杨何。
參考這篇文章
4.線程和隊(duì)列的關(guān)系?主線程和主隊(duì)列沥邻?
- 主隊(duì)列是系統(tǒng)默認(rèn)為我們創(chuàng)建的DispatchQueue.main危虱,它是一個(gè)串行隊(duì)列;
- 主線程在程序啟動(dòng)的時(shí)候唐全,系統(tǒng)會(huì)自動(dòng)啟動(dòng)埃跷,并會(huì)加載在RunLoop上;
- 隊(duì)列是運(yùn)行在線程上的芦瘾,二者本質(zhì)上沒(méi)有什么聯(lián)系的捌蚊,需要注意的是,sync 不會(huì)啟動(dòng)新的線程近弟;
- 隊(duì)列之間也平等的缅糟,系統(tǒng)默認(rèn)會(huì)幫助我們分配兩個(gè)隊(duì)列dispatchMain()和DispatchQueue.global()
五、KVO相關(guān)
1.實(shí)現(xiàn)原理祷愉?如何手動(dòng)關(guān)閉kvo窗宦?通過(guò)KVC修改屬性會(huì)觸發(fā)KVO么?
例如在使用KVO監(jiān)聽(tīng)Person對(duì)象的name屬性二鳄,runtime 動(dòng)態(tài)的生成了一個(gè) NSKVONotifying_Person子類 并重寫了 setName赴涵、class、dealloc方法订讼。首先當(dāng)我們改變p1.name 的值時(shí) 并不是首先執(zhí)行的 setName: 這個(gè)方法 ,而是先調(diào)用了 willChangeValueForKey 其次 調(diào)用父類的 setter 方法 對(duì)屬性賦值 ,然后再調(diào)用 didChangeValueForKey 方法 ,并在 didChangeValueForKey 內(nèi)部 調(diào)用監(jiān)聽(tīng)器的 observeValueForKeyPath方法 告訴外界 屬性值發(fā)生了改變髓窜。
這篇文章分析的很詳細(xì) 大兵布萊恩特·的文章
2.如何手動(dòng)觸發(fā) kvo ? 如何手動(dòng)關(guān)閉 kvo ?
在這里我創(chuàng)建了一個(gè)Person類,我們?cè)O(shè)置監(jiān)聽(tīng)他的name屬性變化
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
id cls1 = object_getClass(p1);
id cls2 = object_getClass(p2);
NSLog(@"添加 KVO 之前: cls1 = %@ cls2 = %@ ",cls1,cls2);
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
cls1 = object_getClass(p1);
cls2 = object_getClass(p2);
NSLog(@"添加 KVO 之后: cls1 = %@ cls2 = %@ ",cls1,cls2);
p1.name = @"jack";
///監(jiān)聽(tīng)name的變化回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"觸發(fā)了kvo");
}
}
- 當(dāng)我們給name賦值后欺殿,系統(tǒng)會(huì)自動(dòng)觸發(fā)observeValueForKeyPath寄纵,假如我們想要在name不發(fā)生賦值事件時(shí),讓系統(tǒng)主動(dòng)調(diào)用observeValueForKeyPath方法怎么做呢脖苏?我們可以重寫將要改變監(jiān)聽(tīng)的key和已經(jīng)改變監(jiān)聽(tīng)的key值:
//手動(dòng)觸發(fā)kvo
[p1 willChangeValueForKey:@"name"];
[p1 didChangeValueForKey:@"name"];
注意:這兩個(gè)方法需要同時(shí)實(shí)現(xiàn)程拭,否則無(wú)法手動(dòng)觸發(fā)kvo
- 既然可以手動(dòng)觸發(fā)kvo,那能否可以手動(dòng)關(guān)閉kvo棍潘,我們不想要監(jiān)聽(tīng)當(dāng)前的對(duì)象了恃鞋,我們可在監(jiān)聽(tīng)的類中崖媚,重寫是否開(kāi)啟對(duì)某個(gè)屬性的監(jiān)聽(tīng):
///Person.m中
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
這里有個(gè)注意的幾點(diǎn):
- 若開(kāi)啟了手動(dòng)監(jiān)聽(tīng)某個(gè)屬性,則設(shè)置的手動(dòng)關(guān)閉會(huì)失效的
- kvo是監(jiān)聽(tīng)是以類對(duì)象為基礎(chǔ)恤浪。self監(jiān)聽(tīng)了p1畅哑,不管p1中是否存在name屬性,都會(huì)創(chuàng)建NSKVONotifying_Person類资锰,并重寫它所有屬性的setter方法敢课。
- 忘記寫監(jiān)聽(tīng)回調(diào)方法 observeValueForKeyPath、add和remove次數(shù)不匹配時(shí)绷杜,會(huì)發(fā)生奔潰,在使用時(shí)需要特別注意濒募!
六鞭盟、Block相關(guān)
block完整原理,可參考這篇文章
1.block內(nèi)部實(shí)現(xiàn)的結(jié)構(gòu)是什么樣的瑰剃?
使用命令行將代碼轉(zhuǎn)化為c++查看其內(nèi)部結(jié)構(gòu)
clang main.m -rewrite-objc -o dest.cpp
int main(int argc, char * argv[]) {
int value = 1;
void (^test)(void) = ^(){
};
test();
}
編譯后c++核心模塊:
///__block_impl是編譯器給我們生成的結(jié)構(gòu)體齿诉,每一個(gè)block都會(huì)用到這個(gè)結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl; ///__block_impl 變量impl
struct __main_block_desc_0* Desc;
int value;///我們聲明的局部變量value
///結(jié)構(gòu)體的構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0, int _value) {
impl.isa = &_NSConcreteStackBlock; ///說(shuō)明block是棧
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
///編譯器根據(jù)block代碼生成的全局態(tài)函數(shù),會(huì)被賦值給impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int value = __cself->value;
}
///編譯器根據(jù)block代碼生成的block描述,主要是記錄下__main_block_impl_0結(jié)構(gòu)體大小
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
int value = 1;
void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA), value);
((void (*)(__block_impl *)) ((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}
從代碼中可以看到晌姚,我們寫在block塊中的代碼封裝成__main_block_func_0函數(shù)粤剧,并將__main_block_func_0函數(shù)的地址傳入了__main_block_impl_0的構(gòu)造函數(shù)中保存在結(jié)構(gòu)體內(nèi)。
- __block_impl結(jié)構(gòu)體中isa指針存儲(chǔ)著&_NSConcreteStackBlock地址挥唠,可以暫時(shí)理解為其類對(duì)象地址抵恋,block就是_NSConcreteStackBlock類型的。
- block代碼塊中的代碼被封裝成__main_block_func_0函數(shù)宝磨,F(xiàn)uncPtr則存儲(chǔ)著__main_block_func_0函數(shù)的地址弧关。
- Desc指向__main_block_desc_0結(jié)構(gòu)體對(duì)象,其中存儲(chǔ)__main_block_impl_0結(jié)構(gòu)體所占用的內(nèi)存唤锉。
總結(jié):局部變量都會(huì)被block捕獲世囊,自動(dòng)變量是值捕獲,靜態(tài)變量為地址捕獲窿祥。全局變量則不會(huì)被block捕獲
2.block是類么株憾?有哪些類型?
block本質(zhì)上也是一個(gè)oc對(duì)象晒衩,他內(nèi)部也有一個(gè)isa指針嗤瞎。block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象,本質(zhì)上繼承自NSObject浸遗。
block有3種類型:
NSGlobalBlock ( _NSConcreteGlobalBlock )類型的block猫胁,因?yàn)檫@樣使用block并沒(méi)有什么意義。
NSStackBlock ( _NSConcreteStackBlock )類型的block存放在棧中跛锌,我們知道棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放弃秆,作用域執(zhí)行完畢之后就會(huì)被立即釋放届惋,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉。
NSMallocBlock ( _NSConcreteMallocBlock )NSMallocBlock是在平時(shí)編碼過(guò)程中最常使用到的菠赚。存放在堆中需要我們自己進(jìn)行內(nèi)存管理脑豹。
局部變量都會(huì)被block捕獲,自動(dòng)變量(auto關(guān)鍵字是系統(tǒng)自動(dòng)添加)是值捕獲衡查,靜態(tài)變量為地址捕獲瘩欺。全局變量在哪里都可以訪問(wèn) ,所以不用捕獲
3.block的屬性修飾詞為什么是copy拌牲?block發(fā)生copy時(shí)機(jī)?
在MRC環(huán)境下調(diào)試俱饿,經(jīng)常需要使用copy來(lái)保存block,將棧上的block拷貝到堆中塌忽,即使棧上的block被銷毀拍埠,堆上的block也不會(huì)被銷毀,需要我們自己調(diào)用release操作來(lái)銷毀土居。而在ARC環(huán)境下回系統(tǒng)會(huì)自動(dòng)copy枣购,是block不會(huì)被銷毀。在ARC環(huán)境下擦耀,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block進(jìn)行一次copy操作棉圈,將block復(fù)制到堆上。
如果不copy的話眷蜓,那么block就不會(huì)在堆空間上分瘾,無(wú)法對(duì)你生命周期進(jìn)行控制。需要注意循環(huán)引用(ARC環(huán)境下 strong 账磺、copy沒(méi)有區(qū)別)
block發(fā)生copy時(shí)機(jī)?
- Block作為函數(shù)返回值返回時(shí);
- 將Block賦值給附有__strong修飾符id類型的類或者Block類型成員變量時(shí);
4.block在修改NSMutableArray芹敌,需不需要添加__block?
不需要垮抗。修改內(nèi)容也是對(duì)數(shù)組的使用氏捞,只有對(duì)對(duì)象賦值的時(shí)候才需要__block。
5.解決循環(huán)引用時(shí)為什么要用__strong冒版、__weak修飾?
__weak __typeof (self)weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"Change" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
[strongSelf reload];
}
}
- 在block之前定義對(duì)self的弱引用weakSelf,因?yàn)槭侨跻靡壕ィ詓elf被釋放時(shí)weakSelf會(huì)變成nil
- 在block中引用該弱引用,考慮到多線程情況辞嗡,通過(guò)強(qiáng)引用strongSelf來(lái)引用該弱引用捆等,如果self不為nil,就會(huì)retain self续室,以防在block內(nèi)部使用過(guò)程中self被釋放
- 強(qiáng)引用strongSelf在block作用域結(jié)束之后栋烤,自動(dòng)釋放
若在 Block 內(nèi)如果需要訪問(wèn) self 的方法、變量挺狰,建議使用 weakSelf明郭;
若在 Block 內(nèi)需要多次訪問(wèn)self买窟,則需要使用 strongSelf。
七薯定、視圖與圖像相關(guān)
1.UIView & CALayer的區(qū)別始绍?
2.系統(tǒng)調(diào)用drawRect和layoutSubViews的時(shí)機(jī)?
注意:UIImageView是專門為顯示圖片做的控件话侄,用了最優(yōu)顯示技術(shù)亏推,是不讓調(diào)用darwrect方法, 要調(diào)用這個(gè)方法年堆,只能從uiview里重寫吞杭。
drawRect調(diào)用時(shí)機(jī):
- 當(dāng)繼承自UIView的視圖被創(chuàng)建,并且設(shè)置frame后变丧,并且已經(jīng)添加到某個(gè)view篇亭,則會(huì)調(diào)用;若不設(shè)置frame則不會(huì)觸發(fā)drawRect
- 當(dāng)view某一屬性例如背景色發(fā)生改變時(shí)锄贷,調(diào)用layoutIfNeeded,會(huì)觸發(fā)drawRect曼月;若view中布局或?qū)傩詻](méi)有發(fā)生變化谊却,則不會(huì)調(diào)用;
八哑芹、數(shù)據(jù)結(jié)構(gòu)與算法
九炎辨、架構(gòu)設(shè)計(jì)
十、系統(tǒng)基礎(chǔ)知識(shí)
1.堆和棧區(qū)的區(qū)別聪姿?誰(shuí)的占用內(nèi)存空間大碴萧?
棧區(qū):
- 由編譯器自動(dòng)分配并釋放,不需要程序員管理變量的內(nèi)存末购。一般用來(lái)存放函數(shù)的參數(shù)值破喻,局部變量等
- 有靜態(tài)分配和動(dòng)態(tài)分配,都是有系統(tǒng)自動(dòng)處理的
- 地址從高到低分配盟榴,遵循先進(jìn)后出
- 只要棧的剩余空間大于stack 對(duì)象申請(qǐng)創(chuàng)建的空間曹质,操作系統(tǒng)就會(huì)為程序提供這段內(nèi)存空間,否則將報(bào)異常提示棧溢出
堆區(qū):
- 由程序員手動(dòng)管理內(nèi)存的分配和釋放
- 堆都是動(dòng)態(tài)分配的擎场,沒(méi)有靜態(tài)分配
- 地址從低到高分配羽德,遵循先進(jìn)先出
NSString 的對(duì)象就是stack 中的對(duì)象,NSMutableString 的對(duì)象就是heap 中的對(duì)象迅办。前者創(chuàng)建時(shí)分配的內(nèi)存長(zhǎng)度固定且不可修改宅静;
后者是分配內(nèi)存長(zhǎng)度是可變的,可有多個(gè)owner, 適用于計(jì)數(shù)管理內(nèi)存管理模式站欺。兩類對(duì)象的創(chuàng)建方法也不同姨夹,前者直接創(chuàng)建“NSString * str1=@"welcome"; “纤垂,而后者需要先分配再初始化“ NSMutableString * mstr1=[[NSMutableString alloc] initWithString:@"welcome"]; ”
更多詳細(xì)介紹
2.消息發(fā)送機(jī)制以及消息轉(zhuǎn)發(fā)機(jī)制?
消息發(fā)送機(jī)制
Objective-C 是一門動(dòng)態(tài)語(yǔ)言匀伏,我們大部分對(duì)象調(diào)用方法洒忧,例如[myClass someMethod]; runtime編譯后都會(huì)變成objc_msgSend (myClass, @selector(someMethod))執(zhí)行。
對(duì)象在runtim下的結(jié)構(gòu):
實(shí)例對(duì)象通過(guò) isa 指針够颠,找到類對(duì)象 Class熙侍;類對(duì)象同樣通過(guò) isa 指針,找到元類對(duì)象履磨;元類對(duì)象也是通過(guò) isa 指針蛉抓,找到根元類對(duì)象;最后剃诅,根元類對(duì)象的 isa 指針巷送,指向自己∶可以發(fā)現(xiàn) NSObject 是整個(gè)消息機(jī)制的核心笑跛,絕大數(shù)對(duì)象都繼承自它。
- 實(shí)例對(duì)象是通過(guò) isa 指針聊品,找到其類對(duì)象(Class)中保存的方法列表中的具體實(shí)現(xiàn)
- 先被編譯成 ((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
- 沿著入?yún)?myClass 的 isa 指針飞蹂,找到 myClass 的類對(duì)象(Class),也就是 MyClass
- 接著在 MyClass 的方法列表 method_lists 中翻屈,找到對(duì)應(yīng)的 objc_method
- 最后找到 objc_method 中的 IMP 指針陈哑,執(zhí)行具體的實(shí)現(xiàn)
- 類對(duì)象的類方法,是保存在元類對(duì)象中的伸眶!類對(duì)象和元類對(duì)象都是 Class 類型惊窖,僅僅服務(wù)的對(duì)象不同罷了。找到了元類對(duì)象厘贼,自然就找到了元類對(duì)象中的 methodLists界酒,接下來(lái)就和實(shí)例對(duì)象的方法尋找調(diào)用一樣的流程。
消息轉(zhuǎn)發(fā)機(jī)制
若上述方法列表 method_list 沒(méi)找到對(duì)應(yīng)的 selector 呢涂臣?系統(tǒng)會(huì)提供三次補(bǔ)救的機(jī)會(huì):
- 第一次:
調(diào)用實(shí)例方法 + (BOOL)resolveInstanceMethod:(SEL)sel {}
或類方法為:+ (BOOL)resolveClassMethod:(SEL)sel {}
我們只需要在 resolveInstanceMethod: 方法中盾计,利用 class_addMethod 方法,將未實(shí)現(xiàn)的 someMethod: 綁定到 myMethod 上就能完成轉(zhuǎn)發(fā)赁遗,最后返回 YES署辉。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (sel == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
return YES;
} else {
return [super resolveInstanceMethod:sel];
}
}
- 第二次:
將 A 類的某個(gè)方法,轉(zhuǎn)發(fā)到 B 類的實(shí)現(xiàn)中去
- (id)forwardingTargetForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [Person new];
} else {
return [super forwardingTargetForSelector:aSelector];
}
}
- 第三次:允許多個(gè)對(duì)象是否實(shí)現(xiàn)方法
//生成一個(gè)有效的方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString: @"foo"]) {
return [NSMethodSignature signatureWithObjCTypes: @"v@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Person *p = [Person new];
///這是一種更安全可靠的消息轉(zhuǎn)發(fā)機(jī)制
if ([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
} else {
[self doesNotRecognizeSelector:sel];
}
}
- 若三次都沒(méi)有找到實(shí)現(xiàn)方法岩四,則拋出異常哭尝,程序Crash
unrecognized selector sent to instance
3.進(jìn)程和線程的區(qū)別?
進(jìn)程是擁有資源的最小單位剖煌。線程是執(zhí)行的最小單位材鹦,每個(gè)App啟動(dòng)就是一個(gè)進(jìn)程逝淹,它可以包含多個(gè)線程工作。
4.TCP/IP介紹
十一桶唐、性能優(yōu)化相關(guān)
1.內(nèi)存泄漏檢測(cè)原理
推薦使用 FBRetainCycleDetector
接入項(xiàng)目栅葡,在Debug模式下開(kāi)啟檢測(cè)內(nèi)存泄漏。
具體的方法是尤泽,為基類 NSObject 添加一個(gè)方法 -willDealloc 方法
該方法的作用是欣簇,先用一個(gè)弱指針指向 self,并在一小段時(shí)間(3秒)后坯约,通過(guò)這個(gè)弱指針調(diào)用 -assertNotDealloc 而 -assertNotDealloc 主要作用是直接中斷言熊咽。
- (BOOL)willDealloc {
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//若3秒后,weakSelf依然沒(méi)有被釋放闹丐,則認(rèn)為viewController沒(méi)有被釋放横殴,發(fā)生內(nèi)存泄漏,則拋出異常
[weakSelf assertNotDealloc];
});
return YES;
}
- (void)assertNotDealloc {
NSAssert(NO, @“異城渌”);
}
- 對(duì)于UIViewController衫仑,我們可以hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法;
- 若UIViewController 被釋放了堕花,但它的 view 沒(méi)被釋放惑畴,或者一個(gè) UIView 被釋放了,但它的某個(gè) subview 沒(méi)被釋放航徙。這種內(nèi)存泄露的情況很常見(jiàn),因此陷虎,我們有必要遍歷基于 UIViewController 的整棵 View-ViewController 樹(shù)到踏,采用遞歸遍歷它的subView來(lái)找到未被釋放的對(duì)象
- 其他對(duì)象若需要做特殊處理,需要加入白名單尚猿。
2.不要濫用宏定義
持續(xù)更新中...