RunTime

runtime:Objective-C是動(dòng)態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時(shí)做的事放到了運(yùn)行時(shí),這個(gè)運(yùn)行時(shí)系統(tǒng)就是runtime

runtime其實(shí)就是一個(gè)庫叶堆,它基本上是用C和匯編寫的一套API,這個(gè)庫使C語言有了面向?qū)ο蟮哪芰Α?/p>

靜態(tài)語言:在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)斥杜。

動(dòng)態(tài)語言(OC):在運(yùn)行的時(shí)候根據(jù)函數(shù)的名稱找到對應(yīng)的函數(shù)來調(diào)用虱颗。

isa:OC中,類和類的實(shí)例在本質(zhì)上沒有區(qū)別蔗喂,都是對象忘渔,任何對象都有isa指針,它指向類或者元類缰儿。

isa:是一個(gè)Class類型的指針畦粮。每個(gè)實(shí)例對象有個(gè)isa的指針,他指向?qū)ο蟮念惞哉螅?code>Class里也有個(gè)isa的指針宣赔,指向meteClass(元類)。元類保存了類方法的列表瞪浸。當(dāng)類方法被調(diào)用時(shí)儒将,先會(huì)從本身查找類方法的實(shí)現(xiàn),如果沒有默终,元類會(huì)向他父類查找該方法椅棺。同時(shí)注意的是:元類(meteClass)也是類,它也是對象齐蔽。元類也有isa指針,它的isa指針最終指向的是一個(gè)根元類(root meteClass)床估。根元類的isa指針指向本身含滴,這樣形成了一個(gè)封閉的內(nèi)循環(huán)。

SELSEL(選擇器)是方法的selector的指針丐巫。方法的selector表示運(yùn)行時(shí)方法的名字谈况。OC在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字递胧、參數(shù)碑韵,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址),這個(gè)標(biāo)識(shí)就是SEL缎脾。

IMPIMP是一個(gè)函數(shù)指針祝闻,指向方法最終實(shí)現(xiàn)的首地址。SEL就是為了查找方法的最終實(shí)現(xiàn)IMP遗菠。

Method:用于表示類定義中的方法联喘,它是結(jié)構(gòu)體中包含一個(gè)SELIMP华蜒,相當(dāng)于在SELIMP之間做了一個(gè)映射。

消息機(jī)制:任何方法的調(diào)用本質(zhì)就是發(fā)送一個(gè)消息豁遭。編譯器會(huì)將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個(gè)消息函數(shù)objc_msgSend(receiver,selector)叭喜。

Runtime的使用:獲取屬性列表,獲取成員變量列表蓖谢,獲得方法列表捂蕴,獲取協(xié)議列表,方法交換(黑魔法)闪幽,動(dòng)態(tài)的添加方法啥辨,調(diào)用私有方法,為分類添加屬性沟使。

一委可、什么是Runtime運(yùn)行時(shí)

runtime主要做了兩件事情:

1.封裝:runtime把對象用C語言的結(jié)構(gòu)體來表示,方法用C語言的函數(shù)來表示腊嗡。這些結(jié)構(gòu)體和函數(shù)被runtime封裝后着倾,我們就可以在程序運(yùn)行的時(shí)候,對類/對象/方法進(jìn)行操作燕少。

2.尋找方法的最終執(zhí)行:當(dāng)執(zhí)行[receiver message]的時(shí)候卡者,相當(dāng)于想receiver發(fā)送一條message腰湾。runtime會(huì)根據(jù)receiver能否處理這條message脐湾,從而做出不同的反應(yīng)。

在OC中颤枪,類是用Class來表示的底挫,而Class實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針恒傻。
struct objc_class{

   Class isa OBJC_ISA_AVAILABILITY; // isa指針
#if !__OBJC2__
   
   Class super_class                        OBJC2_UNAVAILABLE; // 父類
   
   const char *name                         OBJC2_UNAVAILABLE; // 類名
   
   long version                             OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0

   long info                                OBJC2_UNAVAILABLE; // 類信息

   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; // 方法緩存

   struct objc_protocol_list *protocols     OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;

Cache用于緩存最近使用的方法建邓。一個(gè)類只有一部分方法是常用的盈厘,每次調(diào)用一個(gè)方法之后,這個(gè)方法就被緩存到cache中官边,下次調(diào)用時(shí)runtime會(huì)先在cache中查找沸手,如果cache中沒有,才會(huì)去methodList中查找注簿。有了cache契吉,經(jīng)常用到的方法的調(diào)用效率就提高了!

你只要記住诡渴,runtime其實(shí)就是一個(gè)庫捐晶,它是一套API,這個(gè)庫使C語言有了面向?qū)ο蟮哪芰ΑN覀兛梢赃\(yùn)用runtime這個(gè)庫里面的各種方法租悄,在程序運(yùn)行時(shí)的時(shí)候?qū)︻?實(shí)例對象/變量/屬性/方法等進(jìn)行各種操作谨究。

