iOS-RunTime介紹及使用

一舞吭、RunTime概念

RunTime簡(jiǎn)稱運(yùn)行時(shí)泡垃,我們總是聽(tīng)說(shuō)OC是動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)機(jī)制析珊,也就是系統(tǒng)在運(yùn)行時(shí)候的一些機(jī)制,其中最重要的是消息機(jī)制蔑穴。C語(yǔ)言忠寻,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù),如果調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)存和,而OC語(yǔ)言屬于動(dòng)態(tài)調(diào)用過(guò)程奕剃,在編譯時(shí)并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正的運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)函數(shù)來(lái)調(diào)用捐腿,當(dāng)調(diào)用該對(duì)象上某個(gè)方法纵朋,而該對(duì)象上沒(méi)有實(shí)現(xiàn)這個(gè)方法的時(shí)候,可以通過(guò)“消息轉(zhuǎn)發(fā)”進(jìn)行解決叙量,也就是說(shuō)倡蝙,在編譯截?cái)啵琌C可以調(diào)用任何函數(shù)绞佩,即使是這個(gè)函數(shù)沒(méi)有實(shí)現(xiàn)寺鸥,只要聲明過(guò)就不會(huì)報(bào)錯(cuò)。

二品山、OC調(diào)用方法在RunTime中的具體實(shí)現(xiàn)

《一》RunTime消息機(jī)制

消息機(jī)制是運(yùn)行時(shí)里面最重要的機(jī)制胆建,OC是動(dòng)態(tài)語(yǔ)言,本質(zhì)都是發(fā)送消息肘交,每個(gè)方法在運(yùn)行時(shí)會(huì)被動(dòng)態(tài)轉(zhuǎn)化為消息發(fā)送笆载,即:objc_msgSend(receiver, selector)
比如:

  • OC代碼實(shí)例方法調(diào)用底層的實(shí)現(xiàn):
BackView *backView = [[BackView alloc] init];
[backView changeBgColor];

//編譯時(shí)底層轉(zhuǎn)化
//objc對(duì)象的isa指針指向他的類對(duì)象,從而可以找到對(duì)象上的方法
//SEL:方法編號(hào),根據(jù)方法編號(hào)就可以找到對(duì)應(yīng)方法的實(shí)現(xiàn)涯呻。
[backView performSelector:@selector(changeBgColor)];
//performSelector本質(zhì)即為運(yùn)行時(shí)凉驻,發(fā)送消息,誰(shuí)做事情就調(diào)用誰(shuí) 
objc_msgSend(backView, @selector(changeBgColor));
// 帶參數(shù)
objc_msgSend(backView, @selector(changeBgColor:),[UIColor RedColor]);
  • OC代碼類方法調(diào)用底層的實(shí)現(xiàn)
//本質(zhì)是將類名轉(zhuǎn)化成類對(duì)象复罐,初始化方法其實(shí)是創(chuàng)建類對(duì)象涝登。
[BackView changeBgColor];
//BackView 只是表示一個(gè)類名,調(diào)用方法其實(shí)是用的類對(duì)象去調(diào)用的效诅。(類對(duì)象既然稱為對(duì)象胀滚,那它也是一個(gè)實(shí)例。類對(duì)象中也有一個(gè)isa指針指向它的元類(meta class)乱投,即類對(duì)象是元類的實(shí)例咽笼。元類內(nèi)部存放的是類方法列表,根元類的isa指針指向自己戚炫,superclass指針指向NSObject類剑刑。)
//編譯時(shí)底層轉(zhuǎn)化

//RunTime 調(diào)用類方法同樣,類方法也是類對(duì)象去調(diào)用双肤,所以需要獲取類對(duì)象施掏,然后使用類對(duì)象去調(diào)用方法
Class backViewClass = [BackView class];
[backViewClass performSelector:@selector(changeBgColor)];
//performSelector本質(zhì)即為運(yùn)行時(shí)层宫,發(fā)送消息,誰(shuí)做事情就調(diào)用誰(shuí) 

