iOS開(kāi)發(fā)之Runtime

在swift這門(mén)優(yōu)雅的語(yǔ)言還沒(méi)誕生之前从祝,iPhone開(kāi)發(fā)主要使用的是Object-C這門(mén)面向?qū)ο笳Z(yǔ)言牛曹,OC是由C實(shí)現(xiàn)的超集(大部分的OC庫(kù)都有對(duì)應(yīng)的C版本的實(shí)現(xiàn)例如Foundation和CoreFoundation)甸怕,并不需要像JAVA那樣運(yùn)行在虛擬機(jī)中样勃,而且可以很好的結(jié)合C和C++代碼提高程序的性能单绑,除了面向?qū)ο蟮奶匦酝獗也蓿琌C這門(mén)語(yǔ)言還具備了smalltalk的消息機(jī)制,當(dāng)我們調(diào)用了一個(gè)對(duì)象的方法或者說(shuō)函數(shù)時(shí)殷蛇,其實(shí)是向那個(gè)對(duì)象發(fā)送了一條消息实夹。
OC是一門(mén)動(dòng)態(tài)語(yǔ)言,也就是說(shuō)在OC運(yùn)行時(shí)粒梦,有一個(gè)運(yùn)行時(shí)系統(tǒng)亮航,運(yùn)行時(shí)系統(tǒng)的作用就是執(zhí)行編譯后的代碼,動(dòng)態(tài)的加載類匀们,向?qū)ο蟀l(fā)送消息缴淋,運(yùn)行時(shí)系統(tǒng)更像是OC的操作系統(tǒng)。
那么什么是動(dòng)態(tài)呢泄朴?我們來(lái)看看下面這段代碼:

Person *p = [[Person alloc] initWithName:@"Tom" andAge:15];
[p performSelector:@selector(sayHello)]; //雖然Person類中并沒(méi)有這個(gè)sayHello方法重抖,依然可以編譯通過(guò)

這段代碼在編譯階段并不能夠判斷出Person對(duì)象是否存在sayHello這個(gè)方法(盡管會(huì)給出警告,但并不報(bào)錯(cuò))祖灰,可以通過(guò)編譯階段钟沛,但是會(huì)在運(yùn)行時(shí)崩潰。也就是說(shuō)OC語(yǔ)言的動(dòng)態(tài)特性使得類型信息在運(yùn)行時(shí)被檢查局扶,而不是編譯時(shí)恨统。同時(shí)Class也是動(dòng)態(tài)創(chuàng)建的叁扫,也就是說(shuō)你可以在程序運(yùn)行的時(shí)候?yàn)槌绦蛐略鲱悺?duì)象畜埋、以及方法和方法體等莫绣。本文將介紹runtime原理和實(shí)際應(yīng)用:

1. 消息機(jī)制

2. 消息轉(zhuǎn)發(fā)

3. 屬性定義

4. 實(shí)際使用

在了解Runtime機(jī)制之前,先來(lái)簡(jiǎn)單了解一下NSObject這個(gè)公共父類(并不是所有的類都繼承自NSObject悠鞍,例如NSProxy):
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
+ (void)load;
+ (void)initialize;
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
NS_DESIGNATED_INITIALIZER
#endif
.
.
.

可以看到对室,NSObject有一個(gè)成員變量叫做isa,它是Class類型的咖祭,這個(gè)Class其實(shí)是一個(gè)結(jié)構(gòu)體:typedef struct objc_class *Class掩宜,來(lái)研究一下這個(gè)結(jié)構(gòu)體:

struct objc_class {
    Class isa OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
        Class super_class ;
        const char *name ;
        long version ;
        long info ;
        long instance_size ;
        struct objc_ivar_list *ivars ;
        struct objc_method_list **methodLists ;
        struct objc_cache *cache ;
        struct objc_protocol_list *protocols ;
    #endif
} OBJC2_UNAVAILABLE;

