面试题

    xiaoxiao2021-03-25  198

    什么是ARC?

    ARC是iOS5推出的新功能,全称为Automatic Reference Counting。简单的说就是代码始终自动加入retain/release,在MRC中由手动管理引用计数改为由编辑器自动管理引用计数。这不仅能够降低程序崩溃风险更能降低开发者的工作量,能够大幅度的提升程序的流畅性和可预测性

    ARC几个要点: 在对象被创建时 retain count +1,在对象被release时 retain count -1.当retain count 为0 时,销毁对象。 程序中加入autoreleasepool的对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。 那么ARC是为了解决什么问题诞生的呢?这个得追溯到MRC手动内存管理时代说起。 MRC下内存管理的缺点: 1.当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放) 2.释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放) 3.模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。 4.多线程操作时,不确定哪个线程最后使用完毕

    2、 以下 keywords 有什么区别: assign vs weak , __block vs __weak

    assign 和 weak 是用于在声明属性时, 为属性指定内存管理的语义.

    assign 用于简单的赋值, 不改变属性的引用计数, 用于 Objective-C 中的 NSInteger , CGFloat 以及 C 语言中 int , float , double 等数据类型. weak 用于对象类型, 由于 weak 同样不改变对象的引用计数且不持有对象实例, 当该对象废弃时, 该弱引用自动失效并且被赋值为 nil , 所以它可以用于避免两个强引用产生的 循环引用 导致内存无法释放的问题.

    __block 和 __weak 之间的却是确实极大的, 不过它们都用于修饰变量.

    前者用于指明当前声明的变量在被 block 捕获之后, 可以在 block 中改变变量的值. 因为在 block 声明的同时会截获该 block 所使用的全部自动变量的值, 而这些值只在 block 中 只具有”使用权”而不具有”修改权” . 而 __block 说明符就为 block 提供了变量的修改权. 后者是 所有权修饰符 , 什么是所有权修饰符? 这里涉及到另一个问题, 因为在 ARC 有效时, id 类型和对象类型同 C 语言中的其他类型不同, 必须附加所有权修饰符. 所有权修饰符一种有 4 种:

    __strong __weak __unsafe_unretained __autorelease

    __weak 与 weak 的区别只在于, 前者用于变量的声明, 而后者用于属性的声明.

    __block和__weak修饰符的区别其实是挺明显的: 1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。 2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。 3.__block对象可以在block中被重新赋值,__weak不可以。

    也就是说,在MRC下,我们通常使用__block,而在ARC下我们通常使用__weak,或者__unsafe_unretained __block(不安全,不建议使用)来修饰防止循环引用而造成的内存泄露。

    对Objective-C中runtime的理解

    Objective-C是面向runtime(运行时)的语言,在应用程序运行的时候来决定函数内部实现什么以及做出其它决定的语言。程序员可以在程序运行时创建,检 查,修改类,对象和它们的方法,Objective-C runtime库也负责找出方法的最终执行代码。举例说明,当程序执行[object doSomething]时,不会直接调用doSomething这个方法,而是一条消息(doSomething)会发送给对象(object)。runtime库里有个c函数来传递这条消息,[object doSomething]这条代码将会被转化成c函数形式的函数调用:objc_msgSend(object,@selector(doMethodWith:)),函数内部按照以下顺序进行:

    1.检查接受对象是否为nil,如果是,调用nil处理程序。直接表现就是什么都不做。

    2.如果是在支持垃圾收集环境中,进行一些处理,由于ios不支持垃圾收集,所以我们不必关心这步。

    3.检查类缓存中是不是已经有方法实现了,有的话,直接调用。(每个类都有一些经常执行的方法,有些方法则很少执行,当代码里执行到某一个方法时如果都要去类的方法列表里找一遍就太影响效率了,所以运行时系统会把调用过的方法缓存起来,以提高查找的效率,这一步就是在缓存里查找)

    4.比较请求的选择器(就是方法名,比如doSomething)和类中定义的选择器,如果找到了,调用方法执行。

    5.比较请求的选择器和父类中定义的选择器,找到就执行,找不到继续向父类的父类寻找,如果找到就执行,如果一直找到根类都找不到,那么将执行第6步。

    6.调用resolveInstanceMethod:(或resolveClassMethod:)。这个方法返回一个BOOL值,如果返回no,将执行第7步,如果返回yes,则重新从上面的第1步开始。我们可以在这个方法里用runtime给我们提供的class_addMethod函数为类添加方法,这便是给类动态加方法的一个机会。

    7.调用forwardingTargetForSelector:,这个方法返回一个对象,消息将转发给我们返回的对象,这是我们可以把通过以上6步都不能处理的方法转发到其他对象上的机会。

    8.调用methodSignatureForSelector:,生成一个方法签名,创建一个NSInvocation(这个类把target,selector,方法签名和参数打包在一起)并传给forwardInvocation:方法。在forwardInvocation:里这是我们对这个消息进行处理的最后机会,里如果我们不做处理,默认的程序就会crash,抛出找不到该方法的错误信息。

    在这个过程中我们可以给类添加方法(第6步),可以将消息转发到别的对象上(第7步),对NSInvocation(包含了这条消息的所有信息)进行我们想要的处理(第8步)。

    转载请注明原文地址: https://ju.6miu.com/read-1188.html

    最新回复(0)