寫在前面
稍微了解OC運(yùn)行時(shí)機(jī)制的人應(yīng)該都知道:
1. 類和對(duì)象都是id,在給你一個(gè)id的前提下無法直觀的知道這個(gè)對(duì)象是類對(duì)象還是類本身冒窍。簡單的可以簡化成runtime管理的都是id (id的本質(zhì)其實(shí)是objc_object甥绿, objc_class頭部其實(shí)就是id, 也就是isa)。
2. Class在objc中是動(dòng)態(tài)創(chuàng)建的脚囊,selector, method桐磁, imp悔耘,protocol等都是隨后綁定上去的(即所謂的運(yùn)行時(shí)綁定)。
3. 通過runtime能夠查出當(dāng)前運(yùn)行時(shí)環(huán)境中所有的類我擂,每個(gè)類中的方法衬以,每個(gè)類消息的綁定,每個(gè)類的實(shí)現(xiàn)的協(xié)議校摩,每個(gè)協(xié)議的定義看峻,每個(gè)類當(dāng)前的消息緩存等一切你想知道的東西。
4. 類的方法(消息)調(diào)用是間接的衙吩。
正文
好的互妓,在我們知道上述知識(shí)以后。我們來研究下,在不存在NSObject的情況下冯勉,怎樣動(dòng)態(tài)創(chuàng)建一個(gè)類澈蚌。
圖中幾個(gè)方法的用法和作用不再贅述,不清楚的可以自行查閱官方文檔灼狰。有一點(diǎn)需要注意宛瞄,我們?nèi)潭疾荒苁褂?ARC,因?yàn)?ARC 模式下從void *轉(zhuǎn)換到id是需要有一個(gè) bridge 的過程交胚,而這個(gè)過程仍然依賴于NSObject完成份汗,所以我們又會(huì)陷入一個(gè)需要NSObject的死循環(huán)。
運(yùn)行起來會(huì)報(bào)以下錯(cuò)誤:
查看文檔得知承绸,每當(dāng)一個(gè)無效Selector被發(fā)出并沒有得到響應(yīng)裸影,運(yùn)行時(shí)系統(tǒng)將調(diào)用doesNotRecognizeSelector:,該方法執(zhí)行后會(huì)引發(fā)NSInvalidArgumentException军熏,并生成錯(cuò)誤消息轩猩。那好吧,check一下荡澎。
再次運(yùn)行均践。。摩幔。
我們似乎忘了實(shí)現(xiàn)initialize方法彤委。。或衡。該方法會(huì)在某個(gè)類第一次被初始化時(shí)調(diào)用焦影,加上它。
再run一次
額封断,alloc類方法是如何實(shí)現(xiàn)的斯辰?這個(gè)時(shí)候,我們就需要下載Runtime源碼一探究竟了坡疼。彬呻。。
我們知道柄瑰,在OC中闸氮,任何類都是繼承自NSObject這一基類。這么一說教沾,alloc類方法應(yīng)該也是NSobject實(shí)現(xiàn)的蒲跨。
我們打開源碼,,找到NSObject.mm這個(gè)文件授翻,并通過導(dǎo)航菜單快讀定位到alloc方法的位置
按住 ? 一路點(diǎn)擊跟蹤财骨,直到這里:
我們發(fā)現(xiàn)镐作,在Objc2.0之后新增了一種自定義的快捷構(gòu)造方法藏姐。但是殊途同歸隆箩,事實(shí)上它們最終都要調(diào)用class_createInstance這個(gè)方法,我們來看看:
在老的源碼中羔杨,這里會(huì)判斷是否使用GC捌臊。iOS上是不能使用GC的,這是為Mac設(shè)計(jì)的兜材,既然新版本去掉這個(gè)判斷理澎,我們也就沒有必要去理會(huì)了。繼續(xù)跟蹤曙寡,我們來到了_class_createInstanceFromZone函數(shù):
在這里糠爬,runtime主要做了有關(guān)內(nèi)存對(duì)齊的一些計(jì)算,由于zone是nil举庶,這里直接用calloc申請(qǐng)了一塊內(nèi)存执隧。calloc 與 malloc 的區(qū)別,可以自行谷歌户侥。
好的镀琉,接下來就剩最后一步了,objc_constructInstance函數(shù):
這里回歸了我們一開始說的蕊唐,對(duì)象的isa指針屋摔。我們也說了,id的本質(zhì)其實(shí)是objc_object替梨,那么objc_object到底是什么:
objc_object其實(shí)就是一個(gè)C++結(jié)構(gòu)體钓试,有權(quán)限控制,有成員函數(shù)副瀑。通過我們剛才提到的objc_object::initIsa函數(shù)弓熏,可以知道,就是把對(duì)象的isa指針指向這個(gè)類的元信息Class俗扇。
看到這里硝烂,我們其實(shí)就已經(jīng)可以為我們的類寫一個(gè)alloc方法了。但是似乎有個(gè)問題無法解決铜幽,objc_object對(duì)于開發(fā)者而言并不能接觸到滞谢,我們有必要通過直接修改內(nèi)存的方式去修改其isa變量。那么除抛,回到源碼狮杨,我們看看isa那個(gè)isa_t類型究竟是什么:
可以發(fā)現(xiàn),它是個(gè)聯(lián)合體到忽。從源碼中我們也可以看到橄教,它在初始化的時(shí)候直接被當(dāng)做uintptr_t對(duì)待清寇,而uintptr_t又是unsigned long的typedef,所以我們可以寫出如下代碼:
好了护蝶,把實(shí)現(xiàn)的alloc方法添加到我們的類中华烟,然后用初始化一個(gè)實(shí)例對(duì)象,再執(zhí)行持灰。
到此為止盔夜,在不存在NSObject的情況下,動(dòng)態(tài)創(chuàng)建一個(gè)類的工作完成了堤魁。