美文网首页
iOS 面试题总结

iOS 面试题总结

作者: 其实一直都很好 | 来源:发表于2018-04-08 13:33 被阅读2次

    一、RunTime知识点:

    1.objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?

    objc_msgSend()是[obj foo]的具体实现。

    在runtime中,objc_msgSend()是一个c函数,[obj foo]会被翻译成这样的形式objc_msgSend(obj, foo)。

    去obj的对应的类中找方法

    先找缓存,找不到再去找方法列表,

    再找父类,如此向上传递。

    最后再找不到就要转发。


    2.@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

    总结下 @synthesize 合成实例变量的规则,有以下几点:

    如果指定了成员变量的名称,会生成一个指定的名称的成员变量,

    如果这个成员已经存在了就不再生成了.

    如果是 @synthesize foo;

    还会生成一个名称为foo的成员变量,也就是说:

    如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,

    如果是 @synthesize foo = _foo;

    就不会生成成员变量了.

    假如 property 名为 foo,存在一个名为 _foo的实例变量,那么还会自动合成新变量么? 不会。如下图:


    3.在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

    读写属性的自定义 getter 和 setter

    当提供一个自定义的getter 和setter 实现时,属性不会自动合成

    只读属性的自定义getter

    当给只读属性提供一个自定义getter 实现时,不会自动合成

    @dynamic

    当使用@dynamic propertyName, 属性不会自动合成 (很明显,@dynamic 和 @synthesize 是互斥的)

    @protocol 中定义的属性

    当遵守了一个协议,任何协议定义的属性都不会自动合成

    Category 中声明的属性

    这种情况,@synthesize 指令不但不能被编译器自动生成,也不能手动合成属性。虽然category可以声明属性,但不能合成,因为category不能创建类实例变量ivars.为了完整起见,我还是加了这项,因为还是可以通过Objective-C运行时伪造合成属性

    重写属性

    当重写一个父类的属性,必须显示合成它。值得注意的是,合成一个属性会自动合成backing ivar,如果缺少属性合成,ivar也会消失,除非显示声明。

    除了后三个情况,基本思想是当手动指定所有属性相关的信息(通过实现所有访问器方法或使用@dynamic),编译器会认为你想完全控制属性,他会禁用自动合成功能。


    4.objc中向一个nil对象发送消息将会发生什么?

    在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:

    如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:

    Person * motherInlaw = [[aPerson spouse] mother];

    如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。

    如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。

    如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。

    如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。

    具体原因如下:

    objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

    那么,为了方便理解这个内容,还是贴一个objc的源代码:

    // runtime.h(类在runtime中的定义)

    // http://weibo.com/luohanchenyilong

    /// https://github.com/ChenYilong

    structobjc_class{

    Class isa OBJC_ISA_AVAILABILITY;//isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object

    #if!__OBJC2__

    Class super_class OBJC2_UNAVAILABLE;// 父类constchar*name

    OBJC2_UNAVAILABLE;// 类名longversion

    OBJC2_UNAVAILABLE;// 类的版本信息,默认为0longinfo

    OBJC2_UNAVAILABLE;// 类信息,供运行期使用的一些位标识

    longinstance_size OBJC2_UNAVAILABLE;// 该类的实例变量大小

    structobjc_ivar_list*ivars OBJC2_UNAVAILABLE;// 该类的成员变量链表

    structobjc_method_list**methodListsOBJC2_UNAVAILABLE;// 方法定义的链表

    structobjc_cache*cacheOBJC2_UNAVAILABLE;// 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。

    structobjc_protocol_list*protocols OBJC2_UNAVAILABLE;// 协议链表

    #endif

    }

    OBJC2_UNAVAILABLE;

    objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。


    5.什么时候会报unrecognized selector的异常?

    简单来说:

    当调用该对象上某个方法,而该对象上没有实现这个方法的时候,

    可以通过“消息转发”进行解决。

    简单的流程如下,在上一题中也提到过:

    objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

    objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:

    Method resolution

    objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。

    Fast forwarding

    如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。

    只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。

    这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

    Normal forwarding

    这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。


    6.一个objc对象任何进行内存布局(考虑有父类的情况)

    1,所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中。

    2,每个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本身对象的a,对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中)b,成员变量的列表。c,属性列表。类对象内也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有哦一个superclass的指针,指向他的父类对象

    每个Objective-C对象都有相同的结构 :如下图:

    3,根对象就是NSObject,他的superclass指针指向nil

    4,类对象既然称为对象,那它也是一个实例,类对象中也有一个isa指针指向他的元类(meta class),即类对象是元类的实例,元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类。


    7.objc中的类方法和实例方法有什么本质区别和联系

    类方法:

    1,类方法是属于类对象的 2,类方法只能通过类对象调用 3,类方法中的self是类对象 4,类方法中不能访问成员变量 5,类方法可以调用其他的类方法 6,类方法中不能直接调用对象方法

    实例方法:

    1,实例方法是属于实例对象的 2,实例方法只能通过实例对象调用,3,实例方法中的self是实例对象 4,实例方法中可以访问成员变量 5,实例方法中直接调用实例方法6,实例方法中可以直接调用类方法(通过类名)


    8.一个objc对象的isa的指针指向什么?有什么作用?

    Paste_Image.png

    isa 指的就是 是个什么,对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。isa帮助一个对象找到它的方法。

    isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。


    9. runtime怎么添加属性、方法等

    ivar表示成员变量

    class_addIvar

    class_addMethod

    class_addProperty

    class_addProtocol

    class_replaceProperty


    10.runtime 如何实现 weak 属性

    首先要搞清楚weak属性的特点

    weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)

    那么runtime如何实现weak变量的自动置nil?

    runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。

    weak属性需要在dealloc中置nil么

    在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理

    即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil

    在属性所指的对象遭到摧毁时,属性值也会清空

    objc// 模拟下weak的setter方法,大致如下

    - (void)setObject:(NSObject *)object{

    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);

    [object cyl_runAtDealloc:^{ _object = nil; }];

    }


    11.runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

    每一个类对象中都一个对象方法列表(对象方法缓存)

    类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)

    方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

    当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找

    当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找


    12. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

    无论在MRC下还是ARC下均不需要被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放

    补充:对象的内存销毁时间表,分四个步骤

    >1、调用 -release :引用计数变为零

    * 对象正在被销毁,生命周期即将结束.

    * 不能再有新的 __weak弱引用,否则将指向nil.

    * 调用 [selfdealloc]

    >2、 父类调用 -dealloc

    * 继承关系中最直接继承的父类再调用 -dealloc

    * 如果是 MRC 代码 则会手动释放实例变量们(iVars)

    * 继承关系中每一层的父类 都再调用 -dealloc

    >3、NSObject调 -dealloc

    * 只做一件事:调用 Objective-C runtime 中object_dispose() 方法

    >4.调用 object_dispose()

    * 为 C++ 的实例变量们(iVars)调用 destructors

    * 为 ARC 状态下的 实例变量们(iVars) 调用 -release

    * 解除所有使用 runtime Associate方法关联的对象

    * 解除所有 __weak引用

    * 调用 free()


    13. _objc_msgForward函数是做什么的?直接调用它将会发生什么?

    _objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发

    直接调用_objc_msgForward是非常危险

    的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事

    JSPatch就是直接调用_objc_msgForward来实现其核心功能的

    14.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

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

    能向运行时创建的类中添加实例变量;

    分析如下:

    因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量

    运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。


    15. 简述下Objective-C中调用方法的过程(runtime)

    Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:

    * objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类

    * 然后在该类中的方法列表以及其父类方法列表中寻找方法运行

    * 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX

    * 但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会,这三次拯救程序奔溃的说明见问题《什么时候会报unrecognized selector的异常》中的说明

    补充说明:Runtime 铸就了Objective-C 是动态语言的特性,使得C语言具备了面向对象的特性,在程序运行期创建,检查,修改类、对象及其对应的方法,这些操作都可以使用runtime中的对应方法实现。


    16.什么是method swizzling(俗称黑魔法)

    简单说就是进行方法交换

    在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的

    每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP

    交换方法的几种实现方式

    利用 method_exchangeImplementations 交换两个方法的实现

    利用 class_replaceMethod 替换方法的实现

    利用 method_setImplementation 来直接设置某个方法的IMP


    17.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

    Associate政策其实是一组枚举值:

    enum{ 

    OBJC_ASSOCIATION_ASSIGN  =0, 

    OBJC_ASSOCIATION_RETAIN_NONATOMIC  =1, 

    OBJC_ASSOCIATION_COPY_NONATOMIC  =3, 

    OBJC_ASSOCIATION_RETAIN  =01401,

    OBJC_ASSOCIATION_COPY  =01403};

    无论在MRC下还是ARC下均不需要在主对象dealloc的时候释放,关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被NSObject -dealloc调用的object_dispose()方法中释放

    补充:对象的内存销毁时间表,分四个步骤

    1、调用 -release :引用计数变为零

    * 对象正在被销毁,生命周期即将结束.

    * 不能再有新的 __weak弱引用,否则将指向nil.

    * 调用 [selfdealloc]

    2、 父类调用 -dealloc

    * 继承关系中最直接继承的父类再调用 -dealloc

    * 如果是 MRC 代码 则会手动释放实例变量们(iVars)

    * 继承关系中每一层的父类 都再调用

    -dealloc

    3、NSObject调 -dealloc

    * 只做一件事:调用 Objective-C runtime 中object_dispose() 方法

    4.调用 object_dispose()* 为 C++ 的实例变量们(iVars)调用 destructors

    * 为 ARC 状态下的 实例变量们(iVars) 调用 -release

    * 解除所有使用 runtime Associate方法关联的对象

    * 解除所有 __weak引用 * 调用 free()


    18. Protocol、Category中声明属性

    iOS中协议中和分类中是可以用@property形式声明属性的,只不过在协议、分类中声明的属性,只有对应的setter/getter方法,并没有生成对应的成员变量。因为协议中只可以声明方法,分类中只能声明方法和对应的实现。

    那么如何在协议中用@property声明属性,然后在实现协议的类中可以用成员变量直接访问属性的值?

    Protocol:

    @protocol MyProtocol

    @property(nonatomic,strong)NSString*myImage;

    @end

    实现类:

    @interfaceViewController:UIViewController

    @end

    @implementationViewController

    @synthesizemyImage = _myImage;

    - (void)viewDidLoad {   

    [superviewDidLoad];

    self.myImage =@"my string";

    NSLog(@"%@,%@",_myImage,self.myImage

    );

    @end

    上面方法中主要用到了@synthesize上面声明部分的 @synthesize myImage = _myImage; 意思是说,myImage 属性为 _myImage 成员变量合成访问器方法。  也就是说,myImage属性生成存取方法是setMyImage,这个setMyImage方法就是_myImage变量的存取方法,它操作的就是_myImage这个变量。通过这个看似是赋值的这样一个操作,我们可以在@synthesize 中定义与变量名不相同的getter和setter的命名,籍此来保护变量不会被不恰当的访问。比如完全可以写成:

    @interfaceViewController:UIViewController

    @end

    @implementationViewController

    @synthesizemyImage = _helloWorld;//set方法的值保存在_helloWorld中

    - (void)viewDidLoad { 

      [superviewDidLoad];self.myImage =@"my string";

    NSLog(@"%@,%@", _helloWorld,self.myImage);//self.myImage 取的就是成员变量_helloWorld的值;

    @end

    根据上面的结论那么Category中能否一样使用 @synthesize来保存成员变量的值呢?

    只可惜Category中的implementation中不支持用@synthesize 来合成属性,正确的做法可以这样:

    Category:

    @interfaceNSObject(Extension)

    @property(nonatomic,strong)NSString*myTitle;

    @end

    #import

    @implementationNSObject(Extension)

    - (NSString*)myTitle {

    returnobjc_getAssociatedObject(self,@selector(myTitle));

    }

    - (void)setMyTitle:(NSString*)myTitle { 

    objc_setAssociatedObject(self,@selector(myTitle), myTitle,OBJC_ASSOCIATION_RETAIN);

    }

    这样在实现类中同样可以使用.xx获取存取的值:

    @interfaceViewController:UIViewController

    @end

    #import"NSObject+Extension.h"

    @implementationViewController

    - (void)viewDidLoad {   

    [superviewDidLoad];

    NSObject*myObj = [[NSObjectalloc] init]; 

    myObj.myTitle =@"my title";

    NSLog(@"%@",myObj.myTitle);

    @end

    补充:@dynamic 和 @synthesize的区别:

    在@implementation 中通过@dynamic xxx 告诉编译器、xxx属性的setter、getter方法由开发者自己来生成

    @ synthesize xxx = _xxx;  告诉编译器、xxx属性的setter、getter方法由编译器来生成、同时用_xxx 来合成 成员变量

    相关文章

      网友评论

          本文标题:iOS 面试题总结

          本文链接:https://www.haomeiwen.com/subject/yumbcftx.html