二、 什么是iso指針

在解釋isa之前泣棋,你需要知道胶哲,在Objective-C中,所有的類自身也是一個(gè)對象潭辈,我們可以向這個(gè)對象發(fā)送消息(調(diào)用類方法)鸯屿。

先來看一下runtime中實(shí)例對象的結(jié)構(gòu)體objc_object

struct objc_object {

  Class isa OBJC_ISA_AVAILABILITY;
};

從結(jié)構(gòu)體中可以看到把敢,這個(gè)結(jié)構(gòu)體只包含一個(gè)指向其類的isa指針寄摆。

isa指針的作用:當(dāng)我們向一個(gè)對象發(fā)送消息時(shí),runtime會(huì)根據(jù)這個(gè)對象的isa指針找到這個(gè)對象所屬的類修赞,在這個(gè)類的方法列表及父類的方法列表中婶恼,尋找與消息對應(yīng)的selector指向的方法,找到后就運(yùn)行這個(gè)方法柏副。

要徹底理解isa勾邦,還需要了解一下元類的概念。

(meta-class)元類中存儲(chǔ)著一個(gè)類的所有類方法割择。

向一個(gè)對象發(fā)送消息時(shí)眷篇,runtime會(huì)在這個(gè)對象所屬的類的方法列表中查找方法;
向一個(gè)類發(fā)送消息時(shí)荔泳,會(huì)在這個(gè)類的meta-class(元類)的方法列表中查找蕉饼。

meta-class是一個(gè)類,也可以向它發(fā)送消息玛歌,那么它的isa又是指向什么呢昧港?為了不讓這個(gè)結(jié)構(gòu)無限延伸下去,Objective-C的設(shè)計(jì)者讓所有的meta-classisa指向基類(NSObject)meta-class支子,而基類的meta-classisa指針是指向自己(NSObject)

三慨飘、 什么是SEL,IMP译荞,Method

SEL

SEL又叫選擇器,是方法的selector的指針休弃。

typedef struct objc_selector *SEL;

方法的selector用于表示運(yùn)行時(shí)方法的名字吞歼。Objective-C在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字塔猾、參數(shù)序列篙骡,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址),這個(gè)標(biāo)識(shí)就是SEL

兩個(gè)類之間糯俗,無論它們是父子關(guān)系尿褪,還是沒有關(guān)系,只要方法名相同得湘,那么方法的SEL就是一樣的杖玲,每一個(gè)方法都對應(yīng)著一個(gè)SEL,所以在Objective-C同一個(gè)類中淘正,不能存在兩個(gè)同名的方法摆马,即使參數(shù)類型不同也不行。

SEL是一個(gè)指向方法的指針鸿吆,是根據(jù)方法名hash化了的一個(gè)字符串囤采,而對于字符串的比較僅僅需要比較他們的地址就可以了,所以速度上非常優(yōu)秀惩淳,它的存在只是為了加快方法的查詢速度蕉毯。

不同的類可以擁有相同的selector,不同類的實(shí)例對象執(zhí)行相同的selector時(shí)思犁,會(huì)在格子的方法列表中取根據(jù)selector尋找對應(yīng)的IMP代虾。SEL就是為了查找方法的最終實(shí)現(xiàn)IMP

IMP

IMP實(shí)際上是一個(gè)函數(shù)指針抒倚,指向方法實(shí)現(xiàn)的首地址褐着。代表了方法的最終實(shí)現(xiàn)。

id (*IMP)(id,SEL,…)

第一個(gè)參數(shù)是指向self的指針(如果是實(shí)例方法托呕,則是類實(shí)例的內(nèi)存地址;如果是類方法,則是指向元類的指針),第二個(gè)參數(shù)是方法選擇器(selector)含蓉,省略號是方法的參數(shù)。

每個(gè)方法對應(yīng)唯一的SEL项郊,通過SEL快速準(zhǔn)確的獲得對應(yīng)的IMP馅扣,取得IMP后,就獲得了執(zhí)行這個(gè)方法代碼了着降。

Method

Method是用于表示類的方法差油。

typedef struct objc_method *Method;

struct objc_method {

    SEL method_name             OBJC2_UNAVAILABLE; // 方法名
   
    char *method_types          OBJC2_UNAVAILABLE; 

    IMP method_imp          OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}

