Runtime的基本运用
一、什么是runtime(也就是所谓的“运行时”,因为是在运行时实现的。)
1.runtime是一套底层的c语言API(包括很多强大实用的c语言类型,c语言函数); [runtime运行系统]
2.实际上,平时我们编写的oc代码,底层都是基于runtime实现的; [OC语言的动态性]
运行时系统 (runtime system),对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。 runtime就是OC辛苦的幕后工作人员。(编译器会自动帮助我们编译成runtime代码。)
动态特性,使得它在语言层面上支持程序的可扩展性。只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法。利用runtime机制让我们可以在程序运行时动态修改类的具体实现、包括类中的所有私有属性、方法。这也是本文runtime例子的出发点。
我们所敲入的代码转化为运行时的runtime函数代码,最终在程序运行时转成了底层的runtime的c语言代码 ;
举例:
当某个对象使用语法[receiver message]来调用某个方法时,其实[receiver message]被编译器转化为:
id objc_msgSend ( id self, SEL op, ... );
也就是说,我们平时编写的oc代码,方法调用的本质,就是在编译阶段,编译器转化为向对象发送消息。
Demo地址:https://github.com/mengzhihun6
二、runtime的几种使用方法
我们通过继承于NSObject的person类,来对runtime进行学习。
本文共有6个关于runtime机制方法的小例子,分别是:
- 获取person类的所有变量;
- 获取person类的所有方法;
- 改变person类的私有变量name的值;
- 为person的category类增加一个新属性;
- 为person类添加一个方法;
- 交换person类的2个方法的功能;
(个人习惯,喜欢为6个例子添加按钮各自的行为方法,并分别执行相应的行为,以此看清各个runtime函数的具体功能所带来的效果。)
首先,创建新的项目,并在项目中新建一个普通的OC类:person类(继承于NSObject),为了避免后面与其他方法函数搞混,我们把完整的person类编写齐全,用于后面使用runtime的几种方法:
①JGPerson.h如下:
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface JGPerson : NSObject @property (nonatomic, copy) NSString *name; //属性变量 @property (nonatomic, assign) int age; -(void)func1;
-(void)func2; @end NS_ASSUME_NONNULL_END
②JGPerson.m如下:
#import "JGPerson.h" @implementation JGPerson {
double _score; //实例变量
} - (instancetype)init {
if (self = [super init]) { self.name = @"小明";
self.age = ;
_score = 66.5;
}
return self;
} //person的2个普通方法
-(void)func1
{
NSLog(@"执行func1方法。");
}
-(void)func2
{
NSLog(@"执行func2方法。");
} - (NSString *)description {
return [NSString stringWithFormat:@" %d岁的 %@考了%.1f分",self.age, self.name, _score];
} @end
从person类的描述中,我们可以看到person类含有一个可供外类使用的共有属性age,以及一个外界不可以访问私有属性name,但是,有木有想过,其实在外类,name也是可以访问的。OC里面,通过runtime系统,苹果允许不受这些私有属性的限制,对私有属性私有方法等进行访问、添加、修改、甚至替换系统的方法。
那么,为项目的故事板添加6个按钮;
在使用runtime的地方,我们都需要包含头文件: (本文几个例子中,都只需要在ViewController.m中包含.)
#import <objc/runtime.h>
1.获取person类的所有变量
将第一个按钮关联到ViewController.h,添加行为并命名其方法为:“getAllVariable”:
/*1.获取person所有的成员变量*/
- (IBAction)Func1:(id)sender {}
在ViewController.m中的实现如下:
/*1.获取person所有的成员变量*/
- (IBAction)Func1:(id)sender { unsigned int count = ;
//获取类的一个包含所有变量的列表,IVar是runtime声明的一个宏,是实例变量的意思.
Ivar *allIvariables = class_copyIvarList([JGPerson class], &count); for (int i = ; i < count; i++) {
//遍历每一个变量,包含名称和类型(此处没有星号“*”)
Ivar ivar = allIvariables[i];
//获取成员变量名称
const char *VariableName = ivar_getName(ivar);
//获取成员变量类型
const char *VariableType = ivar_getTypeEncoding(ivar); NSLog(@"名称:%s => 类型:%s",VariableName, VariableType);
}
}
得到的输出如下:(i表示类型为int d表示类型为double)
-- ::17.415756+ RuntimeDemo[:] 名称:_score => 类型:d
-- ::17.415868+ RuntimeDemo[:] 名称:_age => 类型:i
-- ::17.415939+ RuntimeDemo[:] 名称:_name => 类型:@"NSString"
分析:Ivar,一个指向objc_ivar结构体指针,包含了变量名、变量类型等信息。
可以看到,私有属性name能够访问到了。 在有些项目中,为了对某些私有属性进行隐藏,某些.h文件中没有出现相应的显式创建,而是如上面的person类中,在.m中进行私有创建,但是我们可以通过runtime这个有效的方法,访问到所有包括这些隐藏的私有变量。
拓展:
①class_copyIvarList能够获取一个含有类中所有成员变量的列表,列表中包括属性变量和实例变量。需要注意的是,如果如本例中,age返回的是"_age",但是如果在person.m中加入:
@synthesize age;
那么控制台第二行返回的是"(Name: age) ----- (Type:i) ;"(因为@property是生成了"_age",而@synthesize是执行了"@synthesize age = _age;",关于OC属性变量与实例变量的区别、@property、@synthesize的作用等具体的知识,有兴趣的童鞋可以自行了解。)
②如果单单需要获取属性列表的话,可以使用函数:class_copyPropertyList();只是返回的属性变量仅仅是“age”,做为实例变量的name是不被获取的。
而class_copyIvarList()函数则能够返回实例变量和属性变量的所有成员变量。
2.获取person类的所有方法
将第二个按钮关联到ViewController.h,添加行为并命名其方法为:“getAllMethod”:
/*2.获取person所有方法*/
- (IBAction)Func2:(id)sender {}
在ViewController.m中的实现如下:
/*2.获取person所有方法*/
- (IBAction)Func2:(id)sender { unsigned int count = ;
//获取方法列表,所有在.m文件显式实现的方法都会被找到,包括setter+getter方法;
Method *allMethods = class_copyMethodList([JGPerson class], &count); for (int i = ; i < count; i++) {
//Method,为runtime声明的一个宏,表示对一个方法的描述
Method md = allMethods[i];
//获取SEL:SEL类型,即获取方法选择器@selector()
SEL sel = method_getName(md);
//得到sel的方法名:以字符串格式获取sel的name,也即@selector()中的方法名称
const char *methodName = sel_getName(sel); NSLog(@"Method%d:%s",i+, methodName);
} }
控制台输出:
-- ::18.052985+ RuntimeDemo[:] Method1:eat
-- ::18.053088+ RuntimeDemo[:] Method2:.cxx_destruct
-- ::18.053154+ RuntimeDemo[:] Method3:description
-- ::18.053212+ RuntimeDemo[:] Method4:name
-- ::18.053273+ RuntimeDemo[:] Method5:setName:
-- ::18.053333+ RuntimeDemo[:] Method6:init
-- ::18.053389+ RuntimeDemo[:] Method7:run
-- ::18.053443+ RuntimeDemo[:] Method8:setAge:
-- ::18.053502+ RuntimeDemo[:] Method9:age
控制台输出了包括set和get等方法名称。【备注:.cxx_destruct方法是关于系统自动内存释放工作的一个隐藏的函数,当ARC下,且本类拥有实例变量时,才会出现;】
分析:Method是一个指向objc_method结构体指针,表示对类中的某个方法的描述。在API中的定义:
typedef struct objc_method *Method;
而objc_method结构体如下
:
truct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
- method_name :
方法选择器@selector(),类型为
相同名字的方法下,即使在不同类中定义,它们的方法选择器也相同。SEL
。 - method_types:方法类型,是个
char
指针,存储着方法的参数类型和返回值类型。 method_imp:指向
方法的具体实现的指针,数据类型为IMP,本质上是一个函数指针。 在第五个按钮行为“增加一个方法”部分会提到。
SEL:数据类型,表示方法选择器,可以理解为对方法的一种包装。在每个方法都有一个与之对应的SEL类型的数据,根据一个SEL数据“@selector(方法名)”就可以找到对应的方法地址,进而调用方法。
因此可以通过:获取Method结构体->得到SEL选择器名称->得到对应的方法名,这样的方式,便于认识OC中关于方法的定义。
3.改变person对象的私有变量name的值.
将第三个按钮关联到ViewController.h,添加行为并命名其方法为:“changeVariable”:
/*3.改变person的name变量属性*/
- (IBAction)Func3:(id)sender {}
在ViewController.m中创建一个person对象,记得初始化
@implementation ViewController {
JGPerson *_person;
} - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. _person = [[JGPerson alloc] init]; }
在ViewController.m中的实现如下:
/*3.改变person的name变量属性*/
- (IBAction)Func3:(id)sender {
NSLog(@"改变之前的Penson:%@",_person); unsigned int count = ; Ivar *allList = class_copyIvarList([JGPerson class], &count); Ivar ivar = allList[];
//从第一个例子getAllVariable中输出的控制台信息,我们可以看到name为第2个实例属性;
// object_setIvar(<#id _Nullable obj#>, <#Ivar _Nonnull ivar#>, <#id _Nullable value#>)
object_setIvar(_person, ivar, @"小刚"); //name属性小明被强制改为小刚。 NSLog(@"改变之后的Penson:%@",_person); }
控制台输出:
-- ::51.312155+ RuntimeDemo[:] 改变之前的Penson: 22岁的 小明考了66.5分
-- ::51.312235+ RuntimeDemo[:] 改变之后的Penson: 22岁的 小刚考了66.5分
4.为person的category类增加一个新属性:
如何在不改动某个类的前提下,添加一个新的属性呢?
答:可以利用runtime为分类添加新属性。
在iOS中,category也就是分类,是不可以为本类添加新的属性的,但是在runtime中我们可以使用对象关联,为person类进行分类的新属性创建:
分类.h文件
#import "JGPerson.h" NS_ASSUME_NONNULL_BEGIN @interface JGPerson (JGCategory) @property (nonatomic, assign) float height; //新属性 @end NS_ASSUME_NONNULL_END
分类.m文件
#import "JGPerson+JGCategory.h"
#import <objc/runtime.h> const char *str = "personKey"; //做为key,字符常量 必须是C语言字符串; @implementation JGPerson (JGCategory) - (void)setHeight:(float)height {
NSNumber *num = [NSNumber numberWithFloat:height]; /*
第一个参数是需要添加属性的对象;
第二个参数是属性的key;
第三个参数是属性的值,类型必须为id,所以此处height先转为NSNumber类型;
第四个参数是使用策略,是一个枚举值,类似@property属性创建时设置的关键字,可从命名看出各枚举的意义;
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
*/ objc_setAssociatedObject(self, str, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} - (float)height {
NSNumber *number = objc_getAssociatedObject(self, str);
return [number floatValue];
} @end
接下来,我们可以在ViewController.m中对person的一个对象进行height的访问了,
将第四个按钮关联到ViewController.h添加行为并命名其方法为:“addVariable”:
/* 4.添加新的属性*/
- (IBAction)Func4:(id)sender { /*
如何在不改动某个类的前提下,添加一个新的属性呢? 答:可以利用runtime为分类添加新属性。
*/
//给新属性height赋值
_person.height= ; //给新属性height赋值
//访问新属性值
NSLog(@"%f",[_person height]); }
点击按钮四、再点击按钮一、二获取类的属性、方法。
-- ::02.265178+ RuntimeDemo[:] 168.000000
-- ::09.185091+ RuntimeDemo[:] 名称:_score => 类型:d
-- ::09.185190+ RuntimeDemo[:] 名称:_age => 类型:i
-- ::09.185265+ RuntimeDemo[:] 名称:_name => 类型:@"NSString"
-- ::11.165884+ RuntimeDemo[:] Method1:func1
-- ::11.165984+ RuntimeDemo[:] Method2:func2
-- ::11.166057+ RuntimeDemo[:] Method3:.cxx_destruct
-- ::11.166123+ RuntimeDemo[:] Method4:description
-- ::11.166176+ RuntimeDemo[:] Method5:name
-- ::11.166228+ RuntimeDemo[:] Method6:setName:
-- ::11.166293+ RuntimeDemo[:] Method7:init
-- ::11.166460+ RuntimeDemo[:] Method8:height
-- ::11.166551+ RuntimeDemo[:] Method9:setHeight:
-- ::11.166661+ RuntimeDemo[:] Method10:setAge:
-- ::11.166771+ RuntimeDemo[:] Method11:age
分析:可以看到分类的新属性可以在per对象中对新属性height进行访问赋值。
获取到person类属性时,依然没有height的存在,但是却有height和setHeight这两个方法;因为在分类中,即使使用@property定义了,也只是生成set+get方法,而不会生成_变量名,分类中是不允许定义变量的。
使用runtime中objc_setAssociatedObject()和objc_getAssociatedObject()方法,本质上只是为对象per添加了对height的属性关联,但是达到了新属性的作用;
使用场景:假设imageCategory是UIImage类的分类,在实际开发中,我们使用UIImage下载图片或者操作过程需要增加一个URL保存一段地址,以备后期使用。这时可以尝试在分类中动态添加新属性MyURL进行存储。
5.为person类添加一个新方法
将第五个按钮关联到ViewController.h,添加行为并命名其方法为:“addMethod”:
/*5.添加新的方法试试(这种方法等价于对Father类添加Category对方法进行扩展):*/
- (IBAction)Func5:(id)sender {}
在ViewController.m中的实现如下:
/*5.添加新的方法试试(这种方法等价于对Father类添加Category对方法进行扩展):*/
- (IBAction)Func5:(id)sender { class_addMethod([_person class], @selector(NewMethod),(IMP)myAddingFunction, ); [_person performSelector:@selector(NewMethod)];
} //具体的实现(方法的内部都默认包含两个参数Class类和SEL方法,被称为隐式参数。)
int myAddingFunction(id self, SEL _cmd)
{
NSLog(@"已新增方法:NewMethod");
return ;
}
控制台输出:
-- ::14.522734+ RuntimeDemo[:] 已新增方法:NewMethod
6.交换person类的2个方法的功能
将第六个按钮关联到ViewController.h,添加行为并命名其方法为:“replaceMethod”:
/* 6.交换两种方法之后(功能对调*/
- (IBAction)Func6:(id)sender {}
在ViewController.m中的实现如下:
/* 6.交换两种方法之后(功能对调*/
- (IBAction)Func6:(id)sender { Method method1 = class_getInstanceMethod([JGPerson class], @selector(func1));
Method method2 = class_getInstanceMethod([JGPerson class], @selector(func2)); method_exchangeImplementations(method1, method2); [_person func1]; }
控制台输出:
-- ::14.902453+ RuntimeDemo[:] 执行func2方法。
交换方法的使用场景:项目中的某个功能,在项目中需要多次被引用,当项目的需求发生改变时,要使用另一种功能代替这个功能,且要求不改变旧的项目(也就是不改变原来方法实现的前提下)。那么,我们可以在分类中,再写一个新的方法(符合新的需求的方法),然后交换两个方法的实现。这样,在不改变项目的代码,而只是增加了新的代码的情况下,就完成了项目的改进,很好地体现了该项目的封装性与利用率。
注:交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次。
参考:https://www.cnblogs.com/azuo/p/5505782.html
Runtime的基本运用的更多相关文章
- runtime梳理。
一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C语言,函数的调用在编译的时候会决定调用哪个函数. 对于OC的函数,属于 ...
- myeclipse 无法启动 java.lang.IllegalStateException: Unable to acquire application service. Ensure that the org.eclipse.core.runtime bundle is resolved and started (see config.ini).
把myeclipse10 按照目录完整拷贝到了另外一台电脑, 另外的目录 原安装目录 D\:\soft\i\myeclipse10 新安装目录 E\:\soft\myeclipse10 双击启动失败, ...
- Objective-C runtime初识
Objective-C Runtime Describes the macOS Objective-C runtime library support functions and data struc ...
- Objective-C runtime的常见应用
用Objective-C等面向对象语言编程时,"对象"(object)就是"基本构造单元"(building block).开发者可以通过对象来存储并传递数据. ...
- Runtime应用防止按钮连续点击 (转)
好久之前就看到过使用Runtime解决按钮的连续点击的问题,一直觉得没啥好记录的.刚好今天旁边同时碰到这个问题,看他们好捉急而且好像很难处理,于是我先自己看看… 前面自己也学习了很多Runtime的东 ...
- iOS开发-- 通过runtime kvc 移除导航栏下方的阴影效果线条
网上查了很多, 都是重新绘制, 感觉有点蠢, 恰巧工作有会闲, 就简单的通过runtime遍历了下属性找寻了下私有类和方法, 这里直接贴方法, 找寻过程也发出来, 能看懂的直接就能看懂, 看不太明白的 ...
- VS2015 出现 .NETSystem.Runtime.Remoting.RemotingException: TCP 错误
错误内容: 界面显示内容为: .NET�������������System.Runtime.Remoting.RemotingException: TCP 淇¢亾鍗忚鍐茬獊: 搴斾负鎶ュご銆� 鍦 ...
- DirectX runtime
DirectX 9.0 runtime etc https://www.microsoft.com/en-us/download/details.aspx?id=7087 DirectX 11 run ...
- runtime
7.runtime实现的机制是什么,怎么用,一般用于干嘛. 你还能记得你所使用的相关的头文件或者某些方法的名称吗? 运行时机制,runtime库里面包含了跟类.成员变量.方法相关的API,比如获取类里 ...
- runtime 第四部分method swizzling
接上一篇 http://www.cnblogs.com/ddavidXu/p/5924597.html 转载来源http://www.jianshu.com/p/6b905584f536 http:/ ...
随机推荐
- 【安装】Microsoft SQL Server的安装
数据库版本:2012 系统环境:windows 7 一.安装 依次选择“安装->全新 SQL Server 独立安装或向现有安装添加功能”;点“确定” 选择版本,推荐标准版,这里是直接输入序列号 ...
- mysql 存储过程简单实例
一.什么是存储过程 存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程 ...
- MVC ---- ckeditor 批量绑定 blur 事件
在项目遇到个问题,就是把循环出来的ckeditor 批量添加 blur 事件,折腾了2天 终于搞定 @{ ].Rows) { <table class="ui-jqgrid-btabl ...
- Cocos2d-x学习笔记(十)CC_CALLBACK回调函数相关宏
这里加入一个插曲,是关于Cocos2d-x回调函数的.首先,让我们Cocos支持的回调函数宏有哪些,以及其原型: // new callbacks based on C++11 #define CC_ ...
- Python 实现协程
协程的概念 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程.(其实并没有说明白~) 我觉得单说协程,比较抽象,如果对线程有一定了解的话,应该就比较 ...
- MongoDB(课时16 分页显示)
3.4.2.11 数据分页显示 在MongoDB里面的数据分页显示也是符合于大数据要求的操作函数: skip(n):表示跨过多少数据行 limit(n):取出的数据行的个数限制 范例:分页显示(比如显 ...
- 使用better-scroll遇到的问题
项目中想给侧边栏添加一个滚动效果,用better-scroll帮助实现,引入better-scroll后,给外层最大盒子添加了<aside ref="asideMenu"&g ...
- 【转】xml节点解析成字符串的方法
网址:http://blog.csdn.net/shanzhizi/article/details/8817532 ZC: 这是 libxml2的 之前汇总了一篇关于xml文档与字符串转换的文章,文章 ...
- Qt5.3.2_CentOS6.4_基本编程环境__20160306【勿删,繁琐】
20160306 全程没有f/q ZC:使用的虚拟机环境是:博客园VMwareSkill 的 “CentOS6.4_x86_120g__20160306.rar” 1. 执行命令“gcc -v”,显示 ...
- Qt4_WebKit_例子
1. 安装包:qt-opensource-windows-x86-vs2010-4.8.6.exe Qt4已经编译好的文件:E:\ZC_software_installDir\Qt_4.8.6\dem ...