Runtime 你為何如此之屌?

一野哭、消息驅(qū)動(dòng)機(jī)制

消息驅(qū)動(dòng)機(jī)制: 運(yùn)行的時(shí)候的一些機(jī)制味赃,最主要的是消息機(jī)制。

消息驅(qū)動(dòng)機(jī)制-動(dòng)態(tài)調(diào)用過程 : 對(duì)于C語言虐拓,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)。編譯完成之后直接順序執(zhí)行傲武,無任何二義性蓉驹。
OC的函數(shù)調(diào)用為消息發(fā)送城榛。屬于動(dòng)態(tài)調(diào)用過程。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)态兴,只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來調(diào)用狠持。
(事實(shí)證明,在編譯階段瞻润,OC可以調(diào)用任何函數(shù)喘垂,即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過就不會(huì)報(bào)錯(cuò)绍撞。而C語言在編譯階段就會(huì)報(bào)錯(cuò)
Objective-C 語言不僅需要一個(gè)編譯器,同時(shí)也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來執(zhí)行編譯好的代碼正勒。運(yùn)行時(shí)系統(tǒng)扮演的角色類似于 Objective-C 語言的操作系統(tǒng),Objective-C 基于該系統(tǒng)來工作。

在OC中的方法調(diào)用:
[object doSomeMethod];
其中object是一個(gè)對(duì)象傻铣,doSomeMethod是一個(gè)函數(shù)名稱章贞。對(duì)于這樣一個(gè)簡(jiǎn)單的調(diào)用。在編譯時(shí)RunTime會(huì)將上述代碼轉(zhuǎn)化成:
objc_msgSend(id object,@selector(doSomeMethod));

消息驅(qū)動(dòng)機(jī)制-核心
發(fā)送消息:
objc_msgSend(receiver, selector) 消息不含有參數(shù)
objc_msgSend(receiver, selector, arg1, arg2, ...)消息含有參數(shù)

二非洲、Runtime實(shí)現(xiàn)面向?qū)ο?/h4>

一個(gè)面向?qū)ο蟮恼Z言一定要實(shí)現(xiàn)類鸭限、屬性、方法两踏、繼承败京、擴(kuò)展。
一切事物皆對(duì)象梦染,通過面向?qū)ο蟮姆绞缴穆螅瑢F(xiàn)實(shí)世界的事物抽象成對(duì)象,現(xiàn)實(shí)世界中的關(guān)系抽象成類弓坞、繼承隧甚,幫助人們實(shí)現(xiàn)對(duì)現(xiàn)實(shí)世界的抽象與數(shù)字建模。
通過面向?qū)ο蟮姆椒ǘ啥常谟萌死斫獾姆绞綄?duì)復(fù)雜系統(tǒng)進(jìn)行分析戚扳、設(shè)計(jì)與編程。提高軟件的重用性族吻、靈活性和擴(kuò)展性帽借。
OC中的對(duì)象通過Runtime轉(zhuǎn)化為對(duì)應(yīng)的結(jié)構(gòu)體:
在Runtime中定義了:objc_class和objc_object結(jié)構(gòu)體 我們知道在OC代碼中NSObject幾乎是所有類的基類,它的核心就是結(jié)構(gòu)體 下面這個(gè)結(jié)構(gòu)體中包含一個(gè)實(shí)例變量鏈表 能夠找到類中所有的實(shí)例變量 還需要有一個(gè)方法鏈表超歌,找到這個(gè)類所有對(duì)應(yīng)的方法砍艾,還要能找到它的父類,在這里我們可以看到Class是objc_class結(jié)構(gòu)體指針的別名巍举,而id是objc_object指針的別名脆荷。在objc_object這個(gè)結(jié)構(gòu)體中包含了一個(gè)結(jié)構(gòu)體指針
在objc_class這個(gè)結(jié)構(gòu)體中主要包括以下:(它需要找到自己所有的實(shí)例變量,于是就有一個(gè)實(shí)例變量鏈表,需要找到自己所有的方法蜓谋,于是就有了一個(gè)方法鏈表等)

