iOS Runtime小知识点好东西

iOS runtime面试题

2017-07-13  本文已影响297人  林大鹏

作者:一缕殇流化隐半边冰霜
神经病院Objective-C Runtime住院第二天——消息发送与转发
神经病院Objective-C Runtime入院第一天——isa和Class
神经病院Objective-C Runtime出院第三天——如何正确使用Runtime

一. [self class] 与 [super class]

下面的代码分别输出什么?

@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

答案: 都是输Son

解释:

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )


/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class.

我们很容易错误的认为[super class]是调用的[super_class class],其实objc_msgSendSuper的工作原理应该是这样:

// 注意这里是从父类开始msgSend,而不是从本类开始,
objc_msgSend(objc_super->receiver, @selector(class))

/// Specifies an instance of a class.  这是类的一个实例
    __unsafe_unretained id receiver;   


// 由于是实例调用,所以是减号方法
- (Class)class {
    return object_getClass(self);
}

由于找到了父类NSObject里面的class方法的IMP,又因为传入的参数objc_super->receiver = self;self就是Son,所以父类的方法class执行IMP之后,输出还是Son,最后输出的两个值都是一样,都是Son.

二. isKindOfClass 与 isMemberOfClass

下面代码输出什么?

 @interface Sark : NSObject
 @end

 @implementation Sark
 @end

 int main(int argc, const char * argv[]) {
@autoreleasepool {
    BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
    BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

   NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}

答案: 1 0 0 0
解答:

image.png

三. Class与内存地址

下面的代码会输出什么?

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
- (void)speak;
@end
@implementation Sark
- (void)speak {
    NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
}
@end

这道题目有两个难点:

  1. obj调用speak方法,到底会不会崩溃。
  2. 如果speak方法不崩溃,应该输出什么?

首先谈谈隐藏参数self和_cmd的问题。
[receiver message]调用方法时,系统会在运行时偷偷的动态传入两个隐藏参数self_cmd,之所以称之为隐藏参数,是因为在源代码中没有声明和定义这两个参数。self就是方法接收者,_cmd表示当前调用方法,其实它就是一个方法的选择器SEL.

难点一: 能不能调用speak方法?

id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];

答案是可以的。obj被转换成了一个指向Sark Class的指针,然后使用id转换成了objc_object类型。obj现在已经是一个Sark类型的实例对象了,当然可以调用speak的方法。

objc_classobjc_object的定义:

typedef struct objc_class *Class;

typedef struct objc_object *id;

@interface Object { 
    Class isa; 
}

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

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;     Class superclass;
    cache_t cache;             // formerly cache pointer and vtable     

    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags }union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

难点二:如果能调用speak,会输出什么?

很多人认为会输出sark相关信息,这样答案就错误了。

正确的答案会输出:

my name's <ViewController: 0x7f9472502940>

内存地址每次运行都不同,但前面一定是ViewController
我们改变下代码,打印更多信息看一下。

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"ViewController = %@ , 地址 = %p", self, &self);

    id cls = [Sark class];
    NSLog(@"Sark class = %@ 地址 = %p", cls, &cls);

    void *obj = &cls;
    NSLog(@"Void *obj = %@ 地址 = %p", obj,&obj);

    [(__bridge id)obj speak];

    Sark *sark = [[Sark alloc]init];
    NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark);

    [sark speak];
}

我们把指针地址打印出来,结果如下:

 ViewController = <ViewController: 0x7fdf75d07260> , 地址 = 0x7fff5d52b998

 Sark class = Sark 地址 = 0x7fff5d52b978

 Void *obj = <Sark: 0x7fff5d52b978> 地址 = 0x7fff5d52b970

 my name's <ViewController: 0x7fdf75d07260>

 Sark instance = <Sark: 0x618000200190> 地址 = 0x7fff5d52b968

 my name's (null)

从地址我们可以看出,随着参数的压栈,地址越来越小。

// 以下数字越高表示地址越小
// 压栈参数1: id self (0)
// 压栈参数2: SEL _cmd (1)
- (void)viewDidLoad {
    // objc_msgSendSuper2[struct objc_super, SEL)
    [super viewDidLoad];

    // struct objc_super2 {
    // id receiver 等价于 self (2)
    // Class super_class 指向父类类型的指针,

    id cls = [Sark class];

    // (4)
    void *obj = &cls;

    [(__bridge id)obj speak];

}

// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);

objc_msgSendSuper2方法参数是一个objc_super *super.

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif

所以按viewDidLoad执行时各个变量入栈顺序从高到低为self,_cmd,super_class(等同于self.class),receiver(等同于self),obj

入栈顺序.png

第一个self和第二个_cmd是隐藏参数。第三个super_class和第四个self[super viewDidLoad]方法执行时候的参数。

在调用self.name的时候,本质上是self指针在内存想高位地址偏移一个指针。
因为在Objc中对象实质上是一个指向ClassObject地址的变量,即id obj = &ClassObject,而对象的实例变量 void *ivar = &obj + offset(N)

- Obj :        0x7fff5d52b978
- Self:        0x7fff5d52b980
- super_class: 0x7fff5d52b988
- _cmd:        0x7fff5d52b990
- self:        0x7fff5d52b998

所以如上,obj就是cls的地址,向上偏移一个指针(这里指针占8个字节)就是self的地址,所以输出:

my name's <ViewController: 0x7f9472502940>

四. Category

下面的代码会?Compile Error / Runtime Crash / NSLog…?

@interface NSObject (Sark)
 + (void)foo;
 - (void)foo;
 @end
@implementation NSObject (Sark)
 - (void)foo
 {
      NSLog(@"IMP: -[NSObject(Sark) foo]");
 }
 @end
 int main(int argc, const char * argv[]) {
  @autoreleasepool {
      [NSObject foo];
      [[NSObject new] foo];
  }
  return 0;
}

**答案: **

IMP: -[NSObject(Sark) foo]
IMP: -[NSObject(Sark) foo]

分析:

上一篇 下一篇

猜你喜欢

热点阅读