Runtime

講一下 OC 的消息機(jī)制

- OC中的方法調(diào)用其實(shí)是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用  給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
- objc_msgSend底層三大階段
  a.消息發(fā)送
  b.動(dòng)態(tài)方法解析
  c.消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)機(jī)制流程

什么是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í)編寫的OC代碼兑凿,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用
- 利用關(guān)聯(lián)對象Associated 給分類添加屬性
- 遍歷類的所有成員變量 (訪問私有成員變量 字典轉(zhuǎn)模型 自動(dòng)歸檔解檔)
- 交換方法實(shí)現(xiàn)(交換系統(tǒng)自帶的方法)
- 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題

OC的動(dòng)態(tài)性是由Runtime來支持的
union 共用體 大家共用一個(gè)內(nèi)存 struct是每個(gè)成員都是獨(dú)立的個(gè)體 都有獨(dú)立的內(nèi)存

按位與能取出特定的值 這個(gè)位置的置為1 其他位置置為0就行

union Data{
    int year;
    char month;
 };

isa

1.在arm64架構(gòu)之前剩蟀,isa就是一個(gè)普通的指針猖辫,存儲(chǔ)著Class、Meta-Class對象的內(nèi)存地址
2.從arm64架構(gòu)開始伍宦,對isa進(jìn)行了優(yōu)化芽死,變成了一個(gè)共用體(union)結(jié)構(gòu)乏梁,還使用位域來存儲(chǔ)更多的信息
isa結(jié)構(gòu).png
nonpointer
0,代表普通的指針关贵,存儲(chǔ)著Class遇骑、Meta-Class對象的內(nèi)存地址
1,代表優(yōu)化過揖曾,使用位域存儲(chǔ)更多的信息

has_assoc
是否有設(shè)置過關(guān)聯(lián)對象落萎,如果沒有,釋放時(shí)會(huì)更快(只要設(shè)置過 不管當(dāng)前是否有)

has_cxx_dtor
是否有C++的析構(gòu)函數(shù)(.cxx_destruct)翩肌,如果沒有模暗,釋放時(shí)會(huì)更快

shiftcls
存儲(chǔ)著Class、Meta-Class對象的內(nèi)存地址信息

magic
用于在調(diào)試時(shí)分辨對象是否未完成初始化

weakly_referenced
是否有被弱引用指向過念祭,如果沒有兑宇,釋放時(shí)會(huì)更快(只要被指向過 不管當(dāng)前是否弱引用)

deallocating
對象是否正在釋放

extra_rc
里面存儲(chǔ)的值是引用計(jì)數(shù)器減1

has_sidetable_rc
引用計(jì)數(shù)器是否過大無法存儲(chǔ)在isa中
如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類的屬性中

類對象 元類對象的地址值 最后3位一定是0
&ISA_MASK 取出來shiftcls 的值 直接取出來不是的 因?yàn)閕sa里面存儲(chǔ)著很多信息(用位域的方式存儲(chǔ))

Class的結(jié)構(gòu)

Class的結(jié)構(gòu).png
class_rw_t里面的methods粱坤、properties隶糕、protocols是二維數(shù)組,是可讀可寫的站玄,包含了類的初始內(nèi)容枚驻、分類的內(nèi)容
class_rw_t結(jié)構(gòu).png
class_ro_t里面的baseMethodList、baseProtocols株旷、ivars再登、baseProperties是一維數(shù)組,是只讀的晾剖,包含了類的
初始內(nèi)容
class_ro_t的結(jié)構(gòu).png

method_t

method_t是對方法\函數(shù)的封裝
method結(jié)構(gòu)_.png

IMP代表函數(shù)的具體實(shí)現(xiàn).png
- SEL代表方法\函數(shù)名锉矢,一般叫做選擇器,底層結(jié)構(gòu)跟char *類似
- 通過@selector 或者sel_registerName() 獲得
- 可以通過sel_getName()和NSStringFromSelector()轉(zhuǎn)成字符串

不同類中相同名字的方法齿尽,所對應(yīng)的方法選擇器是相同的

SEL的結(jié)構(gòu).png

types包含的信息.png

Type Encoding

iOS中提供了一個(gè)叫做@encode的指令沽损,可以將具體的類型表示成字符串編碼
圖1.png

圖2.png

方法緩存

Class內(nèi)部結(jié)構(gòu)中有個(gè)方法緩存(cache_t),用散列表(哈希表)來緩存曾經(jīng)調(diào)用過的方法循头,可以提高方法的查找速度

