Runtime 的一些用法

runtime 就是OC 中經常說的 運行時


這裏 簡單介紹一下 OC 中用到一些場景

1、字典轉模型  

2、給分類添加關聯對象

3、交換方法


runtime  使用的時候一般建立一個 NSObject 的分類Cotegory 。 當然也可以根據 實際情況創建其他類的Cotegory。在創建的文件裏面需要導入

#import <objc/runtime.h>

、字典轉模型。

首先 動態的獲取 類 的屬性名稱 -> 然後使用 KVC 進行賦值.

1、首先獲取類的屬性名稱

.h 

/**
    獲取 屬性的名稱數組
    @return  返回對象的屬性名稱數組
 */
+ (NSArray *)cz_objPropertiesAry;
.m

const void *kPropertiesKey = "kPropertiesKey";

+ (NSArray *)cz_objPropertiesAry
{
    //獲取屬性數組的指針數組
    unsigned int count = 0;
    objc_property_t *property = class_copyPropertyList([self class], &count);
    
    //創建存放數據的數組
    NSMutableArray *array = [NSMutableArray array];
    
    //遍歷屬性數組
    for (unsigned int i = 0; i < count; i++) {
        
        // 指針   C語言中數組的名字 爲第一個元素的指針
        objc_property_t pty = property[i];
        
        //獲取屬性的名字  類型爲C的字符串
        const char *cName  =  property_getName(pty);
        
        //把C字符串轉化爲OC字符串
        NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
        
        //[NSString stringWithUTF8String:cName]; 也可以
        
        //把信息添加到數組裏面
        [array addObject:name];
        
    }
    
    //釋放屬性數組
    free(property);
    
    return array.copy;
}

unsigned int count =0//下面爲數組

 objc_property_t *proList = class_copyPropertyList([selfclass], &count);

注意,這個方法獲取的數組最後要用  free(proList);  釋放掉


class_copyPropertyList  獲取 類的屬性的列表

class_copyIvarList   獲取類的 成員變量的列表

class_copyMethodList  獲取類 的方法的列表

class_copyProtocolList  獲取類的代理的列表

參數 

1、 要獲取的這個類 因爲是Cotegory 分類 ,使用 self

2、列表裏面元素的個數      unsigned int  無符號的整形

返回值:

所有屬性的數組      類型爲 objc_property_t 的數組


再遍歷類的屬性數組 ,因爲類型爲   objc_property_t  是一個指針,使用property_getName獲取這個指針對應屬性的名字。

使用property_getName 獲取的是一個const char  類型C 語言的字符串  再轉化爲OC的字符串 。最後添加到存儲數據的數組裏面。


獲取到類的屬性名稱後,就可以使用KVC 就行賦值了。

//所有字典轉模型框架 核心算法
+ (instancetype)cz_objcWithDictionary:(NSDictionary *)dict
{
    //實例化對象
    id object = [[self alloc]init];
    
    //獲取對象的屬性名稱數組
    //1> 獲得 self 的屬性列表
    NSArray *array = [self cz_objPropertiesAry];
    
    //遍歷字典  使用KVC 爲數組中的屬性賦值
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        //需要先判斷數組中是否包含 字典的Key
        if ([array containsObject:key]) {
            
            //賦值
            [object setValue:obj forKey:key];
        }
    }];
    
    return object;
}


使用:

創建一個Person 類  不實現.m

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double height;
@property (nonatomic, copy) NSString *title;

@end

在ViewController 裏面

導入  

 #import "Person.h"     

#import "NSObject+Runtime.h"

  //獲取Persion的屬性列表數組
    NSArray *properties =   [Person cz_objPropertiesAry];
    NSLog(@"%@",properties);
    
    NSDictionary *dic = @{@"name":@"張三",@"age":@(22),@"height":@(168),@"title":@"運行時",@"place":@"boss"};
    Person *persion = [Person cz_objcWithDictionary:dic];
      NSLog(@"%@",persion);


如果因爲iOS 在運行的時候 裏面的類的對象不會改變,重複的獲取 類 的屬性名稱 會造成 影響APP性能,這個時候可以使用關聯對象的方法。


二、添加關聯 動態的添加屬性值

修改獲取類屬性的方法

const void *kPropertiesKey = "kPropertiesKey";