//類對(duì)象發(fā)送消息
objc_msgSend(backViewClass, @selector(changeBgColor));
// 帶參數(shù)
objc_msgSend(backViewClass, @selector(changeBgColor:),[UIColor RedColor]);

selector(SEL):是一個(gè)SEL方法選擇器其监。
SEL其主要作用是快速的通過(guò)SEL其主要作用是快速的通過(guò)方法名字查找到對(duì)應(yīng)方法的函數(shù)指針,然后調(diào)用其函數(shù)限匣。SEL其本身是一個(gè)Int類型的地址抖苦,地址中存放著方法的名字。
對(duì)于一個(gè)類中米死。每一個(gè)方法對(duì)應(yīng)著一個(gè)SEL锌历。所以一個(gè)類中不能存在2個(gè)名稱相同的方法,即使參數(shù)類型不同峦筒,因?yàn)镾EL是根據(jù)方法名字生成的究西,相同的方法名稱只能對(duì)應(yīng)一個(gè)SEL。

  • 消息傳遞的底層實(shí)現(xiàn)
    這里我們要先說(shuō)一下物喷,一個(gè)Objc對(duì)象如何進(jìn)行內(nèi)存布局的卤材,我們先看一下objc_class源碼:
// runtime.h(類在runtime中的定義)

struct objc_class {
  Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class,因?yàn)镺bjc的類的本身也是一個(gè)Object峦失,為了處理這個(gè)關(guān)系扇丛,runtime就創(chuàng)造了Meta Class,當(dāng)給類發(fā)送[NSObject alloc]這樣消息時(shí)尉辑,實(shí)際上是把這個(gè)消息發(fā)給了Class Object
  #if !__OBJC2__
  Class super_class OBJC2_UNAVAILABLE; // 父類
  const char *name OBJC2_UNAVAILABLE; // 類名
  long version OBJC2_UNAVAILABLE; // 類的版本信息帆精,默認(rèn)為0
  long info OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
  long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
  struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
  struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
  struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存隧魄,對(duì)象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對(duì)象卓练,這時(shí)會(huì)在method Lists中遍歷,如果cache了购啄,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率襟企。
  struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
  #endif
  } OBJC2_UNAVAILABLE;

objc對(duì)象內(nèi)存布局:

<1>所有父類的成員變量和自己的成員變量都會(huì)存放在該對(duì)象所對(duì)應(yīng)的存儲(chǔ)空間中。
<2>每個(gè)對(duì)象內(nèi)部都有一個(gè)isa指針闸溃,指向它的類對(duì)象整吆,類對(duì)象中存放著本對(duì)象的:
    1、對(duì)象方法列表(對(duì)象能夠接受的消息列表辉川,保存再它所對(duì)應(yīng)的類對(duì)象中)
    2表蝙、成員變量的列表
    3、屬性列表

