天天看點

利用runtime為系統類添加屬性、成員變量.......

1️⃣runtime介紹:

runtime是一套比較底層的純C語言API, 包含了很多底層的C語言API。在我們平時編寫的OC代碼中, 程式運作過程時, 其實最終都是轉成了runtime的C語言代碼.

比如說,下面一個建立對象的方法 :

1.[[ZSPerson alloc] init]

2.runtime :objc_msgSend(objc_msgSend(“ZSPerson” , “alloc”), “init”)

2️⃣runtime 用來幹什麼呢??用在那些地方呢?怎麼用呢?

runtime是屬于OC的底層, 可以進行一些非常底層的操作(用OC是無法現實的, 不好實作)

在程式運作過程中, 動态建立一個類(比如KVO的底層實作)

在程式運作過程中, 動态地為某個類添加屬性\方法, 修改屬性值\方法

周遊一個類的所有成員變量(屬性)\所有方法

例如:我們需要對一個類的屬性進行歸檔解檔的時候屬性特别的多,這時候,我們就會寫很多對應的代碼,但是如果使用了runtime就可以動态設定!

例如,ZSPerson.h的檔案如下所示

@interfaceZSPerson:NSObject

@property(nonatomic,assign)intage;

@property(nonatomic,assign)intheight;

@property(nonatomic,copy)NSString*name;

@property(nonatomic,assign)intage2;

@property(nonatomic,assign)intheight2;

@property(nonatomic,assign)intage3;

@property(nonatomic,assign)intheight3;

@property(nonatomic,assign)intage4;

@property(nonatomic,assign)intheight4;

@end

而ZSPerson.m實作檔案的内容如下

#import"ZSPerson.h"

@implementationZSPerson

(void)encodeWithCoder:(NSCoder )encoder 