Cache內(nèi)部結(jié)構(gòu).png
如果在緩存中快速查找到方法
拿到方法名SEL & (緩存的長度 - 1) = 索引值 也就找到了這個(gè)方法的bucket_t

這種方法是以空間換時(shí)間 也就是犧牲到內(nèi)存空間來換取快速查到
散列表是有個(gè)固定的容量 當(dāng)放不下的時(shí)候 就會(huì)擴(kuò)容
一旦散列表放不下 就會(huì)擴(kuò)容(擴(kuò)大為原來的2倍) 這個(gè)時(shí)候會(huì)清掉緩存 因?yàn)閙ask改變了 通過&也無法找到原先緩存的方法的實(shí)現(xiàn)

當(dāng)緩存方法绵估,準(zhǔn)備插入散列表的時(shí)候,發(fā)現(xiàn)所在位置已經(jīng)有了方法的存在 那么就會(huì)執(zhí)行索引值-1 繼續(xù)進(jìn)行插入 直到找到?jīng)]有方
法存在的索引 把這個(gè)方法緩存進(jìn)散列表(當(dāng)索引值為0還是沒有找到的時(shí)候 索引值賦值為mask 再繼續(xù)進(jìn)行插入) 
當(dāng)根據(jù)方法名SEL & (緩存的長度 - 1) = 索引  得到的索引值去相應(yīng)位置找方法實(shí)現(xiàn) 發(fā)現(xiàn)跟方法名不符時(shí) 按照 
索引值 - 1 繼續(xù)查找 直到找到具體的實(shí)現(xiàn)(當(dāng)索引值為0還是沒有找到的時(shí)候 索引值賦值為mask 從后往前 繼續(xù)查找方法
實(shí)現(xiàn)) 

objc_msgSend

OC中的方法調(diào)用,其實(shí)都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用
OC中的方法調(diào)用 消息機(jī)制 給方法調(diào)用者發(fā)送消息

objc_msgSend的執(zhí)行流程可以分為3大階段

1.消息發(fā)送
2.動(dòng)態(tài)方法解析
3.消息轉(zhuǎn)發(fā)
消息發(fā)送

當(dāng)尋找方法 發(fā)現(xiàn)是父類的或者父類的父類的方法的時(shí)候卡骂,找到之后,會(huì)把方法緩存在消息接受者的緩存中去
當(dāng)尋找方法的時(shí)候 如果方法是有順序的 就會(huì)按照折半查找的方式去找方法 如果沒有順序 就按照for循環(huán)的方法查找方法

消息發(fā)送流程.png
動(dòng)態(tài)方法解析

如果已經(jīng)動(dòng)態(tài)添加方法實(shí)現(xiàn) 那么流程會(huì)再回到消息發(fā)送的步驟

- (void)other{
    NSLog(@"%s",__func__);
}


