iOS 执行时
一、什么是执行时(Runtime)?
- 执行时是苹果提供的纯C语言的开发库(执行时是开发中经经常使用到的底层技术)
二、执行时的作用?
- 能获得某个类的全部成员变量
- 能获得某个类的全部属性
- 能获得某个类的全部方法
- 交换方法实现
- 能动态加入一个成员变量
- 能动态加入一个属性
- 能动态加入一个方法
三、案例:执行时获取成员变量名称
- 1、分析
#import <Foundation/Foundation.h>
#import "CKPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 成员变量的数量
unsigned int outCount = 0;
// 获得全部的成员变量
// ivars是一个指向成员变量的指针
// ivars默认指向第0个成员变量
Ivar *ivars = class_copyIvarList([CKPerson class], &outCount);
// 遍历全部的成员变量
for (int i = 0; i<outCount; i++) {
// 取出i位置相应的成员变量
// Ivar ivar = *(ivars + i);
Ivar ivar = ivars[i];
// 获得成员变量的名字
NSLog(@"%s", ivar_getName(ivar));
}
// 假设函数名中包括了copy\new\retain\create等字眼,那么这个函数返回的数据就须要手动释放
free(ivars);
// Ivar ivar = *ivars;
// Ivar ivar2 = *(ivars + 1);
// NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2));
// 一个Ivar就代表一个成员变量
// int *p; 指向int类型的变量
// Ivar *ivars; 指向Ivar类型的变量
}
return 0;
}
- 2、获取UITextFiled成员变量的名称
imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" class="imagebubble-image" alt="" />
// 成员变量的数量
unsigned int outCount = 0;
// 获得全部的成员变量
Ivar *ivars = class_copyIvarList([UITextField class], &outCount);
// 遍历全部的成员变量
for (int i = 0; i<outCount; i++) {
// 取出i位置相应的成员变量
Ivar ivar = ivars[i];
// 获得成员变量的名字
NSLog(@"%s", ivar_getName(ivar));
}
// 假设函数名中包括了copy\new\retain\create等字眼,那么这个函数返回的数据就须要手动释放
free(ivars);
四、iOS底层
1、The Runtime 简介
- Objective-C是一门简单的语言,95%是C。仅仅是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的执行时。它非常小但却非常强大。它的核心是消息分发。
Messages:
- 执行一个方法。有些语言。编译器会执行一些额外的优化和错误检查,由于调用关系非常直接也非常明显。
但对于消息分发来说,就不那么明显了。在发消息前不必知道某个对象能否够处理消息。你把消息发给它,它可能会处理,也可能转给其它的Object来处理。一个消息不必相应一个方法。一个对象可能实现一个方法来处理多条消息。
- 在Objective-C中。消息是通过objc_msgSend()这个runtime方法及相近的方法来实现的。这种方法须要一个target。selector,还有一些參数。理论上来说。编译器仅仅是把消息分发变成objc_msgSend来执行。比方以下这两行代码是等价的。
[array insertObject:foo atIndex:5];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
Objects, Classes, MetaClasses
- 大多数面向对象的语言里有 classes 和 objects 的概念。
Objects通过Classes生成。
可是在Objective-C中,classes本身也是objects,也能够处理消息。这也是为什么会有类方法和实例方法。详细来说。Objective-C中的Object是一个结构体(struct),第一个成员是isa。指向自己的class。这是在objc/objc.h中定义的。
typedef struct objc_object {
Class isa;
} *id;
- object的class保存了方法列表,还有指向父类的指针。
但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型: metaclasses。一个 metaclass被指向class,class被指向object。
它保存了全部实现的方法列表。以及父类的metaclass。假设想更清楚地了解objects,classes以及metaclasses是怎样一起工作地,能够阅读这篇文章。
Methods, Selectors and IMPs
我们知道了执行时会发消息给对象。
我们也知道一个对象的class保存了方法列表。
那么这些消息是怎样映射到方法的,这些方法又是怎样被执行的呢?
第一个问题的答案非常easy。
class的方法列表事实上是一个字典。key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。
非常重要的一点是,selector和IMP之间的关系是在执行时才决定的,而不是编译时。这样我们就能玩出些花样。
IMP一般是指向方法的指针,第一个參数是self。类型为id,第二个參数是_cmd。类型为SEL,余下的是方法的參数。
这也是self和_cmd被定义的地方。以下演示了Method和IMP
- (id)doSomethingWithInt:(int)aInt{}
id doSomethingWithInt(id self, SEL _cmd, int aInt){}
- 如今我们知道了objects,classes,selectors,IMPs以及消息分发,那么执行时究竟能做什么呢?
执行时究竟能做什么呢?
作用:
- 创建、改动、自省classes和objects
- 消息分发
之前已经提过消息分发,只是这仅仅是一小部分功能。全部的执行时方法都有特定的前缀。以下是一些有意思的方法:
class
- class开头的方法是用来改动和自省classes。
- 方法如:
- 能拿到一个class的全部内容
class_addIvar, class_addMethod, class_addProperty和class_addProtocol同意重建classes。 class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList
- 返回单个内容
class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty
- 一些通用的自省方法
class_conformsToProtocol, class_respondsToSelector, class_getSuperclass
- 创建一个object
class_createInstance来创建一个object
- 能拿到一个class的全部内容
ivar
- 这些方法能让你得到名字。内存地址和Objective-C type encoding。
method
- 这些方法主要用来自省。比方:
method_getName, method_getImplementation, method_getReturnType等等
- 也有一些改动的方法,包括:
method_setImplementation和method_exchangeImplementations
objc
- 一旦拿到了object,你就能够对它做一些自省和改动。你能够get/set ivar, 使用object_copy和object_dispose来copy和free object的内存。不仅是拿到一个class,而是能够使用object_setClass来改变一个object的class。
property
- 属性保存了非常大一部分信息。除了拿到名字,你还能够使用property_getAttributes来发现property的很多其它信息。如返回值、是否为atomic、getter/setter名字、是否为dynamic、背后使用的ivar名字、是否为弱引用。
protocol
- Protocols有点像classes,可是精简版的,执行时的方法是一样的。
你能够获取method, property, protocol列表, 检查是否实现了其它的protocol。
sel
- 最后我们有一些方法能够处理 selectors,比方获取名字,注冊一个selector等等。
2、执行时能干什么?(举例)
2.1 Classes And Selectors From Strings
比較基础的一个动态特性是通过String来生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起来非常easy:
Class stringclass = NSClassFromString(@"NSString")
于是我们就得到了一个string class。接下来:
NSString *myString = [stringclass stringWithString:@"Hello World"];
为什么要这么做呢?
直接使用Class不是更方便?通常情况下是。但有些场景下这种方法会非常实用。首先,能够得知是否存在某个class,NSClassFromString 会返回nil,假设执行时不存在该class的话。还有一个使用场景是依据不同的输入返回不同的class或method。
比方你在解析一些数据。每个数据项都有要解析的字符串以及自身的类型(String,Number。Array)。你能够在一个方法里搞定这些,也能够使用多个方法。当中一个方法是获取type,然后使用if来调用匹配的方法。
还有一种是依据type来生成一个selector,然后调用之。以下是两种实现方式:
- (void)parseObject:(id)object {
for (id data in object) {
if ([[data type] isEqualToString:@"String"]) {
[self parseString:[data value]];
} else if ([[data type] isEqualToString:@"Number"]) {
[self parseNumber:[data value]];
} else if ([[data type] isEqualToString:@"Array"]) {
[self parseArray:[data value]];
}
}
}
- (void)parseObjectDynamic:(id)object {
for (id data in object) {
[self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]];
}
}
- (void)parseString:(NSString *)aString {}
- (void)parseNumber:(NSString *)aNumber {}
- (void)parseArray:(NSString *)aArray {}
- 可一看到。你能够把7行带if的代码变成1行。
将来假设有新的类型。仅仅需添加实现方法就可以,而不用再去加入新的 else if。
2.2 Method Swizzling
之前我们讲过。方法由两个部分组成。Selector相当于一个方法的id;IMP是方法的实现。这样分开的一个便利之处是selector和IMP之间的相应关系能够被改变。比方一个 IMP 能够有多个 selectors 指向它。
而 Method Swizzling 能够交换两个方法的实现。也许你会问“什么情况下会须要这个呢?”。我们先来看下Objective-C中,两种扩展class的途径。首先是 subclassing。你能够重写某个方法。调用父类的实现,这也意味着你必须使用这个subclass的实例,但假设继承了某个Cocoa class,而Cocoa又返回了原先的class(比方 NSArray)。这种情况下,你会想加入一个方法到NSArray。也就是使用Category。99%的情况下这是OK的,但假设你重写了某个方法。就没有机会再调用原先的实现了。
Method Swizzling 能够搞定这个问题。
你能够重写某个方法而不用继承,同一时候还能够调用原先的实现。通常的做法是在category中加入一个方法(当然也能够是一个全新的class)。
能够通过method_exchangeImplementations这个执行时方法来交换实现。
来看一个demo,这个demo演示了怎样重写addObject:方法来纪录每个新加入的对象。
#import <objc/runtime.h>
@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end
@implementation NSMutableArray (LoggingAddObject)
+ (void)load {
Method addobject = class_getInstanceMethod(self, @selector(addObject:));
Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
method_exchangeImplementations(addObject, logAddObject);
}
- (void)logAddObject:(id)aobject {
[self logAddObject:aObject];
NSLog(@"Added object %@ to array %@", aObject, self);
}
@end
- 我们把方法交换放到了load中,这种方法仅仅会被调用一次。并且是执行时载入。假设指向暂时用一下,能够放到别的地方。注意到一个非常明显的递归调用logAddObject:。这也是Method Swizzlingeasy把我们搞混的地方,由于我们已经交换了方法的实现,所以事实上调用的是addObject:
动态继承、交换
我们能够在执行时创建新的class,这个特性用得不多,但事实上它还是非常强大的。你能通过它创建新的子类,并加入新的方法。
但这种一个子类有什么用呢?别忘了Objective-C的一个关键点:object内部有一个叫做isa的变量指向它的class。
这个变量能够被改变,而不须要又一次创建。然后就能够加入新的ivar和方法了。
能够通过以下命令来改动一个object的class.
object_setClass(myObject, [MySubclass class]);
这能够用在Key Value Observing。当你開始observing an object时。Cocoa会创建这个object的class的subclass,然后将这个object的isa指向新创建的subclass。
动态方法处理
眼下为止,我们讨论了方法交换。以及已有方法的处理。那么当你发送了一个object无法处理的消息时会发生什么呢?非常明显,"it breaks"。
大多数情况下确实如此。但Cocoa和runtime也提供了一些应对方法。
首先是动态方法处理。通常来说,处理一个方法,执行时寻找匹配的selector然后执行之。
有时,你仅仅想在执行时才创建某个方法,比方有些信息仅仅有在执行时才干得到。要实现这个效果,你须要重写+resolveInstanceMethod: 和/或 +resolveClassMethod:。假设确实添加了一个方法,记得返回YES。
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
if (aSelector == @selector(myDynamicMethod)) {
class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSelector];
}
- 那Cocoa在什么场景下会使用这些方法呢?Core Data用得非常多。
NSManagedObjects有很多在执行时加入的属性用来处理get/set属性和关系。那假设Model在执行时被改变了呢?
消息转发
假设 resolve method 返回NO。执行时就进入下一步骤:消息转发。有两种常见用例。1) 将消息转发到还有一个能够处理该消息的object。2) 将多个消息转发到同一个方法。
消息转发分两步。首先,执行时调用-forwardingTargetForSelector:,假设仅仅是想把消息发送到还有一个object,那么就使用这种方法,由于更高效。
假设想要改动消息,那么就要使用-forwardInvocation:,执行时将消息打包成NSInvocation。然后返回给你处理。处理完之后,调用invokeWithTarget:。
Cocoa有几处地方用到了消息转发,基本的两个地方是代理(Proxies)和响应链(Responder Chain)。
NSProxy是一个轻量级的class,它的作用就是转发消息到还有一个object。
假设想要惰性载入object的某个属性会非常实用。
NSUndoManager也实用到。只是是截取消息,之后再执行。而不是转发到其它的地方。
响应链是关于Cocoa怎样处理与发送事件与行为到相应的对象。
比方说,使用Cmd+C执行了copy命令。会发送-copy:到响应链。首先是First Responder,一般是当前的UI。假设没有处理该消息,则转发到下一个-nextResponder。这么一直下去直到找到能够处理该消息的object,或者没有找到。报错。
使用Block作为Method IMP
- iOS 4.3带来了非常多新的runtime方法。除了对properties和protocols的加强。还带来一组新的以 imp 开头的方法。通常一个 IMP 是一个指向方法实现的指针。头两个參数为 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 带来了block,imp_implementationWithBlock() 能让我们使用block作为 IMP,以下这个代码片段展示了怎样使用block来加入新的方法。
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
NSLog(@"Hello %@", string);
});
class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");
- 能够看到。Objective-C 表面看起来挺简单,但还是非常灵活的,能够带来非常多可能性。动态语言的优势在于在不扩展语言本身的情况下做非常多非常机灵的事情。比方Key Value Observing。提供了优雅的API能够与已有的代码无缝结合,而不须要新增语言级别的特性。
iOS 执行时的更多相关文章
- Unity引擎IOS执行档大小优化
简介 苹果对于IOS执行档的大小是有明确的限制的,其中TEXT段的大小不能超过80M,否则提审将会被苹果拒绝,同时,如果TEXT段过于太大,那么在苹果进行加密之后,很容易出现解压失败等各种异常,最终导 ...
- iOS运行时Runtime浅析
运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...
- 【java】计算一段代码执行时长java.lang.System类里的public static long currentTimeMillis()方法
public class Test_currentTimeMillis { public static void main(String[] args) { long start=System.cur ...
- DbCommandInterceptor抓取EF执行时的SQL语句
EF6.1也出来不少日子了,6.1相比6.0有个很大的特点就是新增了System.Data.Entity.Infrastructure.Interception 命名空间,此命名空间下的对象可以允许我 ...
- html 事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。
看一个简单的例子: html: <head> <meta charset="UTF-8"> <title>Document</title& ...
- C#获取程序代码执行时长
ArrayList list = new ArrayList(); long startTicks = DateTime.Now.Ticks; for (int i = 0; i < 10000 ...
- 用字符串连接SQL语句并用EXEC执行时,出现名称 '‘不是有效的标识符
原文:用字符串连接SQL语句并用EXEC执行时,出现名称 ''不是有效的标识符 用字符串连接SQL语句并用EXEC执行时,出现名称 '这里是字符串连接的一条SQL语句‘不是有效的标识符 才发现,在写e ...
- 承载(Host)通用语言执行时
承载(Host)通用语言执行时(CLR) 还有一种使用COM 的方法是是把须要集成的 F# 代码与已有的 C/C++ 应用程序集成到一起.开成自己定义的承载通用语言执行时.通用语言执行时就是 C++ ...
- 【翻译自mos文章】当并行事务恢复进程在执行时,禁用并行事务恢复的方法
当并行事务恢复进程在执行时,禁用并行事务恢复的方法 How to Disable Parallel Transaction Recovery When Parallel Txn Recovery is ...
随机推荐
- ubuntu系统更换源
一:问题概述 ubuntu,我们在使用apt新装软件的时候,会使用官方的网站去下载软件,但是会因为国内的转接点太多,而导致下载的速度非常慢 ,我们可以通过换成一些中间的节点来进行下载,比如阿里源,中科 ...
- hdu 1142 最短路+记忆化
最短路+记忆化搜索HDU 1142 A Walk Through the Forest链接:http://acm.hdu.edu.cn/showproblem.php?pid=1142 > 题意 ...
- NOI退役啦
没进前一百 签的上交一本 高三加油喽
- MAC使用IDA PRO远程调试LINUX程序
1 背景 在学习Linux系统上的一些漏洞知识的时候,往往需要进行“实地测试”,但是在Linux系统上进行调试并不太方便,因为LINUX自带的GDB调试工具真的不太人性化,即使有GDBTUI之类的“伪 ...
- mac使用基础
Mac 系统的桌面 Mac 的桌面是一个很炫的3D, 背景是一张“星空”图. 2 Dock: 在桌面的下方有一排图标, 叫Dock, 用来快速启动程序, 进入文件夹, 它同时还可以停靠正在运行的程序 ...
- 灰姑娘的水晶鞋(NOIP模拟赛Round 7)
[问题描述] 传说中的水晶鞋有两种颜色:左边的水晶鞋是红色,右边的是蓝色,据说穿上它们会有神奇的力量. 灰姑娘要找到她所有的n双水晶鞋,它们散落在一条数轴的正半轴上,坐标各不相同,每双水晶鞋还有一个权 ...
- linux.backspace乱码(转)
42 linux.backspace乱码 linux环境sqlplus中使用backspace键出现乱码的解决方法2008-04-30 16:32 在linux环境下使用sqlplus,在回删(bac ...
- python登陆github
#!/usr/bin/env python # encoding: utf-8 import requests from headers import headers from lxml import ...
- 关于CI框架加入sphinx官方API接口文件的时候,需要注意的问题
从sphinx下载的官方文件sphinxapi.php中类名为class SphinxClient 加入到CI框架,放在system/libraries/下,由于需要遵从CI框架libraries类名 ...
- Appium+python自动化17-启动iOS模拟器APP源码案例【转载】
前言 上一篇已经可以启动iOS模拟器上的safari浏览器了,启动app比启动浏览器要复杂一点,本篇以github上的源码为案例详细介绍如何启动iOS模拟器的app 一.clone源码 1.githu ...