利用runtime為系統(tǒng)類(lèi)添加屬性、成員變量.......

1??runtime介紹:

runtime是一套比較底層的純C語(yǔ)言API, 包含了很多底層的C語(yǔ)言API。在我們平時(shí)編寫(xiě)的OC代碼中, 程序運(yùn)行過(guò)程時(shí), 其實(shí)最終都是轉(zhuǎn)成了runtime的C語(yǔ)言代碼.

比如說(shuō),下面一個(gè)創(chuàng)建對(duì)象的方法 :

1.[[ZSPerson alloc] init]

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

2??runtime 用來(lái)干什么呢??用在那些地方呢?怎么用呢?

runtime是屬于OC的底層, 可以進(jìn)行一些非常底層的操作(用OC是無(wú)法現(xiàn)實(shí)的, 不好實(shí)現(xiàn))

在程序運(yùn)行過(guò)程中, 動(dòng)態(tài)創(chuàng)建一個(gè)類(lèi)(比如KVO的底層實(shí)現(xiàn))

在程序運(yùn)行過(guò)程中, 動(dòng)態(tài)地為某個(gè)類(lèi)添加屬性\方法, 修改屬性值\方法

遍歷一個(gè)類(lèi)的所有成員變量(屬性)\所有方法

例如:我們需要對(duì)一個(gè)類(lèi)的屬性進(jìn)行歸檔解檔的時(shí)候?qū)傩蕴貏e的多,這時(shí)候,我們就會(huì)寫(xiě)很多對(duì)應(yīng)的代碼,但是如果使用了runtime就可以動(dòng)態(tài)設(shè)置!

例如,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實(shí)現(xiàn)文件的內(nèi)容如下

#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位置對(duì)應(yīng)的成員變量

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);

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

// 取出i位置對(duì)應(yīng)的成員變量

Ivar ivar = ivars[i];

// 查看成員變量

const char*name = ivar_getName(ivar);

// 歸檔

NSString*key = [NSStringstringWithUTF8String:name];

id value = [decoder decodeObjectForKey:key];

// 設(shè)置到成員變量身上

[selfsetValue:value forKey:key];

free(ivars);

? }

returnself;?

? }

@end

這樣我們可以看到歸檔和解檔的案例其實(shí)是runtime寫(xiě)下的

學(xué)習(xí),runtime機(jī)制首先要了解下面幾個(gè)問(wèn)題

1.相關(guān)的頭文件和函數(shù)

a> 頭文件

利用頭文件,我們可以查看到runtime中的各個(gè)方法!

b> 相關(guān)應(yīng)用

NSCoding(歸檔和解檔, 利用runtime遍歷模型對(duì)象的所有屬性)

字典 –> 模型 (利用runtime遍歷模型對(duì)象的所有屬性, 根據(jù)屬性名從字典中取出對(duì)應(yīng)的值, 設(shè)置到模型的屬性上)

KVO(利用runtime動(dòng)態(tài)產(chǎn)生一個(gè)類(lèi))

用于封裝框架(想怎么改就怎么改)

這就是我們r(jià)untime機(jī)制的只要運(yùn)用方向

c> 相關(guān)函數(shù)

objc_msgSend : 給對(duì)象發(fā)送消息

class_copyMethodList : 遍歷某個(gè)類(lèi)所有的方法

class_copyIvarList : 遍歷某個(gè)類(lèi)所有的成員變量

class_…..

這是我們學(xué)習(xí)runtime必須知道的函數(shù)!

2.必備常識(shí)

a> Ivar : 成員變量

b> Method : 成員方法

從上面例子中我們看到我們定義的成員變量,如果要是動(dòng)態(tài)創(chuàng)建方法,可以使用Method。

3??接下來(lái)我們進(jìn)行項(xiàng)目實(shí)戰(zhàn)

首先給UITableViewCell創(chuàng)建一個(gè)分類(lèi)RightDownPlugin

.h文件中

#import<UIKit,UIKit.h>

@interfaceUITableViewCell(RightDownPlugin)

@property(nonatomic,strong)UIImageView*statusImgV;//狀態(tài)圖@property(nonatomic,strong)UILabel*statusLab;//狀態(tài)label

@end

.m文件

#import"UITableViewCell+RightDownPlugin.h"

#include <objc/runtime.h>

@implementationUITableViewCell(RightDownPlugin)

//定義常量 必須是C語(yǔ)言字符串 因?yàn)閞untime是C語(yǔ)言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 給哪個(gè)對(duì)象的屬性賦值

const void *key 屬性對(duì)應(yīng)的key

id value? 設(shè)置屬性值為value

objc_AssociationPolicy policy? 使用的策略,是一個(gè)枚舉值,和copy,retain,assign是一樣的,手機(jī)開(kāi)發(fā)一般都選擇nonatomic

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

*/// 然后就需要自定義set和get方法了,因?yàn)閏ategory默認(rèn)是不能添加屬性的,

- (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);

}

@end

runtime常見(jiàn)的用法總結(jié)

