iOS-runtime通篇詳解-上

原創內容,轉載請注明出處:

http://www.lxweimin.com/p/0e6eb2f9ed5d

前言
上一篇的時候一直說要把runtime說一遍,后來一直沒時間耽誤下來了。現在馬上要換工作了,騰出時間了,可以把這些東西再過一遍了,一方面做一下復習,另外也還一下以前欠下的嘴債。畢竟,出來混早晚是都要還的。
相信現在還有好多朋友不知道runtime,或者說不太理解,不太會用等等。
總之一句話就是還是用不666,不要緊,看完下面這篇應該就可以用6了。

在這里先放上runtime的源碼和runtime官方api:
源碼:objc-runtime
官方API:Objective-C Runtime Reference

所謂的runtime,就是運行時。。。
不知道這么說會不會有人打我,并說我廢話,但是從字面上理解并沒有什么毛病。我們都知道oc是動態語言,所謂的動態語言就是指程序在運行時可以改變其結構:新的函數可以被引進,已有的函數可以被刪除等在結構上的變化。的語言。因為它可以在程序執行過程中對類或者變量等等做操作,而不是代碼寫完了,程序就定型了。
而iOS中的runtime就是可以實現語言動態的一組API.
你可以理解:它就僅僅是一組API而已
只是這組API看起來比oc長得不太一樣,見的少了會覺得它們比較混亂,亂起八糟的。
現在還不會runtime基本上可以歸結為下面兩條原因:

  • 對runtime的api不熟
  • 對api的各個部分關系不太熟

看過了這篇,大家應該都沒什么問題了。
廢話不多說了,開始實質性的東西吧!

runtime的所有知識基本都圍繞兩個中心(1)類的各個方面的動態配置(2)消息傳遞

要動態配置類就需要知道類的本質是什么,我們可以從<objc/runtime.h>里面看到類的定義:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class;//父類
    const char *name;//類名
    long version;//類的版本信息,默認為0
    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;
isa和super_class

不同的類中可以有相同的方法,同一個類中不可以有相同的方法,判斷是不是同一個方法只和方法名有關系,和參數沒關系。
比如說:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{}

的方法名為

@selector(tableView:canEditRowAtIndexPath:)

可見方法名里面并沒有體現參數的位置,所以是否是同一個方法取決于方法名是否相同,和參數沒關系。
要找到方法首先要先確定是那個類。isa和super_class是找到實現函數的關鍵映射,決定找到存放在哪個類的方法實現。(isa用于自省確定所屬類,super_class確定繼承關系)。
實例對象的isa指針指向類,類的isa指針指向其元類(metaClass)。對象就是一個含isa指針的結構體。類存儲實例對象的方法列表,元類存儲類的方法列表,元類也是類對象。

當創建實例對象時,分配的內存包含一個objc_object數據結構,然后是類到父類直到根類NSObject的實例變量的數據。NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來創建objc_object數據結構。
向一個Objective-C對象發送消息時,運行時庫會根據實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表由super_class指針找到父類的方法列表直至根類NSObject中去尋找與消息對應的selector指向的方法。找到后即運行這個方法。

isa和super_class指針的圖解.png
  • isa:實例對象->類->元類->(不經過父元類)直接到根元類(NSObject的元類),根元類的isa指向自己;
  • superclass:類->父類->...->根類NSObject,元類->父元類->...->根元類->根類,NSObject的superclass指向nil。
在正式學習runtime之前你需要知道的
屬性概念
  • SEL:類成員方法的指針,但不同于C語言中的函數指針,函數指針直接保存了方法的地址,但SEL只是方法編號。
  • IMP:一個函數指針,保存了方法的地址
  • Method:方法的結構體,其中保存了方法的名字,實現和類型描述字符串