+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s",__func__);
    
    if(sel == @selector(test)){
        //獲取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));
        
        //動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        //返回YES代表有動(dòng)態(tài)添加方法實(shí)現(xiàn)
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

動(dòng)態(tài)添加C語言函數(shù)方法

void c_other(id self, SEL _cmd){
    NSLog(@"c_other - %@ - %@", self,NSStringFromSelector(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s",__func__);
    
    if(sel == @selector(test)){
       
        //動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
        class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
        //返回YES代表有動(dòng)態(tài)添加方法實(shí)現(xiàn)
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
動(dòng)態(tài)方法解析的流程.png
可以通過實(shí)現(xiàn)+resolveInstanceMethod: 或者+resolveClassMethod: 來動(dòng)態(tài)添加方法解析

動(dòng)態(tài)解析過后国裳,會(huì)重新走“消息發(fā)送”的流程 “從receiverClass的cache中查找方法”這一步開始執(zhí)行(如果找到了方法 就去執(zhí)行 如果沒有找到方法 就不會(huì)再進(jìn)行動(dòng)態(tài)方法解析 而是去執(zhí)行消息轉(zhuǎn)發(fā))

消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)流程.png
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [[MJStudent alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

//方法簽名 返回值類型 參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//NSInvocation 封裝了一個(gè)方法調(diào)用 包括方法調(diào)用者 方法 方法參數(shù)
//anInvocation.target 方法調(diào)用者
//anInvocation.selector 方法名
//[anInvocation getArgument:NULL atIndex:0];
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    
    anInvocation.target = [[MJStudent alloc] init];
    [anInvocation invoke];
}

參數(shù)順序 receiver selector 其他參數(shù)

獲取參數(shù)
int age;
[anInvocation getArgument:&age atIndex:2];
獲取返回值
int ret;
[anInvocation getReturnValue:&ret];

如果同時(shí)存在對象方法 類方法 直接上objc_msgSend方法是走的對象方法
@dynamic是告訴編譯器不用自動(dòng)生成getter和setter的實(shí)現(xiàn),等到運(yùn)行時(shí)再添加方法實(shí)現(xiàn)

super

super調(diào)用全跨,底層會(huì)轉(zhuǎn)換為objc_msgSendSuper2函數(shù)的調(diào)用躏救,接收2個(gè)參數(shù) struct objc_super2 SEL

struct objc_super2結(jié)構(gòu).png

receiver是消息接收者
current_class是receiver的Class對象

self調(diào)用方法是根據(jù)isa指針找到類對象 找相應(yīng)的對象方法 沒有的話 根據(jù)superClass指針往上找 直到找到為之
super調(diào)用方法是直接從父類那里開始查找方法 但是,消息接受者仍然是子類對象

(super message)底層實(shí)現(xiàn)
1.消息接受者仍然是子類對象
2.從父類開始查找方法

isKindOfClass 和 isMemberOfClass 區(qū)別

對象方法
isMemberOfClass 判斷當(dāng)前對象的類對象是否是傳進(jìn)來的這個(gè)類對象
isKindOfClass   判斷當(dāng)前對象的類對象是否是傳進(jìn)來的這個(gè)類對象或者他的子類的類對象
類方法
isMemberOfClass 判斷當(dāng)前對象的元類對象是否是傳進(jìn)來的這個(gè)元類對象
isKindOfClass   判斷當(dāng)前對象的元類對象是否是傳進(jìn)來的這個(gè)元類或者他的子類的元類對象
- 局部變量分配在棧空間
  棧空間分配 從高地址到低地址

LLVM的中間代碼(IR)

Objective-C在變?yōu)闄C(jī)器代碼之前盒使,會(huì)被LLVM編譯器轉(zhuǎn)換為中間代碼(Intermediate Representation)

可以使用以下命令行指令生成中間代碼
clang -emit-llvm -S main.m
LLVM的語法簡介
@ - 全局變量
% - 局部變量
alloca - 在當(dāng)前執(zhí)行的函數(shù)的堆棧幀中分配內(nèi)存崩掘,當(dāng)該函數(shù)返回到其調(diào)用者時(shí),將自動(dòng)釋放內(nèi)存
i32 - 32位4字節(jié)的整數(shù)
align - 對齊
load - 讀出少办,store 寫入
icmp - 兩個(gè)整數(shù)值比較苞慢,返回布爾值
br - 選擇分支,根據(jù)條件來轉(zhuǎn)向label英妓,不根據(jù)條件跳轉(zhuǎn)的話類似 goto
label - 代碼標(biāo)簽
call - 調(diào)用函數(shù)

Runtime的類

- 動(dòng)態(tài)創(chuàng)建一個(gè)類(參數(shù):父類挽放,類名,額外的內(nèi)存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

- 注冊一個(gè)類
void objc_registerClassPair(Class cls) 

- 銷毀一個(gè)類
void objc_disposeClassPair(Class cls)

- 獲取isa指向的Class
Class object_getClass(id obj)

- 設(shè)置isa指向的Class
Class object_setClass(id obj, Class cls)

- 判斷一個(gè)OC對象是否為Class
BOOL object_isClass(id obj)

- 判斷一個(gè)Class是否為元類
BOOL class_isMetaClass(Class cls)

- 獲取父類
Class class_getSuperclass(Class cls)

要在類注冊之前添加成員變量

        //創(chuàng)建類
        //Class  _Nullable __unsafe_unretained superclass  父類
        //const char * _Nonnull name 類名
        //size_t extraBytes 額外的大小 是否需要擴(kuò)充空間
        Class newClass = objc_allocateClassPair([NSObject class], "MJDgo", 0);
        //添加成員變量
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_weight", 4, 1, @encode(int));
        //添加方法
        class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
        //注冊類
        objc_registerClassPair(newClass);
        id dog = [[newClass alloc] init];

Runtime的成員變量

- 獲取一個(gè)實(shí)例變量信息
Ivar class_getInstanceVariable(Class cls, const char *name)

- 拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

- 設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)

- 動(dòng)態(tài)添加成員變量(已經(jīng)注冊的類是不能動(dòng)態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

- 獲取成員變量的相關(guān)信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
        //獲取成員變量信息
        Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
        //成員變量名稱
        NSLog(@"%s %s",ivar_getName(ageIvar),ivar_getTypeEncoding(ageIvar));
        //設(shè)置和獲取成員變量的值
        object_setIvar(person, ageIvar, @10);
        object_getIvar(person, ageIvar);

int類型的無法這么賦值 可以使用kvc賦值 或者 object_setIvar(person, ageIvar, (__bridge id)(void *)10);這樣設(shè)置
runtime里面使用到copy 或者 create的 最后都要釋放掉

       //成員變量的數(shù)量
        unsigned int count;
        Ivar *ivars = class_copyIvarList([MJPerson class], &count);
        for (int i = 0; i < count; i++) {
            //取出i位置的成員變量
            Ivar ivar = ivars[i];
            NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
        }
        free(ivars);

//在不需要這個(gè)類的時(shí)候釋放掉 objc_disposeClassPair(newClass);

簡單的字典轉(zhuǎn)模型
+(instancetype)mj_objectWithJson:(NSDictionary *)json{
    
    id obj = [[self alloc] init];
    //成員變量的數(shù)量
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        //取出i位置的成員變量
        Ivar ivar = ivars[i];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        //刪掉下劃線
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        //設(shè)置
        [obj setValue:json[name] forKey:name];
    }
    return obj;
}

Runtime的方法

- 獲得一個(gè)實(shí)例方法蔓纠、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

- 方法實(shí)現(xiàn)相關(guān)操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

- 拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

- 動(dòng)態(tài)添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

- 動(dòng)態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

- 獲取方法的相關(guān)信息(帶有copy的需要調(diào)用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

- 選擇器相關(guān)
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

- 用block作為方法實(shí)現(xiàn)
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
方法交換
        Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
        Method testthod = class_getInstanceMethod([MJPerson class], @selector(test));
        method_exchangeImplementations(runMethod, testthod);

exchangeImplementations交換的是類對象的class_rw_t里面的methods的method_t的IMP 當(dāng)交換的時(shí)候 就會(huì)清空緩存
表面是一種類型 實(shí)際上是另外一種類型 這種是類簇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辑畦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子腿倚,更是在濱河造成了極大的恐慌纯出,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敷燎,死亡現(xiàn)場離奇詭異暂筝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)硬贯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門焕襟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饭豹,你說我怎么就攤上這事鸵赖。” “怎么了拄衰?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵它褪,是天一觀的道長。 經(jīng)常有香客問我肾砂,道長,這世上最難降的妖魔是什么宏悦? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任镐确,我火速辦了婚禮,結(jié)果婚禮上饼煞,老公的妹妹穿的比我還像新娘源葫。我一直安慰自己,他們只是感情好砖瞧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布息堂。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荣堰。 梳的紋絲不亂的頭發(fā)上床未,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音振坚,去河邊找鬼薇搁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛渡八,可吹牛的內(nèi)容都是我干的啃洋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼屎鳍,長吁一口氣:“原來是場噩夢啊……” “哼宏娄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逮壁,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤孵坚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后貌踏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體十饥,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年祖乳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逗堵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡眷昆,死狀恐怖蜒秤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亚斋,我是刑警寧澤作媚,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站帅刊,受9級(jí)特大地震影響纸泡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赖瞒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一女揭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧栏饮,春花似錦吧兔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灶平。三九已至,卻和暖如春箍土,著一層夾襖步出監(jiān)牢的瞬間逢享,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工涮帘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拼苍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓调缨,卻偏偏與公主長得像疮鲫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子弦叶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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

  • objc_getAssociatedObject返回與給定鍵的特定對象關(guān)聯(lián)的值俊犯。ID objc_getAssoci...
    有一種再見叫青春閱讀 1,583評(píng)論 0 7
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼伤哺,具備了靈活的...
    lylaut閱讀 802評(píng)論 0 4
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 763評(píng)論 0 1
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中燕侠。 以下內(nèi)容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 923評(píng)論 0 6
  • 志在四方 [zhì zài sì fāng],即指有遠(yuǎn)大的志向立莉。出處:“志在四方”由“四方之志”衍變而來绢彤。“四方之...
    牙齒曬曬大太陽閱讀 229評(píng)論 0 0