015*:面试题

一:Runtime Asssociate方法关联的对象,需要在dealloc中释放?

不需要

当我们对象释放时,会调用dealloc

  • 1、C++函数释放 :objc_cxxDestruct
  • 2、移除关联属性:_object_remove_assocations
  • 3、将弱引用自动设置nil:weak_clear_no_lock(&table.weak_table, (id)this);
  • 4、引用计数处理:table.refcnts.erase(this)
  • 5、销毁对象:free(obj)

所以,关联对象不需要我们手动移除,会在对象析构即dealloc时释放

dealloc 源码

dealloc的源码查找路径为:dealloc-> _objc_rootDealloc-> rootDealloc-> object_dispose(释放对象)-> objc_destructInstance-> _object_remove_assocations
1:在objc源码中搜索dealloc的源码实现

2:_objc_rootDealloc

3:进入rootDealloc源码实现,发现其中有关联属性时设置bool值,当有这些条件时,需要进入else流程

4:进入object_dispose源码实现,主要是销毁实例对象

 5:objc_destructInstance

6:进入_object_remove_assocations源码,关联属性的移除,主要是从全局哈希map中找到相关对象的迭代器,然后将迭代器中关联属性,从头到尾的移除

2:类的方法 和 分类方法 重名,如果调用,是什么情况? 

如果同名方法是普通方法,包括initialize -- 先调用分类方法

  因为分类的方法是在类realize之后 attach进去的,插在类的方法的前面,所以优先调用分类的方法(注意:不是分类覆盖主类!!)

  initialize方法什么时候调用? initialize方法也是主动调用,即第一次消息时调用,为了不影响整个load,可以将需要提前加载的数据写到initialize

如果同名方法是load方法 -- 先 主类load,后分类load(分类之间,看编译的顺序)

参考 load_images 原理分析

3:Runtime是什么?

  • runtime是由C和C++汇编实现的一套API,为OC语言加入了 面向对象、以及运行时的功能

  • 运行时是指将数据类型的确定由编译时 推迟到了 运行时

    • 举例:extension 和 category 的区别
  • 平时编写的OC代码,在程序运行的过程中,其实最终会转换成runtime的C语言代码, runtime是OC的幕后工作者

1、category 类别、分类

  • 专门用来给类添加新的方法

  • 不能给类添加成员属性,添加了成员属性,也无法取到

  • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写setter、getter方法

  • 分类中用@property定义变量,只会生成变量的setter、getter方法的声明不能生成方法实现 和 带下划线的成员变量

2、extension 类扩展

  • 可以说成是特殊的分类,也可称作 匿名分类

  • 可以给类添加成员属性,但是是私有变量

  • 可以给类添加方法,也是私有方法

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

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

    • 快速查找(objc_msgSend) - cachelookup缓存消息中查找

    • 慢速查找 - 递归自己|父类 - lookUpImpOrForward

    • 查找不到消息:动态方法解析 - resolveInstanceMethod

    • 消息快速转发 - forwardingTargetForSelector

    • 消息慢速转发 - methodSignatureForSelector & forwardInvocation

  • sel方法编号- 在read_images期间就编译进了内存

  • imp函数实现指针找imp就是找函数的过程

  • sel相当于 一本书的目录title

  • sel相当于 书本的页码

  • 查找具体的函数就是想看这本书具体篇章的内容

    • 1、首先知道想看什么,即目录 title - sel

    • 2、根据目录找到对应的页码 - imp

    • 3、通过页码去翻到具体的内容

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

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

  • 2、只要类没有注册到内存还是可以添加的

  • 3、可以添加属性+方法

【原因】:编译好的实例变量存储的位置是ro,一旦编译完成,内存结构就完全确定了

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

  • [self class]就是发送消息 objc_msgSend,消息接收者是self,方法编号 class

  • [super class]本质就是objc_msgSendSuper,消息的接收者还是 self,方法编号 class,在运行时,底层调用的是_objc_msgSendSuper2【重点!!!】

  • 只是 objc_msgSendSuper2会更快,直接跳过self的查找

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


Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

其底层是获取对象的isa,当前的对象是LGTeacher,其isa是同名的LGTeacher,所以[self class]打印的是LGTeacher

super虽然可以直接调用方法但是它并不是对象只是语法关键字。真正的调用者,是当前对象

superClass(id)class_getSuperclass(objc_getClass("HTPerson"))

所以,最完整的回答如下

  • [self class]方法调用的本质是 发送消息,调用class的消息流程,拿到元类的类型,在这里是因为类已经加载到内存,所以在读取时是一个字符串类型,这个字符串类型是在map_imagesreadClass时已经加入表中,所以打印为LGTeacher

  • [super class]打印的是LGTeacher,原因是当前的super是一个关键字,在这里只调用objc_msgSendSuper2,其实他的消息接收者和[self class]是一模一样的,所以返回的是LGTeacher


7:Runtime是如何实现weak的,为什么可以自动置nil

  • 1、通过SideTable找到我们的 weak_table

  • 2、weak_table根据 referent找到或者创建 weak_entry_t

  • 3、然后append_referrer(entry,referrer)将我的新弱引用的对象加进去entry

  • 4、最后 weak_entry_insert,把entry加入到我们的weak_table

8:指针平移

// ViewController
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    Class cls = [LGPerson class];
    void  *kc = &cls; // 拿到对象的指针地址,赋值给kc指针。
    [(__bridge id)kc saySomething];
    LGPerson *person = [LGPerson alloc];
    [person saySomething];
}
@end

// LGPerson.h
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lg_name;
@property (nonatomic, copy) NSString *lg_hobby;
- (void)saySomething;
@end
// LGPerson.m
@implementation LGPerson
- (void)saySomething { 
    NSLog(@"%s",__func__);
}
@end

下面我们运行代码

我们发现没有报错,很正常的执行了方法,为什么能够调用成功?首先我们要明白方法的调用,我们创建LGPerson对象,对象结构中bits存有saySomething方法,创建person对象,它的isa指向了LGPerson对象,通过指针平移找到saySomething方法进行调用,如下图

9:指针存在哪里?

alloc的对象 都在

指针对象 在中,例如person指向的空间在中,person所在的空间在

临时变量

属性值 在,属性随对象是在

注意:

从小到大,即低地址->高地址

从大到小,即从高地址->低地址分配

函数隐藏参数从前往后一直压,即 从高地址->低地址 开始入栈,

结构体内部成员是从低地址->高地址

一般情况下,内存地址有如下规则

0x60 开头表示在 

0x70 开头的地址表示在 

0x10 开头的地址表示在全局区域

该代码块在内存中的表现形式如下图

 图中可以看到,栈里面存放的是非对象的基本数据类型,堆内存存放着oc对象

c中存的是一个16进制的值。64位的值。8直接的值。

引用:

1:iOS-底层原理 20:OC底层面试解析

2:iOS 堆和栈的区别?

posted on 2020-12-02 22:52  风zk  阅读(96)  评论(0编辑  收藏  举报

导航