[轉(zhuǎn)]神經(jīng)病院Objective-C Runtime入院第一天——isa和Class

前言

我第一次開始重視Objective-C Runtime是從2014年11月1日枕磁,@唐巧老師在微博上發(fā)的一條微博開始。

這是sunnyxx在線下的一次分享會(huì)。會(huì)上還給了4道題目。

這4道題以我當(dāng)時(shí)的知識(shí)涵叮,很多就不確定,拿不準(zhǔn)伞插。從這次入院考試開始割粮,就成功入院了。后來(lái)這兩年對(duì)Runtime的理解慢慢增加了媚污,打算今天自己總結(jié)總結(jié)平時(shí)一直躺在我印象筆記里面的筆記舀瓢。有些人可能有疑惑,學(xué)習(xí)Runtime到底有啥用耗美,平時(shí)好像并不會(huì)用到京髓。希望看完我這次的總結(jié)航缀,心中能解開一些疑惑。

目錄

1.Runtime簡(jiǎn)介

2.NSObject起源

(1)? ? isa_t結(jié)構(gòu)體的具體實(shí)現(xiàn)

(2)? ? cache_t的具體實(shí)現(xiàn)

(3)? ? class_data_bits_t的具體實(shí)現(xiàn)

3.入院考試

一. Runtime簡(jiǎn)介

Runtime 又叫運(yùn)行時(shí)堰怨,是一套底層的 C 語(yǔ)言 API芥玉,是 iOS 系統(tǒng)的核心之一。開發(fā)者在編碼過(guò)程中备图,可以給任意一個(gè)對(duì)象發(fā)送消息灿巧,在編譯階段只是確定了要向接收者發(fā)送這條消息,而接受者將要如何響應(yīng)和處理這條消息揽涮,那就要看運(yùn)行時(shí)來(lái)決定了抠藕。

C語(yǔ)言中,在編譯期蒋困,函數(shù)的調(diào)用就會(huì)決定調(diào)用哪個(gè)函數(shù)盾似。

而OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過(guò)程雪标,在編譯期并不能決定真正調(diào)用哪個(gè)函數(shù)零院,只有在真正運(yùn)行時(shí)才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。

Objective-C 是一個(gè)動(dòng)態(tài)語(yǔ)言汰聋,這意味著它不僅需要一個(gè)編譯器,也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)動(dòng)態(tài)得創(chuàng)建類和對(duì)象喊积、進(jìn)行消息傳遞和轉(zhuǎn)發(fā)烹困。

Objc 在三種層面上與 Runtime 系統(tǒng)進(jìn)行交互:

1. 通過(guò) Objective-C 源代碼

一般情況開發(fā)者只需要編寫 OC 代碼即可,Runtime 系統(tǒng)自動(dòng)在幕后把我們寫的源代碼在編譯階段轉(zhuǎn)換成運(yùn)行時(shí)代碼乾吻,在運(yùn)行時(shí)確定對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)和調(diào)用具體哪個(gè)方法髓梅。

2. 通過(guò) Foundation 框架的 NSObject 類定義的方法

在OC的世界中,除了NSProxy類以外绎签,所有的類都是NSObject的子類枯饿。在Foundation框架下,NSObject和NSProxy兩個(gè)基類诡必,定義了類層次結(jié)構(gòu)中該類下方所有類的公共接口和行為奢方。NSProxy是專門用于實(shí)現(xiàn)代理對(duì)象的類,這個(gè)類暫時(shí)本篇文章不提爸舒。這兩個(gè)類都遵循了NSObject協(xié)議蟋字。在NSObject協(xié)議中,聲明了所有OC對(duì)象的公共方法扭勉。

在NSObject協(xié)議中鹊奖,有以下5個(gè)方法,是可以從Runtime中獲取信息涂炎,讓對(duì)象進(jìn)行自我檢查忠聚。

-(Class)classOBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");-(BOOL)isKindOfClass:(Class)aClass;-(BOOL)isMemberOfClass:(Class)aClass;-(BOOL)conformsToProtocol:(Protocol*)aProtocol;-(BOOL)respondsToSelector:(SEL)aSelector;

-class方法返回對(duì)象的類设哗;

-isKindOfClass: 和 -isMemberOfClass: 方法檢查對(duì)象是否存在于指定的類的繼承體系中(是否是其子類或者父類或者當(dāng)前類的成員變量);

-respondsToSelector: 檢查對(duì)象能否響應(yīng)指定的消息两蟀;

-conformsToProtocol:檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法网梢;

在NSObject的類中還定義了一個(gè)方法

-(IMP)methodForSelector:(SEL)aSelector;

這個(gè)方法會(huì)返回指定方法實(shí)現(xiàn)的地址IMP。

以上這些方法會(huì)在本篇文章中詳細(xì)分析具體實(shí)現(xiàn)垫竞。

3. 通過(guò)對(duì) Runtime 庫(kù)函數(shù)的直接調(diào)用

關(guān)于庫(kù)函數(shù)可以在Objective-C Runtime Reference中查看 Runtime 函數(shù)的詳細(xì)文檔澎粟。