typedef struct objc_class *Class; 
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY; 
};
/// A pointer to an instance of a class.
typedef struct objc_object *id; 
struct objc_class {
        struct objc_class super_class;  /*父類*/
        const char *name;               /*類名字*/
        long version;                   /*版本信息*/
        long info;                      /*類信息*/
        long instance_size;                      /*實(shí)例大小*/
        struct objc_ivar_list *ivars;          /*實(shí)例參數(shù)鏈表*/
        struct objc_method_list **methodLists;  /*方法鏈表*/
        struct objc_cache *cache;                    /*方法緩存*/
        struct objc_protocol_list *protocols;   /*協(xié)議鏈表*/
};

實(shí)例變量及實(shí)例變量表

struct objc_ivar {          // 單個(gè)實(shí)例變量
        char *ivar_name;    // 實(shí)例變量名
        char *ivar_type;    // 實(shí)例變量的類型(屬于哪個(gè)類)
        int ivar_offset;    // 實(shí)例變量的偏移量(OC中訪問實(shí)例變量是通過偏移量獲取到實(shí)例變量的位置)
#ifdef __LP64__
        int space;          // 實(shí)例變量所占空間長(zhǎng)度
#endif
}
struct objc_ivar_list {     // 實(shí)例變量列表結(jié)構(gòu)體(以鏈表的形式管理多個(gè)實(shí)例變量)
        int ivar_count;     // 實(shí)例變量個(gè)數(shù)
#ifdef __LP64__
        int space;          // 實(shí)例變量總空間數(shù)
#endif
        /* variable length structure */
        struct objc_ivar ivar_list[1];  // 實(shí)例變量數(shù)組
}

方法及方法表

struct objc_method {            // 單個(gè)方法
        SEL method_name;        // 方法名
        char *method_types;     // 方法類型
        IMP method_imp;         // 指向方法實(shí)現(xiàn)代碼的地址
};
struct objc_method_list {       // 方法鏈表
        struct objc_method_list *obsolete;
        int method_count;
#ifdef __LP64__
        int space;
#endif
        /* variable length structure */
        struct objc_method method_list[1];
}

關(guān)于Runtime中的結(jié)構(gòu)體總結(jié)如下:

三梦皮、與Runtime系統(tǒng)交互

Objective-C 程序有三種途徑和運(yùn)行時(shí)系統(tǒng)交互:
通過 Objective-C 源代碼;
通過 Foundation 框架中類 NSObject 的方法;
通過直接調(diào)用運(yùn)行時(shí)系統(tǒng)的函數(shù)。

由于Runtime的作用就是講OC的源代碼轉(zhuǎn)化為底層C++的實(shí)現(xiàn)桃焕,所以前兩種交互方式是隱含在代碼內(nèi)部的剑肯,通過直接調(diào)用運(yùn)行時(shí)系統(tǒng)中的函數(shù),我們可以對(duì)運(yùn)行時(shí)系統(tǒng)中的函數(shù)進(jìn)行操作观堂,主要是讀取成員變量的值让网,獲取信息以及一些簡(jiǎn)單的設(shè)置,關(guān)于這些操作函數(shù)總結(jié)如下:

四师痕、動(dòng)態(tài)綁定

所謂的動(dòng)態(tài)綁定就是對(duì)一個(gè)對(duì)象發(fā)送消息溃睹,到運(yùn)行時(shí)系統(tǒng)找到這個(gè)消息所對(duì)應(yīng)的方法實(shí)現(xiàn)并調(diào)用的過程。
消息函數(shù)objc_megSend做了動(dòng)態(tài)綁定所需要的一切:
1七兜、它首先找到選標(biāo)所對(duì)應(yīng)的方法實(shí)現(xiàn)丸凭。因?yàn)椴煌念悓?duì)同一方法可能會(huì)有不同的實(shí)現(xiàn),所以找到的 方法實(shí)現(xiàn)依賴于消息接收者的類型。
2腕铸、然后將消息接收者對(duì)象(指向消息接收者對(duì)象的指針)以及方法中指定的參數(shù)傳給找到的方法實(shí)現(xiàn)惜犀。
3、最后,將方法實(shí)現(xiàn)的返回值作為該函數(shù)的返回值返回狠裹。