結(jié)構(gòu)體的成員中包含了另一個(gè)isa的引用,其它的結(jié)構(gòu)成員在OC2.0之后不可用么翰,但是锭亏,依然可以從中獲取重要的一些信息,例如父類硬鞍、類名慧瘤、對(duì)象大小、變量列表固该、方法列表锅减、該類遵循的協(xié)議等都是以列表的形式保存在objc_class中,其中還有一個(gè)緩存cache伐坏,用于緩存最近使用到的消息怔匣,該文件中還包括對(duì)其它結(jié)構(gòu)體的定義,例如方法桦沉、類目每瞒、屬性等。(可以通過(guò)#import<objc/runtime.h>查看)基礎(chǔ)知識(shí)先說(shuō)到這里纯露,來(lái)看看消息機(jī)制剿骨。

1.消息機(jī)制

什么是消息機(jī)制,舉例來(lái)說(shuō):

Math m = [[Math alloc]init];
[m sum:5 y:6];

通常會(huì)說(shuō)調(diào)用了m對(duì)象的sum方法埠褪,但編譯器會(huì)將函數(shù)調(diào)用轉(zhuǎn)變?yōu)橄驅(qū)ο蟀l(fā)送一條消息浓利,:
objc_msgSend(m , sum ,5 , 6);
現(xiàn)在應(yīng)該說(shuō)是像m對(duì)象發(fā)送了一條sum消息更合適。

首先應(yīng)該了解SEL和IMP钞速,我們暫且可以這么區(qū)分贷掖,一個(gè)方法有方法名和方法體,SEL指的是方法名(可以這么理解但是實(shí)際叫做<b>選擇器</b>渴语,下文會(huì)提到)苹威,而IMP指的是方法體也就是對(duì)應(yīng)的實(shí)現(xiàn),在C語(yǔ)言中例如調(diào)用一個(gè)方法的話驾凶,編譯器會(huì)將方法調(diào)用轉(zhuǎn)換為匯編指令call 并帶一個(gè)地址操作數(shù)牙甫,程序計(jì)數(shù)器會(huì)將下一條要執(zhí)行的指令地址設(shè)為這個(gè)操作數(shù)潮改,并將返回地址壓入棧中。在OC中SEL的定義為:typedef struct objc_selector *SEL;而IMP的定義為:typedef void (*IMP)(void /* id, SEL, ... */ );

在運(yùn)行時(shí)腹暖,消息會(huì)綁定到對(duì)應(yīng)的實(shí)現(xiàn)上:

  • 首先會(huì)根據(jù)對(duì)象m的選擇器sum查找對(duì)應(yīng)的方法實(shí)現(xiàn)(IMP)

  • 傳遞參數(shù)(包括消息的接受對(duì)象、選擇器)翰萨,執(zhí)行方法

  • 將函數(shù)的返回結(jié)果返回

    當(dāng)一個(gè)新的對(duì)象被創(chuàng)建時(shí)脏答,系統(tǒng)要為該對(duì)象分配對(duì)應(yīng)的內(nèi)存,實(shí)例變量被初始化亩鬼,還記得上面說(shuō)的isa么殖告,isa這個(gè)指針變量將被指向該對(duì)象的<b>類結(jié)構(gòu)</b>,之后通過(guò)super_class可以獲取該對(duì)象的父類雳锋,進(jìn)而整個(gè)繼承鏈的類結(jié)構(gòu)信息就都可以獲取了黄绩。編譯器負(fù)責(zé)將類、對(duì)象構(gòu)建為具有運(yùn)行時(shí)信息的結(jié)構(gòu)(包括isa玷过、super_class爽丹、選擇器轉(zhuǎn)發(fā)表等)。另一個(gè)重要的信息就是轉(zhuǎn)發(fā)表辛蚊,可以看做是一個(gè)以SEL為鍵以IMP地址為值的映射粤蝎。
    當(dāng)向一個(gè)對(duì)象發(fā)送消息時(shí),objc_msgSend會(huì)去該對(duì)象的類結(jié)構(gòu)體(isa指針指向的結(jié)構(gòu))中查找轉(zhuǎn)發(fā)表袋马,如果能夠定位指定的選擇器的話初澎,就會(huì)執(zhí)行對(duì)應(yīng)地址處的方法體,如果找不到虑凛,會(huì)沿著繼承鏈一層層去查找每一個(gè)父類的轉(zhuǎn)發(fā)表直到NSObject類碑宴。如下圖所示:

23-22-32.jpg
這樣一層層查找會(huì)有損程序效率,于是就有了上面提到的緩存桑谍,當(dāng)?shù)谝淮握{(diào)用了某個(gè)方法延柠,系統(tǒng)便會(huì)將方法和對(duì)應(yīng)的實(shí)現(xiàn)地址緩存起來(lái)(系統(tǒng)就是如此霸道,一旦第一次使用了某個(gè)方法锣披,系統(tǒng)會(huì)認(rèn)為你還想繼續(xù)使用)捕仔,在查找轉(zhuǎn)發(fā)表之前,會(huì)搜一搜這個(gè)緩存盈罐。
用上面的Math來(lái)說(shuō)明這個(gè)過(guò)程榜跌,當(dāng)我們創(chuàng)建m對(duì)象時(shí),系統(tǒng)會(huì)為m 對(duì)象分配內(nèi)存盅粪,將isa 指針指向Math 類結(jié)構(gòu)钓葫,并配置轉(zhuǎn)發(fā)表,當(dāng)我們向m 發(fā)送sum 消息時(shí)票顾,objc_msgSend 會(huì)去isa 所指的結(jié)構(gòu)體中查找轉(zhuǎn)發(fā)表础浮,去找啥帆调?去找selector 為sum 的地址,如果找到豆同,就執(zhí)行對(duì)應(yīng)地址的方法體番刊,然后將selector 緩存起來(lái),如果找不到影锈,就沿著superClass 鏈向上查找轉(zhuǎn)發(fā)表芹务,如果到了NSObject 這一層還沒(méi)有找到,對(duì)不起鸭廷,程序就拋異常了枣抱。

objc_msgSend至少需要兩個(gè)參數(shù):接收消息的對(duì)象和選擇器,這兩個(gè)參數(shù)是在編譯的時(shí)候被插入的辆床,在OC中我們定一個(gè)方法佳晶,并不需要顯示的指定這兩個(gè)參數(shù)。
剛才說(shuō)了那么多selector和IMP地址讼载,那么如果我們不想通過(guò)消息機(jī)制來(lái)調(diào)用一個(gè)函數(shù)應(yīng)該怎么辦轿秧,可以通過(guò)methodForSelector:SEL來(lái)將方法的實(shí)現(xiàn)取出來(lái):

Student *p = [[Student alloc] initWithName:@"Tom" andAge:15];
typedef NSInteger(*sum)(id ,SEL , NSInteger , NSInteger);
sum s = (sum)[p methodForSelector:@selector(sum:y:)];
NSLog(@"%d",s(p , @selector(sum:y:) , 10 , 5));

輸出結(jié)果為15。注意methodForSelector返回的是IMP結(jié)構(gòu)體咨堤,需要轉(zhuǎn)換為指定函數(shù)指針類型淤刃,并保證前兩個(gè)參數(shù)依然為接受對(duì)象和方法選擇器。

2. 消息轉(zhuǎn)發(fā)

有沒(méi)有過(guò)這樣的經(jīng)歷吱型,當(dāng)我們?cè)噲D調(diào)用一個(gè)不存在的方法是逸贾,會(huì)報(bào)以下的錯(cuò)誤:unrecognized selector sent to instance 0x1004001a0,很多iOS初學(xué)者不知道這句話是什么意思津滞,是說(shuō)地址為0x1004001a0的對(duì)象沒(méi)有定義相關(guān)的方法選擇器铝侵,因此不能夠被識(shí)別,通過(guò)查看該地址處的對(duì)象就可以找到出錯(cuò)的原因触徐,也可以根據(jù)debug的crash信息定位出錯(cuò)的對(duì)象和信息咪鲜。那么如果一個(gè)對(duì)象無(wú)法響應(yīng)某個(gè)消息(就是上面說(shuō)的沒(méi)有定義某個(gè)函數(shù)),運(yùn)行時(shí)向該對(duì)象發(fā)送了這個(gè)未定義的消息撞鹉,程序就一定被判死刑了么疟丙,其實(shí)不一定,當(dāng)一個(gè)對(duì)象無(wú)法響應(yīng)某個(gè)message的時(shí)候鸟雏,系統(tǒng)會(huì)給你三次機(jī)會(huì)來(lái)動(dòng)態(tài)的為一個(gè)對(duì)象增加一個(gè)處理消息的實(shí)現(xiàn)或者實(shí)現(xiàn)消息的轉(zhuǎn)發(fā)享郊,讓我們來(lái)看看第一種方式:

動(dòng)態(tài)決議

+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel

這兩個(gè)方法都是動(dòng)態(tài)的為方法選擇器添加方法實(shí)現(xiàn)又叫做<b>動(dòng)態(tài)方法決議</b>,當(dāng)調(diào)用了對(duì)象的一個(gè)不存在的方法選擇器或者該方法選擇器沒(méi)有對(duì)應(yīng)的方法體孝鹊,消息機(jī)制會(huì)調(diào)用這兩個(gè)方法來(lái)決議(注意:如果消息機(jī)制沿著繼承連找不到對(duì)應(yīng)的selector和IMP之間的映射時(shí)才會(huì)調(diào)用炊琉,也就是說(shuō)只有調(diào)用了類或者對(duì)象不存在的方法體時(shí)才會(huì)嘗試決議),例如:

//Person 類
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic , strong)NSString *name;
@property(nonatomic , assign)NSInteger age;

- (id)initWithName:(NSString *)name andAge:(NSInteger)age;
- (void)say;
- (NSInteger)sum:(NSInteger)x y:(NSInteger)y;
+ (void)sayHello;
@end

對(duì)應(yīng)的implement為:

// implement
#import "Person.h"
@implementation Person
- (id)initWithName:(NSString *)name andAge:(NSInteger)age {
    if(self = [super init]){
        self.name = name;
        self.age = age;
    }
    return self;
}
+ (void)sayHello {} //1
+ (BOOL)resolveClassMethod:(SEL)sel { //2
    NSLog(@"%@",NSStringFromSelector(sel));
    if(sel == @selector(sayHello)) {
            return YES;
    }
    return [Person resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%@",NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}
@end

以類方法sayHello 為例,此時(shí)已經(jīng)實(shí)現(xiàn)了sayHello苔咪,所以并不會(huì)調(diào)用resolve方法锰悼,當(dāng)我們把1處的代碼刪除后,程序會(huì)崩潰团赏,如何動(dòng)態(tài)為類方法sayHello添加方法體呢箕般?
我們將2處的方法修改為:

+ (BOOL)resolveClassMethod:(SEL)sel {
    if(sel == @selector(sayHello)) {
        class_addMethod([NSObject class], @selector(sayHello),         (IMP)sayHelloDynamic, "v@:");//①
        return YES;
    }
    return [super resolveClassMethod:sel];
}

然后添加如下代碼:

void sayHelloDynamic(id target , SEL sel) {
    printf("Hello world\n");
}

執(zhí)行結(jié)果為:Hello world。(此處有疑問(wèn):上面代碼的①處舔清,必須指定為NSObject的類對(duì)象丝里,如果是Person的話,通過(guò)class_getClassMethod得到的結(jié)果為nil鸠踪,也就是說(shuō)添加類方法不成功,這里還需要繼續(xù)調(diào)查复斥,如果有知道的讀者可以留言营密。)
resolveClassMethod動(dòng)態(tài)添加類方法,而resolveInstanceMethod動(dòng)態(tài)添加實(shí)例方法目锭,網(wǎng)上大多數(shù)教程都在解釋后面這個(gè)方法评汰,想必也是因?yàn)閞esolveClassMethod添加類方法失敗。

消息轉(zhuǎn)發(fā)

當(dāng)通過(guò)繼承連定位不到對(duì)象相關(guān)消息的實(shí)現(xiàn)痢虹,同時(shí)resolveInstanceMethod對(duì)應(yīng)的selector返回NO的話被去,系統(tǒng)會(huì)嘗試消息轉(zhuǎn)發(fā)(按照文檔的說(shuō)法,決議優(yōu)先消息轉(zhuǎn)發(fā)奖唯,決議與消息轉(zhuǎn)發(fā)正交惨缆,也就是如過(guò)對(duì)應(yīng)的selector在決議方法中返回true,消息轉(zhuǎn)發(fā)不會(huì)被調(diào)用)丰捷∨髂可以將消息轉(zhuǎn)發(fā)看做是處理不存在消息的第二層保護(hù)。如果向一個(gè)對(duì)象發(fā)送了一個(gè)它不能處理的消息時(shí)病往,運(yùn)行時(shí)系統(tǒng)會(huì)向forwardInvocation:發(fā)送一個(gè)消息捣染,并傳遞NSInvocation對(duì)象,該對(duì)象可以看做是一個(gè)方法調(diào)用的包裝(消息的響應(yīng)對(duì)象停巷、selector耍攘、參數(shù)、返回值)畔勤,通過(guò)重寫(xiě)該方法就可以獲得一個(gè)消息轉(zhuǎn)發(fā)的機(jī)會(huì)蕾各。
還是上面的Person類,如果我們現(xiàn)在調(diào)用Person對(duì)象的say方法庆揪,程序一定崩潰示损,讓我們?cè)趇mplement中加入以下代碼:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if(![self respondsToSelector:anInvocation.selector]){
        return;
    }
}

