2017-01-09 笔记 OC动态语言特性以及与C的对比 上

来源:互联网 发布:怎么修改本机端口号 编辑:程序博客网 时间:2024/05/18 02:58

OC动态语言特性以及僵尸调试模式原理

OC的消息转发流程

1:在C++中调用一个对象不具有的方法将导致崩溃,但在OC中却并不一定。如果向一OC对象发送其不能响应的消息,则会触发OC的消息转发流程:

 

第一步:动态方法解析,询问能否动态添加一个方法:

+(BOOL)resolveInstanceMethod:(SEL)selector {

    NSString *selectorString = NSStringFromSelector(selector);

    if(***) {

        class_addMethod(self,selector,(IMP)autoDictionarySetter,”v@:@”);

    }

}


第二步:询问是否具有备援接收者,询问能否把这条消息转给其他接收者来处理:

  • (id)forwardingTargetForSelector:(SEL)selector



第三步:完整的消息转发,消息重定向:

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

    id target = [realObject1 methodSignatureForSelector:[invocation selector]] ? realObject1 : realObject2;  

    [invocation invokeWithTarget:target];  

}  

理解objc_msgSend

1:在对象上调用方法是Objective-C中经常使用的功能。用OC的术语来说,这叫做“传递消息”(pass a message)。消息有“名称”(name)或“选择子”(selector),可以接受参数,而且可能还有返回值。由于OC是在C的基础上开发的语言,现比较与C语言的函数调用方式的不同。

C语言使用“静态绑定”(static binding),也就是说,在编译器就能决定运行时所应调用的函数。如下代码:

#import <stdio.h>

void printHello() {

    printf(“Hello,word!\n”);

}

void printGoodbye() {

    printf(“Goodbye,word!\n”);

}


void doTheThing(int type) {

    if(type == 0) {

        printHello();

    } else {

        printGoobye();

    }

    return 0;

}

如果不考虑“內联”(inline),那么编译器在编译代码的时候就已经知道程序中有printHello、printGoodbye两个函数,于是会直接生成调用这些函数的指令。而函数地址实际上是硬编码在指令之中的。若是将刚才代码写成如下形式

void doTheThing(int type) {

    void (*fnc)();

    if(type == 0) {

        fnc = printHello;

    } else {

        fnc = printGoodbyte;

    }

    fnc();

    return 0;

}

这就是使用“动态绑定”(dynamic binding)了,调用的函数直到运行期才能确定。OC正式采用了类似的思想。在OC中如果向某个对象传递消息,在底层所有的方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全与运行期决定,甚至在程序运行期间改变,这些特性使得OC成为一门真正的动态语言。


以以下代码为例:

    id returnVlaue = [someObject messageName:parameter];

在本例中someObject称为“接收者”(receiver),messageName叫做“选择子”(selector)。选择子与参数称为“消息”(message)。编译器看到此消息后会把它转成标准的C语言函数调用,所调用函数原型如下:

    void objc_msgSend(id self,SEL cmd,….); 一参数可变函数(variadic function)

上例:

    id returnValue = objc_msgSend(someObject,@selector(messageName:),parmeter);


上面的函数是如何最终完成函数调用的:为了完成此操作,该方法回到接收者所属的类中搜寻其“方法列表”(list of methods),如果能找到与选择子名称相符的方法,就调用实现代码,若找不到则沿着继承体系向上查找,等找到合适的方法之后再跳转。如果最后还是找不到,则执行“消息转发”(message forwarding)操作。


刚才提到,objc_msgSend等函数一旦找到应该调用的方法实现之后,就会“跳转过去”,OC对的的每个方法都可以视为简单的C函数,其原型如下:

    <return_type> Class_selector(id self,SEL _cms,…)

每个类中都有一张表格,其中的指针都会指向这种函数,而选择子的名称就是查找时所用的“key”。objc_msgSend正式通过这张表找到对应方法的。


可以看出此函数原型与objc_msgSend一致,这也不是巧合,而是为了利用“尾调用优化”(tail-call optimization)技术,令“跳至方法实现”这一操作变的更简单些。



类对象概念:

1:在C++中类被看成一种数据类型,而OC中类不仅仅被看成一种数据类型,还代表着一个类对象。消息的接受者究竟是何物?是对象本身么?是对象本身么?运行期系统如何知道某个对象的类型呢?


typedef struct objc_object {

    Class isa;

} *id;


typedef struct objc_class *Class;

struct objc_Class {

    Class isa;

    Class super_class;

    const char *name;

    long version;

    long info;

    long instance_size;

    struct objc_ivar_list *ivars;

    struct objc_method_list **methodLists;

    struct objc_cache *cache;

    stuck objc_protocol_list *protocils;

}


此结构体存放类的“元数据”(methadata),此结构体首个变量也是isa指针,这说明Class本身也是OC对象。

其isa指向另一个类,叫做“元类”(methaclass),用来表述对象本身所具备的元数据。“类方法”也就定义于此处。每个类仅有一个“类对象”,而每个“类对象”仅有一个与之相关的“元类”。

 