當(dāng)對(duì)象收到消息時(shí),消息函數(shù)首先根據(jù)該對(duì)象的isa指針找到該對(duì)象所對(duì)應(yīng)的類的方法表,并從表中尋找該消息對(duì)應(yīng)的方法選標(biāo)虽界。如果找不到,objc_msgSend 將繼續(xù)從父類中尋找,直到 NSObject 類。一旦找到了方法選標(biāo), objc_msgSend 則以消息接收者對(duì)象為參數(shù)調(diào)用,調(diào)用該選標(biāo)對(duì)應(yīng)的方法實(shí)現(xiàn)涛菠。
這就是在運(yùn)行時(shí)系統(tǒng)中選擇方法實(shí)現(xiàn)的方式莉御。在面向?qū)ο缶幊讨?一般稱作方法和消息動(dòng)態(tài)綁定的過程。

五俗冻、動(dòng)態(tài)方法解析

你可以通過實(shí)現(xiàn) resolveInstanceMethod:和 resolveClassMethod:來動(dòng)態(tài)地實(shí)現(xiàn)給定選標(biāo) 的對(duì)象方法或者類方法礁叔。
Objective-C 方法可以認(rèn)為是至少有兩個(gè)參數(shù)——self 和_cmd—— 的 C 函數(shù)。您可以通過 class_addMethod 方法將一個(gè)函數(shù)加入到類的方法中迄薄。例如,有如下的函數(shù):

void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
我們可以做如下操作在項(xiàng)目中創(chuàng)建兩個(gè)類琅关,一個(gè)繼承自NSObject的Student 另一個(gè)繼承自Student的BDStudent
#import "BDStudent.h"
#import <objc/objc-runtime.h>
@implementation LOStudent
+(BOOL)resolveInstanceMethod:(SEL)sel{
    //動(dòng)態(tài)解析 實(shí)例方法
    //創(chuàng)建實(shí)例方法的方法實(shí)現(xiàn)
    void(^resolveInstanceBlock)(id, SEL) = ^(id objc_self,SEL objc_cmd){
        NSLog(@"如果實(shí)例方法未實(shí)現(xiàn)則會(huì)執(zhí)行此方法");
    };
    //為本類添加實(shí)例方法
    class_addMethod([self class], sel, imp_implementationWithBlock(resolveInstanceBlock), "v@:");  //v表示返回值類型 @表示id :表示方法參數(shù)
    printf("%s\n",sel_getName(sel));
    return YES;
    
}
+(BOOL)resolveClassMethod:(SEL)sel{
    //動(dòng)態(tài)解析 類方法
    //首先創(chuàng)建類方法的實(shí)現(xiàn)
    void(^resoveClassMethod)(id,SEL) = ^(id objc_self,SEL objc_cmd){
        NSLog(@"未實(shí)現(xiàn)的類方法,在這里執(zhí)行");
    };
    //為本類添加類方法
    class_addMethod(object_getClass([self class]), sel, imp_implementationWithBlock(resoveClassMethod), "v@:");
    return YES;
}
@end

要是我們以上方法沒寫的話 就會(huì)得到如下結(jié)果 反之讥蔽,則不然涣易。

    /*
    //創(chuàng)建LOStudent的實(shí)例 調(diào)用不存在的方法
    LOStudent *stu  = [[LOStudent alloc]init];
    [stu performSelector:@selector(haha)];
    //會(huì)因?yàn)闊o法識(shí)別方法selector而拋出異常
    */

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

消息轉(zhuǎn)發(fā)很象繼承,并且可以用來在Objective-C程序中模擬多重繼承冶伞。如圖 5-1所示, 一個(gè)對(duì)象通過轉(zhuǎn)發(fā)來響應(yīng)消息,看起來就象該對(duì)象從別的類那借來了或者”繼承“了方法實(shí)現(xiàn)一樣新症。
消息轉(zhuǎn)發(fā)提供了多重繼承的很多特性。
然而,兩者有很大的不同:多重繼承是將不同的行為封裝到單個(gè)的對(duì)象中,有可能導(dǎo)致龐大的,復(fù)雜的對(duì)象响禽。
而消息轉(zhuǎn)發(fā)是將問題分解到更小的對(duì)象中,但是又以一種對(duì)消息發(fā)送對(duì)象來說完全透明的方式將這些對(duì)象聯(lián)系起來徒爹。
消息轉(zhuǎn)發(fā)只是將其他類引入消息鏈荚醒,而不是繼承鏈。所以 respondsToSelector:瀑焦、 isKindOfClass:返回值均為NO腌且,如果您使用的是協(xié)議類, conformsToProtocol:也為NO。若我們以轉(zhuǎn)發(fā)消息方式擴(kuò)展類榛瓮,那么有時(shí)需要重新實(shí)現(xiàn)這些方法,以達(dá)到對(duì)開發(fā)人員透明的效果巫击。
我們假設(shè)有兩個(gè)類都繼承與NSObject :Student和BeiDa 學(xué)生需要實(shí)現(xiàn)消息轉(zhuǎn)發(fā)禀晓,而學(xué)習(xí)作為一個(gè)方法是愛北大實(shí)現(xiàn)的:我們可以做如下處理

