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);
for(int i =0; i<count;i++){
// 取出i位置對應的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char*name = ivar_getName(ivar);
// 歸檔
NSString*key = [NSStringstringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
// 設置到成員變量身上
[selfsetValue:value forKey:key];
free(ivars);
? }
returnself;?
? }
@end
這樣我們可以看到歸檔和解檔的案例其實是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
@end
.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);
}
@end
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;
@end
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]];
}
@end
2.runtime動態加載方法
ViewController中
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@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 "Person.h"
#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@:@");
//? ? ? ? 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://交換方法時候不用導入也可以 因為交換寫在類+(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加載圖片,并不知道圖片是否加載成功??
//需求:在以后調用imageNamed的時候,要知道圖片是否加載成功??
//交換方法的實現 (把imageNamed:方法和ov_imageNamed:方法交換 及 調用imageNamed就是調用ov_imageNamed)
}
@end
UIImage+image.m中
#import"UIImage+image.h"
#import<objc/message.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 獲取哪個類的方法
*? SEL 獲取哪個方法
*
*? 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;
}
@end