Method結(jié)構(gòu)體中包含一個(gè)SELIMP,實(shí)際上相當(dāng)于在SELIMP之間作了一個(gè)映射任洞。有了SEL蓄喇,我們便可以找到對應(yīng)的IMP,從而調(diào)用方法的實(shí)現(xiàn)代碼交掏。

四妆偏、 什么是消息機(jī)制

當(dāng)執(zhí)行了[receiver message]的時(shí)候,相當(dāng)于向receiver發(fā)送一條message盅弛。runtime會(huì)根據(jù)receiver能否處理這條message钱骂,從而做出不同的反應(yīng)叔锐。

方法(消息機(jī)制)的調(diào)用流程

消息直到運(yùn)行時(shí)才綁定到方法的實(shí)現(xiàn)上。編譯器會(huì)將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個(gè)消息函數(shù)见秽,即objc_msgSend(receiver,selector)愉烙。

objc_msgSend(receiver,selector)
// 如果消息中還有其它參數(shù)
objc_msgSend(receiver,selector,arg1,arg2,…)

objc_msgSend做了如下事情:

1.通過對象的isa指針獲取類的結(jié)構(gòu)體。
2.在結(jié)構(gòu)體的方法表里查找方法的selector解取。
3.如果沒有找到selector步责,則通過objc_msgSend結(jié)構(gòu)體中指向父類的指針找到父類,并在父類的方法表里找到方法的selector肮蛹。
4.依次會(huì)一直找到NSObject勺择。
5.一旦找到selector,就會(huì)獲取到方法實(shí)現(xiàn)IMP伦忠。
6.傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實(shí)現(xiàn)省核。
7.如果最終沒有定位到selector,就會(huì)走消息轉(zhuǎn)發(fā)流程昆码。

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

[receiver message]的方式調(diào)用方法气忠,如果receiver無法響應(yīng)message,編譯器會(huì)報(bào)錯(cuò)赋咽。但如果是以performSelecror來調(diào)用旧噪,則需要等到運(yùn)行時(shí)才能確定object是否能接收message消息。如果不能脓匿,則程序崩潰淘钟。

當(dāng)我們不能確定一個(gè)對象是否能接收某個(gè)消息時(shí),會(huì)先調(diào)用respondsToSelector:來判斷一下

    if ([self respondsToselector:@selector(doSomething)]) {

         [self performSelector:@selector(doSomething)];
    }

如果不使用respondsToSelector:來判斷陪毡,那么這就可以用到“消息轉(zhuǎn)發(fā)”機(jī)制米母。

當(dāng)對象無法接收消息,就會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制毡琉,通過這一機(jī)制铁瞒,告訴對象如何處理未知的消息。

這樣就就可以采取一些措施桅滋,讓程序執(zhí)行特定的邏輯慧耍,從而避免崩潰。措施分為三個(gè)步驟丐谋。

1.動(dòng)態(tài)方法解析

對象接收到未知的消息時(shí)芍碧,首先會(huì)調(diào)用所屬類的類方法+resolveInstanceMethod:(實(shí)例方法)或者+resolveClassMethod:(類方法)。

在這個(gè)方法中号俐,我們有機(jī)會(huì)為該未知消息新增一個(gè)“處理方法”师枣。使用該“處理房啊”的前提是已經(jīng)實(shí)現(xiàn),只需要在運(yùn)行時(shí)通過class_addMethod函數(shù)萧落,動(dòng)態(tài)的添加到類里面就可以了践美。代碼如下。

    void functionForMethod1(id self,SEL _cmd) {
    
        NSLog(@“%@,%p”,self,_cmd);
    }

    + (BOOL)resolveInstanceMethod:(SEL)sel {

        NSString *selectorString = NSStringFromSelector(self);
        
        if ([selectorString isEqualToString:@“method1”]) {

            class_addMethod(self.class,@selector(method1),(IMP)functionForMethod1,”@:”);
        }
        
        return [super resolveInstanceMethod:sel];
    }

2. 備用接收者

如果在上一步無法處理消息找岖,則runtime會(huì)繼續(xù)調(diào)下面的方法陨倡。

- (id)forwardingTargetForSelector:(SEL)aSelector

如果這個(gè)方法返回一個(gè)對象,則這個(gè)對象會(huì)作為消息的新接收者许布。注意這個(gè)對象不能是self自身兴革,否則就是出現(xiàn)無限循環(huán)。如果沒有指定對象來處理aSelector蜜唾,則應(yīng)該return [super forwardingTargetForSelector:aSelector]杂曲。