#import <Foundation/Foundation.h>
@interface BeiDa : NSObject
-(void)leart;
@end

#import "BeiDa.h"
@implementation BeiDa
//實(shí)現(xiàn)學(xué)習(xí)iOS開發(fā)的方法
-(void)leart{
    NSLog(@"在北京大學(xué)軟件工程中心開發(fā)實(shí)踐");
}
@end

而作為學(xué)生

#import <Foundation/Foundation.h>
#import "Lanou.h"
@interface Student : NSObject
//接收轉(zhuǎn)發(fā)消息的對(duì)象
@property(nonatomic,strong)Lanou *otherObject;
@end

#import "Student.h"
@implementation Student
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(leart)) {
        return [self.otherObject methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}
//將消息當(dāng)做參數(shù)anInvocation傳到方法里來 通過invokeWithTarget將targrt切換為 otherObject 由otherObject來執(zhí)行l(wèi)eart這個(gè)方法
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    if (anInvocation.selector == @selector(leart)) {
        [anInvocation invokeWithTarget:self.otherObject];
    }
}
@end

對(duì)于以上我們?cè)趺蠢斫饽兀?br> 學(xué)生沒有實(shí)現(xiàn)學(xué)習(xí)的方法 真正的學(xué)習(xí)是在北大
實(shí)現(xiàn)轉(zhuǎn)發(fā)消息的方法
1 需要有一個(gè)對(duì)象 在消息進(jìn)行轉(zhuǎn)發(fā)之前需要從對(duì)象里面取對(duì)應(yīng)方法選標(biāo)的方法簽名 然后在forwardInvocation這個(gè)方法里面將這個(gè)消息轉(zhuǎn)發(fā)給那個(gè)對(duì)象
2 .h定義接收轉(zhuǎn)發(fā)消息的對(duì)象 OtherObject
3 這里去方法選標(biāo)就需要在取這個(gè)方法選標(biāo)在OtherObject這個(gè)類里面對(duì)應(yīng)的方法簽名
4 如果是學(xué)習(xí)的時(shí)候我們才會(huì)轉(zhuǎn)發(fā) 判斷之后 返回方法簽名
5 先判斷 在進(jìn)行消息轉(zhuǎn)發(fā) 將消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象
當(dāng)有l(wèi)earn這個(gè)方法選標(biāo)傳過來的時(shí)候首先會(huì)從otherObject里面去方法選標(biāo)所對(duì)應(yīng)的方法簽名如果取到則會(huì)執(zhí)行forwardInvocation:方法

七、Runtime的項(xiàng)目應(yīng)用

1坝锰、實(shí)例變量遍例

遍例類的所有實(shí)例變量粹懒,實(shí)現(xiàn)自動(dòng)歸檔-反歸檔
Ivar *ivars = class_copyIvarList([self class], &count);

//    KVC中setValue中使用
//    我們知道在KVC中如果直接setValue如果對(duì)象沒有這個(gè)屬性或者是變量就會(huì)直接Crash,如:
    SomeObj *obj = [[SomeObj alloc]init];
    [obj setValue:@"value" forKey:@"objName"];
//    SomeObj 沒有objName這個(gè)屬性
  //    這段代碼會(huì)直接Crash顷级,使用runtime遍例實(shí)例變量避免

我們創(chuàng)建一個(gè)NSObject的類目

#import <Foundation/Foundation.h>
@interface NSObject (AutoEncode)<NSCoding>
-(instancetype)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;
@end