代碼
//在runtime.h里面我們可以看到定義
//Method 是一個方法結構體的指針
typedef struct objc_method *Method;
//方法的結構體包含了方法需要的信息
struct objc_method {
    SEL method_name;
    char *method_types;//方法返回值,和各個參數類型等的字符串描述
    IMP method_imp;
}   
//根據函數獲取函數的SEL
@selector()
//獲取函數指針SEL的函數名字符串
NSString *NSStringFromSelector(SEL aSelector);
//根據函數名獲取函數指針
SEL NSSelectorFromString(NSString *aSelectorName);
//獲取類Class的字符串描述
NSString *NSStringFromClass(Class aClass);
//根據類的字符串描述獲取類Class
Class _Nullable NSClassFromString(NSString *aClassName);
//獲取協議的字符串描述(協議名字)
NSString *NSStringFromProtocol(Protocol *proto)
//根據協議名字獲取協議對象
Protocol * _Nullable NSProtocolFromString(NSString *namestr)

上面的方法我平時用到的頻率還不算太低,在這里就暫時不實際跑一下了。知道了上面的那些平常還有些頻率用到的函數或概念,那么接下來學習runtime就有些輕松了。

下面會用到的編碼值
//下面對應的編碼值可以在官方文檔里面找到
//編碼值   含意
//c     代表char類型
//i     代表int類型
//s     代表short類型
//l     代表long類型,在64位處理器上也是按照32位處理
//q     代表long long類型
//C     代表unsigned char類型
//I     代表unsigned int類型
//S     代表unsigned short類型
//L     代表unsigned long類型
//Q     代表unsigned long long類型
//f     代表float類型
//d     代表double類型
//B     代表C++中的bool或者C99中的_Bool
//v     代表void類型
//*     代表char *類型
//@     代表對象類型
//#     代表類對象 (Class)
//:     代表方法selector (SEL)
//[array type]  代表array
//{name=type…}  代表結構體
//(name=type…)  代表union
//bnum  A bit field of num bits
//^type     A pointer to type
//?     An unknown type (among other things, this code is used for function pointers)
(語法&API)class-get

//獲取類名
//入參:類Class
//返回:類名char數組
const char *class_getName(Class cls)

//獲取父類
//入參:類Class
//返回:類Class
Class class_getSuperclass(Class cls)

//獲取實例大小(返回size_t)
//入參:實例的類Class
//返回:大小size_t
//深究請看這篇文章http://www.lxweimin.com/p/df6b252fbaae
size_t class_getInstanceSize(Class cls)

//獲取類中指定名稱實例成員變量的信息
//入參:類Class,變量名
//返回:變量信息Ivar
//* 1.實例變量是指變量不是屬性.例如某類有個屬性為:username 那么它對應的實例變量為_username
//* 2.這個方法可以獲取屬性的變量,也可以獲取私有變量(這點很重要)
//* 3.如果獲取的變量為空,那么 ivar_getName和 ivar_getTypeEncoding 獲取的值為空,那么[NSString stringWithUTF8String:ivar1Name] 執行崩潰
Ivar class_getInstanceVariable(Class cls, const char *name)

//類成員變量的信息
//入參:類Class,變量名char數組
//返回:Ivar
//* 1.目前沒有找到關于Objective-C中類變量的信息,一般認為Objective-C不支持類變量。注意,返回的列表不包含父類的成員變量和屬性。
Ivar class_getClassVariable(Class cls, const char *name)

//獲取指定的屬性
//入參:類Class,屬性名char數組
//返回:屬性objc_property_t
// *  1.屬性不是變量,此方法只能獲取屬性
// *  2.如果屬性不存在那么返回的結構體為0(可以參考下面的判斷)
// *  3.屬性不存在獲取property_getName 和 property_getAttributes 會崩潰
objc_property_t class_getProperty(Class cls, const char *name)

//獲取方法實現
//入參:類Class,方法名SEL
//返回:方法實現IMP
IMP class_getMethodImplementation(Class cls, SEL name)
//獲取方法實現
//入參:類Class,方法名SEL
//返回:方法實現IMP
IMP class_getMethodImplementation_stret(Class cls, SEL name)

