runtime浅谈(二)概念与补充

1、Runtime

      Runtime有两种,一个 Modern Runtime 和一个 Legacy Runtime。

      Modern Runtime(现代 Runtime): 覆盖了64位的Mac OS X 应用 和所有 iOS 应用,

      Legacy Runtime(过时 Runtime): 是早期用来给32位 Mac OS X Apps 用.


2、Basic types of Methods

      Instance Method 和 Class Method。

      instance method (实例方法):就是带“-”号的,需要实例化才能用的, 

      Class Method (类方法): 就是带“+”号的,类似于静态方法可以直接调用。

      方法(Methods)和 C 的函数很像,是一组代码,执行一个小的任务。


3 、Selector

     Selector 事实上是一个 C 数据的结构体,表示的是一个方法。

typedef struct objc_selector  *SEL;
SEL sel = @select(title);

 
  
消息:
[target getTitleForObject:obj];

       消息是方括号 ‘[]’ 中的那部分,由你要向其发送消息的对象(target),你想要在上面执行的方法(method)还有你发送的参数(arguments)组成。Objective-C 的消息和 C 函数调用是不同的。事实上,你向一个对象发送消息并不意味着它会执行它。Object (对象)会检查消息的发送者,基于这点再决定是执行一个不同的方法还是转发消息到另一个目标对象上。


4、 class 在runtime(一)已经提到。

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id;

       所有的 objc_object 对象结构体都有一个 isa 指针,这个 isa 指向它所属的类,在运行时就靠这个指针来检测这个对象是否可以响应一个 selector。我们看到最后有一个 id 指针。这个指针其实就只是用来代表一个 ObjC 对象,当你拿到一个 id 指针之后,就可以获取这个对象的类,并且可以检测其是否响应一个 selector。这就是对一个 delegate 常用的调用方式。

附:LLVM/Clang 的文档对 Blocks 的定义:

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
 unsigned long int reserved; // NULL
     unsigned long int size;  // sizeof(struct Block_literal_1)
 // optional helper functions
     void (*copy_helper)(void *dst, void *src);
     void (*dispose_helper)(void *src); 
    } *descriptor;
    // imported variables
};

可以看到一个 block 是被设计成一个对象的,拥有一个 isa 指针,所以你可以对一个 block 使用 retain, release, copy 这些方法。


5、IMP (Method Implementations)

      typedef id (*IMP)(id self,SEL _cmd,...); 

       IMP 只想方法实现的函数指针,这是由编译器生成的,当你发起一个 ObjC 消息之后,最终它会执行的那个代码,就是由这个函数指针指定的。


6、Objective-C Classes

     ObjC 类同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做 元类(Meta Class)的东西。

例:

[NSObject alloc];

      你事实上是把这个消息发给了一个类对象(Class Object),runtime通过创建Meta Classes 处理这些。因此这个类对象需要的是一个 Meta Class 的实例,一个 Meta Class 同时也是root MetaClass 的实例。当你继承了 NSObject 成为其子类的时候,你的类指针就会指向 NSObject 为其父类。但是 Meta Class 不太一样,所有的 Meta Class 都指向root Meta Class 作为自己的superclass。一个 Meta Class 持有所有能响应的方法。所以当 [NSObject alloc] 这条消息发出的时候,objc_msgSend() 这个方法会去 NSObject 它的 Meta Class 里面去查找是否有响应这个 selector 的方法,然后对 NSObject 这个类对象执行方法调用。

        对于完全的 O-C 来说,类也是个对象,类是类类型(MetaClass)的实例,所以类的类型描述就是 meta class)。


7、为什么要继承apple Classes

     当我们继承自 NSObject 然后开始写一些代码,享受了很多继承自苹果的类所带来的便利,有一件事你从未意识到,就是我们的对象被设置为使用 Objective-C 的 runtime

MyObject *object = [[MyObject alloc] init];

       这个语句用来初始化一个实例,类似于 C++ 的 new 关键字。这个语句首先会执行 MyObject 这个类的 +alloc 方法,Apple 的官方文档是这样说的:

                   The isa instance variable of the new instance is initialized to a data structure that describes the class; memory for all other instance variables is set to 0.

                   新建的实例中,isa 成员变量会变初始化成一个数据结构体,用来描述所指向的类。其他的成员变量的内存会被置为0.

       所以继承 Apple 的类我们不仅是获得了很多很好用的属性,也继承了能在内存中轻松分配内存的能力和在内存中创建满足 runtime 期望的对象结构(设置 isa 指针指向我们的类)。