{

unsignedintcount =0;

Ivar ivars = class_copyIvarList([ZSPerson class], &count);

for(int i =0; i<count;i++){

// 取出i位置對應的成員變量

Ivar ivar = ivars[i];

// 檢視成員變量

constchar*name = ivar_getName(ivar);

// 歸檔

NSString*key = [NSStringstringWithUTF8String:name];

idvalue = [selfvalueForKey:key]; 

  [encoder encodeObject:value forKey:key]; 

  }

free(ivars);

    } 

  (id)initWithCoder:(NSCoder *)decoder 

  {

if(self= [super init]) {

unsignedintcount =0;

 Ivar *ivars = class_copyIvarList([ZSPerson class], &count);

const char*name = ivar_getName(ivar);

id value = [decoder decodeObjectForKey:key];

// 設定到成員變量身上

[selfsetValue:value forKey:key];

free(ivars);

returnself; 

這樣我們可以看到歸檔和解檔的案例其實是runtime寫下的

學習,runtime機制首先要了解下面幾個問題

1.相關的頭檔案和函數

a> 頭檔案

利用頭檔案,我們可以檢視到runtime中的各個方法!

b> 相關應用

NSCoding(歸檔和解檔, 利用runtime周遊模型對象的所有屬性)

字典 –> 模型 (利用runtime周遊模型對象的所有屬性, 根據屬性名從字典中取出對應的值, 設定到模型的屬性上)

KVO(利用runtime動态産生一個類)

用于封裝架構(想怎麼改就怎麼改)

這就是我們runtime機制的隻要運用方向

c> 相關函數

objc_msgSend : 給對象發送消息

class_copyMethodList : 周遊某個類所有的方法

class_copyIvarList : 周遊某個類所有的成員變量

class_…..

這是我們學習runtime必須知道的函數!

2.必備常識

a> Ivar : 成員變量

b> Method : 成員方法

從上面例子中我們看到我們定義的成員變量,如果要是動态建立方法,可以使用Method。

3️⃣接下來我們進行項目實戰

首先給UITableViewCell建立一個分類RightDownPlugin

利用runtime為系統類添加屬性、成員變量.......

.h檔案中

#import<UIKit,UIKit.h>

@interfaceUITableViewCell(RightDownPlugin)

@property(nonatomic,strong)UIImageView*statusImgV;//狀态圖@property(nonatomic,strong)UILabel*statusLab;//狀态label

.m檔案

#import"UITableViewCell+RightDownPlugin.h"

#include <objc/runtime.h>

@implementationUITableViewCell(RightDownPlugin)

//定義常量 必須是C語言字元串 因為runtime是C語言API,

staticchar*statusImgKey ="statusImgKey";

staticchar*statusLabKey ="statusLabKey";

/* 

OBJC_ASSOCIATION_ASSIGN;            //assign政策

OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy政策

OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain政策

OBJC_ASSOCIATION_RETAIN;

OBJC_ASSOCIATION_COPY;

*//*

id object 給哪個對象的屬性指派

const void *key 屬性對應的key

id value  設定屬性值為value

objc_AssociationPolicy policy  使用的政策,是一個枚舉值,和copy,retain,assign是一樣的,手機開發一般都選擇nonatomic

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

*/// 然後就需要自定義set和get方法了,因為category預設是不能添加屬性的,

- (void)setStatusImgV:(UIImageView*)statusImgV{    objc_setAssociatedObject(self,statusImgKey,statusImgV,OBJC_ASSOCIATION_RETAIN);

}

- (UIImageView*)statusImgV{

return objc_getAssociatedObject(self, statusImgKey);

// Lab

- (void)setStatusLab:(UILabel*)statusLab{    objc_setAssociatedObject(self,statusLabKey,statusLab,OBJC_ASSOCIATION_RETAIN);

- (UILabel*)statusLab{

return objc_getAssociatedObject(self, statusLabKey);

runtime常見的用法總結

1.runtime動态添加屬性

需求:給NSString類添加兩個屬性:name和count

NSString+String.h中

#import<Foundation/Foundation.h>

/**

*  @property在分類裡添加一個屬性 不會有setter getter方法 隻聲明了一個變量 而且 這樣聲明 這個屬性和這個類沒有什麼關系 */

@interface NSString (String)

@property (copy, nonatomic, nullable) NSString *name;

@property (copy, nonatomic, nullable) NSString *count;

NSString+String.m中

#import "NSString+String.h"

#import<objc/message>

*  動态添加屬性的本質是 指向外部已經存在的一個屬性 而不是去在對象中建立一個屬性

*/

@implementation NSString (String)

static NSString *_name;

//在分類裡聲明屬性 需要自己寫set方法和get方法

- (void) setName:(NSString *)name

_name = name;

- (NSString *) name

return _name;

//添加屬性 應該是與對象有關

- (void) setCount:(NSString *)count

//set方法裡設定關聯

//Associated 關聯 聯系

//跟某個對象産生關聯,添加屬性

* id obj 給哪個對象添加屬性(産生關聯)

* const void *key 屬性名 (根據key擷取關聯的對象) void * 相當于 id 萬能指針 傳c或者oc的都可以

* id value 要關聯的值

* objc_AssociationPolicy policy 政策 宏對應assign retain copy (因為weak沒有用 外面指派完馬上就會被銷毀 是以沒有weak)

objc_setAssociatedObject(self, @"count", count, OBJC_ASSOCIATION_ASSIGN);

- (NSString *) count

//get方法裡擷取關聯

return [NSString stringWithFormat:@"%ld",[objc_getAssociatedObject(self, @"count") length]];

2.runtime動态加載方法

ViewController中

#import "ViewController.h"

#import "Person.h"

@interface ViewController ()

@implementation ViewController

- (void) viewDidLoad{    [super viewDidLoad]; 

  //performSelector:動态添加方法   

Person *p0 = [[Person alloc] init];  

[p0 performSelector:@selector(eat)];

//動态添加方法 但是如果沒有實作該方法 還是會崩潰 

[p0 performSelector:@selector(drink:) withObject:@"juice"];

//動态添加方法 但是如果沒有實作該方法 還是會崩潰

Person.m中

#import<objc/message.h>

//預設一個方法都有兩個參數:self 和_cmd  self是方法的調用者 _cmd就是調用方法的編号(方法名) 這兩個參數為隐式參數 但是如果調用的是c的函數 則需要寫出來

@implementation Person

//定義函數 該函數名是啥都可以

void eat(id self,SEL _cmd)

//無傳回值 兩個參數 void(id,SEL) v@:

NSLog(@"%@調用了%@方法",[self class],NSStringFromSelector(_cmd));//SEL本身沒發列印 隻能列印方法名

void drink(id self,SEL _cmd,id param1)

//v void    @ 對象    : 方法編号

NSLog(@"%@調用了%@方法 傳遞參數:%@",[self class],NSStringFromSelector(_cmd),param1);//SEL本身沒發列印 隻能列印方法名

//1.動态添加方法 首先要實作resolveInstanceMethod:方法或resolveClassMethod:方法

//前者對應執行個體方法 後者對應類方法

//這兩個方法的作用是要知道哪個方法沒有被實作

//這兩個方法是在當該類的某個方法沒有實作,但是又被外界調用了的時候調用 (及:外界試用performSelector:調用了該類中某個沒有實作的方法)

//sel參數為沒有被實作的這個方法

+ (BOOL) resolveInstanceMethod:(SEL)sel

//列印該方法名

//    NSLog(@"%@",NSStringFromSelector(sel));

//動态添加方法

if ([NSStringFromSelector(sel) isEqualToString:@"eat"])//sel == @selector(eat)也可以 但是會報警

*  Class 給哪個類添加方法

*  sel 要添加的方法編号(方法名)

*  IMP 方法的實作 ———— 函數的入口(函數的指針 函數名 是啥都可以 不一定和sel相同)

*  types 方法的類型 編碼格式 (類型c語言的字元串) (函數的類型:傳回值類型 參數類型 直接查文檔 文檔有表格)

class_addMethod([self class], sel, (IMP)eat, "v@:");

//處理完了要傳回YES

//        return YES;

else if ([NSStringFromSelector(sel) isEqualToString:@"drink:"])//要加冒号

class_addMethod([self class], sel, (IMP)drink, "v@:@");

//由于不知道傳回的YES還是NO 是以:

return [super resolveInstanceMethod:sel];

+ (BOOL) resolveClassMethod:(SEL)sel

return [super resolveClassMethod:sel];

3.runtime交換方法(ios黑魔法)

ViewController.m中

//#import "UIImage+image.h"//交換方法時候不用導入也可以 因為交換寫在類+(void)load裡

  /** 

  *  交換方法  

*///  

UIImage *image = [UIImage ov_imageNamed:@"123"];  

UIImage *image1 = [UIImage imageNamed:@"123"];  

UIImage *image2 = [UIImage imageNamed:@"123"];

  //用imageNamed加載圖檔,并不知道圖檔是否加載成功  

//需求:在以後調用imageNamed的時候,要知道圖檔是否加載成功  

//交換方法的實作 (把imageNamed:方法和ov_imageNamed:方法交換 及 調用imageNamed就是調用ov_imageNamed)

UIImage+image.m中

#import"UIImage+image.h"

@implementation UIImage (image)

//加載這個分類的時候調用

+ (void) load

NSLog(@"%s",__func__);

//方法都定義在類裡面 是以 交換對象方法也用class_開頭

*  class_getMethodImplementation 擷取類方法的實作

*

*  Class 擷取哪個類的方法

*  SEL 擷取哪個方法

*  class_getMethodImplementation(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*  class_getInstanceMethod 擷取對象方法

*  class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*  class_getClassMethod 擷取類方法

*  class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*  method_exchangeImplementations交換方法

*  Method m1  要被替換的方法

*  Method m2  要替換Method m1的方法

*  method_exchangeImplementations(<#Method m1#>, <#Method m2#>)

//交換方法的實作

//1.拿到兩個方法

Method imageNamedMethod = class_getClassMethod([self class], @selector(imageNamed:));

Method ov_imageNamedMethod = class_getClassMethod([self class], @selector(ov_imageNamed:));

//2.交換

method_exchangeImplementations(imageNamedMethod, ov_imageNamedMethod);

*  分類沒有父類 沒有super

//+ (nullable __kindof UIImage *) imageNamed:(nonnull NSString *)imageName

//{

//    return nil;

//}

*  用其他方法做 這個方法不好的原因是 1.導入頭檔案太蛋疼 2.團隊其他人可能不知道

+ (nullable __kindof UIImage *) ov_imageNamed:(nonnull NSString *)imageName

//1.加載圖檔功能

//    UIImage *image = [UIImage imageNamed:imageName];//由于使用了方法交換 是以這裡再調用該方法就會造成死循環

UIImage *image = [UIImage ov_imageNamed:imageName];//此處直接調用方法本身即可

NSLog(@"%s %d",__func__,__LINE__);

//2.判斷傳回是否為空功能

if (!image)

//NSException 為抛異常(強制崩潰)

//        NSException *e = [NSException

//                          exceptionWithName: @"異常情況"

//                          reason: @"圖檔為空"

//                          userInfo: nil];

//        @throw e;

else

return image;

繼續閱讀