//獲取類方法
//入參:類Class,方法名SEL
//返回:方法Method
Method class_getClassMethod(Class cls, SEL name)
(以上API的)運行測試代碼地址在這里:TFRumtimeAll:demo-runtime-part0
#import <objc/runtime.h>
@interface ViewController ()
@property (nonatomic,strong)NSArray *property0;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //獲取類名
    //入參:類Class
    //返回:類名char數組
    const char *result0 = class_getName([ViewController class]);
    NSLog(@">>>>>>>>0:%@",[NSString stringWithUTF8String:result0]);
    
    //獲取父類
    //入參:類Class
    //返回:類Class
    Class result1 = class_getSuperclass([ViewController class]);
    NSLog(@">>>>>>>>1:%@",result1);
    
    //獲取實例大小(返回size_t)
    //入參:實例的類Class
    //返回:大小size_t
    //深究請看這篇文章http://www.lxweimin.com/p/df6b252fbaae
    size_t result2 = class_getInstanceSize([ViewController class]);
    NSLog(@">>>>>>>>2:%zu",result2);
    
    //獲取類中指定名稱實例成員變量的信息
    //入參:類Class,變量名
    //返回:變量信息Ivar
    //* 1.實例變量是指變量不是屬性.例如某類有個屬性為:username 那么它對應的實例變量為_username
    //* 2.這個方法可以獲取屬性的變量,也可以獲取私有變量(這點很重要)
    //* 3.如果獲取的變量為空,那么 ivar_getName和 ivar_getTypeEncoding 獲取的值為空,那么[NSString stringWithUTF8String:ivar1Name] 執行崩潰
    const char *result3 = [@"property0" UTF8String];
    Ivar result4 = class_getInstanceVariable([ViewController class], result3);
    NSLog(@">>>>>>>>3:%@",result4);
    
    //獲取指定的屬性
    //入參:類Class,屬性名char數組
    //返回:屬性objc_property_t
    // *  1.屬性不是變量,此方法只能獲取屬性
    // *  2.如果屬性不存在那么返回的結構體為0(可以參考下面的判斷)
    // *  3.屬性不存在獲取property_getName 和 property_getAttributes 會崩潰
    const char *result5 = [@"property0" UTF8String];
    objc_property_t result6 = class_getProperty([ViewController class], result5);
    NSLog(@">>>>>>>>4:%@",[NSString stringWithUTF8String:property_getName(result6)]);
    
    //獲取方法實現
    //入參:類Class,方法名SEL
    //返回:方法實現IMP
    IMP result7 = class_getMethodImplementation([ViewController class], @selector(method0));
    result7();
    
    //獲取方法實現
    //入參:類Class,方法名SEL
    //返回:方法實現IMP
    IMP result8 = class_getMethodImplementation_stret([ViewController class], @selector(method1));
    result8();
    
    //獲取類方法
    //入參:類Class,方法名SEL
    //返回:方法Method
    Method result9 = class_getClassMethod([ViewController class], @selector(viewDidLoad));
    NSLog(@">>>>>>>>7:%@",result9);
}
-(void)method0{
    NSLog(@">>>>>>>>5");
}
-(void)method1{
    NSLog(@">>>>>>>>6");
}
@end
(以上運行測試的)打印結果
demo-runtime-part0[984:85403] >>>>>>>>0:ViewController
demo-runtime-part0[984:85403] >>>>>>>>1:UIViewController
demo-runtime-part0[984:85403] >>>>>>>>2:768
demo-runtime-part0[984:85403] >>>>>>>>3:(null)
demo-runtime-part0[984:85403] >>>>>>>>4:property0
demo-runtime-part0[984:85403] >>>>>>>>5
demo-runtime-part0[984:85403] >>>>>>>>6
demo-runtime-part0[984:85403] >>>>>>>>7:(null)
(以上打印結果的)解析
#從上面的測試我們可以發現各個函數的作用:

###class_getName:
獲取類的字符串描述,由此我們可以想到上面提到的一個OC方法:NSStringFromClass(),這個方法和此函數的作用是一樣的。
那么我們可以猜想NSStringFromClass()的上層實現是runtime的class_getName函數(有興趣的朋友可以反編譯驗證一下)。