關(guān)于這一點(diǎn),其實(shí)還有一個(gè)小插曲欢瞪。當(dāng)我們導(dǎo)入了objc/Runtime.h和objc/message.h兩個(gè)頭文件之后活烙,我們查找到了Runtime的函數(shù)之后,代碼打完遣鼓,發(fā)現(xiàn)沒(méi)有代碼提示了啸盏,那些函數(shù)里面的參數(shù)和描述都沒(méi)有了。對(duì)于熟悉Runtime的開發(fā)者來(lái)說(shuō)骑祟,這并沒(méi)有什么難的回懦,因?yàn)閰?shù)早已銘記于胸。但是對(duì)于新手來(lái)說(shuō)次企,這是相當(dāng)不友好的怯晕。而且,如果是從iOS6開始開發(fā)的同學(xué)缸棵,依稀可能能感受到舟茶,關(guān)于Runtime的具體實(shí)現(xiàn)的官方文檔越來(lái)越少了?可能還懷疑是不是錯(cuò)覺(jué)堵第。其實(shí)從Xcode5開始吧凉,蘋果就不建議我們手動(dòng)調(diào)用Runtime的API,也同樣希望我們不要知道具體底層實(shí)現(xiàn)踏志。所以IDE上面默認(rèn)代了一個(gè)參數(shù)阀捅,禁止了Runtime的代碼提示,源碼和文檔方面也刪除了一些解釋针余。

具體設(shè)置如下:

如果發(fā)現(xiàn)導(dǎo)入了兩個(gè)庫(kù)文件之后饲鄙,仍然沒(méi)有代碼提示,就需要把這里的設(shè)置改成NO圆雁,即可傍妒。

二. NSObject起源

由上面一章節(jié),我們知道了與Runtime交互有3種方式摸柄,前兩種方式都與NSObject有關(guān)颤练,那我們就從NSObject基類開始說(shuō)起。

以下源碼分析均來(lái)自objc4-680

NSObject的定義如下

typedefstructobjc_class*Class;@interfaceNSObject<NSObject>{Class isa? OBJC_ISA_AVAILABILITY;}

在Objc2.0之前,objc_class源碼如下:

structobjc_class{Class isa? OBJC_ISA_AVAILABILITY;#if!__OBJC2__Class super_class? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;constchar*name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;longversion? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;longinfo? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;longinstance_size? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_ivar_list*ivars? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_method_list**methodLists? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_cache*cache? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_protocol_list*protocols? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#endif}OBJC2_UNAVAILABLE;

在這里可以看到嗦玖,在一個(gè)類中患雇,有超類的指針,類名宇挫,版本的信息苛吱。

ivars是objc_ivar_list成員變量列表的指針;methodLists是指向objc_method_list指針的指針器瘪。*methodLists是指向方法列表的指針翠储。這里如果動(dòng)態(tài)修改*methodLists的值來(lái)添加成員方法,這也是Category實(shí)現(xiàn)的原理橡疼,同樣解釋了Category不能添加屬性的原因援所。

關(guān)于Category,這里推薦2篇文章可以仔細(xì)研讀一下欣除。

深入理解Objective-C:Category

結(jié)合 Category 工作原理分析 OC2.0 中的 runtime

然后在2006年蘋果發(fā)布Objc 2.0之后住拭,objc_class的定義就變成下面這個(gè)樣子了。

typedefstructobjc_class*Class;typedefstructobjc_object*id;@interfaceObject{Class isa;}@interfaceNSObject<NSObject>{Class isa? OBJC_ISA_AVAILABILITY;}structobjc_object{private:isa_t isa;}structobjc_class:objc_object{// Class ISA;Class superclass;cache_t cache;// formerly cache pointer and vtableclass_data_bits_t bits;// class_rw_t * plus custom rr/alloc flags}unionisa_t{isa_t(){}isa_t(uintptr_t value):bits(value){}Class cls;uintptr_t bits;}

把源碼的定義轉(zhuǎn)化成類圖历帚,就是上圖的樣子滔岳。

從上述源碼中,我們可以看到挽牢,Objective-C 對(duì)象都是 C 語(yǔ)言結(jié)構(gòu)體實(shí)現(xiàn)的谱煤,在objc2.0中,所有的對(duì)象都會(huì)包含一個(gè)isa_t類型的結(jié)構(gòu)體禽拔。

objc_object被源碼typedef成了id類型刘离,這也就是我們平時(shí)遇到的id類型蚤吹。這個(gè)結(jié)構(gòu)體中就只包含了一個(gè)isa_t類型的結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體在下面會(huì)詳細(xì)分析乎折。

objc_class繼承于objc_object仆潮。所以在objc_class中也會(huì)包含isa_t類型的結(jié)構(gòu)體isa。至此琐驴,可以得出結(jié)論:Objective-C 中類也是一個(gè)對(duì)象。在objc_class中,除了isa之外梁只,還有3個(gè)成員變量,一個(gè)是父類的指針埃脏,一個(gè)是方法緩存搪锣,最后一個(gè)這個(gè)類的實(shí)例方法鏈表。

object類和NSObject類里面分別都包含一個(gè)objc_class類型的isa彩掐。

上圖的左半邊類的關(guān)系描述完了构舟,接著先從isa來(lái)說(shuō)起。

當(dāng)一個(gè)對(duì)象的實(shí)例方法被調(diào)用的時(shí)候堵幽,會(huì)通過(guò)isa找到相應(yīng)的類狗超,然后在該類的class_data_bits_t中去查找方法弹澎。class_data_bits_t是指向了類對(duì)象的數(shù)據(jù)區(qū)域。在該數(shù)據(jù)區(qū)域內(nèi)查找相應(yīng)方法的對(duì)應(yīng)實(shí)現(xiàn)努咐。

但是在我們調(diào)用類方法的時(shí)候苦蒿,類對(duì)象的isa里面是什么呢?這里為了和對(duì)象查找方法的機(jī)制一致渗稍,遂引入了元類(meta-class)的概念佩迟。

關(guān)于元類,更多具體可以研究這篇文章What is a meta-class in Objective-C?

在引入元類之后竿屹,類對(duì)象和對(duì)象查找方法的機(jī)制就完全統(tǒng)一了报强。

對(duì)象的實(shí)例方法調(diào)用時(shí),通過(guò)對(duì)象的 isa 在類中獲取方法的實(shí)現(xiàn)羔沙。

類對(duì)象的類方法調(diào)用時(shí)躺涝,通過(guò)類的 isa 在元類中獲取方法的實(shí)現(xiàn)。

meta-class之所以重要扼雏,是因?yàn)樗鎯?chǔ)著一個(gè)類的所有類方法坚嗜。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同诗充。