#import "NSObject+AutoEncode.h"
#import <objc/objc-runtime.h>
@implementation NSObject (AutoEncode)
-(void)encodeWithCoder:(NSCoder *)aCoder{
    //獲取這個(gè)類的實(shí)例變量鏈表
    //創(chuàng)建一個(gè)ivarCount保存實(shí)例變量個(gè)數(shù)
    unsigned int ivarCount = 0;
    Ivar *vars = class_copyIvarList([self class], &ivarCount);
    //循環(huán)遍歷實(shí)例變量鏈表
    for (int i = 0; i < ivarCount; i++) {
        //獲得實(shí)例變量的名字
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(vars[i])];
        //通過kvc獲得實(shí)例變量的值
        id value = [self valueForKey:ivarName];
        //對(duì)此值 以實(shí)例變量名作為key進(jìn)行歸檔
        [aCoder encodeObject:value forKey:ivarName];
    }
    free(vars);//釋放鏈表
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    //初始化
    self = [self init];//根 NSObject
    if (self) {
        //獲取實(shí)例變量鏈表
        unsigned int ivarCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &ivarCount);
        //循環(huán)便利實(shí)例變量鏈表
        for (int i = 0; i < ivarCount; i++) {
            //獲取實(shí)例變量名字
            NSString *varName = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
            //以實(shí)例變量的名字進(jìn)行反歸檔
            id value= [aDecoder decodeObjectForKey:varName];
            
            //通過KVC對(duì)此對(duì)象進(jìn)行賦值
            [self setValue:value forKey:varName];
        }
        free(ivars);
    }
    return self;
}
@end

#import "ViewController.h"
#import "BeiDaStudent.h"
@interface ViewController ()
@end
#在此我們并沒有直接遵守NSCoding協(xié)議 但是能夠自動(dòng)進(jìn)行歸檔個(gè)反歸檔的操作
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建對(duì)象
    BeiDaStudent *stu = [BeiDaStudent new];
    stu.name = @"張三";
    stu.age = 25;
    //進(jìn)行歸檔
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:stu];
    //進(jìn)行反歸檔并打印出數(shù)據(jù)
    BeiDaStudent *stu1 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    NSLog(@"北大的學(xué)生name = %@,age = %ld",stu1.name,stu1.age);
    // Do any additional setup after loading the view, typically from a nib.
}

2凫乖、動(dòng)態(tài)關(guān)聯(lián)對(duì)象
使用Runtime來在一個(gè)已有對(duì)象上動(dòng)態(tài)的掛載另一個(gè)對(duì)象
如:如果你在對(duì)象傳遞(傳參)的時(shí)候需要用到某個(gè)屬性,按照以往的思路:我繼承這個(gè)類重新創(chuàng)建一個(gè)新類就完事了弓颈,這個(gè)思路沒有問題帽芽,但麻煩,要是有一個(gè)方法能直接將我想要的屬性掛載上去豈不是更好翔冀?代碼簡(jiǎn)單导街、易懂。
實(shí)際開發(fā)中纤子,一個(gè)UIViewController中多個(gè)UITableView(或UIAlertView)它們的代理相同搬瑰,動(dòng)態(tài)關(guān)聯(lián)后在代理方法中就可以分別不同對(duì)象進(jìn)行不同處理。
下面就來講解下如何使用Runtime來 在已有對(duì)象上動(dòng)態(tài)掛載另外一個(gè)對(duì)象控硼。

//    關(guān)聯(lián)對(duì)象
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//    獲取關(guān)聯(lián)對(duì)象
    id objc_getAssociatedObject(id object, const void *key)
//    移除關(guān)聯(lián)對(duì)象
    void objc_removeAssociatedObjects(id object)

我們假設(shè)BeiDaStudent作為BeiDa的代理去執(zhí)行學(xué)習(xí)iOS

#import <Foundation/Foundation.h>
@interface BeiDaStudent : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)NSInteger age;
//BeiDaStudent作為BeiDa的代理去執(zhí)行學(xué)習(xí)iOS
-(void)learniOSProgram;
@end

#import "BeiDaStudent.h"
@implementation BeiDaStudent
-(void)learniOSProgram{
    NSLog(@"%@ 努力學(xué)習(xí)iOS編程",self.name);
}
@end

BeiDa類

#import <Foundation/Foundation.h>
@interface BeiDa : NSObject
-(void)addDelegate:(id)aDelegate;
-(void)removeDelegate:(id)aDelegate;
@end

#import "BeiDa.h"
#import "BeiDaStudent.h"
#import <objc/objc-runtime.h>

