很早就知道了Objective-C Runtime这个概念,「Objective-C奇技淫巧」「iOS黑魔法」各种看起来很屌的主题中总会有它的身影;但一直没有深入去学习,一来觉得目前在实际项目中还没有必要了解,二来懒。但,若想成为一个合格的iOS开发者,这个东西是躲不过的,好吧,抡起胳膊开始吧,争取一点点把它整明白吧!和了解其他技术一样,在了解一个东西之前,我总是问自己,这个有啥实际意义?为什么要了解她?多自问一些问题,找到实际意义,在理解上总会容易一些,学习也自然更有目的性一些。

看了十几篇博客和iOS文档,还是很容易针对Objective-C Runtime提出一些问题的,有了这些问题,理解Objective-C Runtime就自然不会过于枯燥了,譬如:

  • 在iOS中调用方法为什么叫「发送消息」,为什么这个概念这么重要?
  • KVO的实现原理是什么,是否涉及Objective-C Runtime?
  • Category的原理是什么?为什么其他语言中没有这个东东?
  • Category中以「关联对象」的方式添加动态属性的原理什么?

看了一些大牛的博客之后,对Objective-C Runtime的重要性在心里基本有谱了,但路要一步一步走,饭要一口一口吃,先将一些基础性的概念整理一下吧!「消息转发」「Method Swizzling」之类的高端东西放在之后的博客分析吧!

关于Objective-C Runtime

Runtime的学习资源

Runtime的学习资源非常丰富,下面是个人的一些整理:

  1. 《Objective-C Runtime Programming Guide》(官方文档);
  2. /usr/include/objc/下的头文件,譬如objc.h``runtime.h``NSObject.h``message.h等;/usr/include/objc/下的头文件不多,都可以看一下;
  3. 《Effective Objective-C 2.0》;
  4. 理解 Objective-C Runtime》;
  5. Objective-C对象模型及应用》;
  6. 深入理解Tagged Point》;
  7. Objective-C Runtime》;
  8. Objective-C Runtime系列博客:
    Objective C Runtime》;
    Method Swizzling 和 AOP 实践》;
    如何自己动手实现 KVO》;
  9. 刨根问底Objective-C Runtime系列博客:
    刨根问底Objective-C Runtime(1)- Self & Super》;
    刨根问底Objective-C Runtime(2)- Object & Class & Meta Class》;
    刨根问底Objective-C Runtime(3)- 消息 和 Category》;
    刨根问底Objective-C Runtime(4)- 成员变量与属性》;
  10. 继承自NSObject的不常用又很有用的函数(2)》;

P.S:值得一提的是,Objective-C Runtime是开源的,任何时候都能从http://opensource.apple.com中获取源代码。

动态语言vs静态语言

在网上阅读Objective-C Runtime相关的信息时都提到了『Objective-C是一门动态语言…』『作为一门动态语言…』之类的字眼,这让我有些诧异。因为根据我之前的理解,虽然动态和静态语言都是相对的,但Objective-C怎么也不算动态语言吧!动态语言是Python、Javascript以及Swift这种弱类型语言吧?!

关于Objective-C是否是动态语言,笔者才疏学浅,不敢妄论,知乎中关于这个有讨论:Objective-C是动态语言吗?为什么?

但无论如何,它比C++、C这类传统静态语言具备更多的动态特性,说它是动态语言或许不为过吧。暂时不纠结这个问题了,希望以后有更多的自信回答这个问题。跟随主流意见,暂时认为Objective-C是一门动态语言吧。

P.S:后来又仔细阅读了一下《Effective Objective-C 2.0》,其中对“Objective-C是动态语言”有更好的佐证,后面阐述Objective-C『的消息传递机制』时再补充说明吧!

Runtime是什么

Runtime是什么?为什么有Runtime这个东东?

《Objective-C Runtime Programming Guide》的introduction的第一段就回答了这个问题:

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

不过貌似不够清晰,理解Objective-C Runtime中有更浅显的解释:

Objective-C是面相运行时的语言(runtime oriented language),就是说它会尽可能的把编译和链接时要执行的逻辑延迟到运行时。这就给了你很大的灵活性,你可以按需要把消息重定向给合适的对象,你甚至可以交换方法的实现,等等;这就需要使用runtime,runtime可以做对象自省查看他们正在做的和不能做的(don’t respond to)并且适当地分发消息。

Objective-C的Runtime是一个运行时库(Runtime Library),它是一个主要使用C和汇编写的库,为C添加了面相对象的能力并创造了Objective-C。这就是说它在类信息(Class Information)中被加载,完成所有的方法分发,方法转发,等等。Objective-C runtime创建了所有需要的结构体,让Objective-C的面相对象编程变为可能。

