为了让大家能相互交流和分享ios面试题,精彩的demo,最经典的资料,群号:776296806!
QQ图片20180531173835.jpg
题目:
1 讲一下你对iOS
内存管理的理解
2 KVO
实现原理
3 观察者模式
4如果让你实现NSNotificationCenter
,讲一下思路
5 如果让你实现 GCD
的线程池
,讲一下思路
6Category
的实现原理,以及 Category
为什么只能加方法不能加实例变量。
7swift
中struct
和class
的区别
8在一个HTTPS
连接的网站里,输入账号密码
点击登录
后,到服务器
返回这个请求前,中间经历了什么
一. 讲一下你对 iOS 内存管理的理解
在Objective-C
的内存管理中,其实就是引用计数(reference count)
的管理。内存管理
就是在程序需要时程序员
分配一段内存空间
,而当使用完之后将它释放。如果程序员
对内存资源
使用不当,有时不仅会造成内存资源
浪费,甚至会导致程序crach
。
1. 引用计数(Reference Count)
为了解释引用计数,我们做一个类比:员工在办公室使用灯的情景。
image.png1.当第一个人进入办公室时,他需要使用灯,于是开灯,引用计数为1
2.当另一个人进入办公室时,他也需要灯,引用计数为2;每当多一个人进入办公室时,引用计数加1
3.当有一个人离开办公室时,引用计数减1,当引用计数为0时,也就是最后一个人离开办公室时,他不再需要使用灯,关灯离开办公室。
2. 内存管理规则
从上面员工在办公室使用灯的例子,我们对比一下灯的动作与Objective-C
对象的动作有什么相似之处:
因为我们是通过
引用计数
来管理灯,那么我们也可以通过引用计数
来管理使用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
对象
4. ARC管理方法
iOS/OS X
内存管理方法有两种:手动引用计数(Manual Reference Counting)
和自动引用计数(Automatic Reference Counting)
。
自动引用计数(Automatic Reference Counting)
简单来说,它让编译器
来代替程序员来自动加入retain
和release
方法来持有
和放弃对象
的所有权
。
在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
: 也会被调用。
三.观察者模式
观察者模式(Observer Pattern)
:定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
在iOS
中典型的观察者模式是:NSNotificationCenter
和KVO
。
1. NSNotificationCenter
image.png-
观察者Observer
,通过NSNotificationCenter
的addObserver: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.png1.客户端打包请求。包括
url
,端口,你的账号密码等等。账号密码登陆应该用的是Post
方式,所以相关的用户信息会被加载到body
里面。这个请求应该包含三个方面:网络地址,协议,资源路径。注意,这里是HTTPS
,就是HTTP + SSL / TLS
,在HTTP
上又加了一层处理加密信息的模块(相当于是个锁)。这个过程相当于是客户端请求钥匙。
2.服务器接受请求。一般客户端的请求会先发送到DNS
服务器。 DNS
服务器负责将你的网络地址解析成IP地址,这个IP地址对应网上一台机器。这其中可能发生Hosts Hijack
和ISP failure
的问题。过了DNS
这一关,信息就到了服务器端,此时客户端会和服务器的端口之间建立一个socket
连接,socket
一般都是以file descriptor
的方式解析请求。这个过程相当于是服务器端分析是否要向客户端发送钥匙模板。
3.服务器端返回数字证书。服务器端会有一套数字证书(相当于是个钥匙模板),这个证书会先发送给客户端。这个过程相当于是服务器端向客户端发送钥匙模板。
4.客户端生成加密信息。根据收到的数字证书(钥匙模板),客户端会生成钥匙,并把内容锁上,此时信息已经加密。这个过程相当于客户端生成钥匙并锁上请求。
5.客户端发送加密信息。服务器端会收到由自己发送出去的数字证书加锁的信息。 这个时候生成的钥匙也一并被发送到服务器端。这个过程是相当于客户端发送请求。
6.服务器端解锁加密信息。服务器端收到加密信息后,会根据得到的钥匙进行解密,并把要返回的数据进行对称加密。这个过程相当于服务器端`解锁请求、生成、加锁回应信息。
7.服务器端向客户端返回信息。客户端会收到相应的加密信息。这个过程相当于服务器端向客户端发送回应。
8.客户端解锁返回信息。客户端会用刚刚生成的钥匙进行解密,将内容显示在浏览器上。
网友评论