8、Class Cache(objc_cache *cache)

runtime 里面有一个指针叫 objc_cache *cache,这是用来缓存方法调用的。每次你调用过一个方法,之后,这个方法就会被存到这个 cache 列表里面去,下次调用的时候 runtime 会优先去 cache 里面查找,提高了调用的效率。

MyObject *obj = [[MyObject alloc] init]; // MyObject 的父类是 NSObject

@implementation MyObject
-(id)init {
    if(self = [super init]){
        [self setVarA:@”blah”];
    }
    return self;
}
@end

这段代码是这样执行的:

  1. >  [MyObject alloc] 先被执行。但是由于 MyObject 这个类没有 +alloc 这个方法,于是去父类 NSObject 查找。
  2. >  检测 NSObject 是否响应 +alloc 方法,发现响应,于是检测 MyObject 类,根据其所需的内存空间大小开始分配内存空间,然后把 isa 指针指向 MyObject 类。那么 +alloc 就被加进 cache 列表里面了。
  3. >  完了执行 -init 方法,因为 MyObject 响应该方法,直接加入 cache。
  4. >  执行 self = [super init] 语句。这里直接通过 super 关键字调用父类的 init 方法,确保父类初始化成功,然后再执行自己的初始化逻辑。

       这就是一个很简单的初始化过程,但是,ObjC 特性允许你的 alloc 和 init 返回的值不同,也就是说,你可以在你的 init 函数里面做一些很复杂的初始化操作,但是返回出去一个简单的对象,这就隐藏了类的复杂性。

#import < Foundation/Foundation.h>

@interface MyObject : NSObject
{
 NSString *aString;
}

@property(retain) NSString *aString;

@end

@implementation MyObject

-(id)init
{
 if (self = [super init]) {
  [self setAString:nil];
 }
 return self;
}

@synthesize aString;

@end

nt main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

 id obj1 = [NSMutableArray alloc];
 id obj2 = [[NSMutableArray alloc] init];

 id obj3 = [NSArray alloc];
 id obj4 = [[NSArray alloc] initWithObjects:@"Hello",nil];

 NSLog(@"obj1 class is %@",NSStringFromClass([obj1 class]));
 NSLog(@"obj2 class is %@",NSStringFromClass([obj2 class]));

 NSLog(@"obj3 class is %@",NSStringFromClass([obj3 class]));
 NSLog(@"obj4 class is %@",NSStringFromClass([obj4 class]));

 id obj5 = [MyObject alloc];
 id obj6 = [[MyObject alloc] init];

 NSLog(@"obj5 class is %@",NSStringFromClass([obj5 class]));
 NSLog(@"obj6 class is %@",NSStringFromClass([obj6 class]));

 [pool drain];
    return 0;
}


输出结果:

obj1 class is __NSPlaceholderArray
obj2 class is NSCFArray
obj3 class is __NSPlaceholderArray
obj4 class is NSCFArray
obj5 class is MyObject
obj6 class is MyObject

       这是因为 ObjC 是允许运行 +alloc 返回一个特定的类,而 init 方法又返回一个不同的类的。可以看到 NSMutableArray 是对普通数组的封装,内部实现是复杂的,但是对外隐藏了复杂性。


9、objc_msgSend 

在编译的时候,你定义的方法比如

-(int)doComputeWithNum:(int)aNum 

会编译成:

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

然后由 runtime 去调用指向你的这个方法的函数指针。那么之前我们说你发起消息其实不是对方法的直接调用,其实 Cocoa 还是提供了可以直接调用的方法的

// 首先定义一个 C 语言的函数指针
int (computeNum *)(id,SEL,int);

// 使用 methodForSelector 方法获取对应与该 selector 的杉树指针,跟 objc_msgSend 方法拿到的是一样的
// **methodForSelector 这个方法是 Cocoa 提供的,不是 ObjC runtime 库提供的**
computeNum = (int (*)(id,SEL,int))[target methodForSelector:@selector(doComputeWithNum:)];

// 现在可以直接调用该函数了,跟调用 C 函数是一样的
computeNum(obj,@selector(doComputeWithNum:),aNum); 

如果你需要的话,你可以通过这种方式你来确保这个方法一定会被调用。


        
相关链接:http://www.justinyan.me/post/1624






你可能感兴趣的:(runtime)