###class_getSuperclass:
獲取某個類的父類。同理我們可以想到NSObject里面有個屬性:
@property (readonly) Class superclass;那么這個屬性的get方法也可能是調用的此函數。

###class_getInstanceSize:
獲取實例大小,單位是字節。這個函數暫時不著重說了。有時間可以和其他幾個不常用的函數單獨細說一下。

###class_getInstanceVariable:
獲取實例的變量。在上面打印3我們看到獲取到的是nil。從代碼中我們命名定義了:
@property (nonatomic,strong)NSArray *property0;
但是為什么找不到?是因為此函數獲取的是變量,上面的屬性對應的變量是_property0所以以property0變量名找是找不到的。
需要強調的是,此函數可以找到任何屬于這個類的變量,包括分類添加的變量,程序動態添加的變量等。

###class_getProperty:
獲取類屬性。注意這里是屬性,不是變量。此函數只能獲取類的屬性。
該函數返回是是objc_property_t結構體。上面demo代碼里面用到了函數:property_getName,這個函數的是獲取屬性名,下面會說到

###class_getMethodImplementation:
###class_getMethodImplementation_stret:
獲取函數的實現。返回函數的實現指針IMP.
從上面代碼看到我們獲取了method0和method1的函數實現并result7();result8();執行了兩個函數,并且函數得到了執行。

###class_getClassMethod:
獲取類方法Method結構體。從runtime.h源碼里可以找到源碼:
typedef struct objc_method *Method;
struct objc_method {
    SEL method_name;
    char *method_types;
    IMP method_imp;
} 
總結:
  • 從上面部分runtime API可以看出,iOS中所謂的runtime.h只是一組c語音的api,它用的c語言的語法,可以做一些OC做不到的功能.
  • 它的語法有一定的規律,這點很重要。比如你可以使用xcode的智能提示,如下圖:


    runtime0.png

知道了上面的規律,那接下來學就方便多了。繼續往下看:

class-copy
//獲取變量列表
//入參:類Class,int變量指針
//返回:變量信息Ivar列表
//*  1.獲取所有私有變量和屬性對應的變量
//*  2.獲取的私有變量的名和定義的名一樣
//*  3.獲取的屬性的名前面都添加了下劃線
//*  4.不能獲取Category添加的變量(動態綁定的變量)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

//獲取屬性列表(只獲取屬性不獲取變量)
//入參:類Class,int變量指針
//返回:屬性信息objc_property_t列表
//*  1.獲取所有屬性
//*  2.獲取的屬性名和你代碼寫的一樣,獲取出來的屬性名不自動添加下劃線
//*  3.不能獲取Category添加的屬性。
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

//獲取方法列表
//入參:類Class,int變量指針
//返回:方法信息Method列表
//*  1.獲取所有實例方法,不包含靜態方法
//*  2.不獲取父類的方法
//*  3.隱式的get set 方法也能獲取到
//*  4.可以獲取分類和動態添加的方法。
Method *class_copyMethodList(Class cls, unsigned int *outCount)