每一個(gè)類都有一個(gè)方法列表Method List乓旗,保存著類里面所有的方法府蛇,根據(jù)SEL傳入的方法編號(hào)找到方法,然后找到方法的實(shí)現(xiàn)屿愚,然后在方法的實(shí)現(xiàn)里面實(shí)現(xiàn)汇跨。

  • 消息發(fā)送動(dòng)態(tài)查找對(duì)應(yīng)的方法
    <1>實(shí)例對(duì)象調(diào)用方法后务荆,底層調(diào)用[objc performSelector:@selector(SEL)];方法,編譯器將代碼轉(zhuǎn)化為objc_msgSend(receiver, selector)穷遂。
    <2>objc_msgSend函數(shù)中函匕,首先通過(guò)objcisa指針找到objc對(duì)應(yīng)的class,在class中先去cache中通過(guò)SEL查找對(duì)應(yīng)函數(shù)的 method蚪黑,如果找到則通過(guò) method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行盅惜。
    <3>如果在cacha中未找到,再去methodList中查找忌穿,如果能找到抒寂,則將method加入到cache中,以方便下次查找掠剑,并通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行屈芜。
    <4>如果在methodlist中未找到,則去superClass中去查找朴译,如果能找到井佑,則將method加入到cache中,以方便下次查找动分,并通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行毅糟。

  • 消息傳遞的過(guò)程
    <接上↑>objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類澜公,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行姆另,即:objc_msgSend(receiver, selector)。如果坟乾,在最頂層的父類中依然找不到相應(yīng)的方法時(shí)迹辐,程序在運(yùn)行時(shí)會(huì)掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前甚侣,objc的運(yùn)行時(shí)會(huì)給出三次拯救程序崩潰的機(jī)會(huì):
    <1> Method resolution
    objc運(yùn)行時(shí)會(huì)調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:實(shí)例方法和類方法)明吩,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù)殷费,那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過(guò)程印荔,否則 ,運(yùn)行時(shí)就會(huì)移到下一步详羡,消息轉(zhuǎn)發(fā)(Message Forwarding)仍律。
    <2> Message Forwarding

    • <1>Fast forwarding
      如果目標(biāo)對(duì)象實(shí)現(xiàn)了-forwardingTargetForSelector:,Runtime 這時(shí)就會(huì)調(diào)用這個(gè)方法实柠,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象的機(jī)會(huì)水泉。 只要這個(gè)方法返回的不是nil和self,整個(gè)消息發(fā)送的過(guò)程就會(huì)被重啟,當(dāng)然發(fā)送的對(duì)象會(huì)變成你返回的那個(gè)對(duì)象草则。否則钢拧,就會(huì)繼續(xù)Normal Fowarding。 這里叫Fast炕横,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制源内。因?yàn)檫@一步不會(huì)創(chuàng)建任何新的對(duì)象,但下一步轉(zhuǎn)發(fā)會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象份殿,所以相對(duì)更快點(diǎn)姿锭。
    • <2>Normal forwarding
      這一步是Runtime最后一次給你挽救的機(jī)會(huì)。首先它會(huì)發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型伯铣。如果-methodSignatureForSelector:返回nil,Runtime則會(huì)發(fā)出-doesNotRecognizeSelector:消息轮纫,程序這時(shí)也就掛掉了腔寡。如果返回了一個(gè)函數(shù)簽名,Runtime就會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象并發(fā)送-forwardInvocation:消息給目標(biāo)對(duì)象掌唾。