关于Runtime的一些术语

各种Runtime资料中有很多术语,有些是以前认识但理解不深刻的,譬如id、Class,有些是很少接触的,譬如isa。

为了更好地理解Runtime,先了解Runtime的一些术语是非常必要的。

首先简单提一下objc_class,它是一个结构体,其中包含一些类信息;然后是objc_object,它也是一个结构体,但它只包括一个条目,isa,后者是一个objc_class指针;

其实,说到底,NSObject就是建立在objc_class这个结构体之上的,所以了解objc_class对于了解NSObject以及Cocoa的对象模型是非常重要的。

P.S:objc_class和isa相对而言信息量比较大,后文再详细阐述。

OK,接着来看建立在objc_class和objc_object基础之上的一些关键字。

Class

Class(不是class哦),它在<objc/objc.h>中定义,如下:

// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以看到Class其实是一种指针类型,即用于指向objc_class结构体。NSObject中定义的方法- (Class)class;用于返回其对应的objc_class结构体指针;

除了- (Class)class;方法之外,NSObject类中还有一些常见方法包含Class类型参数或者返回Class类型返回值,如下:

- (Class)class;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
+ (BOOL)isSubclassOfClass:(Class)aClass;
+ (BOOL)isSubclassOfClass:(Class)aClass;
+ (Class)superclass;
+ (Class)class;

id

对于有过iOS开发经验的人而言,id使用太广泛了,不过似乎对它还没有过比较深入的理解;事实上,一直以为id和NSObject *等价呢!现在看来,这种理解太naive了,它也在<objc/objc.h>中定义:

struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
// objc_object也是一个只包含一个条目isa(指向objc_object结构体的指针)的结构体。 /// A pointer to an instance of a class.
typedef struct objc_object *id;

SEL

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

没找到objc_selector的定义,但根据网友的描述:其实它就是个映射到『方法』的C字符串,可以用Objc编译器命令@selector()或者Runtime系统的sel_registerName函数来获得一个SEL类型的『方法选择器』(通常简称『选择子』)。

考虑到Xcode对@selector的支持比对sel_registerName的支持更好,所以@selector貌似用得更多一些,但有时候sel_registerName或许更简洁一些。

譬如有一段代码:

if ([self.selectedViewController respondsToSelector:@selector(isReadyForEditing)]) {
  boolNumber = [self.selectedViewController performSelector:@selector(isReadyForEditing)];
}

但当前上下文中没有isReadyForEditing这个方法,所以编译器会有警告;当然,可以有各种方式来关闭警告,如下是其中一种:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if ([self.selectedViewController respondsToSelector:@selector(isReadyForEditing)]) {
boolNumber = [self.selectedViewController performSelector:@selector(isReadyForEditing)];
}
#pragma clang diagnostic pop

但此时,若改用sel_registerName,则这个警告就没了!

P.S:不晓得Apple更青睐于哪一个,个人感觉是@selector,因为sel_registerName的使用几乎没碰到过。

IMP

IMP的定义如下:

/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif

可以了解到,IMP是一个函数指针。IMP是Implementation的缩写,一个函数是由一个selector(SEL),和一个implement(IML)组成的;Selector相当于门牌号,而Implement才是真正的住户(函数实现)。理解Selector和Implementation的关系蛮重要的!

Method

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

Ivar

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

Category

/// An opaque type that represents a category.
typedef struct objc_category *Category;

objc_property_t

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

可以通过class_copyPropertyList和protocol_copyPropertyList方法来获取类(Class)和协议(Protocol)中的属性,获取属性之后,还可以使用property_getName获取属性的名字(C字串):

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount);
const char *property_getName(objc_property_t property);

举个例子:

@interface Student : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@property (nonatomic, assign) NSUInteger score; @end @implementation Student @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
unsigned int outCount;
objc_property_t *aProperty = class_copyPropertyList([Student class], &outCount); NSLog(@"一共有%d个属性,它们的名字分别是:", outCount);
for (int i = 0; i < outCount; ++i) {
objc_property_t aP = aProperty[i];
const char * property_name = property_getName(aP);
NSLog(@"%s", property_name);
}
}
@end
/*输出:
Student类一共有3个属性,它们的名字分别是:
name
age
score
*/

这个示例的执行结果也从侧面反映出了,NSObject中没有定义属性(只有一个叫isa成员变量)。

