ObjectTive C语言语法,[译]理解 Objective-C 运行时(下篇)

本文来自网易云社区

作者:宋申易

所以到底 objc_msgSend 发生了什么?

很多事情。看一下这段代码:

[self printMessageWithString:@"Hello World!"];

这实际上被编译器翻译成:

objc_msgSend(self, @selector(printMessageWithString:), @"Hello World!");

我们顺着目标对象的 isa 指针查找,看该对象(或者它其中一个父类)是否能响应 @selector(printMessageWithString:) 选择器。假设我们在分派表(dispatch table)或者缓存中找到了该选择器,我们会跟踪函数指针并执行它。所以 objc_msgSend() 永远不会返回,它开始执行,然后跟踪一个指向你的方法的指针,然后你的方法返回,这看起来就像 objc_msgSend() 返回了一样。

Bill Bumgarner 在(Part 1, Part 2 & Part 3)里描述了更多 objc_msgSend() 的细节。总结一下他的文章结合你看到的 Objective-C 运行时代码:

检查被忽略的选择器和短路。显然,如果我们在垃圾收集下运行,我们可以忽略 -retain,-release 等调用。

检查 nil 目标。和其他语言不同,在 ObjC 里向 nil 发送消息十分合理并且有些情况下确实想要这么做。假如不是 nil 则继续……

接下来在类中找到 IMP,首先通过类缓存来查找,如果找到就跟随指针跳转到对应的函数

如果在缓存中找不到 IMP,则通过分派表来查找,如果找到就跟随指针跳转到对应的函数

如果这两个地方都找不到 IMP,则跳转到转发(forwarding)机制。

这意味着最终你的代码会被编译器转译成 C 函数。你写的某个方法可能是这样:

-(int)doComputeWithNum:(int)aNum

它会被转换成……

int aClass_doComputeWithNum(aClass *self, SEL _cmd, int aNum)

ObjC 运行时会通过调用这些方法的函数指针来真正执行方法。我曾说过你不能直接调用这些转译后的方法,但其实 Cocoa 框架提供了一个获取函数指针的方法……

// C function pointer

int (computeNum *)(id, SEL, int);

// methodForSelector is COCOA & not ObjC Runtime

// gets the same function pointer objc_msgSend gets

computeNum = (int (*)(id, SEL, int))[target methodForSelector:@selector(doComputeWithNum:)];

// execute the C function pointer returned by the runtime

computeNum(obj, @selector(doComputeWithNum:), aNum);

用这种方式你可以直接访问函数并且直接在运行时中执行它,甚至绕过运行时的动态特性(为了确保指定的方法被执行)。ObjC 运行时也用这种方法来调用你的函数,只是用了 objc_msgSend()。

Objecetive-C 消息转发

在 Objective-C 中,发送消息给可能不能响应该消息的对象是合法的(可能是有意设计的)。苹果文档里提到可能的原因一个是模拟 Objective-C 并不原生支持的多重继承,或者是你想把真正接受消息的类或者对象隐藏起来。这也是运行时很有必要的一件事。具体是这样的:

运行时搜索类缓存、类分派表以及父类的所有方法,没有找到指定的方法。

运行时对你的类调用 + (BOOL)resolveInstanceMethod:(SEL)aSEL。这给你提供了一个方法实现的机会,告诉运行时你已经解决了这个方法,如果它应该开始进行搜索,它将会找到方法。具体你可以这样做,定义一个函数:

void fooMethod(id obj, SEL _cmd) {

NSLog(@"Doing Foo");

}

然后可以使用 class_addMethod() 来解析它…

+ (BOOL)resolveInstanceMethod:(SEL)aSEL {

if(aSEL == @selector(doFoo:)) {

class_addMethod([self class],aSEL,(IMP)fooMethod,"v@:");

return YES;

}

return [super resolveInstanceMethod];

}

class_addMethod() 的最后一部分中的 v@: 是该方法返回的内容,也是它的参数。你可以在运行时指南的 Type Encodings 章节中了解可以放入哪些内容。