但是我們只將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對象上,無法對消息進(jìn)行處理袁余,例如操作消息的參數(shù)和返回值擎勘。

    @interface RuntimeTest() {

        RuntimeMethod *_TestMethod;
    }

    @implementation RuntimeTest

    - (instancetype)init {

        self = [super init];
        
        if (self != nil) {

            _TestMethod = [[TestMethod alloc] init];
        }
        return self;
    }

    - (id)forwardingTargetForSelector:(SEL)aSelector {

        NSLog(@“forwardingTargetForSelector”);

        NSString *selectorString = NSStringFromSelector(aSelector);
        
        // 將消息轉(zhuǎn)發(fā)給_TestMethod來處理
        if ([selectorString isEqualToString:@“method2”]) {

            return _TestMethod;
        }
        return [super forwardingTargetForSelector:aSelector];
    }

3.完整消息轉(zhuǎn)發(fā)

如果在上一步還是不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制颖榜。此時(shí)會(huì)調(diào)用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation {

    if ([RuntimeMethod instancesRespondToSelector:anInvocation.selector]) {

        [anInvocation invakeWithTarget:_TestMethod];
    }
}

這是最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對象棚饵。創(chuàng)建一個(gè)表示消息的NSInvocation對象,把與消息的有關(guān)全部細(xì)節(jié)封裝在anInvocation中掩完,包括selector噪漾,目標(biāo)(target)和參數(shù)。在forwardInvocation方法中將消息轉(zhuǎn)發(fā)給其它對象且蓬。

forwardInvocation:方法的實(shí)現(xiàn)有兩個(gè)任務(wù):
1.定位可以響應(yīng)封裝在anInvocation中的消息的對象欣硼。

2.使用anInvocation作為參數(shù),將消息發(fā)送到選中的對象恶阴。anInvocation將會(huì)保留調(diào)用結(jié)果诈胜,runtime會(huì)提取這一結(jié)果并發(fā)送到消息的原始發(fā)送者。

在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能存淫,我們可以對消息的內(nèi)容進(jìn)行修改耘斩。另外,若發(fā)現(xiàn)消息不應(yīng)由本類處理桅咆,則應(yīng)調(diào)用父類的同名方法括授,以便集成體系中的每個(gè)類都有機(jī)會(huì)處理。

另外岩饼,必須重寫下面的方法:

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息轉(zhuǎn)發(fā)機(jī)制從這個(gè)方法中獲取消息來創(chuàng)建NSInvocation對象荚虚。完整的示例如下:

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        
        if (!signature) {

            if ([RuntimeMethod instanceRespondToSelector:aSelector]) {
                
                signature = [RuntimeMethod instanceMethodSignatureForSelector:aSelector];
            }
        }
        return signature;
    }

    - (void)forwardInvocation:(NSInvocation *)anInvocation {

        if ([RuntimeMethod instanceRespondToSelector:anInvocation.selector]) {

            [anInvocation invokeWithTarget:_TestMethod];
        }
    }

NSObjectforwardInvocation方法只是調(diào)用了doesNotRecognizeSelector方法,它不會(huì)轉(zhuǎn)發(fā)任何消息籍茧。如果不在以上所述的三個(gè)步驟中處理未知消息版述,則會(huì)引發(fā)異常。