P.S:属性 vs 成员变量?请参考我之前写的博客:iOS成员变量、实例变量、属性变量三者的联系与区别

objc_class和isa

把objc_class和isa单独拧出来的原因是它们的信息量比较大,稍微复杂一点!

Objective-C是一种面向对象的语言。按照面向对象语言的设计原则,所有事物都应该是对象(严格来说Objective-C并没有完全做到这一点,因为它有象int, double这样的简单变量类型)。所以一定要有这个认识:Objective-C中,类也是对象

在Cocoa中,所有类都继承自NSObject,参考NSObject在<objc/NSObject.h>中的定义如下:

@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}

objc_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 }

可以知道在运行时,所有类对象都有一个名为「isa」的指针,这个指针指向一个objc_class结构体,objc_class结构体中包含一些与类相关的信息。问题是这个isa指向的objc_class结构体的信息所对应的是自己的信息还是对应的类的信息呢?当然是其所对应的类的信息,存储了变量列表、方法列表、遵守的协议列表等等。

简单来说,「isa」指针被称为「is a」指针,顾名思义,它告诉了对象所属的类信息,描述『aX is a X』。

某个对象的isa指针指向的objc_class结构体存放的是其所对应的的『元数据』(metadata),通过它我们可以知道该对象:所对应类的名字(name)、可以做哪些事情(objc_method_list是个二维方法列表)、包括哪些对成员变量(objc_ivar_list)、遵守哪些协议(protocols)。

回到objc_class,可以看到objc_class结构体首变量也是一个isa指针,这也印证了『类也是对象』这个说法。对象的isa指针指向的是该对象的本类,而类的isa指针指向的另外一个类被称为『元类』(metaclass),用来表述类本身所表具备的元数据,类方法就定义于此;也正因为类本身也是一个对象,所以类本身可以接收消息。譬如:考虑到NSObject类本身也是一个对象(是metaclass的一个对象,常称之为『类类型对象』),所以[NSObject alloc]其实可以看成是对NSObject这个类类型对象发送一个消息(调用器alloc实例方法)。

除了isa指针之外,objc_class结构体中还有一个变量super_class,它指向了是这个类的超类(super class),可以看到这个super_class不是一个一位数组,而是一个单独的指针,即一个类有且仅有一个super class,即所谓的『单继承』。

关于isa和super_class的更直观描述,还请看图:

到了这里,Objective-C的对象模型基本上解释清楚了;可能还有一个问题:最终的元类是啥?在Cocoa中,所有类都继承自NSObject,而NSObject的元类(不晓得叫什么名字,假设叫NSObjectMetaClass)(上图中右上角的方框)也继承自NSObject。有些绕,具体来说,NSObject和NSObjectMetaClass的isa指针和super_class指针指向情况是这样的:

  • NSObject的isa指针NSObjectMetaClass(终极meta class),NSObjectMetaClass指针指向自己;
  • NSObject的super_class指针指向nil,NSObjectMetaClass的super_class指向NSObject;

这样就完了?!No!Objective-C的对象模型还有信息可挖,回过头来看objc_class的ivars变量和methodLists变量:

struct objc_class {
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
}

在运行时,类(包括class和metaclass)的objc_class结构体是固定的,不可能往这个结构体中添加数据,只能修改!譬如可以修改isa指针,让它指向一个中间类;在我的理解里,应该也可以修改ivars和methodLists,让它们指向一个新的区域;若可以这样,那么就可以在运行时随意添加/修改/删除成员变量和方法了。

但是,貌似Objective-C Runtime没有提供修改ivars和methodLists指针值的接口。

也因此,ivars在运行时指向的是一个固定区域,当然可以修改这个区域的值了,但这其实只是修改成员变量值而已;『在这个内存区域后面续上一段空余区域用于存放新的成员变量』?呵呵,想多了吧!因此,我们没办法在运行时为对象添加成员变量,这解释了为什么category中不能定义property(dynamic property不算);

P.S:那为什么protocol中可以添加变量,在我的理解里,protocol是在编译器处理的。所以objc_class中有一个变量叫protocols;

和ivars不同,methodLists是一个二维数组。虽然我们没办法扩展methodLists指向的内存区域,但是我们可以改变这个内存区域(这个内存区域存储的都是指针)的值。因此,我们可以在运行时动态添加(以及做其他的处理,譬如交换等)方法!

P.S:objc_class结构体中还有一个变量cache,顾名思义,它是用于缓存的,缓存啥呢?缓存方法,下一篇博客阐述「消息传递机制」时会谈到这个。

到了这里,谁还敢说Objective-C不是动态语言?

