iOS底层-19:类相关面试题解析

1.关联对象需要释放吗?
答:不需要,对象dealloc的时候已经帮我们移除关联对象了。

  • 当对象has_assoc关联的时候,会走下面的object_dispose
  • objc_destructInstance方法
  • 有关联对象时,调用_object_remove_assocations释放关联对象

2.类与category同名方法如何调用?

  • 普通方法:因为分类的方法是在主类realize之后attach进去的,插在前面,所以优先调用分类的方法 (包括initialize)
  • load方法:先调用主类的+load 再调用分类的+load

    +load方法顺序:
 +[LRPerson load]
 +[LRPerson(LRB) load]
 +[LRPerson(LR) load]
  • 打开load_images源码

  • prepare_load_methods


    递归调用schedule_class_load,将类的继承链添加至loadable_classes
    loadable_classes是一个数组,数组中每个元素包含类和该类的load方法

add_category_to_loadable_listcategory和每个category中的+load方法加入loadable_categories

  • 回到load_images中,调用call_load_methods

+load全部会调用,先走主类,再走分类;后添加的分类先走。

3.runtime是什么?

runtime是由C、C++、汇编实现的一套API,为OC语言加入了面向对象,运行时的功能。
runtime将数据类型的确定,由编译时推迟到了运行时
平时写的OC代码,在程序运行的过程中,其实最终会变成runtime的C语言代码

4.方法的本质,sel是什么?IMP是什么?两者之间有什么关系?

方法的本质:发送消息,有以下几个流程:

  • 快速查找impobjc_msgSendcache_t缓存中查找
  • 慢速查找(lookUpImpOrForward):递归父类,先找缓存再找method_list
  • 查找不到,动态方法决议resolveInstanceMethod
  • 消息快速转发 forwardingTargetForSelector
  • 消息慢速转发 methodSignatureForSelector & forwardInvocation

sel就是方法编号,在read_images里就编译进了内存
imp是函数实现的指针,找imp就是找函数的过程:

  • 首先知道sel
  • 通过sel找到imp
  • 通过imp找到函数实现

5.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中增加实例变量?
答案:
1.不能向编译后得到的类中增加实例变量
2.只要类还没有注册到内存中还是可以添加的

原因:编译好的类,实例变量存储在ro中,编译完成后,内存结构就完全确定,无法更改
可以添加属性和方法

6.[self class] 和[super class]的区别及原理分析

@interface LRPerson : NSObject

@end

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

打印结果:

LRPerson ---- LRPerson

[self class] :发送消息objc_msgSend;消息接收者是selfselclass
[super class]:super 是编译器关键字 发送消息objc_msgSendSuper;消息接受者是selfselclass

  • 通过clang编译,看super在底层的实现。


    可以看出,他调用的是objc_msgSendSuper方法

  • 搜索objc_msgSendSuper


    该方法有两个参数:

  1. struct objc_super *super
  2. SEL op
  • 查看struct objc_super

    OBJC2环境下,只有两个变量:receiversuper_class
    receiver : LRPerson
    super_class:NSObject
    查看arm64的汇编源码_objc_msgSendSuper

    传入CacheLookup的参数 是LRPersonNSObject
    实际上就是LRPerson对象调用父类的 - class 方法
- (Class)class {
    return object_getClass(self);
}
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

最终返回的还是LRPerson

重点 :以上的分析是错误的

在一下断点处,查看堆栈


Debug -> Debug Workflow -> Always Show Disassembly


走的其实是objc_msgSendSuper2

  • 搜索objc_msgSendSuper2

    注释的意思是在当前类查找,而不是superclass
  • 查看objc_msgSendSuper2汇编
    image.png

    是在这里把p16指向了superclass

7.以下代码会输出什么?

@interface LRPerson : NSObject

@property (nonatomic,copy) NSString *name;

- (void)personMethod;

- (void)sayName;
@end
@implementation LRPerson

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

- (void)sayName {
    NSLog(@"%@",self.name);
}
@end

#import "ViewController.h"
#import "LRPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Class cls = [LRPerson class];
    void *lr = &cls;
    [(__bridge id)lr personMethod];
    [(__bridge id)lr sayName];
}


@end

打印结果:

-[LRPerson personMethod]
<ViewController: 0x7ffac4309100>

1.为什么(__bridge id)lr可以调用- personMethod

上图为lr和一个LRPerson新建对象在内存中的情形,lr->cls->LRPerson与对象person的指向完全一致,欺骗了编译器。编译器错把lr当成是LRPerson对象,所有可以调用对象方法。
[(__bridge id)lr personMethod] 可以打印出-[LRPerson personMethod]

2.为什么第二个打印的是ViewController?
name是对象里的第二个变量,取name是从isa向下偏移8字节。

  • 进入- (void)viewDidLoad函数,会先把两个隐藏参数压入栈中;先压入self,再压入sel
  • 调用[super viewDidLoad];会存入struct objc_super结构体,结构体有两个变量:receiver、class
    super关键字使用的是objc_msgSendSuper2方法,class是当前类,也就是viewControllerreceiverself当前viewController

结构体压栈:最后面的先进栈

  • 当调用方法取self.name的时候,就是lr指针向上平移8个字节,取到了当前viewController,故打印<ViewController: 0x7ffac4309100>
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容