作为一门动态编程语言,Objective-C 会尽可能的将编译和链接时要做的事情推迟到运行时。只要有可能,Objective-C 总是使用动态 的方式来解决问题。这意味着 Objective-C 语言不仅需要一个编译环境,同时也需要一个运行时系统来执行编译好的代码。运行时系统(runtime)扮演的角色类似于 Objective-C 语言的操作系统,Objective-C 基于该系统来工作。因此,runtime好比Objective-C的灵魂,很多东西都是在这个基础上出现的。所以它是值的你花功夫去理解的。
我们将从以下几个方面了解Objective-C的运行时:
一、与静态语言编译后的区别
1、静态语言
一个静态语言程序,如下所示的C程序:
#include < stdio.h > int main(int argc, const char **argv[]) { printf("Hello World!"); return 0; }会经过编译器的语法分析,优化然后将你最佳化的代码翻译成汇编语言,然后完全按照你设计的逻辑和你的代码自上而下的执行。
2、Objective-C
很常见的一个消息发送语句:
[receiver message]会被编译器转化成
objc_msgSend(receiver, selector)如果有参数则为
objc_msgSend(receiver, selector, arg1, arg2, …)消息只有到运行时才会和函数实现绑定起来,而不是按照编译好的逻辑一成不变的执行。按照我的理解,编译阶段只是确定了要去向receiver对象发送message消息,但是却没有发送,真正发送是等到运行的时候进行。因此,编译阶段完全不知道message方法的具体实现,甚至,该方法到底有没有被实现也不知道。这就有可能导致运行时崩溃问题。
二、Objective-c runtime的几点说明
1、runtime是开源的
是的,你没看错,runtime确实是开源的。目前苹果公司和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。其中苹果的版本可以猛击该链接下载objc4-437.1.tar.gz
2、runtime是由C语言实现的
runtime做为Objective-C最核心的部分,几乎全部由C语言实现。这里的“几乎”所指的例外就包含有的方法(比如下面要说道的objc_msgSend方法)甚至是用汇编实现的!!
3、runtime的两个版本
Objective-C运行时系统有两个已知版本:早期版本(Legacy)和现行版本(Modern)。
在现行版本中,最显著的新特性就是实例变量是"健壮“(non-fragile)的:
在早期版本中,如果您改变类中实例变量的布局,您必须重新编译该类的所有子类。
在现行版本中,如果您改变类中实例变量的布局,您无需重新编译该类的任何子类。
此外,现行版本支持声明property的synthesis属性器。
目前iPhone 程序和 Mac OS X v10.5 及以后的系统中的 64 位程序使用的都是 Objective-C 运行时系统的现行版 本。其它情况(Mac OS X 系统中的 32 位程序)使用的是早期版本。
三、和runtime system交互的三种方式
1、通过Objective-C源代码
大部分情况下,运行时系统在后台自动运行,我们只需编写和编译 Objective-C 源代码。
当编译Objective-C类和方法时,编译器为实现语言动态特性将自动创建一些数据结构和函数。这些数据 结构包含类定义和协议类定义中的信息,如在Objective-C 2.0 程序设计语言中定义类和协议类一节所讨论 的类的对象和协议类的对象,方法选标,实例变量模板,以及其它来自于源代码的信息。运行时系统的主要功能就是根据源代码中的表达式发送消息。
2、通过类NSObject的方法
Cocoa程序中绝大部分类都是NSObject类的子类,所以大部分都继承了NSObject类的方法,因而继承 了NSObject的行为(NSProxy类是个例外)。然而,某些情况下, NSObject类仅仅定义了完成某件事情的模板,而没有提供所有需要的代码。
例如,NSObject 类定义了description方法,返回该类内容的字符串表示。这主要是用来调试程序 ——GDB 中的 print-object 方法就是直接打印出该方法返回的字符串。NSObject 类中该方法的 实现并不知道子类中的内容,所以它只是返回类的名字和对象的地址。NSObject 的子类可以重新实现该方法以提供更多的信息。例如,NSArray 类改写了该方法来返回 NSArray 类包含的每个对象的内容。
某些 NSObject 的方法只是简单地从运行时系统中获得信息,从而允许对象进行一定程度的自我检查。
例如,class 返回对象的类;isKindOfClass:和 isMemberOfClass:则检查对象是否在指定的 类继承体系中;respondsToSelector:检查对象能否响应指定的消息;conformsToProtocol: 检查对象是否实现了指定协议类的方法;methodForSelector:则返回指定方法实现的地址。
3、通过运行时系统的函数
运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明 头文件在/usr/include/objc中。这些函数支持用纯C的函数来实现和Objective-C同样的功能。还有一些函数构成了 NSObject 类方法的基础。这些函数使得访问运行时系统接口和提供开发工具成为可 能。尽管大部分情况下它们在 Objective-C 程序不是必须的,但是有时候对于 Objecitve-C 程序来说某些函 数是非常有用的。 这些函数的文档参见 Objective-C 2.0 运行时系统参考库。
-------------未完待续-------------
原文地址:http://developer.android.com/training/basics/firstapp/running-app.html
---------------------------------------------
If you followed the previous lesson to create an Android project, it includes a default set of "Hello World" source files that allow you to immediately run the app.
如果你按照上一节课中创建了一个Android项目,它将包含一个默认的“Hello World”源文件,从而允许你立即运行app。
How you run your app depends on two things: whether you have a real Android-powered device and whether you're using Eclipse. This lesson shows you how to install and run your app on a real device and on the Android emulator, and in both cases with either Eclipse or the command line tools.
怎样运行你的app取决于两件事情:你是否有一个真实的Android设备,以及你是否使用Eclipse。这节课向你展示如何在真实的设备上以及Android虚拟机上安装并运行你的app,均可使用Eclipse或命令行。
Before you run your app, you should be aware of a few directories and files in the Android project:
在运行你的app之前,你应该知道Android项目中的一些目录和文件:
AndroidManifest.xmlThe manifest file describes the fundamental characteristics of the app and defines each of its components. You'll learn about various declarations in this file as you read more training classes.
清单文件描述了app的基本性质,并且定义了它的每一个组件。你将在下面的训练课程中学到这个文件中更多的声明。
One of the most important elements your manifest should include is the <uses-sdk> element. This declares your app's compatibility with different Android versions using the android:minSdkVersion andandroid:targetSdkVersion attributes. For your first app, it should look like this:
在你的清单里,其中一个最重要的元素就是<uses-sdk>元素。它使用 android:minSdkVersion 和 android:targetSdkVersion 属性,声明了你的app对于不同Android版本的兼容性。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ... > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> ... </manifest>
You should always set the android:targetSdkVersion as high as possible and test your app on the corresponding platform version. For more information, read Supporting Different Platform Versions.
你应该尽可能高得设置android:targetSdkVersion的值,并且在对应的平台版本上测试你的app。更多的信息,请阅读Supporting Different Platform Versions。
src/Directory for your app's main source files. By default, it includes an Activity class that runs when your app is launched using the app icon. 你的app的主要源文件目录。默认下,它包含了一个Activity类,它在使用app图标启动时将会运行。res/Contains several sub-directories for app resources. Here are just a few:
包含了一些app resources的子目录。下面只是其中一些:
drawable-hdpi/Directory for drawable objects (such as bitmaps) that are designed for high-density (hdpi) screens. Other drawable directories contain assets designed for other screen densities. 为高分辨率设计的可绘对象(例如位图)的目录。其他可绘目录包含了为其他分辨率屏幕设计的元素。layout/Directory for files that define your app's user interface. 定义你的app用户界面的文件目录。values/Directory for other various XML files that contain a collection of resources, such as string and color definitions. 其他各种XML文件目录,包含了一个资源集合,例如字符串和颜色定义等。When you build and run the default Android app, the default Activity class starts and loads a layout file that says "Hello World." The result is nothing exciting, but it's important that you understand how to run your app before you start developing.
当你建立并运行默认的Android app时,默认的Activity类将会启动,并加载一个叫"Hello World"的布局文件。这看起来并不那么令人兴奋,但这对于你在开始开发前,理解如何与运行你的app是十分重要的。
Run on a Real Device —— 在真实的设备上运行If you have a real Android-powered device, here's how you can install and run your app:
如果你有一部真实的Android设备,你可以这样安装并运行你的app:
- On most devices running Android 3.2 or older, you can find the option under Settings > Applications > Development. 在大多数Android 3.2或更老的设备上,你可以在设置 > 应用程序> 开发下找到这个选项。
- On Android 4.0 and newer, it's in Settings > Developer options. 在Android 4.0及更高版本,它在设置
> 开发人员选项下。
Note: On Android 4.2 and newer, Developer options is hidden by default. To make it available, go toSettings > About phone and tap Build number seven times. Return to the previous screen to find Developer options.
注意:在Android 4.2及更高版本上,开发人员选项默认下是隐藏的。为了使它可用,进入设置 > 关于手机,然后点击版本号7下。返回上一个界面并找到开发人员选项。
To run the app from Eclipse:
从Eclipse中运行app:
Eclipse installs the app on your connected device and starts it.
Eclipse将在你连接的设备上安装并启动它。
Or to run your app from a command line:
或者从命令行中运行你的app:
ant debug
adb install bin/MyFirstApp-debug.apk
That's how you build and run your Android app on a device! To start developing, continue to the next lesson.
Run on the Emulator —— 在模拟器中运行Whether you're using Eclipse or the command line, to run your app on the emulator you need to first create an Android Virtual Device (AVD). An AVD is a device configuration for the Android emulator that allows you to model different devices.
无论你使用Eclipse或命令行,为了在模拟器中运行你的app,你需要首先创建一个Android Virtual Device (AVD)。一个AVD是Android模拟器的一个设备配置,它允许你模拟不同的设备。
Figure 1. The AVD Manager showing a few virtual devices.
To create an AVD:
创建一个AVD:
android avd
To run the app from Eclipse:
从Eclipse里运行app:
Eclipse installs the app on your AVD and starts it.
Eclipse在你的AVD上安装你的app并启动它。
Or to run your app from the command line:
或者从命令行中运行你的app:
ant debug
adb install bin/MyFirstApp-debug.apk
That's how you build and run your Android app on the emulator! To start developing, continue to the next lesson.
这就是如何在模拟器里建立并运行你的Android app!继续开发请学习下一节课。
四、runtime中的消息
1、什么是消息
进入今天的正题之前,先来说说跟message息息相关的几个概念
①message(消息)
message的具体定义很难说,因为并没有真正的代码描述,简单的讲message 是一种抽象,包括了函数名+参数列表,他并没有实际的实体存在。
②method(方法)
method是真正的存在的代码。如:- (int)meaning { return 42; }
③selector(方法选择器)
selector 通过SEL类型存在,描述一个特定的method 或者说 message。在实际编程中,可以通过selector进行检索方法等操作。
2、两个跟消息相关的概念
①SEL
SEL又叫方法选择器,这到底是个什么玩意呢?在objc.h中是这样定义的:
typedef struct objc_selector *SEL;这个SEL表示什么?首先,说白了,方法选择器仅仅是一个char *指针,仅仅表示它所代表的方法名字罢了,有如下证据:
SEL selector = @selector(message); //@selector不是函数调用,只是给这个坑爹的编译器的一个提示 NSLog (@"%s", (char *)selector); //print message这时打印的结果就是:message
Objective-C在编译的时候,会根据方法的名字,生成一个用 来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字相同,那么它们的ID都是相同的。就是说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么ID就是一样的。
而这也就导致了Objective-C在处理有相同函数名和参数个数但参数类型不同的函数的能力非常的弱,比如当你想在程序中实现下面两个方法:
-(void)setWidth:(int)width; -(void)setWidth:(double)width;这样的函数则被认为是一种编译错误,而这最终导致了一个非常非常奇怪的Objective-C特色的函数命名:
-(void)setWidthIntValue:(int)width; -(void)setWidthDoubleValue:(double)width;
可能有人会问,runtime费了那么老半天劲,究竟想做什么?GC来了。
刚才我们说道,编译器会根据每个方法的方法名为那个方法生成唯一的SEL,这些SEL组成了一个Set集合,这个Set简单的说就是一个经过了优化过的hash表。而Set的特点就是唯一,也就是SEL是唯一的,因此,如果我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,犀利,速度上无语伦比!!但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用的是perfect hash)。但是不管使用什么样的方法加速,如果能够将总量减少(多个方法可能对应同一个SEL),那将是最犀利的方法。那么,我们就不难理解,为什么SEL仅仅是函数名了。
到这里,我们明白了,本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度!!!!②IMP
IMP在objc.h中是如此定义的:
typedef id (*IMP)(id, SEL, ...);这个比SEL要好理解多了,熟悉C语言的同学都知道,这其实是一个函数指针。前面介绍过的SEL,就是为IMP服务的。由于每个方法都对应唯一的SEL,因此我们可以通过SEL方便、快速、准确的获得它所对应的IMP(也就是函数指针),而在取得了函数指针之后,也就意味着我们取得了执行的时候的这段方法的代码的入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。当然我们可以把函数指针作为参数传递到其他的方法,或者实例变量里面,从而获得极大的动态性。
下面的例子,介绍了取得函数指针,即函数指针的用法:
void (* performMessage)(id,SEL);//定义一个IMP(函数指针) performMessage = (void (*)(id,SEL))[self performSelector:@selector(message)];//通过performSelector方法根据SEL获取对应的函数指针 performMessage(self,@selector(message));//通过取到的IMP(函数指针)跳过runtime消息传递机制,直接执行message方法
用IMP 的方式,省去了runtime消息传递过程中所做的一系列动作,比直接向对象发送消息高效一些。
3、传递消息所用的几个runtime方法
上篇文章中我们说过,下面的方法:
[receiver message]在编译后会变成:
objc_msgSend(receiver, selector)实际上,同objc_msgSend方法类似的还有几个:
objc_msgSend_stret(返回值是结构体)
objc_msgSend_fpret(返回值是浮点型)
objc_msgSendSuper(调用父类方法)
objc_msgSendSuper_stret(调用父类方法,返回值是结构体)
它们的作用都是类似的,为了简单起见,后续介绍消息和消息传递机制都以objc_msgSend方法为例。
-------------未完待续-------------