//獲取協議列表
//入參:類Class,int變量指針
//返回:方法協議Protocol列表
//* 1.不能獲取分類實現的協議
Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)
(以上API的)運行測試代碼地址在這里:TFRumtimeAll:demo-runtime-part1
#import "ViewController.h"
#import <objc/message.h>
@interface ViewController ()<UITableViewDataSource>
{
    NSArray *_property0;
    NSArray *property1;
}
@property (nonatomic,strong)UIColor *property2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //class獲取--獲取整個成員變量列表
    /**
     *  1.獲取所有私有變量和屬性
     *  2.獲取的私有變量的名和定義的名一模一樣
     *  3.獲取的屬性的名前面都添加了下劃線
     */
    unsigned int copyIvarListCount = 0;
    Ivar *ivars = class_copyIvarList([self class], &copyIvarListCount);
    for (NSInteger i = 0; i< copyIvarListCount; i ++) {
        
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSLog(@">>>>>>>>0:class_copyIvarList:%s",name);
    }
    free(ivars);//釋放
    NSLog(@"\n");
    
    //class獲取--獲取整個屬性列表(只獲取屬性不獲取變量)
    /**
     *  1.獲取所有屬性
     *  2.獲取的屬性名和你代碼寫的一樣,獲取出來的屬性名不自動添加下劃線
     */
    unsigned int copyPropertyListCount = 0;
    objc_property_t *propertys = class_copyPropertyList([self class], &copyPropertyListCount);
    for (NSInteger i = 0; i < copyPropertyListCount; i++) {
        objc_property_t property = propertys[i];
        const char *name = property_getName(property);
        NSLog(@">>>>>>>>1:copyPropertyList:%s",name);
    }
    free(propertys);//釋放
    NSLog(@"\n");
    
    
    //class獲取--獲取整個類的實例方法的方法列表
    /**
     *  1.獲取所有實例方法,不包含靜態方法
     *  2.不獲取父類的方法
     *  3.隱式的get set 方法也能獲取到
     */
    unsigned int copycopyMethodListCount = 0;
    Method *methods = class_copyMethodList([self class], &copycopyMethodListCount);
    for (NSInteger i = 0; i < copycopyMethodListCount; i++) {
        Method method = methods[i];
        SEL name = method_getName(method);
        NSLog(@">>>>>>>>2:copyMethodList:%@",NSStringFromSelector(name));
    }
    free(methods);//釋放
    NSLog(@"\n");
    
    
    //添加--協議
    /**
     * 1.class_addProtocol  參數含義:第一個:要添加協議的類,第二個:協議對象
     * 2.獲取協議列表具體細節參照Class1里的內容
     */
    unsigned int copyProtocolListCount = 0;
    Protocol * __unsafe_unretained *protocals = class_copyProtocolList([self class], &copyProtocolListCount);
    for (NSInteger i = 0; i < copyProtocolListCount; i++) {
        Protocol * protocal = protocals[i];
        const char *name = protocol_getName(protocal);
        NSLog(@">>>>>>>>3:copyProtocolList:%s",name);
    }
    free(protocals);//釋放
    NSLog(@"\n");
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{return 0;}

// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{return nil;}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
(以上運行測試的)打印結果
demo-runtime-part1[3085:698841] >>>>>>>>0:class_copyIvarList:_property0
demo-runtime-part1[3085:698841] >>>>>>>>0:class_copyIvarList:property1
demo-runtime-part1[3085:698841] >>>>>>>>0:class_copyIvarList:_property2
demo-runtime-part1[3085:698841] 
demo-runtime-part1[3085:698841] >>>>>>>>1:copyPropertyList:property2
demo-runtime-part1[3085:698841] >>>>>>>>1:copyPropertyList:hash
demo-runtime-part1[3085:698841] >>>>>>>>1:copyPropertyList:superclass
demo-runtime-part1[3085:698841] >>>>>>>>1:copyPropertyList:description
demo-runtime-part1[3085:698841] >>>>>>>>1:copyPropertyList:debugDescription
demo-runtime-part1[3085:698841] 
demo-runtime-part1[3085:698841] >>>>>>>>2:copyMethodList:property2
demo-runtime-part1[3085:698841] >>>>>>>>2:copyMethodList:setProperty2:
demo-runtime-part1[3085:698841] >>>>>>>>2:copyMethodList:.cxx_destruct
demo-runtime-part1[3085:698841] >>>>>>>>2:copyMethodList:tableView:numberOfRowsInSection:
demo-runtime-part1[3085:698841] >>>>>>>>2:copyMethodList:tableView:cellForRowAtIndexPath:
demo-runtime-part1[3085:698841] >>>>>>>>2:copyMethodList:didReceiveMemoryWarning
demo-runtime-part1[3085:698841] >>>>>>>>2:copyMethodList:viewDidLoad
demo-runtime-part1[3085:698841] 
demo-runtime-part1[3085:698841] >>>>>>>>3:copyProtocolList:UITableViewDelegate
demo-runtime-part1[3085:698841] >>>>>>>>3:copyProtocolList:UITableViewDataSource
demo-runtime-part1[3085:698841] 

