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

1??runtime介紹:

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

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

1.[[ZSPerson alloc] init]

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

2??runtime 用來干什么呢吧慢??用在那些地方呢慷彤?怎么用呢娄蔼?

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

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

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

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

例如:我們需要對(duì)一個(gè)類的屬性進(jìn)行歸檔解檔的時(shí)候?qū)傩蕴貏e的多,這時(shí)候底哗,我們就會(huì)寫很多對(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寫下的

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

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è)類)

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

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

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

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

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

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

class_…..

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

2.必備常識(shí)

a> Ivar : 成員變量

b> Method : 成員方法

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

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

首先給UITableViewCell創(chuàng)建一個(gè)分類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àn)閞untime是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 給哪個(gè)對(duì)象的屬性賦值

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

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

objc_AssociationPolicy policy? 使用的策略,是一個(gè)枚舉值氢惋,和copy洞翩,retain,assign是一樣的焰望,手機(jī)開發(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é)

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

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

NSString+String.h中

#import<Foundation/Foundation.h>

/**

*? @property在分類里添加一個(gè)屬性 不會(huì)有setter getter方法 只聲明了一個(gè)變量 而且 這樣聲明 這個(gè)屬性和這個(gè)類沒有什么關(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;

//在分類里聲明屬性 需要自己寫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 萬能指針 傳c或者oc的都可以

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

* objc_AssociationPolicy policy 策略 宏對(duì)應(yīng)assign retain copy (因?yàn)閣eak沒有用 外面賦值完馬上就會(huì)被銷毀 所以沒有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)添加方法 但是如果沒有實(shí)現(xiàn)該方法 還是會(huì)崩潰?

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

//動(dòng)態(tà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ù) 則需要寫出來

@implementation Person

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

void eat(id self,SEL _cmd)

{

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

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

}

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

{

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

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

}

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

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

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

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

//sel參數(shù)為沒有被實(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è)類添加方法

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

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

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

*/

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寫在類+(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è)分類的時(shí)候調(diào)用

+ (void) load

{

NSLog(@"%s",__func__);

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

/**

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

*

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

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

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

*/

/**

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

*

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

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

*

*? 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#>)

*/

//交換方法的實(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);

}

/**

*? 分類沒有父類 沒有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)系作者
  • 序言:七十年代末熊赖,一起剝皮案震驚了整個(gè)濱河市来屠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌震鹉,老刑警劉巖俱笛,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異足陨,居然都是意外死亡嫂粟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門墨缘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人零抬,你說我怎么就攤上這事镊讼。” “怎么了平夜?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蝶棋,是天一觀的道長。 經(jīng)常有香客問我忽妒,道長玩裙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任段直,我火速辦了婚禮吃溅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸯檬。我一直安慰自己决侈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布喧务。 她就那樣靜靜地躺著赖歌,像睡著了一般枉圃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庐冯,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天孽亲,我揣著相機(jī)與錄音,去河邊找鬼展父。 笑死返劲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的犯祠。 我是一名探鬼主播旭等,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼衡载!你這毒婦竟也來了搔耕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤痰娱,失蹤者是張志新(化名)和其女友劉穎弃榨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梨睁,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲸睛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坡贺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片官辈。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖遍坟,靈堂內(nèi)的尸體忽然破棺而出拳亿,到底是詐尸還是另有隱情,我是刑警寧澤愿伴,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布肺魁,位于F島的核電站,受9級(jí)特大地震影響隔节,放射性物質(zhì)發(fā)生泄漏鹅经。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一怎诫、第九天 我趴在偏房一處隱蔽的房頂上張望瘾晃。 院中可真熱鬧,春花似錦刽虹、人聲如沸酗捌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胖缤。三九已至尚镰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哪廓,已是汗流浹背狗唉。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涡真,地道東北人分俯。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像哆料,于是被迫代替她去往敵國和親缸剪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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

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