對(duì)應(yīng)關(guān)系的圖如下圖苍蔬,下圖很好的描述了對(duì)象,類蝴蜓,元類之間的關(guān)系:

圖中實(shí)線是 super_class指針碟绑,虛線是isa指針。

Root class (class)其實(shí)就是NSObject茎匠,NSObject是沒(méi)有超類的格仲,所以Root class(class)的superclass指向nil。

每個(gè)Class都有一個(gè)isa指針指向唯一的Meta class

Root class(meta)的superclass指向Root class(class)诵冒,也就是NSObject凯肋,形成一個(gè)回路。

每個(gè)Meta class的isa指針都指向Root class (meta)汽馋。

我們其實(shí)應(yīng)該明白侮东,類對(duì)象和元類對(duì)象是唯一的,對(duì)象是可以在運(yùn)行時(shí)創(chuàng)建無(wú)數(shù)個(gè)的豹芯。而在main方法執(zhí)行之前悄雅,從 dyld到runtime這期間,類對(duì)象和元類對(duì)象在這期間被創(chuàng)建铁蹈。具體可看sunnyxx這篇iOS 程序 main 函數(shù)之前發(fā)生了什么

(1)isa_t結(jié)構(gòu)體的具體實(shí)現(xiàn)

接下來(lái)我們就該研究研究isa的具體實(shí)現(xiàn)了宽闲。objc_object里面的isa是isa_t類型。通過(guò)查看源碼,我們可以知道isa_t是一個(gè)union聯(lián)合體容诬。

structobjc_object{private:isa_t isa;public:// initIsa() should be used to init the isa of new objects only.// If this object already has an isa, use changeIsa() for correctness.// initInstanceIsa(): objects with no custom RR/AWZvoidinitIsa(Class cls/*indexed=false*/);voidinitInstanceIsa(Class cls,bool hasCxxDtor);private:voidinitIsa(Class newCls,bool indexed,bool hasCxxDtor);}

那就從initIsa方法開始研究围辙。下面以arm64為例。

inlinevoidobjc_object::initInstanceIsa(Class cls,bool hasCxxDtor){initIsa(cls,true,hasCxxDtor);}inlinevoidobjc_object::initIsa(Class cls,bool indexed,bool hasCxxDtor){if(!indexed){isa.cls=cls;}else{isa.bits=ISA_MAGIC_VALUE;isa.has_cxx_dtor=hasCxxDtor;isa.shiftcls=(uintptr_t)cls>>3;}}

initIsa第二個(gè)參數(shù)傳入了一個(gè)true放案,所以initIsa就會(huì)執(zhí)行else里面的語(yǔ)句姚建。

#if__arm64__#defineISA_MASK? ? ? ? 0x0000000ffffffff8ULL#defineISA_MAGIC_MASK? 0x000003f000000001ULL#defineISA_MAGIC_VALUE 0x000001a000000001ULLstruct{uintptr_t indexed:1;uintptr_t has_assoc:1;uintptr_t has_cxx_dtor:1;uintptr_t shiftcls:33;// MACH_VM_MAX_ADDRESS 0x1000000000uintptr_t magic:6;uintptr_t weakly_referenced:1;uintptr_t deallocating:1;uintptr_t has_sidetable_rc:1;uintptr_t extra_rc:19;#defineRC_ONE? (1ULL<<45)#defineRC_HALF? (1ULL<<18)};#elif__x86_64__#defineISA_MASK? ? ? ? 0x00007ffffffffff8ULL#defineISA_MAGIC_MASK? 0x001f800000000001ULL#defineISA_MAGIC_VALUE 0x001d800000000001ULLstruct{uintptr_t indexed:1;uintptr_t has_assoc:1;uintptr_t has_cxx_dtor:1;uintptr_t shiftcls:44;// MACH_VM_MAX_ADDRESS 0x7fffffe00000uintptr_t magic:6;uintptr_t weakly_referenced:1;uintptr_t deallocating:1;uintptr_t has_sidetable_rc:1;uintptr_t extra_rc:8;#defineRC_ONE? (1ULL<<56)#defineRC_HALF? (1ULL<<7)};

ISA_MAGIC_VALUE = 0x000001a000000001ULL轉(zhuǎn)換成二進(jìn)制是11010000000000000000000000000000000000001,結(jié)構(gòu)如下圖:

關(guān)于參數(shù)的說(shuō)明:

第一位index吱殉,代表是否開啟isa指針優(yōu)化掸冤。index = 1,代表開啟isa指針優(yōu)化友雳。

在2013年9月稿湿,蘋果推出了iPhone5s,與此同時(shí)押赊,iPhone5s配備了首個(gè)采用64位架構(gòu)的A7雙核處理器饺藤,為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋果提出了Tagged Pointer的概念流礁。對(duì)于64位程序涕俗,引入Tagged Pointer后,相關(guān)邏輯能減少一半的內(nèi)存占用神帅,以及3倍的訪問(wèn)速度提升再姑,100倍的創(chuàng)建、銷毀速度提升找御。

在WWDC2013的《Session 404 Advanced in Objective-C》視頻中元镀,蘋果介紹了 Tagged Pointer。 Tagged Pointer的存在主要是為了節(jié)省內(nèi)存霎桅。我們知道栖疑,對(duì)象的指針大小一般是與機(jī)器字長(zhǎng)有關(guān),在32位系統(tǒng)中滔驶,一個(gè)指針的大小是32位(4字節(jié))遇革,而在64位系統(tǒng)中,一個(gè)指針的大小將是64位(8字節(jié))瓜浸。

假設(shè)我們要存儲(chǔ)一個(gè)NSNumber對(duì)象澳淑,其值是一個(gè)整數(shù)比原。正常情況下插佛,如果這個(gè)整數(shù)只是一個(gè)NSInteger的普通變量,那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān)量窘,在32位CPU下占4個(gè)字節(jié)雇寇,在64位CPU下是占8個(gè)字節(jié)的。而指針類型的大小通常也是與CPU位數(shù)相關(guān),一個(gè)指針?biāo)加玫膬?nèi)存在32位CPU下為4個(gè)字節(jié)锨侯,在64位CPU下也是8個(gè)字節(jié)嫩海。如果沒(méi)有Tagged Pointer對(duì)象,從32位機(jī)器遷移到64位機(jī)器中后囚痴,雖然邏輯沒(méi)有任何變化叁怪,但這種NSNumber、NSDate一類的對(duì)象所占用的內(nèi)存會(huì)翻倍深滚。如下圖所示:

蘋果提出了Tagged Pointer對(duì)象奕谭。由于NSNumber、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個(gè)字節(jié)痴荐,拿整數(shù)來(lái)說(shuō)血柳,4個(gè)字節(jié)所能表示的有符號(hào)整數(shù)就可以達(dá)到20多億(注:2^31=2147483648,另外1位作為符號(hào)位)生兆,對(duì)于絕大多數(shù)情況都是可以處理的难捌。所以,引入了Tagged Pointer對(duì)象之后鸦难,64位CPU下NSNumber的內(nèi)存圖變成了以下這樣:

關(guān)于Tagged Pointer技術(shù)詳細(xì)的根吁,可以看上面鏈接那個(gè)文章。

has_assoc

對(duì)象含有或者曾經(jīng)含有關(guān)聯(lián)引用合蔽,沒(méi)有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存

has_cxx_dtor

表示該對(duì)象是否有 C++ 或者 Objc 的析構(gòu)器

shiftcls

類的指針婴栽。arm64架構(gòu)中有33位可以存儲(chǔ)類指針。

源碼中isa.shiftcls = (uintptr_t)cls >> 3;

將當(dāng)前地址右移三位的主要原因是用于將 Class 指針中無(wú)用的后三位清除減小內(nèi)存的消耗辈末,因?yàn)轭惖闹羔樢凑兆止?jié)(8 bits)對(duì)齊內(nèi)存愚争,其指針后三位都是沒(méi)有意義的 0。具體可以看從 NSObject 的初始化了解 isa這篇文章里面的shiftcls分析挤聘。

magic

判斷對(duì)象是否初始化完成轰枝,在arm64中0x16是調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間。

weakly_referenced

對(duì)象被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量组去,沒(méi)有弱引用的對(duì)象可以更快釋放

deallocating

對(duì)象是否正在釋放內(nèi)存

has_sidetable_rc

判斷該對(duì)象的引用計(jì)數(shù)是否過(guò)大鞍陨,如果過(guò)大則需要其他散列表來(lái)進(jìn)行存儲(chǔ)。

extra_rc

存放該對(duì)象的引用計(jì)數(shù)值減一后的結(jié)果从隆。對(duì)象的引用計(jì)數(shù)超過(guò) 1诚撵,會(huì)存在這個(gè)這個(gè)里面,如果引用計(jì)數(shù)為 10键闺,extra_rc的值就為 9寿烟。

ISA_MAGIC_MASK 和 ISA_MASK 分別是通過(guò)掩碼的方式獲取MAGIC值 和 isa類指針。

inlineClass objc_object::ISA(){assert(!isTaggedPointer());return(Class)(isa.bits&ISA_MASK);}

關(guān)于x86_64的架構(gòu)辛燥,具體可以看從 NSObject 的初始化了解 isa文章里面的詳細(xì)分析筛武。

(2)cache_t的具體實(shí)現(xiàn)

還是繼續(xù)看源碼

structcache_t{structbucket_t*_buckets;mask_t _mask;mask_t _occupied;}typedefunsignedintuint32_t;typedefuint32_t mask_t;// x86_64 & arm64 asm are less efficient with 16-bitstypedefunsignedlonguintptr_t;typedefuintptr_t cache_key_t;structbucket_t{private:cache_key_t _key;IMP _imp;}

根據(jù)源碼缝其,我們可以知道cache_t中存儲(chǔ)了一個(gè)bucket_t的結(jié)構(gòu)體,和兩個(gè)unsigned int的變量徘六。

mask:分配用來(lái)緩存bucket的總數(shù)内边。

occupied:表明目前實(shí)際占用的緩存bucket的個(gè)數(shù)。

bucket_t的結(jié)構(gòu)體中存儲(chǔ)了一個(gè)unsigned long和一個(gè)IMP待锈。IMP是一個(gè)函數(shù)指針漠其,指向了一個(gè)方法的具體實(shí)現(xiàn)。

cache_t中的bucket_t *_buckets其實(shí)就是一個(gè)散列表竿音,用來(lái)存儲(chǔ)Method的鏈表辉懒。

Cache的作用主要是為了優(yōu)化方法調(diào)用的性能。當(dāng)對(duì)象receiver調(diào)用方法message時(shí)谍失,首先根據(jù)對(duì)象receiver的isa指針查找到它對(duì)應(yīng)的類眶俩,然后在類的methodLists中搜索方法,如果沒(méi)有找到快鱼,就使用super_class指針到父類中的methodLists查找颠印,一旦找到就調(diào)用方法。如果沒(méi)有找到抹竹,有可能消息轉(zhuǎn)發(fā)线罕,也可能忽略它。但這樣查找方式效率太低窃判,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用钞楼,占總調(diào)用次數(shù)的80%。所以使用Cache來(lái)緩存經(jīng)常調(diào)用的方法袄琳,當(dāng)調(diào)用方法時(shí)询件,優(yōu)先在Cache查找,如果沒(méi)有找到唆樊,再到methodLists查找宛琅。

