iOS面试iOS面试题

iOS面试 -- Objective-C

2020-01-17  本文已影响0人  唐师兄

前言

话说 iOS 面试怎么离得开 Objective-C 这苹果公司使用的划时代的语言呢?我贴出维基百科的一段objC的介绍吧。
Objective-C最大的特色是承自Smalltalk的讯息传递模型(message passing),此机制与今日C++式之主流风格差异甚大。Objective-C里,与其说物件互相呼叫方法,不如说物件之间互相传递讯息更为精确。此二种风格的主要差异在于呼叫方法/讯息传递这个动作。C++里类别方法的关系严格清楚,一个方法必定属于一个类别,而且在编译时(compile time)就已经紧密绑定,不可能呼叫一个不存在类别里的方法。但在Objective-C,类别讯息的关系比较松散,呼叫方法视为对物件发送讯息,所有方法都被视为对讯息的回应。所有讯息处理直到执行时(runtime)才会动态决定,并交由类别自行决定如何处理收到的讯息。也就是说,一个类别不保证一定会回应收到的讯息,如果类别收到了一个无法处理的讯息,程式只会抛出异常,不会出错或崩溃。

链接 https://zh.wikipedia.org/zh-hans/Objective-C

博客的目的是为了面试嘛,所以下面咱们看看Objective-C (下文简写为OC)到底如何面试,我们该掌握啥?看看下面这张图,OC 面试主要就是围绕着分类、关联对象、拓展、代理、通知、KVO、KVC以及属性关键字这几个方面,其中需要重点掌握分类实现的原理,关联对象的底层实现机制、通知以及 KVO、KVC的底层实现原理和属性关键字的作用,拓展和代理简单的看看即可。

1

分类

1. 分类结构体

struct_category_t{
  //类名
  constchar *name;
  //分类所属的类
  struct_class_t *cls;
  //category中所有给类添加的实例方法的列表(instanceMethods)
  conststruct_method_list_t *instance_methods;
  //category中所有添加的类方法的列表(classMethods)
  conststruct_method_list_t *class_methods;
  //category实现的所有协议的列表(protocols)
  conststruct_protocol_list_t*protocols;
  //category中添加的所有属性(instanceProperties)
  conststruct_prop_list_t*properties;
};

由上可得,分类在编译过程中,会生成 类方法列表、实例方法列表、属性列表 等,但是却 没有 实例变量列表(_ivar_list_t),可对比分类所属类的编译结果看,分类所属类是存在实例变量列表的。然后,再来对比 实例方法列表 ,还能发现分类的实例方法列表中,并未对分类属性生成 getter/setter 方法。

2. 分类加载调用栈

//runtime的初始化函数,进行初始化操作,注册了镜像状态改变时的回调函数
_objc_init   
//加锁并调用 map_images_nolock
└── map_2_images 
//完成所有 class 的注册、fixup等工作,还有初始化自动释放池、初始化 side table 等工作并在函数后端调用了 
    └── map_images_nolock _read_images
//加载类、Protocol、Category,加载分类的代码就写在 _read_images 函数的尾部
        └── _read_images 

_objc_init 函数在 objc-os.mm 中,_read_images 方法在objc-runtime-new.mm 中。

加载过程

3. 你用分类干了啥?

4. 分类和拓展的区别?

1. 分类

关联对象以及关联对象底层实现机制

1. 为分类添加成员变量代码实现

-(void)setName:(NSString *)name {
// 设置 value,通过 key 和 value 建立映射,通过Policy策略将映射关系关联到设置的对象上
    objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSString *)name {
// 根据指定的 key,到 obj 中找到与当前 key 相对应的关联值
    return objc_getAssociatedObject(self, @"name");    
}
问题 1:使用关联对象为分类添加成员变量,这个成员变量添加到了哪里呢?

为分类添加的实例变量是没有添加到原宿主类中,而分类的结构体中也没有接收成员变量的列表,所以也是没有添加到分类中。关联对象是由AssociationsManager管理并在 AssociationsHashMap中存储,所有类的关联内容都在同一个全局容器中

关联对象的本质

关联对象本质
其中 ObjcAssociation 中有两个成员结构,一个是成员变量的关联策略 Policy,一个是我们要关联对象的值(Value);然后进行进一步的封装,数据结构是 ObjectAssociationMapObjectAssociationMap中有成员变量Value就是ObjcAssociation,key 往往就是分类中的某一个属性或者选择器的方法名 @selector(text),从而让ObjcAssociation和 key 建立映射;最后我们将ObjectAssociationMap放到上面提到的AssociationsHashMap全局容器中,主要是根据当前对象被关联的指针值DISGUISE(obj)建立和ObjectAssociationMap的映射,从而实现将一个值关联到某一个对象上。
源码解析