《二》使用RunTime動(dòng)態(tài)的添加對(duì)象的成員變量和方法

  • 動(dòng)態(tài)添加方法
    動(dòng)態(tài)給某各類添加方法放前,相當(dāng)于懶加載機(jī)制。這里我們以實(shí)例方法為例糯彬,首先我們先不實(shí)現(xiàn)對(duì)象方法凭语,當(dāng)調(diào)用performSelector:方法的時(shí)候,再去動(dòng)態(tài)加載方法調(diào)用撩扒。[bg performSelector:@selector(changeBgColor)];當(dāng)編譯時(shí)是不會(huì)報(bào)錯(cuò)的似扔,運(yùn)行時(shí)才會(huì)報(bào)錯(cuò),因?yàn)檫@里我們BaseView類中并沒(méi)有實(shí)現(xiàn)changeBgColor這個(gè)方法搓谆,當(dāng)去類的Method List中發(fā)現(xiàn)找不到changeBgColor方法炒辉,會(huì)報(bào)錯(cuò)找不到這個(gè)方法。這里我們就用到了上面提到的消息轉(zhuǎn)發(fā)機(jī)制泉手。當(dāng)調(diào)用了沒(méi)有實(shí)現(xiàn)的對(duì)象方法時(shí)黔寇,就會(huì)調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel方法,當(dāng)調(diào)用了沒(méi)有實(shí)現(xiàn)的類方法的時(shí)候斩萌,就會(huì)調(diào)用+(BOOL)resolveClassMethod:(SEL)sel方法缝裤。所以通過(guò)這兩個(gè)方法就可以動(dòng)態(tài)添加方法,參數(shù)sel即表示沒(méi)有實(shí)現(xiàn)的方法颊郎。一個(gè)objective - C方法最終都是一個(gè)C函數(shù)憋飞,默認(rèn)任何一個(gè)方法都有兩個(gè)參數(shù)。self : 方法調(diào)用者 _cmd : 調(diào)用方法編號(hào)袭艺。我們可以使用函數(shù)class_addMethod為類添加一個(gè)方法以及實(shí)現(xiàn)搀崭。
    動(dòng)態(tài)添加方法:
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    // 動(dòng)態(tài)添加changeBgColor方法
    // 首先判斷sel是不是changeBgColor方法 也可以轉(zhuǎn)化成字符串進(jìn)行比較。    
    if (sel == @selector(changeBgColor)) {
    /** 
     第一個(gè)參數(shù): cls:給哪個(gè)類添加方法
     第二個(gè)參數(shù): SEL name:添加方法的編號(hào)
     第三個(gè)參數(shù): IMP imp: 方法的實(shí)現(xiàn),函數(shù)入口瘤睹,函數(shù)名可與方法名不同(建議與方法名相同)
     第四個(gè)參數(shù): types :方法類型升敲,需要用特定符號(hào),參考API
     */
      class_addMethod(self, sel, (IMP) newChangeBgColor , "v@:");
        // 處理完返回YES
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void newChangeBgColor(id self ,SEL _cmd)
{

}

types:方法類型表:


type方法類型表.png
  • 動(dòng)態(tài)添加變量
    1-> 動(dòng)態(tài)獲取類中的所有屬性(包括私有)
Ivar *ivar = class_copyIvarList([self.baseView class], &count);

2->遍歷屬性找到對(duì)應(yīng)屬性字段

const char *varName = ivar_getName(var);

3->修改對(duì)應(yīng)的字段

object_setIvar(self.baseView, var, @"newName");

具體:

-(void)addNewName{
    unsigned int count = 0;
    Ivar *ivar = class_copyIvarList([baseView class], &count);
    for (int i = 0; i<count; i++) {
        Ivar var = ivar[i];
        const char *varName = ivar_getName(var);
        NSString *name = [NSString stringWithUTF8String:varName];

        if ([name isEqualToString:@"oldName"]) {
            object_setIvar(baseView, var, @"newName");
            break;
        }
    }
  
    self.nameLabel.text = baseView.oldName;
}

《三》動(dòng)態(tài)交換方法

當(dāng)遇到所使用的系統(tǒng)方法或者不可修改的靜態(tài)庫(kù)方法功能不夠時(shí)轰传,需要給此類方法擴(kuò)展一些功能驴党。比如我們有個(gè)BaseView類中有個(gè)changeBgColor的方法,此時(shí)我們想在這個(gè)方法里做些操作获茬,我們定義一個(gè)newChangeBgColor的方法港庄。因?yàn)榻粨Q只需進(jìn)行一次,所以我們?cè)贐aseView的Categary中的load方法中恕曲,當(dāng)加載分類的時(shí)候交換方法即可鹏氧。交換方法的本質(zhì)其實(shí)是交換兩個(gè)方法的實(shí)現(xiàn)
即:
1根據(jù)SEL方法編號(hào)在Method List中找到方法
2交換兩個(gè)IMP指針指向的方法實(shí)現(xiàn)

交換方法內(nèi)部實(shí)現(xiàn).png
+(void)load
{
    // 獲取要交換的兩個(gè)方法
    // 獲取類方法  用Method 接受一下
    // class :獲取哪個(gè)類方法 
    // SEL :獲取方法編號(hào),根據(jù)SEL就能去對(duì)應(yīng)的類找方法佩谣。
    Method oldChangeColorMethod = class_getClassMethod([UIImage class], @selector(changeBgColor));
    // 獲取第二個(gè)類方法
    Method newChangeColorMethod = class_getClassMethod([UIImage class], @selector(newChangeBgColor));
    // 交換兩個(gè)方法的實(shí)現(xiàn) 方法一 把还,方法二。
    method_exchangeImplementations(oldChangeColorMethod, newChangeColorMethod);
    // IMP其實(shí)就是 implementation的縮寫:表示方法實(shí)現(xiàn)茸俭。
}