(3)class_data_bits_t的具體實(shí)現(xiàn)

源碼實(shí)現(xiàn)如下:

structclass_data_bits_t{// Values are the FAST_ flags above.uintptr_t bits;}structclass_rw_t{uint32_t flags;uint32_t version;constclass_ro_t*ro;method_array_t methods;property_array_t properties;protocol_array_t protocols;Class firstSubclass;Class nextSiblingClass;char*demangledName;}structclass_ro_t{uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;#ifdef__LP64__uint32_t reserved;#endifconstuint8_t*ivarLayout;constchar*name;method_list_t*baseMethodList;protocol_list_t*baseProtocols;constivar_list_t*ivars;constuint8_t*weakIvarLayout;property_list_t*baseProperties;method_list_t*baseMethods()const{returnbaseMethodList;}};

在 objc_class結(jié)構(gòu)體中的注釋寫到 class_data_bits_t相當(dāng)于 class_rw_t指針加上 rr/alloc 的標(biāo)志。

class_data_bits_t bits;// class_rw_t * plus custom rr/alloc flags

它為我們提供了便捷方法用于返回其中的 class_rw_t *指針:

class_rw_t*data(){returnbits.data();}

Objc的類的屬性逗旁、方法嘿辟、以及遵循的協(xié)議在obj 2.0的版本之后都放在class_rw_t中。class_ro_t是一個(gè)指向常量的指針片效,存儲(chǔ)來(lái)編譯器決定了的屬性红伦、方法和遵守協(xié)議。rw-readwrite淀衣,ro-readonly

在編譯期類的結(jié)構(gòu)中的 class_data_bits_t *data指向的是一個(gè) class_ro_t *指針:

在運(yùn)行時(shí)調(diào)用 realizeClass方法昙读,會(huì)做以下3件事情:

從 class_data_bits_t調(diào)用 data方法,將結(jié)果從 class_rw_t強(qiáng)制轉(zhuǎn)換為 class_ro_t指針

初始化一個(gè) class_rw_t結(jié)構(gòu)體

設(shè)置結(jié)構(gòu)體 ro的值以及 flag

最后調(diào)用methodizeClass方法舌缤,把類里面的屬性箕戳,協(xié)議,方法都加載進(jìn)來(lái)国撵。

structmethod_t{SEL name;constchar*types;IMP imp;structSortBySELAddress:public std::binary_function<constmethod_t&,constmethod_t&,bool>{booloperator()(constmethod_t&lhs,constmethod_t&rhs){returnlhs.name<rhs.name;}};};

方法method的定義如上陵吸。里面包含3個(gè)成員變量。SEL是方法的名字name介牙。types是Type Encoding類型編碼壮虫,類型可參考Type Encoding,在此不細(xì)說(shuō)环础。

IMP是一個(gè)函數(shù)指針囚似,指向的是函數(shù)的具體實(shí)現(xiàn)。在runtime中消息傳遞和轉(zhuǎn)發(fā)的目的就是為了找到IMP线得,并執(zhí)行函數(shù)饶唤。

整個(gè)運(yùn)行時(shí)過(guò)程可以描述如下:

更加詳細(xì)的分析,請(qǐng)看@Draveness的這篇文章深入解析 ObjC 中方法的結(jié)構(gòu)

到此贯钩,總結(jié)一下objc_class 1.0和2.0的差別募狂。

三. 入院考試

(一)[self class] 與 [super class]

下面代碼輸出什么?

@implementationSon:Father

-(id)init{self=[superinit];if(self){NSLog(@"%@",NSStringFromClass([selfclass]));NSLog(@"%@",NSStringFromClass([superclass]));}returnself;}@end

self和super的區(qū)別:

self是類的一個(gè)隱藏參數(shù),每個(gè)方法的實(shí)現(xiàn)的第一個(gè)參數(shù)即為self角雷。

super并不是隱藏參數(shù)祸穷,它實(shí)際上只是一個(gè)”編譯器標(biāo)示符”,它負(fù)責(zé)告訴編譯器勺三,當(dāng)調(diào)用方法時(shí)雷滚,去調(diào)用父類的方法,而不是本類中的方法吗坚。

在調(diào)用[super class]的時(shí)候祈远,runtime會(huì)去調(diào)用objc_msgSendSuper方法,而不是objc_msgSend

OBJC_EXPORTvoidobjc_msgSendSuper(void/* struct objc_super *super, SEL op, ... */)/// Specifies the superclass of an instance. structobjc_super{/// Specifies an instance of a class.__unsafe_unretained id receiver;/// Specifies the particular superclass of the instance to message. #if!defined(__cplusplus)? &&? !__OBJC2__/* For compatibility with old objc-runtime.h header */__unsafe_unretained Class class;#else__unsafe_unretained Class super_class;#endif/* super_class is the first class to search */};

在objc_msgSendSuper方法中商源,第一個(gè)參數(shù)是一個(gè)objc_super的結(jié)構(gòu)體绊含,這個(gè)結(jié)構(gòu)體里面有兩個(gè)變量,一個(gè)是接收消息的receiver炊汹,一個(gè)是當(dāng)前類的父類super_class躬充。

入院考試第一題錯(cuò)誤的原因就在這里,誤認(rèn)為[super class]是調(diào)用的[super_class class]讨便。

objc_msgSendSuper的工作原理應(yīng)該是這樣的:

從objc_super結(jié)構(gòu)體指向的superClass父類的方法列表開始查找selector充甚,找到后以objc->receiver去調(diào)用父類的這個(gè)selector。注意霸褒,最后的調(diào)用者是objc->receiver伴找,而不是super_class!

那么objc_msgSendSuper最后就轉(zhuǎn)變成

// 注意這里是從父類開始msgSend废菱,而不是從本類開始技矮,謝謝@Josscii 和他同事共同指點(diǎn)出此處描述的不妥抖誉。objc_msgSend(objc_super->receiver,@selector(class))/// Specifies an instance of a class.? 這是類的一個(gè)實(shí)例__unsafe_unretained id receiver;// 由于是實(shí)例調(diào)用,所以是減號(hào)方法-(Class)class{returnobject_getClass(self);}

由于找到了父類NSObject里面的class方法的IMP衰倦,又因?yàn)閭魅氲娜雲(yún)bjc_super->receiver = self袒炉。self就是son,調(diào)用class樊零,所以父類的方法class執(zhí)行IMP之后我磁,輸出還是son,最后輸出兩個(gè)都一樣驻襟,都是輸出son夺艰。

