一、Runtime前言
最近研究Runtime,基础不够好,研究好久了,才了解一些些,知道个大概,这里做一个笔记。OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类的对象相应的方法。利用runtime机制让我们可以在程序运行时动态修改类,对象中的所有属性,方法,就算是私有方法以及私有属性都可以动态的修改。所以我所理解的就是 动态创建类,修改类,访问私有方法等一些基本特性,应该说理解runtime的基本用法吧!
二、Runtime简介
Runtime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制,runtime是一套比较底层的纯C语言API,属于一个C语言库,我们平时写的OC代码中,程序在运行过程时,其实最终都是转成了runtime的C语言代码,runtime算是OC的幕后工作者。比如 OC: [[Person alloc] init]; runtime: objc_msgSend(objc_msgSden(“Peroson”, “allo”), “init”) runtime是开源库,在这里可以查看苹果官网的开源的源代码
三、Runtime用在什么场景
1.在程序运行过程中,动态创建一个类(比如KVO的底层实现)
2.在程序运行过程中,动态地为某个类添加属性\方法,修改属性值\方法
3.遍历一个类的所有成员变量(属性)\所有方法
4.交换方法实现
5.动态创建类
四、Runtime 术语
我们知道 [Person message]; 转换成 objc_msgSend(Persong, @selelct(message)); 它本身是这样的
id obje_msgSend(id self, SEL op, …);
1.id
objc_msgSend第一个参数类型id,它是一个指向类实例的指针:
typedef struct objc_object *id (objc_object是什么 查看第4点)
2.SEL objc_msgSend函数的第二个参数类型SEL, 它是selector在Objc中的表示类型。selector是方法选择器,可以理解为区分方法的ID
3. … 是参数
4.struct objc_object {Class isa; };
objc_object 结构体包含一个 isa 指针,根据isa指针就可以找到对象所属的类。 (可以查看源代码 )
4.Class
isa是指针是因为Class其实是一个指向objc_class结构体的指针
typedef struct objc_class *Class
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
这边包含了 超类指针, 类名, 成员变量, 方法,缓存, 还有附属的协议
Method:是一种代表类中的某个方法的类型
Ivar: 是一种代表类中实例变量的类型
IMP:这个函数指针就是指向这个方法的实现
Cache:缓存
相关函数
objc_msgSend : 给对象发送消息
class_copyMethodList : 遍历某个类所有的方法
class_copyIvarList : 遍历某个类所有的成员变量
必备常识 1> Ivar : 成员变量 2> Method : 成员方法
五、Runtime 场景举例
每个按钮对应一个操作
首先创建一个Person类
Person.h文件
//
// Person.h
// RuntimeTestDemo
//
// Created by GongHui_YJ on 16/6/2.
// Copyright ? 2016年 YangJian. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *address;
- (void)eat;
- (void)test1;
- (void)test2;
@end
Person.m文件
//
// Person.m
// RuntimeTestDemo
//
// Created by GongHui_YJ on 16/6/2.
// Copyright ? 2016年 YangJian. All rights reserved.
//
#import "Person.h"
@implementation Person
{
int age;
NSString *sex;
}
- (instancetype)init
{
self = [super init];
if (self) {
_name = @"张三";
_address = @"浙江省杭州市";
age = 18;
sex = @"男";
}
return self;
}
- (void)eat
{
NSLog(@"吃饭");
}
- (void)test1
{
NSLog(@"我是test1方法");
}
- (void)test2
{
NSLog(@"我是test2方法");
}
- (NSString *)description
{
return [NSString stringWithFormat:@"name: %@ -- age: %i -- sex:%@ -- %@", _name, age, sex, _address];
}
@end
在ViewControll.m实现如下
1.获取属性\成员变量列表 使用 class_copyIvaeList函数
/**
* 获取属性/成员
*
* @param sender sender
*/
- (IBAction)getProperty:(id)sender {
Class classPerson = NSClassFromString(@"Person");
NSLog(@"--------------获取所有成员变量列表打印结果如下-----------------");
// 获取所有成员变量列表 使用 class_copyIvarList
unsigned int count = 0; //
Ivar *ivarList = class_copyIvarList(classPerson, &count); // 获取所有的成员变量列表 count 记录变量的数量
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i]; // 取出第i个位置的成员变量
const char *perosonName = ivar_getName(ivar); //获取变量名
const char *perosonType = ivar_getTypeEncoding(ivar); // 获取变量编码类型
NSLog(@"%s ---- %s\n", perosonName, perosonType);
}
NSLog(@"--------------分割线-----------------");
NSLog(@"--------------获取所有属性列表打印结果如下-----------------");
// 获取属性列表 使用 class_copyPropertyList
unsigned int countProperty = 0;
objc_property_t *propertyList = class_copyPropertyList(classPerson, &countProperty);
for (int i = 0; i < countProperty; i++) {
const char *cName = property_getName(propertyList[i]);
const char *butes = property_getAttributes(propertyList[i]);
NSLog(@"%s --- %s\n", cName, butes);
}
// 获取成员变量列表打印结果 (使用class_copyIvarList函数)
/**
2016-06-02 19:26:16.522 RuntimeTestDemo[32706:3539390] --------------获取所有成员变量列表打印结果如下-----------------
2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] age ---- i
2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] sex ---- @"NSString"
2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] _name ---- @"NSString"
2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] _address ---- @"NSString"
2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] --------------分割线-----------------
2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] --------------获取所有属性列表打印结果如下-----------------
2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] name --- [email protected]"NSString",&,N,V_name
2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] address --- [email protected]"NSString",&,N,V_address
*/
}
2.修改私有变量的值
/**
* 修改私有变量的值
*
* @param sender
*/
- (IBAction)updatePrivateValue:(id)sender {
Person *person = [[Person alloc] init];
NSLog(@"修改前数据: %@", [person description]);
unsigned int count = 0; //
Ivar *ivarList = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar var = ivarList[i];
if (i == 0) // 1 是表示私有变量 age 刚刚上面打印出来 第2个
{
object_setIvar(person, var, @28); // 把私有变量age的值改成 28
}
if (i == 1) // 私有变量 sex
{
object_setIvar(person, var, @"女");
}
}
NSLog(@"修改后数据---: %@", [person description]);
/** 打印结果 发现 私有变量 age 和 sex 的值已经改变
2016-06-02 19:48:32.218 RuntimeTestDemo[33349:3554863] 修改前数据: name: 张三 -- age: 18 -- sex:男 -- 浙江省杭州市
2016-06-02 19:48:32.218 RuntimeTestDemo[33349:3554863] 修改后数据---: name: 张三 -- age: 450 -- sex:女 -- 浙江省杭州市
*/
}
3.获取类的所有方法
/**
* 获取类的所有方法(包括私有)
*
* @param sender sender
*/
- (IBAction)getAllMethod:(id)sender {
unsigned int count = 0;
Method *memberFuncs = class_copyMethodList([Person class], &count); // 获取所有方法名
for (int i = 0; i < count; i++) {
SEL name = method_getName(memberFuncs[i]);
const char *nameMethod = sel_getName(name); // 获取方法名
NSLog(@"%s", nameMethod);
}
/** 获取所有.m 文件的所有方法 其中包括属性的get set方法.cxx_destruct 系统的
2016-06-02 19:55:26.411 RuntimeTestDemo[33544:3558588] eat
2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] address
2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] .cxx_destruct
2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] description
2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] name
2016-06-02 19:55:26.413 RuntimeTestDemo[33544:3558588] setName:
2016-06-02 19:55:26.413 RuntimeTestDemo[33544:3558588] init
2016-06-02 19:55:26.413 RuntimeTestDemo[33544:3558588] setAddress:
*/
}
4.动态添加方法
/**
* 动态添加方法
*
* @param sender sender
*/
- (IBAction)addMethod:(id)sender {
// class_addMethod函数参数的含义:
// 第一个参数Class cls, 类型
// 第二个参数SEL name, 被解析的方法
// 第三个参数 IMP imp, 指定的实现 这里表示具体的实现方法 myTestMethod
// 第四个参数const char *types,方法的类型(方法的参数) 0代表没有参数
// const char *cs = getprogname();
class_addMethod([Person class], @selector(NewMethod::), (IMP)myTestMethod, "[email protected]:[email protected]"); // 这里会报警告 可以忽略
//调用方法 【如果使用[per method]方法!(在ARC下会报no visible @interface 错误)】
[person1 performSelector:@selector(NewMethod::)];
}
// 具体的实现, 即IMP所指向的方法
int myTestMethod(id self, SEL _cmd, int var1, NSString *str)
{
NSLog(@"已经新增方法");
return var1;
}
/**打印结果 打印如下 说明已经添加成功
2016-06-03 11:00:58.040 RuntimeTestDemo[34732:3750980] 已经新增方法
*/
/** 此刻再打印获取类的所有方法 NewMethod:: 这个新增的方法已经添加进来
2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] NewMethod::
2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] eat
2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] address
2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] .cxx_destruct
2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] description
2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] name
2016-06-03 11:00:59.186 RuntimeTestDemo[34732:3750980] setName:
2016-06-03 11:00:59.186 RuntimeTestDemo[34732:3750980] init
2016-06-03 11:00:59.186 RuntimeTestDemo[34732:3750980] setAddress:
*/
5.交换方法实现
/**
* 方法交换
*
* @param sender sender
*/
- (IBAction)methodExchange:(id)sender {
[person1 test1]; // 未交换前的输出结果
Method method1 = class_getInstanceMethod([person1 class], @selector(test1));
Method method2 = class_getInstanceMethod([person1 class], @selector(test2));
// 方法交换
method_exchangeImplementations(method1, method2);
[person1 test1]; // 输出交换后的结果
/** 打印结果 交换完之后 test1的方法 打印test2的结果
2016-06-03 11:27:57.921 RuntimeTestDemo[35341:3767704] 我是test1方法
2016-06-03 11:27:57.922 RuntimeTestDemo[35341:3767704] 我是test2方法
*/
}
6.动态创建一个类
/**
* 动态添加类
*
* @param sender
*/
- (IBAction)addClass:(id)sender {
// 添加一个Student类
Class classStudent = objc_allocateClassPair([Person class], "Student", 0);
// 添加一个NSStrig的变量
if (class_addIvar(classStudent, "schoolName", sizeof(NSString *), 0, "@")) {
NSLog(@"添加成员变量 schollName成功");
}
// 为Student类添加方法
if (class_addMethod(classStudent, @selector(printSchool), (IMP)printSchool, "[email protected]:")) {
NSLog(@"添加方法printSchool成功");
}
// 注册这个类到runtime系统中 可以使用他
objc_registerClassPair(classStudent); // 返回void
// 创建类
id student = [[classStudent alloc] init];
NSString *schoolName = @"福建师范大学";
// 给刚刚添加的变量赋值
[student setValue:schoolName forKey:@"schoolName"];
// 动态调用
[student performSelector:@selector(printSchool) withObject:nil];
/** 打印结果
2016-06-03 11:49:26.724 RuntimeTestDemo[35846:3781380] 添加成员变量 schollName成功
2016-06-03 11:49:26.725 RuntimeTestDemo[35846:3781380] 添加方法printSchool成功
2016-06-03 11:49:26.725 RuntimeTestDemo[35846:3781380] 我的学校是福建师范大学
*/
}
//方法的实现
void printSchool(id self, SEL _cmd)
{
NSLog(@"我的学校是%@", [self valueForKey:@"schoolName"]);
}
六、Runtime使用心得
Runtime很强大,这里我只是初体验,对于很多的东西还不懂,不理解,算是一个初步的了解吧,应该算了解了runtime的基本用法,用来确实很爽。要理解透彻,完全的应用到实际项目中,还需努力。对新人了解应该有帮助,首先知道runtime是什么,runtime的基本使用,再慢慢挖掘,逐步吃透。博客中的demo我已经上传,需要的可以下载运行看下打印的日志。如果不足还望指出。
七、demo地址和参考博客,感言致谢
demo地址:http://download.csdn.net/detail/yj229201093/9540125 (积分2分,分多任性 哈哈)
github地址:https://github.com/yj229201093/RuntimeTestDemo
这里感谢两位大神的博客,受益良多。
主要参考链接(当然网络上还有很多好的博客都学习了O(∩_∩)O哈哈~)
1.http://www.bkjia.com/IOSjc/1012702.html
2.http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/