注意:交換方法的時(shí)候newMethod里就不能再調(diào)用oldMethod方法了吊履,因?yàn)檎{(diào)用oldMethod方法實(shí)質(zhì)上相當(dāng)于調(diào)用newMethod方法,會(huì)循環(huán)引用造成死循環(huán)调鬓。

《四》RunTim動(dòng)態(tài)添加屬性

XCode運(yùn)行在Category的.h文件聲明@property編譯通過(guò)艇炎,但運(yùn)行時(shí)如果沒(méi)有runtime處理,進(jìn)行賦值取值腾窝,就會(huì)報(bào)錯(cuò)缀踪。

  • @property的本質(zhì)是什么
    @property = ivar + getter + setter;
    說(shuō)人話:

“屬性”(property)有兩大概念:ivar(實(shí)例變量)、存取方法(access method = getter + setter)虹脯。

“屬性”(property)作為OC的一項(xiàng)特性辜贵,主要的作用就在于封裝對(duì)象總的數(shù)據(jù)。OC 對(duì)象通常會(huì)把其所需要的數(shù)據(jù)保存為各種實(shí)例變量归形,實(shí)例變量一般通過(guò)“存取方法”(access method)來(lái)訪問(wèn)托慨,其中,“獲取方法”(getter)用于讀取變量值暇榴,而“設(shè)置方法”(setter)用于寫入變量值厚棵。在正規(guī)的OC編碼風(fēng)格中,存取方法有著嚴(yán)格的命名規(guī)范蔼紧,正因?yàn)橛辛诉@種嚴(yán)格的命名規(guī)范婆硬,所以O(shè)C可以根據(jù)名稱自動(dòng)創(chuàng)建出存取方法,其實(shí)也可以把屬性當(dāng)做一種關(guān)鍵字奸例,可以表示:

編譯器會(huì)自動(dòng)寫出一套存取方法彬犯,用以訪問(wèn)給定類型中具有給定名稱的變量向楼,所以你也可以這么說(shuō):@property=getter+setter;
比如下面的這個(gè)類:

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

上述代碼寫出來(lái)的類與下面這種寫法等效:

@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end

而objc_property是一個(gè)結(jié)構(gòu)體谐区,包括name和attributes湖蜕,定義如下:

struct property_t {
    const char *name;
    const char *attributes;
};

而attributes本質(zhì)是objc_property_attribute_t,定義了property的一些屬性宋列,定義如下:

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

而attributes的具體內(nèi)容是什么呢昭抒?其實(shí),包括:類型炼杖,原子性灭返,內(nèi)存語(yǔ)義和對(duì)應(yīng)的實(shí)例變量。
例如:我們定義一個(gè)string的property@property (nonatomic, copy) NSString *string;
坤邪,通過(guò) property_getAttributes(property)
獲取到attributes并打印出來(lái)之后的結(jié)果為T@"NSString",C,N,V_string

其中T就代表類型熙含,可參閱Type Encodings,C就代表Copy艇纺,N代表nonatomic婆芦,V就代表對(duì)于的實(shí)例變量。
ivar喂饥、getter、setter 是如何生成并添加到這個(gè)類中的?

“自動(dòng)合成”( autosynthesis)
完成屬性定義后肠鲫,編譯器會(huì)自動(dòng)編寫訪問(wèn)這些屬性所需的方法员帮,此過(guò)程叫做“自動(dòng)合成”(autosynthesis)。需要強(qiáng)調(diào)的是导饲,這個(gè)過(guò)程由編譯 器在編譯期執(zhí)行讹语,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼鹅搪。除了生成方法代碼 getter、setter 之外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量抡草,并且在屬性名前面加下劃線,以此作為實(shí)例變量的名字千扔。在前例中砸烦,會(huì)生成兩個(gè)實(shí)例變量,其名稱分別為 _firstName 與 _lastName听盖。也可以在類的實(shí)現(xiàn)代碼里通過(guò) @synthesize 語(yǔ)法來(lái)指定實(shí)例變量的名字.

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