通过这张布局关系图即可执行“类型信息查询”。可以查出对象是否响应某个选择子,是否遵循某项协议,确认其“类继承体系等”。

OC中的类别(分类)Category概念

分类能够做到的事情主要是:即使在你不知道一个类的源码情况下,向这个类添加扩展的方法。

#import “UIViewController.h”

@interface UIViewControllerCustomView

-voidextMethod

@end


使用分类为类添加方法(Add Methods to Classes)。

通过在interface中声明一个额外的方法并且implementation中定义相同名字的方法即可。分类的名字(也就是括号括起来的CustomView)表示的是:对于声明于其他地方的这个类(UIViewController),在此处添加的方法是额外的,而不是表示这是一个新的类。但是你不可以通过分类为一个类添加额外的成员变量。



僵尸对象模式

启用这项调试功能之后,远行期系统会把所有已经回收的实例转化成特殊的“僵尸对象”,而不会真正回收它们。这种对象所在的核心内存无法重用,因此不可能遭到复写。僵尸对象收到消息后,会抛出异常,其中准确说明了发送过来的消息,并描述了回收之前的那个对象。僵尸对象是调试内存管理问题的最佳方式。

给僵尸对象发消息后,控制台会打印消息,而应用程序则会终止。打印出来的消息就像这样:

    -[CFString respondsToSelector:]:message sent to deallocated instance 0xffab45….

那么僵尸对象的工作原理是什么:

系统在即将回收对象的时候如果发现环境变量启用了僵尸对象功能,那么将执行一个附加步骤。这一步就是把对象转化成僵尸对象,而不彻底回收。


如有如下代码,采用手动引用计数MRC(manual reference counter):

#import<Foundation/Foundation.h>

#import<objc/runtime.h>


@interface EOCClass : NSObject

@end


@implementation EOCClass

@end


void PrintClassInfo(id obj) {

    Class cls = object_getClass(obj);

    Class superCls = class_getSuperclass(cls);

    NSLog(@“=== %s : %s ===”,class_getName(cls),class_getName(superCls);

}


int main(int argc, char *argv[]) {

    EOCClass *obj = [[EOCClass alloc] init];

    NSLog(@“Before release:”);


    PrintClassInfo(obj);


    [obj release];

    NSLog(@“After release:”);

    PrintClassInfo(obj);

}


输出:

Before release:

=== EOCClass:NSObject ===

After release:

=== _NSZombie_EOCClass:nil ===


对象由EOCClass变成了_NSZombie_EOCClass。


我们的代码中没有定义过这个类,那么这个类来自哪里呢,如果说编译器没看到一种可能变成僵尸的对象,就创建一个对应的僵尸类,就太低效率了。

_NSZombie_EOCClass实际上是在运行期间生成的,当首次碰到EOCClass类的对象变成僵尸对象时,就会创建一个类。僵尸类只是充当一个标记,协助调试罢了。


下面是一块为代码,演示僵尸类如何被创建的,以及僵尸类如何把待回收的对象转换成僵尸对象。


// Obtain the class of the object being deallocated

Class cls = object_getClass(self);


// Get the class’s name

const char *clsName = class_getName(cls);


// Prepend _NSZombie_ to the class name

const char *zombieClsName = “_NSZombie_” + clsName;


// See if the specific zombie class exists

Class zombieCls = objc_lookUpClass(zombieClsName);


// If the specific zombie class does’t exits.

// Then it needs to be created

if (!zombieCls) {

    // Obtain the template zombie class called _NSZombie_

    Class baseZombieCls = objc_lookUpClass(“_NSZombie_”);

    // Duplicate the base zombie class,where the new class’s name is the prepended string from above,它把整个_NSZombie_类结构拷贝一份,并赋予其新的名字。

    zombieCls = objc_dublicateClass(baseZombieCls,zombieClsName,0);

}


// Perform normal destruction of the object being deallocated

object_destructInstance(self);


// Set the class of the object being deallocated to the zombie class

// 修改了对象的isa指针,令其指向特殊的僵尸类,从而使该对象变成僵尸对象。僵尸类能够响应所有的选择子,响应方式为:打印一条包含消息内容极其接收者的消息,然后终止应用程序。

objc_setClass(self,zombieCls);


// The class of ’self’ in now _NSZombie_OriginalClass


这个过程实在NSObject的dealloc方法所做的事。运行期系统如果发现NSZombieEnable环境变量已设置,那么就在dealloc方法中执行上述代码。执行到最后,对象所属的类已经变成_NSZombie_+原类名了。


代码的关键之处在于:对象所占内存没有(通过调用free())方法释放,因此,这块内存不可复用。虽然内存泄漏了,但这只是调试手段。


僵尸类的作用会在消息转发例程中体现出来。此类没有超类,只有一个isa实例变量。这个轻量级的类没有实现任何方法,所以发给他的全部消息都要经过“完整的消息转发机制”。



0 0
原创粉丝点击