//動(dòng)態(tài)關(guān)聯(lián)對(duì)象的函數(shù)需要有一個(gè)key泽论,通過key進(jìn)行賦值取值
const char *kMultiDelegateKey = "kMultiDelegateKey";
@implementation BeiDa

-(void)addDelegate:(id)aDelegate{
//    關(guān)聯(lián)數(shù)組
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, kMultiDelegateKey);
    if (!delegateArray) {
        
        delegateArray = [NSMutableArray new];
        //第一個(gè)參數(shù):當(dāng)前對(duì)象
        //第二個(gè)參數(shù):關(guān)聯(lián)是所需要的key
        //第三個(gè)參數(shù):就是與當(dāng)前對(duì)象進(jìn)行關(guān)聯(lián)的對(duì)象
        //第四個(gè)參數(shù):關(guān)聯(lián)規(guī)則 枚舉類型
        objc_setAssociatedObject(self, kMultiDelegateKey, delegateArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    if(aDelegate){
        [delegateArray addObject:aDelegate];
    }
}
-(void)removeDelegate:(id)aDelegate{
    NSMutableArray *delegateArray = objc_getAssociatedObject([self class], kMultiDelegateKey);
    [delegateArray removeObject:aDelegate];
}

//消息轉(zhuǎn)發(fā)
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMutableArray *delegateArray =objc_getAssociatedObject(self , kMultiDelegateKey);
    if (delegateArray) {
        for (id aDelegate in delegateArray) {
            return [aDelegate methodSignatureForSelector:aSelector];
        }
    }
    return [self methodSignatureForSelector:@selector(doNothing)];
}
//什么都不做
-(void)doNothing{
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, kMultiDelegateKey);
    if (delegateArray) {
        for (id aDelegate in delegateArray) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [anInvocation invokeWithTarget:aDelegate];
            });
        }
    }
}
@end

接下來我們?cè)赩iewController里面做:

BeiDa *beida = [BeiDa new];

    //創(chuàng)建多個(gè)student作為多代理
    BeiDaStudent *student1 = [[BeiDaStudent alloc]init];
    student1.name =@"張三";
    BeiDaStudent *student2 = [[BeiDaStudent alloc]init];
    student2.name = @"李四";
    BeiDaStudent *student3 = [[BeiDaStudent alloc]init];
    student3.name = @"王五";
    
    //將三個(gè)student對(duì)象添加為beida的代理
    [beida addDelegate:student1];
    [beida addDelegate:student2];
    [beida addDelegate:student3];
    
    //通過 performSelector:aSelector方法,調(diào)用代理方法
    [beida performSelector:@selector(learniOSProgram)];  

運(yùn)行結(jié)果是:



3、動(dòng)態(tài)關(guān)聯(lián)函數(shù)/方法
動(dòng)態(tài)關(guān)聯(lián)方法卡乾,在運(yùn)時(shí)期動(dòng)態(tài)增加翼悴、交換方法。
替換系統(tǒng)方法说订,自動(dòng)實(shí)現(xiàn)功能抄瓦。
遍例系統(tǒng)類的方法,查看新特性陶冷。

