interview

OC底层原理探索—经典面试题原理

2021-07-26  本文已影响0人  十年开发初学者

1.loadinitialize方法的调用原则和调用顺序?

load
initialize
c++构造函数

方法的本质

消息发送的流程

能否向编译后的得到的类中增加实例变量?能否向运⾏时创建的类中添加实例变量?

1.不能向编译后得到的类增加实例变量

  1. 可以向运⾏时创建的类中添加实例变量
`        const char *className = "SHObject";
        Class objc_class = objc_getClass(className);
        if (!objc_class) {
            Class superClass = [NSObject class];
            objc_class = objc_allocateClassPair(superClass, className, 0);
        }
        class_addIvar(objc_class, "name", sizeof(NSString *), log2(_Alignof(NSString *)),  @encode(NSString *));
        class_addMethod(objc_class, @selector(addName:), (IMP)addName, "V@:");

[self class]和[super class]区别

来看下下面案例,LGTeacher类继承自LGPerson,在LGTeacher的init初始化方法中,调用了[self class]和[super class],结果会是什么

    // LGPerson
    @interface LGPerson : NSObject
    @end

    @implementation LGPerson
    @end
    
    // LGTeacher
    @interface LGTeacher : LGPerson
    @end

    @implementation LGTeacher
    - (instancetype)init{
        self = [super init];
        if (self) {
           NSLog(@"%@ - %@", [self class], [super class]);
        }
        return self;
    }
    @end

首先这两个类中都没有实现class方法,那么根据继承关系,他们最终会调用到NSObject中的class方法

- (Class)class {
    return object_getClass(self);
}

由上图可知这两个方法返回的self对应的类。关于这个self是谁,这里涉及到消息发送objc_msgSend,有两个隐形参数,分别是id self 和 SEL sel,这里SEL sel没啥好说的,主要来说下id self

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull 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 _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

我们来查看下objc_super结构体

由上图知:id receiverClass super_class两个参数,其中super_class表示第一个要去查找的类,至此我们可以得出结论,在LGTeacher中调用[super class],其内部会调用objc_msgSendSuper方法,并且会传入参数objc_super,其中receiver是LGTeacher对象,super_class是LGTeacher的父类,也就是要第一个查找的类。

内存偏移

案例1

创建一个person类

@interface Person : NSObject
@property (nonatomic,copy)NSString *name;


- (void)say1;
@end

@implementation Person

- (void)say1{
    NSLog(@"%s",__func__);
}

@end

viewDidload中添加以下代码

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    Person *person = [[Person alloc] init];
    [person say1];
    
    Class cls = [Person class];
    void *sh  =&cls;
    [(__bridge  id)sh say1];
    
}

查看下打印


image.png

分析:

image.png
由上图知,sh是指向Person类的 image.png

总结:

案例2

接着上面的案例,我们新增一个属性打印

@implementation Person

- (void)say1{
    NSLog(@"%s,%@",__func__,self.name);
}

@end

查看打印


image.png

首先我们先要了解,取出属性的值,其实是要先计算出偏移大小,在通过内存平移获取值。其实是Person类内部存储着成员变量,每次偏移8字节进行存取

至于sh打印的self.name的值是Person;0X600...,是因为cls只有Person类的内存首地址,但是没有person对象的内存结构,所以sh只能在栈里面进行内存平移。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    Class cls = [Person class] ;
    
    void *sh  =&cls;
    [(__bridge  id)sh say1];
    Person *person = [[Person alloc] init];
    [person say1];
    
    
    // 下面代码为打印栈结构
    void *sp = (void *)&self;
    void *end = (void *)&person;
    long count = (sp - end) / 0x8;
    
    for (long i = 0; i < count; i++) {
        void *address = sp - 0x8 * I;
        if (i == 1) {
            NSLog(@"%p : %s",address, *(char **)address);
        } else {
            NSLog(@"%p : %@",address, *(void **)address);
        }
    }
}

看下栈结构打印


image.png

什么可以压栈

struct objc_super sh_objc_super;
sh_objc_super.super_class = class;
sh_objc_super.receiver = receiver;
上一篇下一篇

猜你喜欢

热点阅读