1.runtime動(dòng)態(tài)添加屬性

需求:給NSString類(lèi)添加兩個(gè)屬性:name和count

NSString+String.h中

#import<Foundation/Foundation.h>

/**

*? @property在分類(lèi)里添加一個(gè)屬性 不會(huì)有setter getter方法 只聲明了一個(gè)變量 而且 這樣聲明 這個(gè)屬性和這個(gè)類(lèi)沒(méi)有什么關(guān)系 */

@interface NSString (String)

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

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

@end

NSString+String.m中

#import "NSString+String.h"

#import<objc/message>

/**

*? 動(dòng)態(tài)添加屬性的本質(zhì)是 指向外部已經(jīng)存在的一個(gè)屬性 而不是去在對(duì)象中創(chuàng)建一個(gè)屬性

*/

@implementation NSString (String)

static NSString *_name;

//在分類(lèi)里聲明屬性 需要自己寫(xiě)set方法和get方法

- (void) setName:(NSString *)name

{

_name = name;

}

- (NSString *) name

{

return _name;

}

//添加屬性 應(yīng)該是與對(duì)象有關(guān)

- (void) setCount:(NSString *)count

{

//set方法里設(shè)置關(guān)聯(lián)

//Associated 關(guān)聯(lián) 聯(lián)系

//跟某個(gè)對(duì)象產(chǎn)生關(guān)聯(lián),添加屬性

/**

* id obj 給哪個(gè)對(duì)象添加屬性(產(chǎn)生關(guān)聯(lián))

* const void *key 屬性名 (根據(jù)key獲取關(guān)聯(lián)的對(duì)象) void * 相當(dāng)于 id 萬(wàn)能指針 傳c或者oc的都可以

* id value 要關(guān)聯(lián)的值

* objc_AssociationPolicy policy 策略 宏對(duì)應(yīng)assign retain copy (因?yàn)閣eak沒(méi)有用 外面賦值完馬上就會(huì)被銷(xiāo)毀 所以沒(méi)有weak)

*/

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

}

- (NSString *) count

{

//get方法里獲取關(guān)聯(lián)

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

}

@end

2.runtime動(dòng)態(tài)加載方法

ViewController中

#import "ViewController.h"

#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

- (void) viewDidLoad{? ? [super viewDidLoad];?

? //performSelector:動(dòng)態(tài)添加方法? ?

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

[p0 performSelector:@selector(eat)];

//動(dòng)態(tài)添加方法 但是如果沒(méi)有實(shí)現(xiàn)該方法 還是會(huì)崩潰?

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

//動(dòng)態(tài)添加方法 但是如果沒(méi)有實(shí)現(xiàn)該方法 還是會(huì)崩潰

}

Person.m中

#import "Person.h"

#import<objc/message.h>

//默認(rèn)一個(gè)方法都有兩個(gè)參數(shù):self 和_cmd? self是方法的調(diào)用者 _cmd就是調(diào)用方法的編號(hào)(方法名) 這兩個(gè)參數(shù)為隱式參數(shù) 但是如果調(diào)用的是c的函數(shù) 則需要寫(xiě)出來(lái)

@implementation Person

//定義函數(shù) 該函數(shù)名是啥都可以

void eat(id self,SEL _cmd)

{

//無(wú)返回值 兩個(gè)參數(shù) void(id,SEL) v@:

NSLog(@"%@調(diào)用了%@方法",[self class],NSStringFromSelector(_cmd));//SEL本身沒(méi)發(fā)打印 只能打印方法名

}

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

{

//v void? ? @ 對(duì)象? ? : 方法編號(hào)

NSLog(@"%@調(diào)用了%@方法 傳遞參數(shù):%@",[self class],NSStringFromSelector(_cmd),param1);//SEL本身沒(méi)發(fā)打印 只能打印方法名

}

//1.動(dòng)態(tài)添加方法 首先要實(shí)現(xiàn)resolveInstanceMethod:方法或resolveClassMethod:方法

//前者對(duì)應(yīng)實(shí)例方法 后者對(duì)應(yīng)類(lèi)方法

//這兩個(gè)方法的作用是要知道哪個(gè)方法沒(méi)有被實(shí)現(xiàn)

//這兩個(gè)方法是在當(dāng)該類(lèi)的某個(gè)方法沒(méi)有實(shí)現(xiàn),但是又被外界調(diào)用了的時(shí)候調(diào)用 (及:外界試用performSelector:調(diào)用了該類(lèi)中某個(gè)沒(méi)有實(shí)現(xiàn)的方法)

//sel參數(shù)為沒(méi)有被實(shí)現(xiàn)的這個(gè)方法

+ (BOOL) resolveInstanceMethod:(SEL)sel