1钙姊、聲明并實(shí)現(xiàn)函數(shù)
void newFunction(id self, IMP _imp) {
    // doSomething在這里實(shí)現(xiàn)功能
}
2、為類添加新方法
class_addMethod([self class], sel_registerName("someNewMethod"), (IMP)newFunction, “v@:");
3埂伦、調(diào)用此方法
objc_msgSend((id)self, sel_registerName("someNewMethod"));

接下來我們通過runtime事先觀察者KVO

#import <Foundation/Foundation.h>

@interface NSObject (CKKVO)

- (void)ck_addObserver:(id)observer ForKey:(NSString *)key WithBlock:(void(^)(id observedObject,NSString *key,id oldValue,id newValue))block;

- (void)ck_removeObserver:(id)observer ForKey:(NSString *)key;

@end


#import "NSObject+CKKVO.h"
#import <objc/objc-runtime.h>

NSString *const kCKKVOClassPrefix = @"CKKVOClassPrefix_";
NSString *const kCKObserversAssociatedKey = @"CKObserversAssociatedKey";

typedef void(^ObserverBlock)(id observedObject, NSString *key, id oldValue, id newValue);
// 創(chuàng)建一個(gè)用于存放觀察者info的類
@interface CKObserverInfo : NSObject
// 觀察者屬性
@property (nonatomic, weak) id observer;
// key屬性
@property (nonatomic, copy) NSString *key;
// 回調(diào)block
@property (nonatomic, copy) ObserverBlock block;
@end


@implementation CKObserverInfo
// 初始化方法
- (instancetype)initWithObserver:(id)observer ForKey:(NSString *)key WithBlock:(ObserverBlock)block {
    self = [super init];
    if (self) {
        _observer = observer;
        _key = key;
        _block = block;
    }
    return self;
}

@end



@implementation NSObject (CKKVO)
- (void)ck_addObserver:(id)observer ForKey:(NSString *)key WithBlock:(void(^)(id,NSString *,id,id))block {
    // 獲取 setterName
    NSString *setName = setterName(key);
    SEL setSelector = NSSelectorFromString(setName);
    // 通過SEL獲得方法
    Method setMethod = class_getInstanceMethod(object_getClass(self), setSelector);
    if (!setMethod) {
        @throw [NSException exceptionWithName:@"CKKVO Error" reason:@"若無setter方法煞额,無法KVO" userInfo:nil];
    }
    // 獲得當(dāng)前類
    // 判斷是否已經(jīng)創(chuàng)建衍生類
    Class thisClass = object_getClass(self);
    NSString *thisClassName = NSStringFromClass(thisClass);
    if (![thisClassName hasPrefix:kCKKVOClassPrefix]) {
        thisClass = [self makeKVOClassWithOriginalClassName:thisClassName];
        // 改變類的標(biāo)識(shí)
        object_setClass(self, thisClass);
    }
    // 判斷衍生類中是否實(shí)現(xiàn)了setter方法
    if(![self hasSelector:setSelector]) {
        const char *setType = method_getTypeEncoding(setMethod);
        class_addMethod(object_getClass(self), setSelector, (IMP)ck_setter, setType);
    }
    // 將observer添加到觀察者數(shù)組
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kCKObserversAssociatedKey));
    if (!observers) {
        observers = [NSMutableArray new];
        objc_setAssociatedObject(self, (__bridge const void *)(kCKObserversAssociatedKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    // 創(chuàng)建觀察者info類
    CKObserverInfo *observerInfo = [[CKObserverInfo alloc]initWithObserver:observer ForKey:key WithBlock:block];
    [observers addObject:observerInfo];
}
void ck_setter(id objc_self, SEL cmd_p, id newValue) {
    // setterName轉(zhuǎn)為name
    NSString *setName = NSStringFromSelector(cmd_p);
    NSString *key = nameWithSetName(setName);
    // 通過KVC獲取key對(duì)應(yīng)的value
    id oldValue = [objc_self valueForKey:key];
    // 將set消息轉(zhuǎn)發(fā)給父類
    struct objc_super selfSuper = {
        .receiver = objc_self,
        .super_class = class_getSuperclass(object_getClass(objc_self))
    };
    objc_msgSendSuper(&selfSuper,cmd_p,newValue);
    // 調(diào)用block
    NSMutableArray *observers = objc_getAssociatedObject(objc_self, (__bridge const void *)kCKObserversAssociatedKey);
    for (CKObserverInfo *info in observers) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            if ([info.key isEqualToString:key]) {
                info.block(objc_self,key,oldValue,newValue);
            }
        });
    }
}
// 從setterName轉(zhuǎn)為name
NSString *nameWithSetName(NSString *setName) {
    if (setName.length <= 4 || ![setName hasPrefix:@"set"] || ![setName hasSuffix:@":"]) {
        @throw [NSException exceptionWithName:@"CKKVO Error" reason:@"set方法not available" userInfo:nil];
    }
    NSString *Name = [setName substringWithRange:NSMakeRange(3, setName.length - 4)];
    NSString *firstCharacter = [Name substringToIndex:1];
    return [[firstCharacter lowercaseString] stringByAppendingString:[Name substringFromIndex:1]];
}
// 判斷set方法是否存在
- (BOOL)hasSelector:(SEL)aSelector {
    unsigned int mCount = 0;
    Method *methods = class_copyMethodList(object_getClass(self), &mCount);
    for (int i = 0; i < mCount; i ++) {
        Method method = methods[i];
        SEL setSelector = method_getName(method);
        if (setSelector == aSelector) {
            free(methods);
            return YES;
        }
    }
    free(methods);
    return NO;
}
// 通過runtime創(chuàng)建類
- (Class)makeKVOClassWithOriginalClassName:(NSString *)className {
    NSString *kvoClassName = [kCKKVOClassPrefix stringByAppendingString:className];
    Class kvoClass = NSClassFromString(kvoClassName);
    if (kvoClass) {
        return kvoClass;
    }
    // objc_allocateClassPair創(chuàng)建類
    kvoClass = objc_allocateClassPair(object_getClass(self), kvoClassName.UTF8String, 0);
    objc_registerClassPair(kvoClass);
    return kvoClass;
}
// 通過key獲取對(duì)應(yīng)的setterName
NSString *setterName(NSString *key) {
    if (key.length == 0) {
        @throw [NSException exceptionWithName:@"CKKVO Error" reason:@"沒有對(duì)應(yīng)的key" userInfo:nil];
    }
    NSString *firstCharacter = [key substringToIndex:1];
    NSString *Name = [[firstCharacter uppercaseString]stringByAppendingString:[key substringFromIndex:1]];
    return [NSString stringWithFormat:@"set%@:",Name];
}
- (void)ck_removeObserver:(id)observer ForKey:(NSString *)key {
    // 刪除觀察者
    CKObserverInfo *removeInfo = nil;
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kCKObserversAssociatedKey));
    for (CKObserverInfo *info in observers) {
        if (info.observer == observer && [info.key isEqualToString:key]) {
            removeInfo = info;
        }
    }
    [observers removeObject:removeInfo];
}
@end