但是單單是重寫(xiě)了forwardInvocation方法還是不夠的,還需要重寫(xiě)methodSignatureForSelector:方法來(lái)為forwardInvocation:的anInvocation參數(shù)提供必要的信息嚷硫,代碼如下:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"----%@",NSStringFromSelector(aSelector));
    if([self respondsToSelector:aSelector]) {
        return [super methodSignatureForSelector:aSelector];
    } else {
        [self noMessage:aSelector];
    return [NSMethodSignature signatureWithObjCTypes:"v@:"]; }
}

- (void)noMessage:(SEL) sel{
    NSLog(@"No this function %@",NSStringFromSelector(sel));
}

我們使用signatureWithObjCTypes創(chuàng)建NSMethodSignature對(duì)象检访,這里需要傳一個(gè)參數(shù)始鱼,就是函數(shù)的編碼類型,由返回值類型脆贵、參數(shù)類型決定医清,可以參看官方的圖解:


23-35-44.jpg

例如我們定一個(gè)函數(shù)void sum(int x , int y)那么這個(gè)函數(shù)的編碼就為"vii",如果是void msgHand(id target , SEL selector)這個(gè)函數(shù)的編碼為"v@:"這種格式是消息實(shí)現(xiàn)體IMP常使用的格式。
以上代碼我們并沒(méi)有轉(zhuǎn)發(fā)消息卖氨,而是將不能處理的消息打印出來(lái)并swallow掉会烙,如果想實(shí)現(xiàn)轉(zhuǎn)發(fā)的話,可以轉(zhuǎn)換為如下代碼筒捺,只需要修改forwardInvocation的代碼就可以了:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if (anInvocation.selector == @selector(say)){
        Student *stu = [[Student alloc] init];
        [anInvocation invokeWithTarget:stu];
    }
}

這樣就把消息轉(zhuǎn)發(fā)給了stu對(duì)象了柏腻,可以看出來(lái),OC的對(duì)象可以作為消息轉(zhuǎn)發(fā)的中心系吭,也可作為錯(cuò)誤消息的垃圾站五嫂,如上述實(shí)現(xiàn)。

3. 屬性定義

當(dāng)編譯器遇到屬性聲明時(shí)(@property)肯尺,將會(huì)生成一個(gè)關(guān)于此屬性的原型數(shù)據(jù)沃缘,我們可以通過(guò)系統(tǒng)的api獲取一個(gè)類、協(xié)議中的屬性以及其對(duì)應(yīng)的原型则吟。

typedef struct objc_property *objc_property_t;

屬性也是結(jié)構(gòu)體指針槐臀,但是該結(jié)構(gòu)體不可見(jiàn),只能通過(guò)相關(guān)函數(shù)獲取內(nèi)部的信息氓仲。
獲取一個(gè)類和協(xié)議的全部屬性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)//獲取一個(gè)類的全部屬性
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)//獲取協(xié)議中的全部屬性
const char *property_getName(objc_property_t property) //返回屬性名
const char *property_getAttributes(objc_property_t property) //返回屬性的編碼類型信息

以下函數(shù)獲取Person類的全部屬性和屬性的類型信息:

- (void)demo {
    unsigned int num;
    objc_property_t *properties = class_copyPropertyList([self class], &num); //1
    for (int i = 0 ; i < num; i++) { //2
        objc_property_t one = properties[i];
        NSString *attrName = [NSString stringWithCString:property_getName(one)       encoding:NSUTF8StringEncoding]; //3
        NSString *typeString = [NSString stringWithCString:property_getAttributes(one) encoding:NSUTF8StringEncoding]; //4
        NSLog(@"attr is %@ , type is %@",attrName , typeString);
    }
    free(properties);
}

1 處獲取Person類的全部屬性并保存在properties數(shù)組中水慨,并將數(shù)組長(zhǎng)度保存在num中。
2 循環(huán)遍歷properties
3 4 獲取屬性名和屬性的編碼類型信息
輸出結(jié)果:

attr is name , type is T@"NSString",&,N,Gname,V_name
attr is age , type is Tq,N,V_age

當(dāng)然我們可以在程序運(yùn)行的時(shí)候動(dòng)態(tài)地添加屬性:

BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