{

//打印該方法名

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

//動(dòng)態(tài)添加方法

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

{

/**

*? Class 給哪個(gè)類(lèi)添加方法

*? sel 要添加的方法編號(hào)(方法名)

*? IMP 方法的實(shí)現(xiàn) ———— 函數(shù)的入口(函數(shù)的指針 函數(shù)名 是啥都可以 不一定和sel相同)

*? types 方法的類(lèi)型 編碼格式 (類(lèi)型c語(yǔ)言的字符串) (函數(shù)的類(lèi)型:返回值類(lèi)型 參數(shù)類(lèi)型 直接查文檔 文檔有表格)

*/

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

//處理完了要返回YES

//? ? ? ? return YES;

}

else if ([NSStringFromSelector(sel) isEqualToString:@"drink:"])//要加冒號(hào)

{

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

//? ? ? ? return YES;

}

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

return [super resolveInstanceMethod:sel];

}

+ (BOOL) resolveClassMethod:(SEL)sel

{

return [super resolveClassMethod:sel];

}

@end

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

ViewController.m中

#import "ViewController.h"

#import<objc/message.h>

#import "Person.h"

//#import "UIImage+image.h"http://交換方法時(shí)候不用導(dǎo)入也可以 因?yàn)榻粨Q寫(xiě)在類(lèi)+(void)load里

@interface ViewController ()

@end

@implementation ViewController

- (void) viewDidLoad{? ? [super viewDidLoad];?

? /**?

? *? 交換方法??

*///??

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

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

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

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

//需求:在以后調(diào)用imageNamed的時(shí)候,要知道圖片是否加載成功??

//交換方法的實(shí)現(xiàn) (把imageNamed:方法和ov_imageNamed:方法交換 及 調(diào)用imageNamed就是調(diào)用ov_imageNamed)

}

@end

UIImage+image.m中

#import"UIImage+image.h"

#import<objc/message.h>

@implementation UIImage (image)

//加載這個(gè)分類(lèi)的時(shí)候調(diào)用

+ (void) load

{

NSLog(@"%s",__func__);

//方法都定義在類(lèi)里面 所以 交換對(duì)象方法也用class_開(kāi)頭

/**

*? class_getMethodImplementation 獲取類(lèi)方法的實(shí)現(xiàn)

*

*? Class 獲取哪個(gè)類(lèi)的方法

*? SEL 獲取哪個(gè)方法

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

*/

/**

*? class_getInstanceMethod 獲取對(duì)象方法

*

*? Class 獲取哪個(gè)類(lèi)的方法

*? SEL 獲取哪個(gè)方法

*

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

*/

/**

*? class_getClassMethod 獲取類(lèi)方法

*

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

*/

/**

*? method_exchangeImplementations交換方法

*

*? Method m1? 要被替換的方法

*? Method m2? 要替換Method m1的方法

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

*/

//交換方法的實(shí)現(xiàn)

//1.拿到兩個(gè)方法

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

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

//2.交換

method_exchangeImplementations(imageNamedMethod, ov_imageNamedMethod);

}

/**

*? 分類(lèi)沒(méi)有父類(lèi) 沒(méi)有super

*/

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

//{

//? ? return nil;

//}

/**

*? 用其他方法做 這個(gè)方法不好的原因是 1.導(dǎo)入頭文件太蛋疼 2.團(tuán)隊(duì)其他人可能不知道

*/

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

{

//1.加載圖片功能

//? ? UIImage *image = [UIImage imageNamed:imageName];//由于使用了方法交換 所以這里再調(diào)用該方法就會(huì)造成死循環(huán)

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

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

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

if (!image)

{

//NSException 為拋異常(強(qiáng)制崩潰)

//? ? ? ? NSException *e = [NSException

//? ? ? ? ? ? ? ? ? ? ? ? ? exceptionWithName: @"異常情況"

//? ? ? ? ? ? ? ? ? ? ? ? ? reason: @"圖片為空"

//? ? ? ? ? ? ? ? ? ? ? ? ? userInfo: nil];

//? ? ? ? @throw e;

}

else

{

}

return image;

}

@end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,032評(píng)論 0 9
  • 對(duì)于從事 iOS 開(kāi)發(fā)人員來(lái)說(shuō),所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,799評(píng)論 7 64
  • OC最實(shí)用的runtime總結(jié),面試、工作你看我就足夠了! 前言runtime的資料網(wǎng)上有很多了,部分有些晦澀難懂...
    small_Sun閱讀 967評(píng)論 1 12
  • 從小就是乖乖的小孩,拿到新書(shū)本要包書(shū)皮,一頁(yè)漂亮的紙簽怎么也舍不得寫(xiě),要收藏起來(lái)。長(zhǎng)大后物質(zhì)資源呈爆炸式增長(zhǎng),留戀...
    迷豆醬閱讀 497評(píng)論 0 0
  • 藍(lán)藍(lán)的天,白白的云 ,金黃又明媚的陽(yáng)光,天地自有一番美好!一陣夏天的風(fēng)吹過(guò),云動(dòng)了,流水唱了,樹(shù)、小草也盡情搖擺,...
    清風(fēng)明月秋閱讀 343評(píng)論 0 0

友情鏈接更多精彩內(nèi)容