Objective-C Runtime(一)预备知识的更多相关文章

  1. 自动化预备知识上&&下--Android自动化测试学习历程

    章节:自动化基础篇——自动化预备知识上&&下 主要讲解内容及笔记: 一.需要具备的能力: 测试一年,编程一年,熟悉并掌握业界自动化测试工具(monkey--压力测试.monkeyrun ...

  2. 1 预备知识--Hadoop简介

    1 预备知识--Hadoop简介 Hadoop是Apache的一个开源的分布式计算平台,以HDFS分布式文件系统和MapReduce分布式计算框架为核心,为用户提供了一套底层透明的分布式基础设施Had ...

  3. 驱动开发学习笔记. 0.06 嵌入式linux视频开发之预备知识

    驱动开发读书笔记. 0.06  嵌入式linux视频开发之预备知识 由于毕业设计选择了嵌入式linux视频开发相关的项目,于是找了相关的资料,下面是一下预备知识 UVC : UVC,全称为:USB v ...

  4. 受限玻尔兹曼机(RBM)学习笔记(一)预备知识

    去年 6 月份写的博文<Yusuke Sugomori 的 C 语言 Deep Learning 程序解读>是囫囵吞枣地读完一个关于 DBN 算法的开源代码后的笔记,当时对其中涉及的算法原 ...

  5. 学习Identity Server 4的预备知识

    我要使用asp.net core 2.0 web api 搭建一个基础框架并立即应用于一个实际的项目中去. 这里需要使用identity server 4 做单点登陆. 下面就简单学习一下相关的预备知 ...

  6. JAVA面向对象-----面向对象(基础预备知识汇总)

    终于整理好了面向对象基础预备知识,但是有点多,所以你们懂的,贴图,较长的代码我还是会排版出来的,我不想把时间浪费在排版上在word里排版一次已经很浪费时间了,所以请谅解. public class C ...

  7. 用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 (2) + 准备项目

    上一部分预备知识在这 http://www.cnblogs.com/cgzl/p/9010978.html 如果您对ASP.NET Core很了解的话,可以不看本文, 本文基本都是官方文档的内容. A ...

  8. ASP.NET Core的实时库: SignalR -- 预备知识

    大纲 本系列会分为2-3篇文章. 第一篇介绍SignalR的预备知识和原理 然后会介绍SignalR和如何在ASP.NET Core里使用SignalR. 本文的目录如下: 实时Web简述 Long ...

  9. 学习Identity Server 4的预备知识 (误删, 重补)

    我要使用asp.net core 2.0 web api 搭建一个基础框架并立即应用于一个实际的项目中去. 这里需要使用identity server 4 做单点登陆. 下面就简单学习一下相关的预备知 ...

随机推荐

  1. 记录一下 ps命令找出线程占用cpu情况

    https://blog.csdn.net/xnn2s/article/details/11865339

  2. 关于傅里叶变换NTT(FNT)的周边

    NTT:快速数论变化,对于FFT精度减少的情况,NTT可以避免但是会慢一点,毕竟是数论有Mod,和快速米 引用:http://blog.csdn.net/zz_1215/article/details ...

  3. Redhat 5 无法安装elfutils-libelf-devel-0.137问题

    http://whr25.blog.sohu.com/263584338.html 问题: RHEL5.5安装oracle11gR2的时候需要安装elfutils-libelf-devel-0.137 ...

  4. 【Todo】C++类 & 通用面试题分析记录 & 最难的bug

    1. the most difficult bug u fixed and how u solved this problem.. 解决过很多疑难bug.最困难的分为两类.一类是并发.多线程类的,因为 ...

  5. HDU 4857 topological_sort

    逃生 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission ...

  6. ActiveMQ(四) 转

    package pfs.y2017.m11.mq.activemq.demo04; import javax.jms.Connection; import javax.jms.ConnectionFa ...

  7. Page Design for Sexable Forum

    Design Demo 1. Home of Sexable Forum 1.1  home page not logined. 1,2 home page logined. 2. Pages wit ...

  8. 第8章4节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-启动AndroidDebugBridge

    上一节我们看到在启动AndroidDebugBridge的过程中会调用其start方法,而该方法会做2个基本的事情: 715行startAdb:开启AndroidDebugBridge 722-723 ...

  9. adb pull 与 push

    adb pull <remote> <local> Copies a specified file from an emulator/device instance to yo ...

  10. java 调用ant的自己定义task,默认不是build.xml 的一点问题

    java  调用ant的自己定义task, File buildFile = new File(".//ee-build.xml");         // 创建一个ANT项目   ...