forwardInvocation就像一個(gè)未知消息的分發(fā)中心寞冯,將這些未知的消息轉(zhuǎn)發(fā)給其它對象渴析⊥砘铮或者也可以像一個(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對象,取決于具體的實(shí)現(xiàn)俭茧。

消息的轉(zhuǎn)發(fā)機(jī)制可以用下圖來幫助理解咆疗。

五、 runtime的使用

  1. 獲取屬性列表
  2. 獲取成員變量列表
  3. 獲取方法列表
  4. 獲取協(xié)議列表
  5. 方法交換(黑魔法)
  6. 添加方法
  7. 調(diào)用私有方法
  8. 為分類添加屬性

1.獲取屬性列表

運(yùn)用class_copyPropertyList方法來獲得屬性列表母债,遍歷把屬性加入數(shù)組中午磁,最終返回此數(shù)組。其中[self dictionaryWithProperty:properties[i]]方法是用來拿到屬性的描述毡们,例如copy迅皇,readonlyNSString等信息衙熔。

 /** 屬性列表 */
 - (NSArray *)propertiesInfo
 {
    return [[self class] propertiesInfo];
 }
 + (NSArray *)propertiesInfo
    {
        NSMutableArray *propertieArray = [NSMutableArray array];
        unsigned int propertyCount;
        objc_property_t *properties = class_copyPropertyList([self class],&propertyCount);
        
        for (int index = 0; index < propertyCount; index++)
        {
            [propertieArray addObject:({
        
                NSDictionary *dictionary = [self dictionaryWithProperty:properties[index]];

                dictionary;
            })];
        }

        free(properties);

        return propertieArray;
    }
// 格式化之后的屬性列表
    + (NSArray *)propertiesWithCodeFormat
    {
        NSMutableArray  *array = [NSMutableArray array];

        NSArray *properties = [[self class] propertiesInfo];
        
        for (NSDictionary *item in properties)
        {
            NSMutableString *format = ({

                NSMutableString *formatString = [NSMutableString stringWithFormat:@“property ”];
                // attribute
                NSArray *attribute = [item objectForKey:@“attribute”];
                attribute = [attribute sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        
                    return [obj1 compare:obj2 options:NSNumericSearch];
                }];

                if (attribute && attribute.count > 0)
                {
                    NSString *attributeStr = [NSString stringWithFormat:@“(%@)”,[attribute componentsJoinedByString:@“, ”]];
                    [formatString appendString:attributeStr];
                }

                // type
                NSString *type = [item objectForKey:@“type”];
                if (type) 
                {
                    [formatString appendString:@“ ”];
                    [formatString appednString:type];
                }
            
                // name
                NSString *name = [item objectForKey:@“name”];
                if (name)
                {   
                    [formatString appendString:@“ ”];
                    [formatString appendString:name];
                    [formatString appendString:@“;”];
                } 

                formatString;
            });

            [array addObject:format];
        }
        
        return array;
    }

    + (NSDictionary *)dictionaryWithProperty:(objc_property_t)property
    {
        NSMutableDictionary *result = [NSMutableDictionary dictionary];

        // name
        NSString *propertyName  = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        [result setObject:propertyName forKey:@“name”];

        // attribute
        
        NSMutableDictionary *attributeDictionary = ({

            [NSMutableDictionary dictionary];

            unsigned int attributeCount;
            objc_property_attribute_t *attrs = property_copyAttributeList(property,&attributeCount);

            for (int index = 0; index < attributeCount; index++) 
            {
                NSString *name = [NSString stringWithCString:attrs[index].name encoding:NSUTF8StringEncoding];
                NSString *value = [NSString stringWithCString:attrs[index].value encoding:NSUTF8StringEncoding];
            }

            free(attrs);

            dictionary;
        });

        NSMutableArray *attributeArray  = [NSMutableArray array];

        // R
        if ([attributeDictionary objectForKey:@“R”]) 
        {
            [attributeArray addObject:@“readonly”];
        }
        // C
        if ([attributeDictionary objectForKey:@“C”])
        {
            [attributeArray addObject:@“copy”];
        }
        // &
        if ([attributeDictionary objectForKey:@“&”])
        {
            [attributeArray addObject:@“strong”];
        }
        // N
        if ([attributeDictionary objectForKey:@“N”])
        {
            [attributeArray addObject:@“nonatomic”];    
        } else 
        {
            [attributeArray addObject:@“atomic”];
        }
        // G<name>
        if ([attributeDictionary objectForKey:@“G”]) 
        {
            [attributeArray addObject:[NSString stringWithFormat:@“getter=%@”,[attributeDictionary objectForKey:@“G”]]];
        }
        // S<name>
        if ([attributeDictionary objectForKey:@“S”])
        {
            [attributeArray addObject:[NSString stringWithFormat:@“setter=%@”,[attributeDictionary objectForKey:@“G”]]];
        }
        // D
        if ([attributeDictionary objectForKey:@“D”])
        {
            [result setObject:[NSNumber numberWithBool:YES] forKey:@“isDynamic”];
        } else
        {
            [result setObject:[NSNumber numberWithBool:NO] forKey:@“isDynamic”];
        }   
        // W
        if ([attributeDictionary objectForKey:@“W”]) 
        {
            [attributeArray addObject:@“weak”];
        } 
        // P
        if ([attributeArray objectForKey:@“P”])
        {
            
        }
        // T
        if ([attributeDictionary objectForKey:@“T”])
        {
            NSDictionary *typeDic   = @{
                        @“c”:@“char”,
                        @“i”:@“int”,
                        @“s”:@“short”,
                        @“l(fā)”:@“l(fā)ong”,
                        @“q”:@“l(fā)ong long”,
                        @“C”:@“unsigned char”,
                        @“I”:@“unsigned int”,
                        @“S”:@“unsigned short”,
                        @“L”:@“unsigned long”,
                        @“Q”:@“unsigned long long”,
                        @“f”:@“float”,
                        @“d”:@“double”,
                        @“B”:@“BOOL”,
                        @“v”:@“void”,
                        @“*”:@“char *”,
                        @“@”:@“id”,
                        @“#”:@“Class”,
                        @“:”:@“SEL”,
                            };
            // TODO:An array
            NSString *key   = [attributeDictionary objectForKey:@“T”];

            id type_str = [typeDic objectForKey:key];

            if (type_str == nil) 
            {
                if ([[key substringToIndex:1] isEqualToString:@“@”] && [key rangeOfString:@“?”].location == NSNotFound)
                {
                    type_str = [[key substringWithRange:NSMakeRange(2,key.length - 3)] stringByAppendingString:@“*”] ;
                } 
                else if ([[key substringToIndex:1] isEqualToString:@“^”])
                {
                    id str = [typeDic objectForKey:[key substringFromIndex:1]];
                    if (str) 
                    {
                        type_str = [NSString stringWithFormat:@“%@ *”,str];
                    }
                }
                else 
                {
                    type_str    = @“unknow”;
                }
            }
            
            [result setObject:type_str forKey:@“type”];
        }
        [result setObject:attributeArray forKey:@“attribute”];
        
        return result;
    }

