iOS開(kāi)發(fā)需要掌握的原理

目錄:
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ǔ)信息等)?
結(jié)構(gòu)模型圖
/** 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);
  1. instance中存放著isa和所有對(duì)象的值吨瞎,instance的isa指向的是class痹兜。
  2. 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ò)屈暗。
  3. 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)) 有以下的特性:

  1. Objective-C 為我們維護(hù)了一個(gè)巨大的選擇子表
  2. 在使用 @selector() 時(shí)會(huì)從這個(gè)選擇子表中根據(jù)選擇子的名字查找對(duì)應(yīng)的 SEL薄腻。如果沒(méi)有找到,則會(huì)生成一個(gè) SEL 并添加到表中
  3. 在編譯期間會(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ì)象的地址為鍵值的記錄

點(diǎn)擊查看詳細(xì)介紹

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ì)象結(jié)構(gòu)
  1. 關(guān)聯(lián)對(duì)象的值實(shí)際上是通過(guò)AssociationsManager對(duì)象負(fù)責(zé)管理的。在AssociationsManager中有一個(gè)spinlock類型的自旋鎖lock术幔。保證每次只有一個(gè)線程對(duì)AssociationsManager進(jìn)行操作另萤,保證線程安全。
  2. 關(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
  3. 先初始化一個(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):

  1. 由于不改變?cè)惖膶?shí)現(xiàn)值骇,所以可以給原生類或者是打包的庫(kù)進(jìn)行擴(kuò)展,一般配合Category實(shí)現(xiàn)完整的功能移国。
  2. ObjC類定義的變量吱瘩,由于runtime的特性,都會(huì)暴露到外部桥狡,使用關(guān)聯(lián)對(duì)象可以隱藏關(guān)鍵變量搅裙,保證安全。
  3. 可以用于KVO裹芝,使用關(guān)聯(lián)對(duì)象作為觀察者部逮,可以避免觀察自身導(dǎo)致循環(huán)。
  4. 這里需要注意的是標(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í),要格外注意
  5. 關(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);
  6. 關(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)怕膛?
  1. 第一個(gè)風(fēng)險(xiǎn)是,需要在 +load 方法中進(jìn)行方法交換秦踪。因?yàn)槿绻谄渌麜r(shí)候進(jìn)行方法交換褐捻,難以保證另外一個(gè)線程中不會(huì)同時(shí)調(diào)用被交換的方法,從而導(dǎo)致程序不能按預(yù)期執(zhí)行椅邓。
  2. 第二個(gè)風(fēng)險(xiǎn)是柠逞,被交換的方法必須是當(dāng)前類的方法,不能是父類的方法景馁,直接把父類的實(shí)現(xiàn)拷貝過(guò)來(lái)不會(huì)起作用板壮。父類的方法必須在調(diào)用的時(shí)候使用,而不是方法交換時(shí)使用合住。
  3. 第三個(gè)風(fēng)險(xiǎn)是绰精,交換的方法如果依賴了 cmd,那么交換后透葛,如果 cmd 發(fā)生了變化笨使,就會(huì)出現(xiàn)各種奇怪問(wèn)題,而且這些問(wèn)題還很難排查僚害。特別是交換了系統(tǒng)方法硫椰,你無(wú)法保證系統(tǒng)方法內(nèi)部是否依賴了 cmd。
  4. 第四個(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ǔ)是以nameobject為維度的杆烁,即判定是不是同一個(gè)通知要從nameobject區(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

17495317-90f472cc5d134bdb.png
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)景?

一共可以分為以下幾類:

  1. 使用NSLock(普通鎖逼纸;已實(shí)現(xiàn)NSLocking協(xié)議)實(shí)現(xiàn)鎖
  2. 使用Synchronized指令實(shí)現(xiàn)鎖
  3. 使用C語(yǔ)言的pthread_mutex_t實(shí)現(xiàn)鎖
  4. 使用GCD的dispatch_semaphore_t(信號(hào)量)實(shí)現(xiàn)鎖
  5. 使用NSRecursiveLock(遞歸鎖;已實(shí)現(xiàn)NSLocking協(xié)議)實(shí)現(xiàn)鎖济蝉;可以在遞歸場(chǎng)景中使用杰刽。如果使用NSLock,會(huì)出現(xiàn)死鎖
  6. 使用NSConditionLock(條件鎖王滤;已實(shí)現(xiàn)NSLocking協(xié)議)實(shí)現(xiàn)鎖贺嫂;可以在需要符合條件才進(jìn)行鎖操作的場(chǎng)景中使用
  7. 使用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ì)列沥邻?
  1. 主隊(duì)列是系統(tǒng)默認(rèn)為我們創(chuàng)建的DispatchQueue.main危虱,它是一個(gè)串行隊(duì)列;
  2. 主線程在程序啟動(dòng)的時(shí)候唐全,系統(tǒng)會(huì)自動(dòng)啟動(dòng)埃跷,并會(huì)加載在RunLoop上;
  3. 隊(duì)列是運(yùn)行在線程上的芦瘾,二者本質(zhì)上沒(méi)有什么聯(lián)系的捌蚊,需要注意的是,sync 不會(huì)啟動(dòng)新的線程近弟;
  4. 隊(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):
  1. 若開(kāi)啟了手動(dòng)監(jiān)聽(tīng)某個(gè)屬性,則設(shè)置的手動(dòng)關(guān)閉會(huì)失效的
  2. kvo是監(jiān)聽(tīng)是以類對(duì)象為基礎(chǔ)恤浪。self監(jiān)聽(tīng)了p1畅哑,不管p1中是否存在name屬性,都會(huì)創(chuàng)建NSKVONotifying_Person類资锰,并重寫它所有屬性的setter方法敢课。
  3. 忘記寫監(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)。

  1. __block_impl結(jié)構(gòu)體中isa指針存儲(chǔ)著&_NSConcreteStackBlock地址挥唠,可以暫時(shí)理解為其類對(duì)象地址抵恋,block就是_NSConcreteStackBlock類型的。
  2. block代碼塊中的代碼被封裝成__main_block_func_0函數(shù)宝磨,F(xiàn)uncPtr則存儲(chǔ)著__main_block_func_0函數(shù)的地址弧关。
  3. 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ī)?

  1. Block作為函數(shù)返回值返回時(shí);
  2. 將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];
    }
}
  1. 在block之前定義對(duì)self的弱引用weakSelf,因?yàn)槭侨跻靡壕ィ詓elf被釋放時(shí)weakSelf會(huì)變成nil
  2. 在block中引用該弱引用,考慮到多線程情況辞嗡,通過(guò)強(qiáng)引用strongSelf來(lái)引用該弱引用捆等,如果self不為nil,就會(huì)retain self续室,以防在block內(nèi)部使用過(guò)程中self被釋放
  3. 強(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ī):

  1. 當(dāng)繼承自UIView的視圖被創(chuàng)建,并且設(shè)置frame后变丧,并且已經(jīng)添加到某個(gè)view篇亭,則會(huì)調(diào)用;若不設(shè)置frame則不會(huì)觸發(fā)drawRect
  2. 當(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ū):

  1. 由編譯器自動(dòng)分配并釋放,不需要程序員管理變量的內(nèi)存末购。一般用來(lái)存放函數(shù)的參數(shù)值破喻,局部變量等
  2. 有靜態(tài)分配和動(dòng)態(tài)分配,都是有系統(tǒng)自動(dòng)處理的
  3. 地址從高到低分配盟榴,遵循先進(jìn)后出
  4. 只要棧的剩余空間大于stack 對(duì)象申請(qǐng)創(chuàng)建的空間曹质,操作系統(tǒng)就會(huì)為程序提供這段內(nèi)存空間,否則將報(bào)異常提示棧溢出

堆區(qū):

  1. 由程序員手動(dòng)管理內(nèi)存的分配和釋放
  2. 堆都是動(dòng)態(tài)分配的擎场,沒(méi)有靜態(tài)分配
  3. 地址從低到高分配羽德,遵循先進(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)
  1. 先被編譯成 ((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
  2. 沿著入?yún)?myClass 的 isa 指針飞蹂,找到 myClass 的類對(duì)象(Class),也就是 MyClass
  3. 接著在 MyClass 的方法列表 method_lists 中翻屈,找到對(duì)應(yīng)的 objc_method
  4. 最后找到 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, @“異城渌”);
}
  1. 對(duì)于UIViewController衫仑,我們可以hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法;
  2. 若UIViewController 被釋放了堕花,但它的 view 沒(méi)被釋放惑畴,或者一個(gè) UIView 被釋放了,但它的某個(gè) subview 沒(méi)被釋放航徙。這種內(nèi)存泄露的情況很常見(jiàn),因此陷虎,我們有必要遍歷基于 UIViewController 的整棵 View-ViewController 樹(shù)到踏,采用遞歸遍歷它的subView來(lái)找到未被釋放的對(duì)象
  3. 其他對(duì)象若需要做特殊處理,需要加入白名單尚猿。
2.不要濫用宏定義

持續(xù)更新中...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窝稿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子凿掂,更是在濱河造成了極大的恐慌伴榔,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庄萎,死亡現(xiàn)場(chǎng)離奇詭異踪少,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)糠涛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門援奢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人忍捡,你說(shuō)我怎么就攤上這事集漾∏星” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵具篇,是天一觀的道長(zhǎng)纬霞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)驱显,這世上最難降的妖魔是什么诗芜? 我笑而不...
    開(kāi)封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮秒紧,結(jié)果婚禮上绢陌,老公的妹妹穿的比我還像新娘。我一直安慰自己熔恢,他們只是感情好脐湾,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著叙淌,像睡著了一般秤掌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹰霍,一...
    開(kāi)封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天闻鉴,我揣著相機(jī)與錄音,去河邊找鬼茂洒。 笑死孟岛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的督勺。 我是一名探鬼主播渠羞,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼智哀!你這毒婦竟也來(lái)了次询?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瓷叫,失蹤者是張志新(化名)和其女友劉穎屯吊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體摹菠,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盒卸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了次氨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片世落。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出屉佳,到底是詐尸還是另有隱情谷朝,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布武花,位于F島的核電站圆凰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏体箕。R本人自食惡果不足惜专钉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望累铅。 院中可真熱鬧跃须,春花似錦、人聲如沸娃兽。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)投储。三九已至第练,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玛荞,已是汗流浹背娇掏。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勋眯,地道東北人婴梧。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像客蹋,于是被迫代替她去往敵國(guó)和親志秃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 面向?qū)ο蟮娜筇匦裕悍庋b嚼酝、繼承、多態(tài) OC內(nèi)存管理 _strong 引用計(jì)數(shù)器來(lái)控制對(duì)象的生命周期竟坛。 _weak...
    運(yùn)氣不夠技術(shù)湊閱讀 1,092評(píng)論 0 10
  • 面向?qū)ο蟮娜筇卣?并作簡(jiǎn)單的介紹闽巩。 面向?qū)ο蟮娜齻€(gè)基本特征是:封裝、繼承担汤、多態(tài)涎跨。 1.封裝是面向?qū)ο蟮奶卣髦?...
    xiny123閱讀 1,426評(píng)論 0 6
  • 1,NSObject中description屬性的意義崭歧,它可以重寫嗎?答案:每當(dāng) NSLog(@"")函數(shù)中出現(xiàn) ...
    eightzg閱讀 4,138評(píng)論 2 19
  • 作者:Nico2017年第三篇文章 感謝阿何老師的課程指導(dǎo)屋彪!此篇為第二課作業(yè),標(biāo)題引用阿何老師提供的標(biāo)題參考绒尊,萬(wàn)分...
    吐黃膽水的少女閱讀 814評(píng)論 0 1
  • 2019-06-08 周六 今日亮點(diǎn): 1.花了差不多4h一早上的時(shí)間把所有的WFD全部錄制了畜挥,前期比較慢,后期越...
    不要記得我閱讀 91評(píng)論 0 0