我们看到其实内部调用的是_object_set_associative_reference函数,我们来到_object_set_associative_reference函数中



_object_set_associative_reference函数内部我们可以全部找到我们上面说过的实现关联对象技术的核心对象。接下来我们来一个一个看其内部实现原理探寻他们之间的关系。
AssociationsManager 就是关联对象的管理类,是C++实现的一个类
AssociationsHashMap 关联对象管理类维护的一个 hashmap,可以理解为一个字典,是一个全局的容器。上述代码大概意思是:

参考了下面这个博客,建议大家看这个,我感觉我写的有点儿迷
https://www.jianshu.com/p/0f9b990e8b0a

代理

通知(NSNotification)

通知是使用\color{red}{观察者模式用于跨层传递消息的机制}

1. 如何实现通知机制?(NS开头的代码未开源)

在通知中心维护一个 NotificationMap,然后在 NotificationMap 中有一个key通知名NotificationName 和一个value观察者的列表Observers_list,因为通知是一对多,所以这里用列表来管理观察者,列中的观察者接收到通知也会有对应的执行方法,这样就简单的设计出通知的实现机制了,如下图 通知实现流程

2. 通知是同步的还是异步的?

具体情况具体分析,一般我们所使用的 NSNOtification 都是同步的,就是说 post 消息发出时,接收者接收且执行了相关的动作时,才会进行后续操作,如果涉及到NSNOtificationQueue时,则是异步的

3. 通知和代理的区别?

KVO

1. KVO的实现原理

如下图所示

当我们注册一个观察者的时候,也就是调用系统方法 [addObserver:forKeypath]时,以上图为例,当注册观察者观察 A这一对象的某一个属性的值的变化时,系统会在运行时动态的创建一个NSKVONotification_A,并且将 A的指针指向NSKVONotification_A,将 isa pointer进行修改,实际上就是 isa 混写技术的一个标志,NSKVONotification_A是 A 的一个子类,然后由NSKVONotification_A重写 setter 方法并且通知观察者 A 的变化,从而实现 KVO

2. 为什么通过 KVC 设置 value 也可以使 KVO 生效呢?

通过成员变量直接赋值是否能使 KVO 生效呢?手动 KVO 是什么样的呢?

KVC

valueForKey 系统调用流程

KVC valueForKey

系统通过 Key 去访问当前实例变量是否存在 getter 方法,如果存在则调用,然后结束,如果不存在,则先判断实例变量是否存在,如果实例变量存在,直接获取实例变量的值,结束调用,如果不存在,系统就会调用 [valueForUndinedKey:]最后抛出异常

Accessor Method

访问器方法是否存在的判断规则
依次匹配这些样式:_<key>, _is<Key>, <key>, is<Key>。如果找到匹配的变量,且变量是对象型,那么对象引用计数会增1且变量也会被赋值,之后原来变量指向的旧值引用计数会减1

setValue:ForKey 系统调用流程

如下图,同 valueForKey


setValue:forKey 流程

属性关键字

属性关键字分类

atomic(系统默认) noatomic 的区别

引用计数

关键字 assign 和 weak 的区别

assign

weak

weak修饰的对象为什么在被废弃后会被置为nil ?(内存管理)

copy

当一个可变数组对象在声明时用 copy 修饰,那么在使用该数组对象时,会发生什么?为什么?

浅拷贝

浅拷贝是对内存地址的复制,让目标对象指针和源对象指针指向同一片内存空间

深拷贝

深拷贝让目标对象指针和源对象指针指向两块内容相同的内存空间

深拷贝和浅拷贝的区别?
copy 总结

OC 笔试题

MRC 下如何重写 retain 修饰变量的 setter 方法?

- (void)setObj:(id)obj {
// 为啥这里要做一个判断?
// 如果不做判断,就会有这样的问题,当传递进来的对象恰好是原来的对象时,那么我们会对原来的对象进行 release 操作,有可能我们传进来的 obj 对象被我们提前释放了,那么我们的 obj 对象就已经是 nil 了,如果对一个 nil 对象再次 retain,则会造成程序的 crash,所以这里要加一个判断
    if (_obj != obj) {  
        [_obj release];
        _obj = [obj retain];
    }
}

请简述分类的实现原理?

分类是在运行时决议,不同分类当中,含有同名的方法,谁最终生效,取决于谁最后参与编译,最后参与的分类中的方法最终生效;如果分类中含有和宿主类同名的方法,那么最终宿主类方法会被分类方法覆盖,是由于消息在传递的过程中,会优先查找数组靠前的元素,如果找到同名方法就会被调用

KVO的实现原理?

能否为分类添加实例变量?

最后看个小姐姐养养眼吧
上一篇 下一篇

猜你喜欢

热点阅读