2.獲取成員變量列表

運(yùn)用class_copyIvarList方法來獲得變量列表登颓,通過遍歷把變量加入到數(shù)組中,最終返回此數(shù)組青责。其中[[self class] decodeType:ivar_getTypeEncoding(ivars[i])]方法是用來拿到變量的類型挺据,例如char,int,unsigned long 等信息。

/** 成員變量列表 */
- (NSArray *)ivarInfo
{
    return [[self class] invarInfo];
}

+ (NSArray *)ivarInfo
{
    unsigned int outCount;
    Ivar *ivars = class_copyIvarList([self class],&outCount);
    for (int index = 0; index < outCount; index++)
    {
        NSString *type  = [[self class] decodeType:ivar_getTypeEncoding(ivars[index])];
        NSString *name  = [NSString stringWithCString:ivar_getName(ivars[index]) encoding:NSUTF8StringEncoding];
        NSString *ivarDescription = [NSString stringWithFormat:@“%@ %@”,type,name];
        [result addObject:ivarDescription];
    }
    free(ivars);
    return result.count ? [result copy] : nil;
}

+ (NSString *)decodeType:(const char *)cString
{
    if (!strcmp(cString,@encode(char)))
    {
        return @“char”;
    }
    if (!strcmp(cString,@encode(int)))
    {
        return @“int”;
    }
    if (!strcmp(cString,@encode(short)))
    {
        return @“short”;
    }
    if (!strcmp(cString,@encode(long)))
    {
        return @“l(fā)ong”;
    }
    if (!strcmp(cString,@encode(long long)))
    {
        return @“l(fā)ong long”;
    }
    if (!strcmp(cString,@encode(unsigned char)))
    {
        return @“unsigned char”;
    }
    if (!strcmp(cString,@encode(unsigned int)))
    {
        return @“unsigned int”;
    }
    if (!strcmp(cString,@encode(unsigned short)))
    {
        return @“unsigned short”;
    }
    if (!strcmp(cString,@encode(unsigned long)))
    {
        return @“unsigned long”;
    }
    if (!strcmp(cString,@encode(unsigned long long)))
    {
        return @“unsigned long long”;
    }
    if (!strcmp(cString,@encode(float)))
    {
        return @“float”;
    }
    if (!strcmp(cString,@encode(double)))
    {
        return @“double”;
    }
    if (!strcmp(cString,@encode(bool)))
    {
        return @“bool”;
    }
    if (!strcmp(cString,@encode(_Bool)))
    {
        return @“_Bool”;
    }
    if (!strcmp(cString,@encode(void)))
    {
        return @“void”;
    }
    if (!strcmp(cString,@encode(char *)))
    {
        return @“char *”;
    }
    if (!strcmp(cString,@encode(id)))
    {
        return @“id”;
    }
    if (!strcmp(cString,@encode(Class)))
    {
        return @“class”;
    }
    if (!strcmp(cString,@encode(SEL)))
    {
        return @“SEL”;
    }
    if (!strcmp(cString,@encode(BOOL)))
    {
        return @“BOOL”;
    }
    
    NSString *result    = [NSString stringWithCString:cString encoding:NSUTF8StringEncoding];
    
    if ([[result substringToIndex:1] isEqualToString@“@”] && [result rangeOfString:@“?”].location == NSNotFound)
    {
        result = [[result substringWithRange:NSMakeRange(2,result.length - 3)] stringByAppendingString:@“*”];
    }
    else 
    {
        if ([[result substringToIndex:1] isEqualToString:@“^”])
        {
            result = [NSString stringWithFormat:@“%@ *”,[NSString decodeType:[result substringFromIndex:1] cStringUsingEncoding:NSUTF8StringEncoding]];
        }
    }
    retuen result;
}

3.獲取方法列表

通過runtimeclass_copyMethodList方法來獲取方法列表脖隶,通過遍歷把方法加入到數(shù)組中扁耐,最終返回此數(shù)組。