(以上打印結果的)解析
#從上面的測試我們可以發現各個函數的作用:

###class_copyIvarList:
拷貝變量列表。返回的一個Ivar列表的指針。獲取Ivar需要遍歷這個列表。
注意:調用copy的函數需要釋放資源free();
###class_copyPropertyList:
拷貝屬性列表。返回的一個objc_property_t列表的指針。獲取objc_property_t需要遍歷這個列表。
注意:調用copy的函數需要釋放資源free();

###class_copyMethodList:
拷貝方法列表。返回的一個Method列表的指針。獲取Method需要遍歷這個列表。
注意:調用copy的函數需要釋放資源free();
此函數可以獲取分類方法。

###class_copyProtocolList:
拷貝協議列表。返回的一個Ivar列表的指針。獲取Ivar需要遍歷這個列表。
注意:調用copy的函數需要釋放資源free();
此函數不能獲取分類中添加的協議。
此函數可以獲取動態添加的協議。
class-add
//動態添加變量
//入參:類Class,變量名char數組,變量類型大小size_t,變量在內存中的對齊方式,變量的type類型
//返回:添加結果,是否成功。
//* 1.只能給動態創建的類添加變量也就是用 objc_allocateClassPair 創建的類
//* 2.添加變量只能在函數 objc_allocateClassPair 和 class_getInstanceVariable 之間添加才有效
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)

//動態添加方法
//入參:類Class,方法名SEL,方法實現IMP,方法返回值各個參數類型等配置字符串
//返回:添加結果,是否成功。
//* 1.添加屬性不用再objc_registerClassPair之前,因為添加屬性其實就是添加變量的set 和 get方法而已
//* 2.添加的屬性和變量不能用kvc設置值和取值
BOOL class_addMethod(Class cls, SEL name, IMP imp,   const char *types)

//動態添加協議
//入參:類Class,協議結構體Protocol
//返回:添加結果,是否成功。
BOOL class_addProtocol(Class cls, Protocol *protocol)

//動態添加屬性
//入參:類Class,屬性名char數組,屬性的配置屬性,objc_property_attribute_t,屬性的屬性數量。
//返回:添加結果,是否成功。
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
(以上API的)運行測試代碼地址在這里:TFRumtimeAll:demo-runtime-part2
#import "ViewController.h"
#import <objc/message.h>
@interface ViewController ()<UITableViewDataSource>
{
    NSArray *_property0;
    NSArray *property1;
}
@property (nonatomic,strong)UIColor *property2;

@end

@implementation ViewController

//get方法
NSString *attribute0Getter(id classInstance, SEL _cmd) {
    Ivar ivar = class_getInstanceVariable([classInstance class], "_attribute0");//獲取變量,如果沒獲取到說明不存在
    return object_getIvar(classInstance, ivar);
}