如果我们不能解析该方法,运行时会继续调用 - (id)forwardingTargetForSelector:(SEL)aSelector。它所做的是给你一个机会,让运行时指向在另一个可以响应消息的对象。最好在开销更大的 - (void)forwardInvocation:(NSInvocation *)anInvocatio 方法接管之前调用,例如:

{

if(aSelector == @selector(mysteriousMethod:)) {

return alternateObject;

}

return [super forwardingTargetForSelector:aSelector];

}

显然,你不想从这个方法中返回 self,因为这样会导致无限循环。

运行时最后一次尝试发送一个消息发送到它的预定目标,调用 - (void)forwardInvocation:(NSInvocation *)anInvocation。NSInvocation 本质上是一个 Objective-C 消息的的对象形式。一旦你有了一个 NSInvocation,你基本上可以改变任何信息,包括它的目标,选择器和参数。例如你可以做:

- (void)forwardInvocation:(NSInvocation *)invocation {

SEL invSEL = invocation.selector;

if([altObject respondsToSelector:invSEL]) {

[invocation invokeWithTarget:altObject];

} else {

[self doesNotRecognizeSelector:invSEL];

}

}

如果你的对象继承了 NSObject, 默认情况下 - (void)forwardInvocation:(NSInvocation *)anInvocation 实现会调用 -doesNotRecognizeSelector:方法。你可以重写这个方法如果你想最后再做点什么。

不脆弱的(Non Fragile)实例变量列表(ivars) (现代运行时)

现代运行时新增加了不脆弱的(Non Fragile) ivars 的概念。当编译你的类的时候,编译器生成了一个实例变量内存布局(ivar layout),来告诉运行时去那里访问你的类的实例变量们。这是一个底层实现细节:ivars 是实例变量分别相对于你的对象地址的偏移量,读取 ivars 的字节数就是读取的变量的大小。你的 ivar 布局可能看起来像这样(第一列是字节偏移量):

da7d76c11064faec1e1aa05420d9bf9e.png

这里我们画出了一个 NSObject 的实例变量内存布局。我们有一个继承了 NSObject 的类,增加了一些新的实例变量。这没什么问题,直到苹果发布了新的 Mac OS X 10.x 系统,NSObject 突然增加两个新的实例变量,于是:

280450f6ceb9c6c0df2a92d5beccd5d1.png

你的自定义对象和 NSObject 对象重叠的部分被清除。如果 Apple 永远不改变之前的布局可以避免这种情况,但如果他们那样做,那么他们的框架就永远不会进步。在“脆弱的 ivars” 下,你必须重新编译你从 Apple 继承的类,来恢复兼容性。那么在不脆弱的情况下会发生什么呢?

2c7b648873003b6c98d76f4545bf3fcd.png

在不脆弱的 ivars 下,编译器生成与脆弱 ivars 相同的 ivars 布局。然而,当运行时检测到和父类有重叠时,它会调整偏移量,以增加对类的补充,保留了在子类中添加的内容。

Objective-C 关联对象(Associated Objects)

Mac OS X 10.6 Snow Leopard 中引入了关联引用。Objective-C 没有原生支持动态地将变量添加到对象上。因此,你需要竭尽全力构建基础架构,以假装正在向类中添加一个变量。在 Mac OS X 10.6 中,Objective-C 运行时提供了原生支持。如果我们想给每个已经存在的类添加一个变量,比如 NSView,我们可以这样做:

#import //Cocoa

#include //objc runtime api’s

@interface NSView (CustomAdditions)

@property(retain) NSImage *customImage;

@end

@implementation NSView (CustomAdditions)

static char img_key; //has a unique address (identifier)

- (NSImage *)customImage {

return objc_getAssociatedObject(self,&img_key);

}

- (void)setCustomImage:(NSImage *)image {

objc_setAssociatedObject(self,&img_key,image,

OBJC_ASSOCIATION_RETAIN);

}

@end

你可以在 runtime.h 看到。如何存储传递给 objc_setAssociatedObject() 的值的选项:

/* Associated Object support. */

/* objc_setAssociatedObject() options */