// 對象方法列表
- (NSArray *)instanceMethodList
{
    u_int count;
    NSMutableArray  *methodList = [NSMutableArray array];
    Method *methods = class_copyMethodList([self class],&count);
    for (int index = 0; index < count; index++)
    {
        SEL name = method_getName(methods[index]);
        NSString *strName   = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        [methodList addObject:strName];
    }
    free(methods);
    return methods;
}

+ (NSArray *)instanceMethodList
{
    u_int count;
    NSMutableArray  *methodList = [NSMutableArray array];
    Method *methods = class_copyMethodList([self class],&count);
    for (int index = 0; index < count; index++)
    {
        SEL name = method_getName(methods[index]);
        NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
    }
    free(methods);

    return methodList;
}

// 獲取類方法列表
+ (NSArray *)classMethodList
{
    unsigned int count = 0;
    Method *methodList  = class_copyMethodList(object_getClass(self),&count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int index = 0; index < count; index++)
    {
        Method method = methodList[index];
        SEL methodName = method_getName(method);
        [mutableList addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return [NSArray arrayWithArray:mutableList];
}

4.獲取協(xié)議列表
運(yùn)用class_copyProtocolList方法來獲得協(xié)議列表产阱。

//協(xié)議列表
- (NSDictionary *)protocolList
{
    retuen [[self class] protocolList];
}

+ (NSDictionary *)protocolList
{
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

    unsigned int count;

    Protocol * __unsigned_unretained *protocols = class_copyProtocolList([self class],&count);
    
    for (int index = 0; index < count; index ++)
    {
        Protocol *protocol = protocols[index];
    
        NSString *protocolName  = [NSString stringWithCString:protocol_getName(protocol)];

        NSMutableArray *superProtocolArray = ({

            NSMutableArray *array = [NSMutableArray array];

            unsigned int superProtocolCount;

            Protocol * __unsafe_unretained *superProtocols = protocol_copyProtocolList(protocol,&superProtocolCount);

            for (int ii = 0; ii < superProtocolCount; ii++)
            {
                Protocol *superProtocol = superProtocols[ii];

                NSString *superProtocolName = [NSString stringWithCString:protocol_getName(superProtocol) encoding:NSUTF8StringEncoding];

                [array addObject:superProtocolName];
            }
            free(superProtocols);
    
            array;
        });

        [dictionary setObject:superProtocolArray forKey:protocolName];
    }
    free(protocols);

    return dictionary;
}

5.方法交換(黑魔法)

runtime的重頭戲婉称,被稱作黑魔法的方法交換Swizzling。交換方法是在method_exchangeImplementations里發(fā)生的构蹬。

/** 交換實(shí)例方法 */   
+ (void)SwizzlingInstanceMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
    Class class = [self class];
    
    SEL originalSelector = oldMethod;
    SEL swizzledSelector = newMethod;

    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) 
    {
class_replaceMethod(class,swizzledSelector,method_getImpLementation(originalMethod),method_getTypeEncoding(originalMethod));
    }
    else 
    {
        method_exchangeImplementations(originalMethod,swizzledMethod);  
    }
}

