IOS常见8道面试题

作者: 冬天里寒风起 | 来源:发表于2018-06-21 15:12 被阅读402次

    为了让大家能相互交流和分享ios面试题,精彩的demo,最经典的资料,群号:776296806!


    QQ图片20180531173835.jpg

    题目:

    1 讲一下你对iOS内存管理的理解
    2 KVO实现原理
    3 观察者模式
    4如果让你实现NSNotificationCenter,讲一下思路
    5 如果让你实现 GCD线程池,讲一下思路
    6Category的实现原理,以及 Category 为什么只能加方法不能加实例变量。
    7swiftstructclass的区别
    8在一个HTTPS连接的网站里,输入账号密码点击登录后,到服务器返回这个请求前,中间经历了什么

    一. 讲一下你对 iOS 内存管理的理解

    Objective-C的内存管理中,其实就是引用计数(reference count)的管理。内存管理就是在程序需要时程序员分配一段内存空间,而当使用完之后将它释放。如果程序员内存资源使用不当,有时不仅会造成内存资源浪费,甚至会导致程序crach

    image.png

    1. 引用计数(Reference Count)

    为了解释引用计数,我们做一个类比:员工在办公室使用灯的情景。

    image.png

    1.当第一个人进入办公室时,他需要使用灯,于是开灯,引用计数为1

    2.当另一个人进入办公室时,他也需要灯,引用计数为2;每当多一个人进入办公室时,引用计数加1

    3.当有一个人离开办公室时,引用计数减1,当引用计数为0时,也就是最后一个人离开办公室时,他不再需要使用灯,关灯离开办公室。

    2. 内存管理规则

    从上面员工在办公室使用灯的例子,我们对比一下灯的动作与Objective-C对象的动作有什么相似之处:

    image.png
    因为我们是通过引用计数来管理灯,那么我们也可以通过引用计数来管理使用Objective-C对象。
    image.png
    Objective-C对象的动作对应有哪些方法以及这些方法对引用计数有什么影响? image.png
    当你alloc一个对象objc,此时RC=1;在某个地方你又retain这个对象objc,此时RC加1,也就是RC=2;由于调用alloc/retain一次,对应需要调用release一次来释放对象objc,所以你需要release对象objc两次,此时RC=0;而当RC=0时,系统会自动调用dealloc方法释放对象

    3. Autorelease Pool

    在开发中,我们常常都会使用到局部变量局部变量一个特点就是当它超过作用域时,就会自动释放。而autorelease pool局部变量类似,当执行代码超过autorelease pool块时,所有放在autorelease pool的对象都会自动调用release。它的工作原理如下:

    创建一个NSAutoreleasePool对象

    autorelease pool块的对象调用autorelease方法

    释放NSAutoreleasePool对象

    image.png

    4. ARC管理方法

    iOS/OS X内存管理方法有两种:手动引用计数(Manual Reference Counting)自动引用计数(Automatic Reference Counting)

    自动引用计数(Automatic Reference Counting)简单来说,它让编译器来代替程序员来自动加入retainrelease方法来持有放弃对象所有权

    ARC内存管理机制中,id其他对象类型变量必须是以下四个ownership qualifiers其中一个来修饰:
    所以在管理Objective-C对象内存的时候,你必须选择其中一个,下面会用一些列子来逐个解释它们的含义以及如何选择它们。

    __strong:被它修饰的变量持有对象的所有权(默认,如果不指定其他,编译器就默认加入)
    
    __weak: 被它修饰的变量都不持有对象的所有权,而且当变量指向的对象的RC为0时,变量设置为nil。
    
    __unsafe_unretained:被它修饰的变量都不持有对象的所有权,但当变量指向的对象的RC为0时,变量并不设置为nil,而是继续保存对象的地址;这样的话,对象有可能已经释放,但继续访问,就会造成非法访问(Invalid Access)。
    
    __autoreleasing:相比之前的创建、使用和释放NSAutoreleasePool对象,现在你只需要将代码放在@autoreleasepool块即可。你也不需要调用autorelease方法了,只需要用__autoreleasing修饰变量即可。
    
    

    5.Property(属性)

    image.png

    二. KVO实现原理

    KVO基本原理:

    1.KVO是基于runtime机制实现的

    2.当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制

    3.如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

    4.每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法

    5.键值观察通知依赖于NSObject的两个方法: willChangeValueForKey: 和didChangevlueForKey:;在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

    image.png

    三.观察者模式

    观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

    iOS中典型的观察者模式是:NSNotificationCenterKVO

    1. NSNotificationCenter
    image.png
    • 观察者Observer,通过NSNotificationCenteraddObserver:selector:name:object接口来注册对某一类型通知感兴趣。在注册时候一定要注意,NSNotificationCenter会对观察者进行引用计数+1的操作,我们在程序中释放观察者的时候,一定要去从center中将其移除。

    • 通知中心NSNotificationCenter,通知的枢纽。

    • 被观察的对象,通过postNotificationName:object:userInfo:发送某一类型通知,广播改变。

    • 通知对象NSNotification,当有通知来的时候,Center会调用观察者注册的接口来广播通知,同时传递存储着更改内容的NSNotification对象。

    2. KVO

    KVO的全称是Key-Value Observer,即键值观察。是一种没有中心枢纽的观察者模式的实现方式。一个主题对象管理所有依赖于它的观察者对象,并且在自身状态发生改变的时候主动通知观察者对象。

    • 注册观察者
    [object addObserver:self forKeyPath:property options:NSKeyValueObservingOptionNew context:]。
    
    • 更改主题对象属性的值,即触发发送更改的通知。

    • 在制定的回调函数中,处理收到的更改通知。

    • 注销观察者[object removeObserver:self forKeyPath:property]

    四. 如果让你实现 NSNotificationCenter,讲一下思路

    • NSNotificationCenter是一个单例

    • NSNotificationCenter内部使用可变字典NSMutableDictionary来存储,以通知名称postName作为key,以数组NSAray作为值,该数组存储着每个观察者的信息:观察者对象、观察者处理方法、通知名称等。

    • 当发送通知时,以通知名称为key去获取相应的观察者信息数组,然后遍历这个数组,取出观察者对象和相对应处理方法,进行实例方法调用。

    五. 如果让你实现 GCD 的线程池,讲一下思路

    • 线程池包含如下8个部分:

    • 线程池管理器(ThreadPoolManager):用于创建并管理线程池,是一个单例

    • 工作线程(WorkThread): 线程池中线程

    • 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。

    • 任务队列:用于存放没有处理的任务。提供一种缓冲机制。

    • corePoolSize核心池的大小:默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

    • maximumPoolSize线程池最大线程数:它表示在线程池中最多能创建多少个线程;

    • 存活时间keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,这是如果一个线程空闲的时间达到keepAliveTime,则会终止直到线程池中的线程数不大于corePoolSize.

    具体流程:

    • 当通过任务接口向线程池管理器中添加任务时,如果当前线程池管理器中的线程数目小于corePoolSize,则每来一个任务,就会通过线程池管理器创建一个线程去执行这个任务;

    • 如果当前线程池中的线程数目大于等于corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;

    • 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

    • 如果线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize

    六.Category 的实现原理,以及Category 为什么只能加方法不能加实例变量。

    category是可以添加属性,不能添加实例变量对吧!之所以不能添加实例变量,是因为一个类的实例变量在编译阶段,就会在objc_class的class_ro_t这里进行存储和布局,而category是在运行时才进行加载的,

    然后在加载 ObjC运行时的过程中在 realizeClass 方法中:

    // 从 `class_data_bits_t `调用 `data` 方法,将结果从 `class_rw_t `强制转换为 `class_ro_t `指针
    const class_ro_t *ro = (const class_ro_t *)cls->data();
    // 初始化一个 `class_rw_t` 结构体
    class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    // 设置`结构体 ro` 的值以及 `flag`
    rw->ro = ro;
    // 最后设置正确的` data`。
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);
    

    运行时加载的时候 class_ro_t里面的方法、协议、属性等内容赋值给 class_rw_t,而 class_rw_t里面没有用来存储相关变量的数组,这样的结构也就注定实例变量是无法在运行期进行填充.

    七. swift 中 struct和class的区别

    swift中, class是引用类型, struct是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。

    class有这几个功能 struct没有的:

    • class可以继承,这样子类可以使用父类的特性和方法

    • 类型转换可以在 runtime的时候检查和解释一个实例的类型

    • 可以用 deinit来释放资源

    • 一个类可以被多次引用

      struct也有这样几个优势:

    • 结构较小,适用于复制操作,相比于一个 class的实例被多次引用更加安全。

    • 无须担心内存 memory leak或者多线程冲突问题

    八.在一个HTTPS连接的网站里,输入账号密码点击登录后,到服务器返回这个请求前,中间经历了什么

    image.png
    1.客户端打包请求。包括url,端口,你的账号密码等等。账号密码登陆应该用的是Post方式,所以相关的用户信息会被加载到body里面。这个请求应该包含三个方面:网络地址,协议,资源路径。注意,这里是HTTPS,就是HTTP + SSL / TLS,在HTTP上又加了一层处理加密信息的模块(相当于是个锁)。这个过程相当于是客户端请求钥匙。

    2.服务器接受请求。一般客户端的请求会先发送到DNS服务器。 DNS服务器负责将你的网络地址解析成IP地址,这个IP地址对应网上一台机器。这其中可能发生Hosts HijackISP failure的问题。过了DNS这一关,信息就到了服务器端,此时客户端会和服务器的端口之间建立一个socket连接,socket一般都是以file descriptor的方式解析请求。这个过程相当于是服务器端分析是否要向客户端发送钥匙模板。

    3.服务器端返回数字证书。服务器端会有一套数字证书(相当于是个钥匙模板),这个证书会先发送给客户端。这个过程相当于是服务器端向客户端发送钥匙模板。

    4.客户端生成加密信息。根据收到的数字证书(钥匙模板),客户端会生成钥匙,并把内容锁上,此时信息已经加密。这个过程相当于客户端生成钥匙并锁上请求。

    5.客户端发送加密信息。服务器端会收到由自己发送出去的数字证书加锁的信息。 这个时候生成的钥匙也一并被发送到服务器端。这个过程是相当于客户端发送请求。

    6.服务器端解锁加密信息。服务器端收到加密信息后,会根据得到的钥匙进行解密,并把要返回的数据进行对称加密。这个过程相当于服务器端`解锁请求、生成、加锁回应信息。

    7.服务器端向客户端返回信息。客户端会收到相应的加密信息。这个过程相当于服务器端向客户端发送回应。

    8.客户端解锁返回信息。客户端会用刚刚生成的钥匙进行解密,将内容显示在浏览器上。

    相关文章

      网友评论

        本文标题:IOS常见8道面试题

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