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

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