enum {

OBJC_ASSOCIATION_ASSIGN = 0,

OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,

OBJC_ASSOCIATION_COPY_NONATOMIC = 3,

OBJC_ASSOCIATION_RETAIN = 01401,

OBJC_ASSOCIATION_COPY = 01403

};

这些与你可以在@property语法中传递的选项相匹配。

混合 vTable 分发

如果你看一下现代运行时代码,你会看到这个(在 objc-runtime-new.m)。

/***********************************************************************

*vtable dispatch

**每个类都有一个 vtable 指针。vtable 是一个 IMP 数组,

*所有的类的 vtable 中表示的选择器数量都是相同的。(i.e.

*没有一个类有更大或更小的 vtable).

*每个 vtable 索引都有一个关联的蹦床,该蹦床在接收者类的

*vtable 的该索引处分派给 IMP(检查 NULL 后)。分派

*fixup 使用了蹦床而不是 objc_msgSend.

*脆弱性:vtable 的大小和选择器列表在启动时已经设定好了。

*编译器生成的代码无法依赖于任何特定的vtable配置,甚至

*根本不使用 vtable 调度。

*内存大小:如果一个类的 vtable 和它的父类相同(i.e. 该类

*没有重写任何 vtable 选择器), 那么这个类直接指向它的父

*类的 vtable。这意味着被选中包含在 vtable 中的选择器应

*该有以下特点:

*(1) 经常被调用,但是 (2) 不经常被重写。

*特别的是,-dealloc 是一个坏的选择。

*转发: 如果一个类没有实现 vtable 中的部分选择器, 这个类的

*vtable 中的这些选择器的 IMP 会被设置成 objc_msgSend。

*+initialize: 每个类保持默认的 vtable(总是重定向到

*objc_msgSend)直到其 +initialize 初始化方法完成。否则,

*一个类的第一个消息可能是一个 vtable 调度,而 vtable

*蹦床不包括 +initialize 初始化检查。

*改变: Categories, addMethod, 和 setImplementation 如果影响

*到了 vtable 的选择器,类和所有的子类的 vtable 都将强制重建。

**********************************************************************/

这背后的思想是,运行时试图在这个 vtable 里面存储最常被调用的选择器,这可以给 app 加速,因为这比 objc_msgSend 使用了更少的指令。这个 vtable 包含 16 个最常被调用的选择器,占据了绝大部分全局调用的选择器。你可以看到垃圾回收 app 和非垃圾回收 app 的默认选择器都是什么。

static const char * const defaultVtable[] = {

"allocWithZone:",

"alloc",

"class",

"self",

"isKindOfClass:",

"respondsToSelector:",

"isFlipped",

"length",

"objectForKey:",

"count",

"objectAtIndex:",

"isEqualToString:",

"isEqual:",

"retain",

"release",

"autorelease",

};

static const char * const defaultVtableGC[] = {

"allocWithZone:",

"alloc",

"class",

"self",

"isKindOfClass:",

"respondsToSelector:",

"isFlipped",

"length",

"objectForKey:",

"count",

"objectAtIndex:",

"isEqualToString:",

"isEqual:",

"hash",

"addObject:",

"countByEnumeratingWithState:objects:count:",

};

那么你怎么知道是否使用了 vtable 中的方法了呢?你会在调试的堆栈跟踪中看到以下几个方法。这些方法你可以看成调试版的 objc_msgSend()。

objc_msgSend_fixup 代表 runtime 调用一个方法并正要把它加入到 vtable 中。

objc_msgSend_fixedup 代表你调用方法曾经在 vtable 中,现在已经不在里面了。

objc_msgSend_vtable[0-15] 代表上述 vtable 中的一个常用方法。runtime 可以随意分配或取消它想要的值。所以这一次 objc_msgSend_vtable10 对应于 -length 方法,下一次运行可能对应方法就变了。

总结

我希望你喜欢这些,这篇文章大体上组成了我在我给 Des Moines Cocoaheads 的 ObjC 演讲中提到的内容。ObjC 运行时写的很棒,它提供了许多我们在 Cocoa / Objective-C 中习以为常的特性。如果你还没看过 Apple 的 ObjC 运行时文档,希望你去看一看。谢谢!