屬性的類型信息:
以T開(kāi)始后接類型的@類型加一個(gè)','敬扛,由V_屬性名結(jié)束讥巡,中間就是該屬性的描述符,用,號(hào)隔開(kāi)舔哪。附贈(zèng)一張?zhí)O果官方的屬性類型編碼:


23-28-28.jpg

4. 實(shí)際使用

Runtime的使用比較多樣也比較靈活欢顷,但是比較流行的用法就是method swizzling,也就是互換方法體捉蚤,如下圖所示在進(jìn)行method swizzling前后selector和IMP之間的關(guān)系:

Group 2.png

Group 3.png

交換方法體之后的對(duì)應(yīng)關(guān)系抬驴,在實(shí)際中有什么作用呢,例如項(xiàng)目開(kāi)發(fā)了一大半缆巧,突然有一個(gè)采集數(shù)據(jù)的需求布持,需要每次用戶進(jìn)入頁(yè)面都要統(tǒng)計(jì)每個(gè)頁(yè)面進(jìn)入的次數(shù),由于項(xiàng)目已經(jīng)接近尾聲陕悬,再假如說(shuō)沒(méi)有做關(guān)于VC的同一調(diào)用接口题暖,一個(gè)個(gè)頁(yè)面修改起來(lái)就會(huì)很麻煩,如果我們能夠在所有的VC執(zhí)行viewDidAppear 時(shí)做一個(gè)其它的事情還不用每個(gè)頁(yè)面都修改這是最好的辦法,那么method swizzling就很適合你:

#import <UIKit/UIKit.h>

@interface UIViewController (Swizzle)

@end

#import "UIViewController+Swizzle.h"
#import <objc/runtime.h>
@implementation UIViewController (Swizzle)
+ (void)load{
    SEL selDidAppear = @selector(viewDidAppear:);
    Method impDidAppear = class_getInstanceMethod([self class], selDidAppear);
    SEL selLog = @selector(logVC:);
    Method impLog = class_getInstanceMethod([self class], selLog);
    method_exchangeImplementations(impDidAppear, impLog);
}

- (void)logVC:(BOOL) nouse {
    NSLog(@"進(jìn)入了頁(yè)面 %@", NSStringFromClass([self class]));
    [self logVC:nouse];
}
@end

這樣VC的viewDidAppear和logVC的方法實(shí)現(xiàn)就交換了胧卤,當(dāng)系統(tǒng)調(diào)用viewDidAppear實(shí)際調(diào)用的是logVC的方法體唯绍,而調(diào)用logVC實(shí)際走的是viewDidAppear。在實(shí)際中的應(yīng)用還有很多例如數(shù)據(jù)統(tǒng)計(jì)枝誊、動(dòng)態(tài)加載代碼况芒、為類目添加屬性等。
項(xiàng)目代碼:<a >gitHub-iOSRuntime</a>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叶撒,一起剝皮案震驚了整個(gè)濱河市绝骚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祠够,老刑警劉巖压汪,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異古瓤,居然都是意外死亡止剖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)湿滓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滴须,“玉大人舌狗,你說(shuō)我怎么就攤上這事叽奥。” “怎么了痛侍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵朝氓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我主届,道長(zhǎng)赵哲,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任君丁,我火速辦了婚禮枫夺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绘闷。我一直安慰自己橡庞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布印蔗。 她就那樣靜靜地躺著扒最,像睡著了一般。 火紅的嫁衣襯著肌膚如雪华嘹。 梳的紋絲不亂的頭發(fā)上吧趣,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼强挫。 笑死岔霸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纠拔。 我是一名探鬼主播秉剑,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼稠诲!你這毒婦竟也來(lái)了侦鹏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤臀叙,失蹤者是張志新(化名)和其女友劉穎略水,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體劝萤,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渊涝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了床嫌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跨释。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖厌处,靈堂內(nèi)的尸體忽然破棺而出鳖谈,到底是詐尸還是另有隱情,我是刑警寧澤阔涉,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布缆娃,位于F島的核電站,受9級(jí)特大地震影響瑰排,放射性物質(zhì)發(fā)生泄漏贯要。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一椭住、第九天 我趴在偏房一處隱蔽的房頂上張望崇渗。 院中可真熱鬧,春花似錦京郑、人聲如沸宅广。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乘碑。三九已至,卻和暖如春金拒,著一層夾襖步出監(jiān)牢的瞬間兽肤,已是汗流浹背套腹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留资铡,地道東北人电禀。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像笤休,于是被迫代替她去往敵國(guó)和親尖飞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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