屬性是怎么實(shí)現(xiàn)的呢胀溺?

1、OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset)皆看,這個(gè)偏移量是“硬編碼” (hardcode)仓坞,表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)。
2腰吟、setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)
3无埃、ivar_list :成員變量列表
4、method_list :方法列表
5、prop_list :屬性列表
也就是說(shuō)我們每次在增加一個(gè)屬性,系統(tǒng)都會(huì)在 ivar_list 中添加一個(gè)成員變量的描述,在 method_list 中增加 setter 與 getter 方法的描述,在屬性列表中增加一個(gè)屬性的描述,然后計(jì)算該屬性在對(duì)象中的偏移量,然后給出 setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn),在 setter 方法中從偏移量的位置開(kāi)始賦值,在 getter 方法中從偏移量開(kāi)始取值,為了能夠讀取正確字節(jié)數(shù),系統(tǒng)對(duì)象偏移量的指針類型進(jìn)行了類型強(qiáng)轉(zhuǎn).
如何在@protocol和category中使用@property嫉称?
1侦镇、在 protocol 中使用 property 只會(huì)生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對(duì)象能實(shí)現(xiàn)該屬性

2、category 使用 @property 也是只會(huì)生成 setter 和 getter 方法的聲明,但是不會(huì)自動(dòng)生成私有屬性澎埠,如果我們真的需要給 category 增加屬性的實(shí)現(xiàn),需要借助于運(yùn)行時(shí)的兩個(gè)函數(shù):

1虽缕、動(dòng)態(tài)添加屬性
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
參數(shù)一:id object : 給哪個(gè)對(duì)象添加屬性,這里要給自己添加屬性蒲稳,用self氮趋。
參數(shù)二:void * == id  key : 屬性名,根據(jù)key獲取關(guān)聯(lián)對(duì)象的屬性的值江耀,在objc_getAssociatedObject中通過(guò)次key獲得屬性的值并返回剩胁。
參數(shù)三:id value : 關(guān)聯(lián)的值,也就是set方法傳入的值給屬性去保存祥国。
參數(shù)四:objc_AssociationPolicy policy : 策略昵观,屬性以什么形式保存。
>>>>>
 typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
 OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一個(gè)弱引用相關(guān)聯(lián)的對(duì)象
 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關(guān)對(duì)象的強(qiáng)引用舌稀,非原子性
 OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相關(guān)的對(duì)象被復(fù)制啊犬,非原子性
 OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相關(guān)對(duì)象的強(qiáng)引用,原子性
 OBJC_ASSOCIATION_COPY = 01403     // 指定相關(guān)的對(duì)象被復(fù)制壁查,原子性   
};
獲得屬性
objc_getAssociatedObject(id object, const void *key);
參數(shù)一:id object : 獲取哪個(gè)對(duì)象里面的關(guān)聯(lián)的屬性觉至。
參數(shù)二:void * == id  key : 什么屬性,與objc_setAssociatedObject中的key相對(duì)應(yīng)睡腿,即通過(guò)key值取出value语御。
此時(shí)已經(jīng)成功給NSObject添加name屬性,并且NSObject對(duì)象可以通過(guò)點(diǎn)語(yǔ)法為屬性賦值席怪。

下面這個(gè)也是一樣的:

-(void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
    return objc_getAssociatedObject(self, @"name");    
}
《RunTime字典轉(zhuǎn)模型》

通過(guò)給NSObject添加分類应闯,聲明并實(shí)現(xiàn)使用Runtime字典轉(zhuǎn)模型的類方法:

+ (instancetype)modelWithDict:(NSDictionary *)dict

KVC字典轉(zhuǎn)模型和RunTime轉(zhuǎn)模型的區(qū)別:

KVC:KVC字典轉(zhuǎn)模型實(shí)現(xiàn)原理是遍歷字典中所有Key,然后去模型中查找相對(duì)應(yīng)的屬性名挂捻,要求屬性名與Key必須一一對(duì)應(yīng)碉纺,字典中所有key必須在模型中存在。
RunTime:RunTime字典轉(zhuǎn)模型實(shí)現(xiàn)原理是遍歷模型中的所有屬性名刻撒,然后去字典查找相對(duì)應(yīng)的Key惜辑,也就是以模型為準(zhǔn),模型中有哪些屬性疫赎,就去字典中找那些屬性盛撑。

RunTime字典轉(zhuǎn)模型的優(yōu)點(diǎn):當(dāng)服務(wù)器返回的數(shù)據(jù)過(guò)多,而我們只使用其中很少一部分時(shí)捧搞,沒(méi)有用的屬性就沒(méi)有必要定義成屬性浪費(fèi)不必要的資源抵卫。只保存最有用的屬性即可狮荔。

字典轉(zhuǎn)模型簡(jiǎn)要過(guò)程:
1、創(chuàng)建模型對(duì)象
2介粘、使用class_copyIvarList方法copy成員屬性列表

unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);

參數(shù)一:__unsafe_unretained Class cls : 獲取哪個(gè)類的成員屬性列表殖氏。這里是self,因?yàn)檎l(shuí)調(diào)用分類中類方法姻采,誰(shuí)就是self雅采。
參數(shù)二:unsigned int *outCount : 無(wú)符號(hào)int型指針,這里創(chuàng)建unsigned int型count慨亲,&count就是他的地址婚瓜,保證在方法中可以拿到count的地址為count賦值。傳出來(lái)的值為成員屬性總數(shù)刑棵。
返回值:Ivar * : 返回的是一個(gè)Ivar類型的指針 巴刻。指針默認(rèn)指向的是數(shù)組的第0個(gè)元素,指針+1會(huì)向高地址移動(dòng)一個(gè)Ivar單位的字節(jié)蛉签,也就是指向第一個(gè)元素胡陪。Ivar表示成員屬性。
3碍舍、遍歷成員屬性列表柠座,獲得屬性列表

for (int i = 0 ; i < count; i++) {
     // 獲取成員屬性
     Ivar ivar = ivarList[i];
}

4、使用ivar_getName(ivar)獲得成員屬性名片橡,因?yàn)槌蓡T屬性名返回的是C語(yǔ)言字符串妈经,將其轉(zhuǎn)化成OC 字符串

NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
或者ivar_getTypeEncoding(ivar)方法

5、獲得的成員屬性名是帶的成員屬性锻全,去掉,獲得屬性名录煤,也就是字典的key鳄厌。

// 獲取key
NSString *key = [propertyName substringFromIndex:1];

6、獲取字典中key對(duì)應(yīng)的Value妈踊。

id value = dict[key];

7了嚎、給模型屬性賦值,并將模型返回

if (value) {
// KVC賦值:不能傳空
[objc setValue:value forKey:key];
}
return objc;

二級(jí)模型轉(zhuǎn)化方法:

+ (instancetype)modelWithDict:(NSDictionary *)dict{
    // 1.創(chuàng)建對(duì)應(yīng)類的對(duì)象
    id objc = [[self alloc] init];
    // count:成員屬性總數(shù)
    unsigned int count = 0;
   // 獲得成員屬性列表和成員屬性數(shù)量
    Ivar *ivarList = class_copyIvarList(self, &count);
    for (int i = 0 ; i < count; i++) {
        // 獲取成員屬性
        Ivar ivar = ivarList[i];
        // 獲取成員名
       NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 獲取key
        NSString *key = [propertyName substringFromIndex:1];
        // 獲取字典的value key:屬性名 value:字典的值
        id value = dict[key];
        // 獲取成員屬性類型
        NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        // 二級(jí)轉(zhuǎn)換
        // value值是字典并且成員屬性的類型不是字典,才需要轉(zhuǎn)換成模型
        if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
            // 進(jìn)行二級(jí)轉(zhuǎn)換
            // 獲取二級(jí)模型類型進(jìn)行字符串截取廊营,轉(zhuǎn)換為類名
            NSRange range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringFromIndex:range.location + range.length];
            range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringToIndex:range.location];
            // 獲取需要轉(zhuǎn)換類的類對(duì)象
           Class modelClass =  NSClassFromString(propertyType);
           // 如果類名不為空則進(jìn)行二級(jí)轉(zhuǎn)換
            if (modelClass) {
                // 返回二級(jí)模型賦值給value
                value =  [modelClass modelWithDict:value];
            }
        }
        if (value) {
            // KVC賦值:不能傳空
            [objc setValue:value forKey:key];
        }
    }
    // 返回模型
    return objc;
}

總結(jié)

上述對(duì)RunTime的總結(jié)只是一些自己平時(shí)的積累歪泳,借鑒了一些好的博文資料加上自己的一些理解,還有很多東西沒(méi)有理解到位露筒,還請(qǐng)多多指教呐伞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慎式,隨后出現(xiàn)的幾起案子伶氢,更是在濱河造成了極大的恐慌趟径,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癣防,死亡現(xiàn)場(chǎng)離奇詭異蜗巧,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蕾盯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門幕屹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人级遭,你說(shuō)我怎么就攤上這事望拖。” “怎么了装畅?”我有些...
    開(kāi)封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵靠娱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我掠兄,道長(zhǎng)像云,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任蚂夕,我火速辦了婚禮迅诬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婿牍。我一直安慰自己侈贷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布等脂。 她就那樣靜靜地躺著俏蛮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪上遥。 梳的紋絲不亂的頭發(fā)上搏屑,一...
    開(kāi)封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音粉楚,去河邊找鬼辣恋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛模软,可吹牛的內(nèi)容都是我干的伟骨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼燃异,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼携狭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起回俐,我...
    開(kāi)封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤暑中,失蹤者是張志新(化名)和其女友劉穎壹瘟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鳄逾,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稻轨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雕凹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殴俱。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖枚抵,靈堂內(nèi)的尸體忽然破棺而出线欲,到底是詐尸還是另有隱情,我是刑警寧澤汽摹,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布李丰,位于F島的核電站,受9級(jí)特大地震影響逼泣,放射性物質(zhì)發(fā)生泄漏趴泌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一拉庶、第九天 我趴在偏房一處隱蔽的房頂上張望嗜憔。 院中可真熱鬧,春花似錦氏仗、人聲如沸吉捶。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)呐舔。三九已至,卻和暖如春慷蠕,著一層夾襖步出監(jiān)牢的瞬間珊拼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工砌们, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杆麸,地道東北人搁进。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓浪感,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親饼问。 傳聞我的和親對(duì)象是個(gè)殘疾皇子影兽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,681評(píng)論 0 9
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門動(dòng)態(tài)語(yǔ)言莱革,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢峻堰?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,176評(píng)論 0 7
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 748評(píng)論 0 1
  • 《封神傳奇》周五上映捐名。 (不出意外)被口水淹沒(méi)旦万。 打一星的占70%多 有沒(méi)注意到—— 近年來(lái)華語(yǔ)“魔(奇)幻大片”...
    Sir電影閱讀 3,427評(píng)論 9 46
  • 現(xiàn)在轉(zhuǎn)念的速度越來(lái)越快了,從朝外的眼光到向內(nèi)看自己镶蹋。今天身體非常不舒服成艘,從公司出來(lái)剛好一條信息不對(duì),我的語(yǔ)氣語(yǔ)調(diào)就...
    粟莎閱讀 191評(píng)論 0 1