(二)isKindOfClass 與 isMemberOfClass

下面代碼輸出什么?

@interfaceSark:NSObject

@end

@implementationSark

@end

intmain(intargc,constchar*argv[]){

@autoreleasepool{BOOL res1=[(id)[NSObject class]isKindOfClass:[NSObject class]];BOOL res2=[(id)[NSObject class]isMemberOfClass:[NSObject class]];

BOOL res3=[(id)[Sark class]isKindOfClass:[Sark class]];

BOOL res4=[(id)[Sark class]isMemberOfClass:[Sark class]];

NSLog(@"%d %d %d %d",res1,res2,res3,res4);

}return0;

}

先來(lái)分析一下源碼這兩個(gè)函數(shù)的對(duì)象實(shí)現(xiàn)

+(Class)class{returnself;}-(Class)class{returnobject_getClass(self);}Classobject_getClass(id obj){if(obj)returnobj->getIsa();elsereturnNil;}inlineClass objc_object::getIsa(){if(isTaggedPointer()){uintptr_t slot=((uintptr_t)this>>TAG_SLOT_SHIFT)&TAG_SLOT_MASK;returnobjc_tag_classes[slot];}returnISA();}inlineClass objc_object::ISA(){assert(!isTaggedPointer());return(Class)(isa.bits&ISA_MASK);}+(BOOL)isKindOfClass:(Class)cls{for(Class tcls=object_getClass((id)self);tcls;tcls=tcls->superclass){if(tcls==cls)returnYES;}returnNO;}-(BOOL)isKindOfClass:(Class)cls{for(Class tcls=[selfclass];tcls;tcls=tcls->superclass){if(tcls==cls)returnYES;}returnNO;}+(BOOL)isMemberOfClass:(Class)cls{returnobject_getClass((id)self)==cls;}-(BOOL)isMemberOfClass:(Class)cls{return[selfclass]==cls;}

首先題目中NSObject 和 Sark分別調(diào)用了class方法沉衣。

+ (BOOL)isKindOfClass:(Class)cls方法內(nèi)部郁副,會(huì)先去獲得object_getClass的類,而object_getClass的源碼實(shí)現(xiàn)是去調(diào)用當(dāng)前類的obj->getIsa()豌习,最后在ISA()方法中獲得meta class的指針霞势。

接著在isKindOfClass中有一個(gè)循環(huán),先判斷class是否等于meta class斑鸦,不等就繼續(xù)循環(huán)判斷是否等于super class愕贡,不等再繼續(xù)取super class,如此循環(huán)下去巷屿。

[NSObject class]執(zhí)行完之后調(diào)用isKindOfClass固以,第一次判斷先判斷NSObject 和 NSObject的meta class是否相等,之前講到meta class的時(shí)候放了一張很詳細(xì)的圖嘱巾,從圖上我們也可以看出憨琳,NSObject的meta class與本身不等。接著第二次循環(huán)判斷NSObject與meta class的superclass是否相等旬昭。還是從那張圖上面我們可以看到:Root class(meta) 的superclass 就是 Root class(class)篙螟,也就是NSObject本身。所以第二次循環(huán)相等问拘,于是第一行res1輸出應(yīng)該為YES遍略。

同理,[Sark class]執(zhí)行完之后調(diào)用isKindOfClass骤坐,第一次for循環(huán)绪杏,Sark的Meta Class與[Sark class]不等,第二次for循環(huán)纽绍,Sark Meta Class的super class 指向的是 NSObject Meta Class蕾久, 和 Sark Class不相等。第三次for循環(huán)拌夏,NSObject Meta Class的super class指向的是NSObject Class僧著,和 Sark Class 不相等履因。第四次循環(huán),NSObject Class 的super class 指向 nil盹愚, 和 Sark Class不相等栅迄。第四次循環(huán)之后,退出循環(huán)杯拐,所以第三行的res3輸出為NO霞篡。

如果把這里的Sark改成它的實(shí)例對(duì)象世蔗,[sark isKindOfClass:[Sark class]端逼,那么此時(shí)就應(yīng)該輸出YES了。因?yàn)樵趇sKindOfClass函數(shù)中污淋,判斷sark的isa指向是否是自己的類Sark顶滩,第一次for循環(huán)就能輸出YES了。

isMemberOfClass的源碼實(shí)現(xiàn)是拿到自己的isa指針和自己比較寸爆,是否相等礁鲁。

第二行isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等赁豆。第四行仅醇,isa指向Sark的Meta Class,和Sark Class也不等魔种,所以第二行res2和第四行res4都輸出NO析二。

(三)Class與內(nèi)存地址