网易云免费体验馆,0成本体验20+款云产品!

更多网易研发、产品、运营经验分享请访问网易云社区。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281560.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

菜鸟学习MVC实录:弄清项目各类库的作用和用法

MVC模式即:模型(Model)-视图(View)-控制器(Controller) Model (模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责数据库中存取数据View…

SSL服务器

2019独角兽企业重金招聘Python工程师标准>>> SSL 是一个安全协议,它提供使用 TCP/IP 的通信应用程序间的隐私与完整性。因特网的 超文本传输协议(HTTP)使用 SSL 来实现安全的通信。 在客户端与服务器间传输的数据是通过使用对称算…

微软Skype Translator将支持阿拉伯语即时语音翻译

据美国科技时代网(Tech Times)3月9日报道,日前,微软旗下即时翻译软件Skype Translator再添新语种,微软宣布Skype Translator已经支持阿拉伯语。Skype用户可通过使用阿拉伯语即时翻译与朋友、家人以及海外商业伙伴进行交流。 据报道&#xff0…

是什么让.NET7的Min和Max方法性能暴增了45倍?

简介在之前的一篇文章.NET性能系列文章一:.NET7的性能改进中我们聊到Linq中的Min()和Max()方法.NET7比.NET6有高达45倍的性能提升,当时Benchmark代码和结果如下所示:[Params(1000)] public int Length { get; set; }private int[] arr;[Globa…

html标记语言 --框架

html标记语言 --框架六、框架1、什么是框架 框架将浏览器划分成不同的部分&#xff0c;每一部分加载不同的网页 实现同一浏览器窗口中加载多个页面的效果。 语法格式<frameset>.......</frameset>2. 属性2.1 cols使用“像素数”和%分割左右窗口&#xff0c;“*” 表…

c语言兔子洞,数据结构水题选讲 - osc_y08db3kb的个人空间 - OSCHINA - 中文开源技术交流社区...

[Ynoi2011]ODT\(O(nlog^2n)\) 的做法非常显然直接把树重链剖分一下&#xff0c;每个点维护轻儿子的平衡树就行但是这题 \(1e6\) 的数据范围使得 \(O(nlog^2n)\) 没那么容易卡过去(当然很多人卡过去了考虑给一个点很多重儿子那么若一个点有 \(k\) 个重儿子&#xff0c;修改复杂度…

centos 7.x systemd service 配置方法整理

一、存放路径/etc/systemd/system二、service配置整理2.1 zookeeper.service[Unit]DescriptionZooKeeper ServiceAftersyslog.targetAfternetwork.target[Service]#使用shell脚本启动的要用forking模式TypeforkingUserzookeeperGroupzookeeper#脚本启动ExecStart/usr/local/zoo…

MAVEN集成测试环境搭建

1. MAVEN SVN HUDSON SONAR集成测试环境搭建、1.1 软件准备 Hudson、Jenkins、Sonar1.2 软件安装 说明&#xff1a;本例均使用将应用程序部署至web容器下&#xff0c;Hudson和Sonar有其他部署启动方式&#xff0c;如有需要请自行使用&#xff0c;本文不做赘述。1.2.1 安装hu…

ubus c语言例子,openwrt之ubus例子

好一个icrootLEDE:/# ubus call test_ubus helloworld {"id":1,"msg":"hi","array":["a","b"]}{"id": 1,"msg": "hi","shuzu": ["a","b"]}文件目…

使用Spring访问Mongodb的方法大全——Spring Data MongoDB查询指南

1.概述 Spring Data MongoDB 是Spring框架访问mongodb的神器&#xff0c;借助它可以非常方便的读写mongo库。本文介绍使用Spring Data MongoDB来访问mongodb数据库的几种方法&#xff1a; 使用Query和Criteria类JPA自动生成的查询方法使用Query 注解基于JSON查询在开始前&#…

mysqldump导出备份数据库报Table ‘performance_schema.session_variables‘ doesn‘t exist

今天在bash进行本地数据库往云端数据库导数据的时候&#xff0c;在本地导出.sql文件这第一步就出现了错误问题&#xff0c;导出sql文件的命令&#xff1a; 1 mysqldump -u 用户名 -p 数据库名 > xxx.sql 在做这一步将数据导出的时候报了这么一个错误&#xff0c; 1 mysqldu…

在Identity框架中使用RoleBasedAuthorization

本文将介绍在 Identity 框架中如何使用 Sang.AspNetCore.RoleBasedAuthorization[1] 库。核心介绍Identity 和 jwt 的基本配置我们在这里不再赘述&#xff0c;可以参考最后的项目样例。核心的代码主要为 IRolePermission 的实现。internal class MyRolePermission : IRolePermi…

2016年印度公有云服务市场将达13亿美元

根据IT咨询公司Gartner最新调查数据显示&#xff0c;2016年印度公有云服务市场预计将增长35.9%&#xff0c;达到13亿美元。 增长最快的是云系统基础设施即服务&#xff08;IaaS&#xff09;&#xff0c;2016年预计将增长45.5%&#xff1b;其次是平台即服务&#xff08;PaaS&…

PAT 1042. 字符统计

1042. 字符统计 请编写程序&#xff0c;找出一段给定文字中出现最频繁的那个英文字母。 输入格式&#xff1a; 输入在一行中给出一个长度不超过1000的字符串。字符串由ASCII码表中任意可见字符及空格组成&#xff0c;至少包含1个英文字母&#xff0c;以回车结束&#xff08;回车…

Magicodes.IE 2.7.0-beta发布

2.7.0-beta2022.10.27使用SixLabors.ImageSharp替代System.Drawing&#xff0c;感谢linch90 &#xff08;见pr#454&#xff09;2.6.92022.10.26fix: 动态数据源导出到多个sheet的问题 &#xff08;见#449&#xff09;2.6.82022.10.18Excel模板导出添加API&#xff0c;以支持通过…

光伏逆变器“领跑”:不止于技术

从无到有&#xff0c;从效率比拼到突破99%&#xff0c;在跟进速度上没话说的国内光伏逆变器企业难免深陷“价格战”、同质化的泥潭。随着“领跑者”计划跃居光伏主流&#xff0c;嗅到市场红利的企业再次蜂拥而至。 目前&#xff0c;鉴衡认证发布的第一批光伏并网逆变器“领跑者…

Ubuntu 18.04上Qmmp安装教程

Qmmp&#xff0c;一个开源的基于Qt的多媒体播放器。它具有多种音频文件格式支持&#xff0c;DSP效果&#xff0c;视觉效果;输出系统支持&#xff08;OSS4&#xff08;FreeBSD&#xff09;&#xff0c;ALSA&#xff08;Linux&#xff09;&#xff0c;Pulse Audio&#xff0c;JAC…

android自动跑马灯,Android-最强跑马灯

Android--最强跑马灯Android 跑马灯已经有很多版本&#xff0c;从最基本的TextView&#xff0c;到重写TextView使TextView取消焦点限制&#xff0c;还有重写TextView利用ScrollTo方法写的&#xff0c;基本都能满足一般需要。然而在使用过程中&#xff0c;发现一些意外---有时会…

python:软件目录结构规范

为什么要设计好目录结构&#xff1f; “设计项目目录结构”&#xff0c;就和“代码编码风格”一样&#xff0c;属于个人风格问题。对于这种风格上的规范&#xff0c;一直都存在两种态度&#xff1a; 1.一种认为&#xff0c;这种个人风格问题“无关紧要”。理由是能让程序work就…

开启智能生活新时代 河北省智慧社区建设从各个击破

智慧社区作为智慧城市的重要组成部分&#xff0c;是城市智慧落地的触点&#xff0c;是城市管理、政务服务和市场服务的载体。随着智慧城市的推广以及新一代技术的普及&#xff0c;智慧社区的项目必将迎来新一轮的快速发展。2016年智慧社区成为企业业务落地的承载点&#xff0c;…