/** 交換類方法 */
+ (void)SwizzlingClassMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
    Class class = object_getClass((id)self);
    SEL originalSelector = oldMethod;
    SEL swizzledSelector = newMethod;

    Method originalMethod   = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod   = class_getInstanceMethod(class, swizzledSelector);

    BOOL didAddMethod = class_addMethod(class, originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
    if (didAddMethod)
    {
        class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
    }
    else 
    {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

使用Swizzling的過程中要注意兩個(gè)問題:

Swizzling要在+load方法中執(zhí)行

運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用每個(gè)類的兩個(gè)方法王暗,+load+initialize

+load會(huì)在main函數(shù)之前調(diào)用庄敛,并且一定會(huì)調(diào)用俗壹。

+initialize是在第一次調(diào)用類方法或?qū)嵗椒ㄖ氨徽{(diào)用,有可能一直不被調(diào)用

一般使用Swizzling是為了影響全局藻烤,所以為了方法交換一定成功绷雏,Swizzling要放在+load中執(zhí)行。

新建工程創(chuàng)建類怖亭,A類和B類的load順序是無序的 但是運(yùn)行過一次后load順序就固定了
假如A類先load
在A類的load方法中創(chuàng)建B類的實(shí)例 會(huì)使B類先調(diào)用initialize方法 后調(diào)用 load 方法
正常情況下涎显,會(huì)先調(diào)用load方法,后調(diào)用initialize方法

Swizzling要在dispatch_once中執(zhí)行

Swizzling是為了影響全局兴猩,所以只讓它執(zhí)行一次就可以了期吓,所以要放在dispatch_once中。

6.添加方法

/** 添加方法 */
+ (void)addMethodWithSEL:(SEL)methodSEL methodIMP:(SEL)methodIMP
{
    Method method   = class_getInstanceMethod(self, methodIMP);
    IMP getMethodIMP = method_getImplementation(method);
    const char *types = method_getTypeEncoding(method);
    class_addMethod(self, methodSEL, getMethodIMP, types);
}

添加方法的運(yùn)用

接收到未知的消息時(shí)倾芝,首先會(huì)調(diào)用所屬類的類方法
+resolveInstanceMethod:(實(shí)例方法)+resolveClassMethod:(類方法)讨勤。

第一種情況是箭跳,根據(jù)已知的方法名動(dòng)態(tài)的添加一個(gè)方法。

第二種情況是悬襟,直接添加一個(gè)方法衅码。

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    // 1.如果方法名是addMethod,就添加一個(gè)MethodOne方法來執(zhí)行
    if (sel == NSSelectorfromString(@“addMethod”))
    {
        class_addMethod(self, sel, (IMP)MethodOne, “v@:”);
        return YES;
    }
    
    // 2.如果找不到方法脊岳,就添加一個(gè)addMethod來執(zhí)行
    [self addMethodWithSEL:sel methodIMP:@selector(addMethod)];
    return YES;
}   

7.調(diào)用私有方法

由于消息機(jī)制,runtime可以通過objc_msgSend來幫我們調(diào)用一些私有方法

TestRuntime *test   = [[TestRuntime alloc] init];
objc_msgSend(test, @selector(privateMethod));

使用objc_msgSend需要注意兩個(gè)問題:

1.需要導(dǎo)入頭文件#import <objc/message.h>
2.在Build Settings里設(shè)置Enable Strict Checking of objc_msgSend Calls 的值為NO

8.為分類添加屬性

在分類中屬性不會(huì)自動(dòng)生成實(shí)例變量和存取方法垛玻,但是可以運(yùn)用runtime的關(guān)聯(lián)對象(Associated Object)來解決這個(gè)問題割捅。

@interface UIImage (swizzlingImage)

// 用關(guān)聯(lián)對象為分類添加屬性
@property (nonatomic, copy) NSString *categoryProperty;

@end

@implementation UIImage (swizzlingImage)

// getter
- (NSString *)categoryProperty
{
    return objc_getAssociatedObject(self, _cmd);
}
// setter
- (void)setCategoryProperty:(NSString *)categoryProperty
{
    objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

使用objc_getAssociatedObjectobjc_setAssociatedObject 來做到存取方法,使用關(guān)聯(lián)對象模擬實(shí)例變量帚桩。下面是兩個(gè)方法的原型:

id objc_getAssociatedObject(id object, const void *key);

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

方法中的@selector(categoryProperty)就是參數(shù)key亿驾,使用@selector(categoryProperty)作為key傳入,可以確保key的唯一性账嚎。

OBJC_ASSOCIATION_COPY_NONATOMIC 是屬性修飾符

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy)
{
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_PETAIN = 01401,
    OBJC_ASSOCIATION_COPY   = 01403
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末莫瞬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子郭蕉,更是在濱河造成了極大的恐慌疼邀,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件召锈,死亡現(xiàn)場離奇詭異旁振,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涨岁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門拐袜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梢薪,你說我怎么就攤上這事蹬铺。” “怎么了秉撇?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵甜攀,是天一觀的道長。 經(jīng)常有香客問我畜疾,道長赴邻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任啡捶,我火速辦了婚禮姥敛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞎暑。我一直安慰自己彤敛,他們只是感情好与帆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著墨榄,像睡著了一般玄糟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袄秩,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天阵翎,我揣著相機(jī)與錄音,去河邊找鬼之剧。 笑死郭卫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的背稼。 我是一名探鬼主播贰军,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蟹肘!你這毒婦竟也來了词疼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤帘腹,失蹤者是張志新(化名)和其女友劉穎贰盗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竹椒,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡童太,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胸完。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片书释。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赊窥,靈堂內(nèi)的尸體忽然破棺而出爆惧,到底是詐尸還是另有隱情,我是刑警寧澤锨能,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布扯再,位于F島的核電站,受9級特大地震影響址遇,放射性物質(zhì)發(fā)生泄漏熄阻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一倔约、第九天 我趴在偏房一處隱蔽的房頂上張望秃殉。 院中可真熱鬧,春花似錦、人聲如沸钾军。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吏恭。三九已至拗小,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間樱哼,已是汗流浹背哀九。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搅幅,地道東北人勾栗。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像盏筐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子砸讳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉琢融,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,557評論 33 466
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢簿寂?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評論 0 7
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,135評論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 733評論 0 2