下面的代碼會(huì)?Compile Error / Runtime Crash / NSLog…?

@interfaceSark:NSObject

@property(nonatomic,copy)NSString*name;-(void)speak;@end@implementationSark-(void)speak{NSLog(@"my name's %@",self.name);}@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];id cls=[Sark class];void*obj=&cls;[(__bridge id)obj speak];}@end

這道題有兩個(gè)難點(diǎn)节预。難點(diǎn)一叶摄,obj調(diào)用speak方法,到底會(huì)不會(huì)崩潰安拟。難點(diǎn)二蛤吓,如果speak方法不崩潰,應(yīng)該輸出什么糠赦?

首先需要談?wù)勲[藏參數(shù)self和_cmd的問(wèn)題会傲。

當(dāng)[receiver message]調(diào)用方法時(shí),系統(tǒng)會(huì)在運(yùn)行時(shí)偷偷地動(dòng)態(tài)傳入兩個(gè)隱藏參數(shù)self和_cmd拙泽,之所以稱它們?yōu)殡[藏參數(shù)唆铐,是因?yàn)樵谠创a中沒(méi)有聲明和定義這兩個(gè)參數(shù)。self在上面已經(jīng)講解明白了奔滑,接下來(lái)就來(lái)說(shuō)說(shuō)_cmd艾岂。_cmd表示當(dāng)前調(diào)用方法,其實(shí)它就是一個(gè)方法選擇器SEL朋其。

難點(diǎn)一王浴,能不能調(diào)用speak方法脆炎?

id cls=[Sark class];void*obj=&cls;

答案是可以的。obj被轉(zhuǎn)換成了一個(gè)指向Sark Class的指針氓辣,然后使用id轉(zhuǎn)換成了objc_object類型秒裕。obj現(xiàn)在已經(jīng)是一個(gè)Sark類型的實(shí)例對(duì)象了。當(dāng)然接下來(lái)可以調(diào)用speak的方法钞啸。

難點(diǎn)二几蜻,如果能調(diào)用speak,會(huì)輸出什么呢体斩?

很多人可能會(huì)認(rèn)為會(huì)輸出sark相關(guān)的信息梭稚。這樣答案就錯(cuò)誤了。

正確的答案會(huì)輸出

my name is

內(nèi)存地址每次運(yùn)行都不同絮吵,但是前面一定是ViewController弧烤。why?

我們把代碼改變一下蹬敲,打印更多的信息出來(lái)暇昂。

-(void)viewDidLoad{[superviewDidLoad];NSLog(@"ViewController = %@ , 地址 = %p",self,&self);id cls=[Sark class];NSLog(@"Sark class = %@ 地址 = %p",cls,&cls);void*obj=&cls;NSLog(@"Void *obj = %@ 地址 = %p",obj,&obj);[(__bridge id)obj speak];Sark*sark=[[Sark alloc]init];NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark);[sark speak];}

我們把對(duì)象的指針地址都打印出來(lái)。輸出結(jié)果:

ViewController = , 地址 = 0x7fff543f5aa8Sark class = Sark 地址 = 0x7fff543f5a88Void *obj = 地址 = 0x7fff543f5a80my name is Sark instance = 地址 = 0x7fff543f5a78my name is (null)

// objc_msgSendSuper2() takes the current search class, not its superclass.OBJC_EXPORT idobjc_msgSendSuper2(structobjc_super*super,SEL op,...)__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_2_0);

objc_msgSendSuper2方法入?yún)⑹且粋€(gè)objc_super *super伴嗡。

/// Specifies the superclass of an instance. structobjc_super{/// Specifies an instance of a class.__unsafe_unretained id receiver;/// Specifies the particular superclass of the instance to message. #if!defined(__cplusplus)? &&? !__OBJC2__/* For compatibility with old objc-runtime.h header */__unsafe_unretained Class class;#else__unsafe_unretained Class super_class;#endif/* super_class is the first class to search */};#endif

所以按viewDidLoad執(zhí)行時(shí)各個(gè)變量入棧順序從高到底為self, _cmd, super_class(等同于self.class), receiver(等同于self), obj急波。

第一個(gè)self和第二個(gè)_cmd是隱藏參數(shù)。第三個(gè)self.class和第四個(gè)self是[super viewDidLoad]方法執(zhí)行時(shí)候的參數(shù)瘪校。

在調(diào)用self.name的時(shí)候澄暮,本質(zhì)上是self指針在內(nèi)存向高位地址偏移一個(gè)指針。

從打印結(jié)果我們可以看到渣淤,obj就是cls的地址赏寇。在obj向上偏移一個(gè)指針就到了0x7fff543f5a90,這正好是ViewController的地址价认。

所以輸出為my name is <ViewController: 0x7fb570e2ad00>嗅定。

至此,Objc中的對(duì)象到底是什么呢用踩?

實(shí)質(zhì):Objc中的對(duì)象是一個(gè)指向ClassObject地址的變量渠退,即 id obj = &ClassObject , 而對(duì)象的實(shí)例變量 void *ivar = &obj + offset(N)

加深一下對(duì)上面這句話的理解脐彩,下面這段代碼會(huì)輸出什么碎乃?

-(void)viewDidLoad{[superviewDidLoad];NSLog(@"ViewController = %@ , 地址 = %p",self,&self);NSString*myName=@"halfrost";id cls=[Sark class];NSLog(@"Sark class = %@ 地址 = %p",cls,&cls);void*obj=&cls;NSLog(@"Void *obj = %@ 地址 = %p",obj,&obj);[(__bridge id)obj speak];Sark*sark=[[Sark alloc]init];NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark);[sark speak];}

ViewController = ,? 地址? = 0x7fff56a48a78Sark class = Sark? 地址? = 0x7fff56a48a50Void *obj = ? 地址 = 0x7fff56a48a48my name is halfrostSark instance = ? 地址 = 0x7fff56a48a40my name is (null)