然后在viewDidLoad:里面

    // 異步Block的KVO
    [p1 ck_addObserver:self ForKey:@"name" WithBlock:^(id observedObject, NSString *key, id oldValue, id newValue) {
        
        NSLog(@"oldValue = %@,%@",oldValue,[NSThread currentThread]);
        NSLog(@"newValue = %@,%@",newValue,[NSThread currentThread]);
    }];
    p1.name = @"456";
    p1.name = @"789";
    [p1 ck_removeObserver:self ForKey:@"name"];
    p1.name = @"hahahaha";

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膊毁,一起剝皮案震驚了整個(gè)濱河市胀莹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婚温,老刑警劉巖描焰,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異栅螟,居然都是意外死亡荆秦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門力图,熙熙樓的掌柜王于貴愁眉苦臉地迎上來步绸,“玉大人,你說我怎么就攤上這事吃媒∪拷椋” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵赘那,是天一觀的道長(zhǎng)刑桑。 經(jīng)常有香客問我,道長(zhǎng)漓概,這世上最難降的妖魔是什么漾月? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮胃珍,結(jié)果婚禮上梁肿,老公的妹妹穿的比我還像新娘。我一直安慰自己觅彰,他們只是感情好吩蔑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著填抬,像睡著了一般烛芬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上飒责,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天赘娄,我揣著相機(jī)與錄音,去河邊找鬼宏蛉。 笑死遣臼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拾并。 我是一名探鬼主播揍堰,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鹏浅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了屏歹?” 一聲冷哼從身側(cè)響起隐砸,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝙眶,沒想到半個(gè)月后季希,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幽纷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年胖眷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霹崎。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖冶忱,靈堂內(nèi)的尸體忽然破棺而出尾菇,到底是詐尸還是另有隱情,我是刑警寧澤囚枪,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布派诬,位于F島的核電站,受9級(jí)特大地震影響链沼,放射性物質(zhì)發(fā)生泄漏默赂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一括勺、第九天 我趴在偏房一處隱蔽的房頂上張望缆八。 院中可真熱鬧,春花似錦疾捍、人聲如沸奈辰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奖恰。三九已至,卻和暖如春宛裕,著一層夾襖步出監(jiān)牢的瞬間瑟啃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工揩尸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛹屿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓疲酌,卻偏偏與公主長(zhǎng)得像蜡峰,于是被迫代替她去往敵國(guó)和親了袁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉湿颅,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,719評(píng)論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,560評(píng)論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂樂的簡(jiǎn)書閱讀 2,135評(píng)論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 735評(píng)論 0 2
  • Runtime是什么 Runtime 又叫運(yùn)行時(shí)载绿,是一套底層的 C 語言 API,其為 iOS 內(nèi)部的核心之一油航,我...
    SuAdrenine閱讀 878評(píng)論 0 3