//set方法
void attribute0Setter(id classInstance, SEL _cmd, NSString *newName) {
    Ivar ivar = class_getInstanceVariable([classInstance class], "_attribute0");//獲取變量,如果沒獲取到說明不存在
    id oldName = object_getIvar(classInstance, ivar);
    if (oldName != newName) object_setIvar(classInstance, ivar, [newName copy]);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //添加--為動態創建類添加變量
    /**
     * 1.只能給動態創建的類添加變量也就是用 objc_allocateClassPair 創建的類
     * 2.添加變量只能在函數 objc_allocateClassPair 和 class_getInstanceVariable 之間添加才有效
     */
    Class CreatClass0 = objc_allocateClassPair([NSObject class], "CreatClass0", 0);
    class_addIvar(CreatClass0, "_attribute0", sizeof(NSString *), log(sizeof(NSString *)), "i");
    Ivar ivar = class_getInstanceVariable(CreatClass0, "_attribute0");//獲取變量,如果沒獲取到說明不存在
    NSLog(@">>>>>>>>0:%@",[NSString stringWithUTF8String:ivar_getName(ivar)]);
    objc_registerClassPair(CreatClass0);
    NSLog(@"\n");
    
    
    //添加--為動態創建的類添加變量然后添加屬性,類和變量和屬性都是動態創建的
    /**
     * 1.各個屬性:暫時不知道
     * 2.下面這個反駁了上面的第二標,這個證明id不是不會報錯,規律是如果id調用的是系統的類的方法,那么就不會報錯,
     詳細介紹:上面的@selector(name) 和 @selector(setName:) name是好多系統類都有方法,所以id會認為本身代表的是那個類
     所以不會報錯,但是如果你硬寫一個完全沒有的方法,它就會報錯
     * 3.添加屬性不用再objc_registerClassPair之前,因為添加屬性其實就是添加變量的set 和 get方法而已
     * 4.添加的屬性和變量不能用kvc設置值和取值
     */
    
    objc_property_attribute_t type2 = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership2 = { "C", "" }; // C = copy
    objc_property_attribute_t backingivar2  = { "V", "_attribute0" };
    objc_property_attribute_t attrs2[] = { type2, ownership2, backingivar2 };
    class_addProperty(CreatClass0, "_attribute0", attrs2, 3);
    
    SEL getter = NSSelectorFromString(@"attribute0");
    SEL setter = NSSelectorFromString(@"setAttribute0:");
    BOOL suc0 = class_addMethod(CreatClass0, getter, (IMP)attribute0Getter, "@@:");
    BOOL suc1 = class_addMethod(CreatClass0, setter, (IMP)attribute0Setter, "v@:@");
    NSLog(@">>>>>>>>3:%@:%@",@(suc0),@(suc1));
    id idclass = [[CreatClass0 alloc]init];
    NSLog(@">>>>>>>>1:%@",[idclass performSelector:getter withObject:nil]);
    [idclass performSelector:setter withObject:@"為動態創建類先添加變量再添加屬性"];
    NSLog(@">>>>>>>>2:%@",[idclass performSelector:getter withObject:nil]);
    //class獲取--獲取整個類的實例方法的方法列表
    /**
     *  1.獲取所有實例方法,不包含靜態方法
     *  2.不獲取父類的方法
     *  3.隱式的get set 方法也能獲取到
     *  4.關于Method的更多用法參考Class2類
     */
    unsigned int copycopyMethodListCount = 0;
    Method *methods = class_copyMethodList([self class], &copycopyMethodListCount);
    for (NSInteger i = 0; i < copycopyMethodListCount; i++) {
        Method method = methods[i];
        SEL name = method_getName(method);
        NSLog(@">>>>>>>>2:copyMethodList:%@",NSStringFromSelector(name));
    }
    free(methods);//釋放
    NSLog(@"\n");
    
    BOOL result0 = class_addProtocol([self class], NSProtocolFromString(@"UITableViewDelegate"));
    NSLog(@">>>>>>>>3:添加協議成功");
    //添加--協議
    /**
     * 1.class_addProtocol  參數含義:第一個:要添加協議的類,第二個:協議對象
     * 2.獲取協議列表具體細節參照Class1里的內容
     */
    unsigned int copyProtocolListCount = 0;
    Protocol * __unsafe_unretained *protocals = class_copyProtocolList([self class], &copyProtocolListCount);
    for (NSInteger i = 0; i < copyProtocolListCount; i++) {
        Protocol * protocal = protocals[i];
        const char *name = protocol_getName(protocal);
        NSLog(@">>>>>>>>4:copyProtocolList:%s",name);
    }
    free(protocals);//釋放
    NSLog(@"\n");
    
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{return 0;}

// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{return nil;}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

(以上運行測試的)打印結果
demo-runtime-part2[3439:758345] >>>>>>>>0:_attribute0
demo-runtime-part2[3439:758345] 
demo-runtime-part2[3439:758345] >>>>>>>>3:1:1
demo-runtime-part2[3439:758345] >>>>>>>>1:(null)
demo-runtime-part2[3439:758345] >>>>>>>>2:為動態創建類先添加變量再添加屬性
demo-runtime-part2[3439:758345] >>>>>>>>2:copyMethodList:property2
demo-runtime-part2[3439:758345] >>>>>>>>2:copyMethodList:setProperty2:
demo-runtime-part2[3439:758345] >>>>>>>>2:copyMethodList:.cxx_destruct
demo-runtime-part2[3439:758345] >>>>>>>>2:copyMethodList:tableView:numberOfRowsInSection:
demo-runtime-part2[3439:758345] >>>>>>>>2:copyMethodList:tableView:cellForRowAtIndexPath:
demo-runtime-part2[3439:758345] >>>>>>>>2:copyMethodList:didReceiveMemoryWarning
demo-runtime-part2[3439:758345] >>>>>>>>2:copyMethodList:viewDidLoad
demo-runtime-part2[3439:758345] 
demo-runtime-part2[3439:758345] >>>>>>>>3:添加協議成功
demo-runtime-part2[3439:758345] >>>>>>>>4:copyProtocolList:UITableViewDelegate
demo-runtime-part2[3439:758345] >>>>>>>>4:copyProtocolList:UITableViewDataSource
demo-runtime-part2[3439:758345] 

(以上打印結果的)解析
#從上面的測試我們可以發現各個函數的作用:

###class_addIvar:
添加變量。添加屬性的具體解釋和調用注意點上面備注已經寫的很清楚。
從打印結果可以看出我們添加屬性已經成功。
上面代碼中涉及到兩個函數objc_allocateClassPair和objc_registerClassPair這兩個函數我們后面會繼續說。

###class_addProperty:
添加屬性。這個函數比較復雜,用到的頻率也比較低。有興趣的朋友可以參考官方文檔。

###class_addMethod:
添加方法。這個函數還是比較重要的,后面說幾個runtime的幾個應用實例會用的到,更多用法也可以去看一下JSPatch源碼。
該函數一共需要四個參數,前三個分別是:要添加方法的類,方法名,方法實現。
第四個函數的字符串:
get方法:第一個個@代表返回的類型為非基本數據類型,如果返回的數據是int那么第一個字符應該為i
set方法:第一個個v代表返回的類型為void,如果返回的數據是int那么第一個字符應該為i,最后一個@代表函數的第一個試用參數類型為非基本數據類型
set和get方法的共同部分是@:分別代表方法的兩個默認函數target和SEL。

###class_addProtocol:
添加協議。這個函數比較簡單,具體上面代碼注釋已經詳細說過了。
class-replace
//屬性替換
//入參:
//返回:char數組
//低平率函數,有興趣的可以看官方文檔https://developer.apple.com/reference/objectivec/objective_c_runtime
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

//方法替換
//入參:要替換方法所在的類Class,  要替換的方法名SEL,方法的實現,方法的描述字符串
//返回:char數組
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
(以上API的)運行測試代碼地址在這里:TFRumtimeAll:demo-runtime-part3
#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self method0];
    BOOL result0 = class_replaceMethod([self class], @selector(method0), (IMP)method1, NULL);
    NSLog(@">>>>>>>>2:%@",@(result0));
    [self method0];
    
}

-(void)method0{
    NSLog(@">>>>>>>>0");

}
void method1(){
    NSLog(@">>>>>>>>1");
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end
(以上運行測試的)打印結果
demo-runtime-part3[4256:976005] >>>>>>>>0
demo-runtime-part3[4256:976005] >>>>>>>>2:1
demo-runtime-part3[4256:976005] >>>>>>>>1

(以上打印結果的)解析
#從上面的測試我們可以發現各個函數的作用:

###class_replaceMethod:
從上面的代碼可以看出。我們第一次調用了[self method0];然后把method0替換成了method1,當我再次調用method0的時候執行的是method1方法。
上面代碼只是簡單演示基本功能。更多的用法,后面會說幾個應用實例。

在篇尾

程序員不需要打賞,只希望自己的項目能幫助更多人,請支持我的git開源框架:TFEasyCoder

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,148評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,579評論 33 466
  • 本文轉載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 772評論 0 1