由于加了一個(gè)字符串,結(jié)果輸出就完全變了惠奸,[(__bridge id)obj speak];這句話會(huì)輸出“my name is halfrost”

原因還是和上面的類似梅誓。按viewDidLoad執(zhí)行時(shí)各個(gè)變量入棧順序從高到底為self,_cmd,self.class( super_class )梗掰,self ( receiver )嵌言,myName,obj及穗。obj往上偏移一個(gè)指針摧茴,就是myName字符串,所以輸出變成了輸出myName了埂陆。

這里有一點(diǎn)需要額外說(shuō)明的是苛白,棧里面有兩個(gè) self,可能有些人認(rèn)為是指針偏移到了第一個(gè) self 了焚虱,于是打印出了 ViewController:

my name is<ViewController:0x7fb570e2ad00>

其實(shí)這種想法是不對(duì)的购裙,從 obj 往上找 name 屬性,完全是指針偏移了一個(gè) offset 導(dǎo)致的著摔,也就是說(shuō)指針只往下偏移了一個(gè)缓窜。那么怎么證明指針只偏移了一個(gè)定续,而不是偏移了4個(gè)到最下面的 self 呢谍咆?

obj 的地址是 0x7fff5c7b9a08,self 的地址是 0x7fff5c7b9a28私股。每個(gè)指針占8個(gè)字節(jié)摹察,所以從 obj 到 self 中間確實(shí)有4個(gè)指針大小的間隔。如果從 obj 偏移一個(gè)指針倡鲸,就到了 0x7fff5c7b9a10供嚎。我們需要把這個(gè)內(nèi)存地址里面的內(nèi)容打印出來(lái)。

LLDB 調(diào)試中峭状,可以使用examine命令(簡(jiǎn)寫是x)來(lái)查看內(nèi)存地址中的值克滴。x命令的語(yǔ)法如下所示:

x/

n、f优床、u是可選的參數(shù)劝赔。

n 是一個(gè)正整數(shù),表示顯示內(nèi)存的長(zhǎng)度胆敞,也就是說(shuō)從當(dāng)前地址向后顯示幾個(gè)地址的內(nèi)容着帽。

f 表示顯示的格式,參見上面移层。如果地址所指的是字符串仍翰,那么格式可以是s,如果地十是指令地址观话,那么格式可以是 i予借。

u 表示從當(dāng)前地址往后請(qǐng)求的字節(jié)數(shù),如果不指定的話,GDB默認(rèn)是4個(gè)bytes灵迫。u參數(shù)可以用下面的字符來(lái)代替喧笔,b表示單字節(jié),h表示雙字節(jié)龟再,w表示四字節(jié)书闸,g表示八字節(jié)。當(dāng)我們指定了字節(jié)長(zhǎng)度后利凑,GDB會(huì)從指內(nèi)存定的內(nèi)存地址開始浆劲,讀寫指定字節(jié),并把其當(dāng)作一個(gè)值取出來(lái)哀澈。

我們用 x 命令分別打印出? 0x7fff5c7b9a10 和 0x7fff5c7b9a28 內(nèi)存地址里面的內(nèi)容牌借,我們會(huì)發(fā)現(xiàn)兩個(gè)打印出來(lái)的值是一樣的,都是 0x7fbf0d606aa0割按。

這兩個(gè) self 的地址不同膨报,里面存儲(chǔ)的內(nèi)容是相同的。

所以 obj 是偏移了一個(gè)指針适荣,而不是偏移到最下面的 self 现柠。

最后

入院考試由于還有一題沒(méi)有解答出來(lái),所以醫(yī)院決定讓我住院一天觀察弛矛。

未完待續(xù)够吩,請(qǐng)大家多多指教。

作者:一縷殤流化隱半邊冰霜

鏈接:http://www.reibang.com/p/9d649ce6d0b8

來(lái)源:簡(jiǎn)書

著作權(quán)歸作者所有丈氓。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)周循,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末万俗,一起剝皮案震驚了整個(gè)濱河市湾笛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闰歪,老刑警劉巖嚎研,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異课竣,居然都是意外死亡嘉赎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門于樟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)公条,“玉大人,你說(shuō)我怎么就攤上這事迂曲“谐鳎” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)关霸。 經(jīng)常有香客問(wèn)我传黄,道長(zhǎng),這世上最難降的妖魔是什么队寇? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任膘掰,我火速辦了婚禮,結(jié)果婚禮上佳遣,老公的妹妹穿的比我還像新娘识埋。我一直安慰自己,他們只是感情好零渐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布窒舟。 她就那樣靜靜地躺著,像睡著了一般诵盼。 火紅的嫁衣襯著肌膚如雪惠豺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天风宁,我揣著相機(jī)與錄音洁墙,去河邊找鬼。 笑死杀糯,一個(gè)胖子當(dāng)著我的面吹牛扫俺,可吹牛的內(nèi)容都是我干的苍苞。 我是一名探鬼主播固翰,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼羹呵!你這毒婦竟也來(lái)了骂际?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤冈欢,失蹤者是張志新(化名)和其女友劉穎歉铝,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凑耻,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡太示,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了香浩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片类缤。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖邻吭,靈堂內(nèi)的尸體忽然破棺而出餐弱,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布膏蚓,位于F島的核電站瓢谢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驮瞧。R本人自食惡果不足惜氓扛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望论笔。 院中可真熱鬧幢尚,春花似錦、人聲如沸翅楼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毅臊。三九已至理茎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間管嬉,已是汗流浹背皂林。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚯撩,地道東北人础倍。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像胎挎,于是被迫代替她去往敵國(guó)和親沟启。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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