+ (NSArray *)cz_objPropertiesAry
{
    
#pragma mark 關聯對象 獲取 屬性值 沒有 則添加屬性值  (動態的添加屬性值)
    /*
     此方法雖然能夠獲取類的屬性數組  但是如果每次調用都要執行一次的話 耗費的時間長,
     使用 關聯對象  動態的添加屬性值  分別在方法的開頭和結尾
     
     */
    /*
     參數:
        1、對象  self 
        2、const void 的key
     
     返回值  : Id類型
     添加的屬性值
     */
    
    NSArray *proList =  objc_getAssociatedObject(self, kPropertiesKey);
    if (proList) { //如果獲取的關聯對象裏面有元素 就直接返回
        return proList;
    }
    
    //獲取屬性數組的指針數組
    unsigned int count = 0;
    objc_property_t *property = class_copyPropertyList([self class], &count);
    
    //創建存放數據的數組
    NSMutableArray *array = [NSMutableArray array];
    
    //遍歷屬性數組
    for (unsigned int i = 0; i < count; i++) {
        
        // 指針   C語言中數組的名字 爲第一個元素的指針
        objc_property_t pty = property[i];
        
        //獲取屬性的名字  類型爲C的字符串
        const char *cName  =  property_getName(pty);
        
        //把C字符串轉化爲OC字符串
        NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
        
        //[NSString stringWithUTF8String:cName]; 也可以
        
        //把信息添加到數組裏面
        [array addObject:name];
        
    }
    
    //釋放屬性數組
    free(property);
    
#pragma mark 關聯對象 2 添加屬性值 動態的添加屬性值
    
    /*
     參數:
        1、對象 self
        2、const void 的key  同第一步
        3、添加的屬性值  
        4、關聯的協議
     */
    objc_setAssociatedObject(self, kPropertiesKey, array.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    return array.copy;
}

修改的方法 裏面 多了幾行代碼

 NSArray *proList =  objc_getAssociatedObject(self, kPropertiesKey);
    if (proList) {
        return proList;
    }


objc_setAssociatedObject(self, kPropertiesKey, array.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

分別在方法的開始和結尾

objc_getAssociatedObject    獲取關聯對象動態添加的屬性值

參數:

1、對象 self

2、const void 的key 

返回值:

Id類型, 添加的屬性值


objc_setAssociatedObject 設置動態添加關聯對象的屬性值

參數:

1、對象 self 

2、const void 的key 跟上面獲取的key一致

3、要添加的屬性值,(如果設置了,上面的objc_getAssociatedObject 就能直接獲取到,下次調用就能直接獲取屬性值)

4、關聯的協議   OBJC_ASSOCIATION_RETAIN_NONATOMIC


添加了 了關聯對象 的方法,下次調用該方法的時候,運行到objc_getAssociatedObject 就能直接獲取到 數據了,不用再往下執行了。


三、交換方法

特點: 

在無法修改系統方法 ,和第三方框架的時候,

1、利用交換方法,先執行自定義的方法

2、再執行系統方法或第三方框架方法。

被稱之爲 黑魔法 ,對系統和框架有很強的依賴性。


舉個例子:

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
    imageView.center = self.view.center;
    [self.view addSubview:imageView];
    
    imageView.image = [UIImage imageNamed:@"image5.png"];

在UIImageView 的時候,系統的設置圖片的方法, imageView.image = [UIImage imageNamed:@""]; 

imageView.image  相當於 imageView setImage: UIImage

我們可以自定義一個方法,替換掉系統的setImage: 方法

創建一個UIImageView 的Cotegory

//在類 被加載到運行時的時候,就會執行
+ (void)load{
    
    //交叉方法  就下面的3句話
    
    //獲取 類 實例化的原始方法   setImage:
    Method originalMethod = class_getInstanceMethod([self class], @selector(setImage:));
    
    //獲取 自定義的類的實例化的方法  cz_setImage:
    Method swizzleMethod = class_getInstanceMethod([self class], @selector(cz_setImage:));
    
    //交換兩個方法 setImage: 和 cz_setImage:  完成之後
    //1> 調用setImage: 相當於調用 cz_setImage:
    //2> 調用 cz_setImage: 相當於調用 setImage:
    method_exchangeImplementations(originalMethod, swizzleMethod);
}

//自定義 的 類 的實例化方法  Cotegory
- (void)cz_setImage:(UIImage *)image
{
    //這裏是我們想要做的事情
    NSLog(@"調用的自定義的方法: %s",__FUNCTION__);
    
 <span style="white-space:pre">	</span>
    //再調用系統 的默認方法
    /*重點 :
            爲什麼 方法名是自定義方法 而不是系統默認的方法名字
            是因爲load 裏面 系統默認方法和自定義的方法進行了交換  
            系統默認的方法 setImage: 變成了 cz_setImage: 
            所以 在交換方法完成之後 再次調用系統默認的方法就變成了 我們自定義的方法
     
     */
    [self cz_setImage:result];
}

首先先自定義一個設置圖片的方法- (void) cz_setImage:(UIImage *)image 

裏面 輸出 當前的方法。


在方法 + (void) load{} 裏面添加 我們的交換方法

就3句話,

1、獲取系統默認的 設置圖片的方法

2、獲取自定義的設置圖片的方法

3、交換這兩個方法

class_getInstanceMethod  獲取類的實例的方法

參數:

1、類的實例  [self class]   (這是一種特殊的實例化對象)

2、獲取的實例方法  SEL 

返回值:

Method  方法 類型


method_exchangeImplementations

參數:

1、要交換的第一個方法

2、要交換的第二個方法


重點注意:

在方法交換之後,兩個方法已經被交換了,調用系統方法 變成調用自定義的方法名

調用自定義的方法 變成調用系統的方法名


所以下面:

//自定義 的 類 的實例化方法  Cotegory
- (void)cz_setImage:(UIImage *)image
{
    //這裏是我們想要做的事情  輸出當前調用的方法
    NSLog(@"調用的自定義的方法: %s",__FUNCTION__);
    
 <span>	</span>
    //再調用系統 的默認方法
    /*重點 :
            爲什麼 方法名是自定義方法 而不是系統默認的方法名字
            是因爲load 裏面 系統默認方法和自定義的方法進行了交換  
            系統默認的方法 setImage: 變成了 cz_setImage: 
            所以 在交換方法完成之後 再次調用系統默認的方法就變成了 我們自定義的方法
     
     */  這個時候系統默認的方法名 在這裏變成了   <span style="font-family: Arial, Helvetica, sans-serif;">cz_setImage:</span>

    [self cz_setImage:result];
}

ViewController 的運行結果爲

#import "UIImageView+RuntimeCrossoverMethod.h"

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
    imageView.center = self.view.center;
    [self.view addSubview:imageView];
    
    imageView.image = [UIImage imageNamed:@"image5.png"];

2016-10-14 16:38:01.248 Runtime[4947:837294]調用的自定義的方法: -[UIImageView(RuntimeCrossoverMethod) cz_setImage:]


我們調用系統默認的方法 變成調用我們自定義的方法,當然我們在自定義方法裏面又掉用了系統的方法(這個時候系統的方法名變成了我們自定義的方法名)。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章