丁香园iOS电话面试问题总结

5,516 阅读18分钟

今天下午去面试去面试一家初创公司,然后又接到到了丁香园的电话面试,这篇blog记录一下面试的一些问题,有的回答的还行,有点感觉不太好,主要是有些英文单词说的太low了估计被鄙视了吧,下面给大家总结一下面试的一些问题,有些回答是摘要一些大神blog的出处,都有给原链接,希望见谅~~

简单讲解一下http请求,以及GET POST的区别

这个问得其实不是很难,主要看你了不了解了,我因为了解一些后台的东西,所以回答的还行,下面我给大家看两幅图片大家就基本了解了:

  • 请求Request的原数据

  • 返回Resonse的原数据

总结一下,其实http请求就是发送和接受报文,报文的具体格式就如上图所示,具体由三部分构成,GET 和 POST比较明显的不同就是请求方式和参数的位置不同,其他原理的不同大家可以去下面的链接去看:

  • 请求的方法URL协议/版本
  • 请求头(Request Header)
  • 请求正文(Content)

其中请求头里面可以放很多参数,比如报文的大小啊,啥的一些参数,具体可以百度,这里就不展开了。

下面给两个大神们推荐的链接,大家可以自行查看:

https的加密方式和几次握手

这个问题回答的就比较菜了,程序比较复制,当时特意记了一下,没想到面试的时候还是有点蒙,回答的非常菜,现在在普及一下,下面是一篇讲的很详细的blog地址,大家可以去原地址去看,我下面也简单总结一下:

https的加密方式

  • 对称加密

​ 对称加密是指加密和解密使用相同密钥的加密算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信至关重要。

对称加密算法的优、缺点:

优点:算法公开、计算量小、加密速度快、加密效率高。

缺点:
1)交易双方都使用同样钥匙,安全性得不到保证;

2)每对用户每次使用对称加密算法时,都需要使用其他人不知道的惟一钥匙,这会使得发收信双方所拥有的钥匙数量呈几何级数增长,密钥管理成为用户的负担。

3)能提供机密性,但是不能提供验证和不可否认性。

  • 非对称加密

​ 这种加密或许理解起来比较困难,这种加密指的是可以生成公钥和私钥。凡是公钥加密的数据,公钥自身不能解密,而需要私钥才能解密;凡是私钥加密的数据,私钥不能解密,需要公钥才能解密。这种算法事实上有很多,常用的是RSA,其基于的数学原理是两个大素数的乘积很容易算,而拿到这个乘积去算出是哪两个素数相乘就很复杂了,具体原理有兴趣可以自行研究。

非对称加密相比对称加密更加安全,但也存在两个明显缺点:

​ 1)CPU计算资源消耗非常大。一次完全TLS握手,密钥交换时的非对称解密计算量占整个握手过程的90%以上。而对称加密的计算量只相当于非对称加密的0.1%,如果应用层数据也使用非对称加解密,性能开销太大,无法承受。

​ 2)非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是2048位,意味着待加密内容不能超过256个字节。

所以公钥加密目前只能用来作密钥交换或者内容签名,不适合用来做应用层传输内容的加解密。

加密的详细过程

​ 首先服务器端用非对称加密(RSA)产生公钥和私钥。然后把公钥发给客 户端,路径或许有人会截取,但是没有用,因为用公钥加密的文件只有私钥可以解密,而私钥永远都不会离开服务器的。当公钥到达客户端之后,客户端会用对称加密产生一个秘钥并且用公钥来加密发送给服务器端,这个秘钥就是以后用来通信的钥匙。这样服务器端收到公钥加密的秘钥时就可以用私钥来解公钥从而获得秘钥。这样的话客户端和服务器端都获得了秘钥,信息交流相对是安全的。流程图如下:

​ 听起来确实是挺安全的,但实际上,还有一种更恶劣的攻击是这种方法无 法防范的,这就是传说中的“中间人攻击”。在身份认证的过程中,出现了一个“中间人”拦截我们的信息,他有意想要知道你们的消息。我们将这个中间人称为M。当服务器第一次给客户端发送公钥的时候,途径M。M知道你要进行密钥交换了,它把公钥扣了下来,假装自己是客户端,伪造了一个伪秘钥(对称加密产生的),然后用服务器发来的公钥加密了伪秘钥发还给服务器,这样服务器以为和客户端完成了密钥交换,实际上服务器是和M完成了密钥交换(获得了伪秘钥)。同时M假扮成服务器自行用非对称加密产生伪公钥和伪私钥,与客户端进行秘钥交换,拿到客户端发送过来的秘钥。现在客户端拿着秘钥,M拿着秘钥和为伪秘钥,服务器拿着伪秘钥,整个交流的过程就是:

还有很多大家直接去大神的blog去看吧,写的很详细,我这就点到为止了~~

在不知道二进制文件格式的情况下如何区分文件

当听到这么问题的时候还是有点仓促的,隐约记得是通过二进制的头部的标识来区分的,当时也不太确定就含糊的回答了一下,说是通过二进制文件的头部标识来区分的,看面试官的样子不是很满意,回答百度学习一波,百度结果如下所示,附带原链接

可以通过二进制头识别文件类型,可以使用UE或者WinHex软件打开:

1). JPEG/JPG

  • 文件头标识 (2 bytes): $ff, $d8 (SOI) (JPEG文件标识)
  • 文件结束标识 (2 bytes): $ff, $d9 (EOI)

2). TGA

  • 未压缩的前5字节 00 00 02 0000
  • RLE压缩的前5字节 00 00 10 0000

3). PNG

  • 文件头标识 (8 bytes) 89 50 4E 470D 0A 1A 0A

4). GIF

  • 文件头标识 (6 bytes) 47 49 46 3839(37) 61
    ​ G I F 8 9 (7) a

5). BMP

  • 文件头标识 (2 bytes) 424D
    ​ B M

6). PCX

  • 文件头标识 (1 bytes) 0A

7). TIFF

  • 文件头标识 (2 bytes) 4D 4D 或 4949

8). ICO

  • 文件头标识 (8 bytes) 00 00 01 0001 00 20 20

9). CUR

  • 文件头标识 (8 bytes) 00 00 02 0001 00 20 20

10). IFF

  • 文件头标识 (4 bytes) 46 4F 524D
    ​ F O R M

11). ANI

  • 文件头标识 (4 bytes) 52 49 4646
    ​ R I F F

以上是一些文件的区别方式,回答的总方向还是对的,可能回答的不够好,下次就知道了。

常见的几种线程锁

这个问题比较尴尬,因为英文不太好,加上平时用的也不多,回答的比较吞吞吐吐,就说了NSLock@synchronizeddispatch的semaphore,其中有些单词的读法还不太准,想想就很尴尬,下面大概总结一下,有一下几种:

  • NSLock
  • @synchronized
  • dispatch的semaphore
  • 条件锁NSCondition
  • 条件锁NSConditionLock
  • NSDistributedLock
  • 互斥锁POSIX
  • 自旋锁OSSpinLock

下面总结一下,说实话太多有点记不过来了 - . -,附带详细的原文地址

各种线程锁 使用场景和简单介绍
@synchronized 适用线程不多,任务量不大的多线程加锁
NSLock 比较常用的一种锁,性能一般
dispatch_semaphore_t 使用信号来做加锁,性能很好
NSCondition 使用其做多线程之间的通信调用不是线程安全的
NSConditionLock 单纯加锁性能非常低,比NSLock低很多,但是可以用来做多线程处理不同任务的通信调用
NSRecursiveLock 递归锁的性能出奇的高,但是只能作为递归使用,所以限制了使用场景
NSDistributedLock 因为是MAC开发的,就不讨论了
POSIX(pthread_mutex) 底层的api,复杂的多线程处理建议使用,并且可以封装自己的多线程
OSSpinLock 性能也非常高,可惜出现了线程问题

再总结一下,总的意思就是一般用dispatch_semaphore_t就行了,再简单点用
NSLock,另外带一个swift出的一个线程锁的方式:

func synchronized(lock: AnyObject, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

怎么保证线程安全

这个问题我是接着上一个问题之后回答的,感觉线程安全主要是数据竞争带来的,下面简单讲解一下:

线程安全的代码可以从多个线程或并发任务安全地调用,而不会造成任何问题(数据损坏,系统崩溃等)。例如当你多线程编程时,你用let定义一个数组,因为它是只读的,你能在同一时间不同线程去使用它,而不会造成线程安全的问题,然而当你用var定义一个数组时就不一样了,它不是线程安全的,当多个线程在同一时间访问和修改数组时会产生不可预知的结果。

SDWebImage具体实现和具体类

这个问题回答的一般般吧,说了一下简单的构造和实现,然后让我说具体类的时候有点心累了,因为确实记得不是很清楚了,下面简单总结一下一些主要的类:

  • SDWebImageManager
  • SDWebImageCombinedOperation
  • SDImageCache
  • SDWebImageDownloader
  • 各种类目

这是一些简单的类,大家想要详细了解可以去这篇文章去看,非常详细!!!

Alamofire实现原理和主要的类

跟上个问题一样,简单的回答了一下,都怪自己没仔细专研过这些,只是简单看过,停留在应用层面上,下面同样简单介绍一下,附带大神blog地址吧:

  • Manager
  • SessionDelegate
  • ResponseSerialization
  • URLStringConvertible

这是一些简单的类,大家想要详细了解可以去这篇文章去看,非常详细!!!

RxSwift的原理使用和主要类?

这个幸好用过了,不过也没深入过,就简单抽象的讲了一下响应式编程的思想,然后从应用使用方面讲解了一下:RxSwift的目的是让让数据/事件流和异步任务能够更方便的序列化处理,能够使用swift进行响应式编程;让后让我说一下RxSwift里面有哪些Subjects,这个就比较尴尬了,这让我只用过PublishSubjectDriver的人情何以堪,下面同样列一下Subjects列表和大神地址

  • PublishSubject

  • ReplaySubject

  • BehaviorSubject

  • Variable

  • Driver

    下面是大神blog,有详细介绍大家可以去阅读~~~

realm的简单介绍和使用时的线程问题

这个也简单用过,也是没往深入研究,也是大概说了一下使用过程,和多线程数据共享的坑,首先realm是一个跨平台移动数据库引擎,支持iOSOS XObjective-CSwift)以及Android,核心数据引擎C++打造,并不是建立在SQLite之上的ORM

跨线程时的使用

废话不多说,直接上代码:

let person = Person(name: "Jane")
try! realm.write {
  realm.add(person)
}
// 以下是跨线程必要的操作,先建一个Reference
let personRef = ThreadSafeReference(to: person)
// 然后在需要返回数据的线程里面去resolve
DispatchQueue(label: "background").async {
    let realm = try! Realm()
    guard let person = realm.resolve(personRef) else {
    // person 已被删除
      return 
    }
    try! realm.write {
      person.name = "Jane Doe"
    }
}

这里是官方中文文档,大家可以去看看,非常详细~~~

简单讲一讲RunTime和RunLoop

runtimerrunloop因为看过一篇文章写的特别好,有一定了解,说了runtime的一些主要功能和应用的地方,下面简单介绍一下:

RunTime

  • RunTime简称运行时;
  • OC就是运行时机制,就是在运行的时候调用一些机制;
  • 对于C语言,在编译的时候会决定调用哪个函数;
  • 对于OC,在编译的时候并不能决定调用哪个函数,只有在真正运行的时候才会根据函数的SEL来调用对应的函数。

RunTime 应用范围

  • 发送消息
  • 交换方法
  • 动态添加方法
  • 动态添加属性

其中用的比较多的就是用类目给某个类动态添加属性。

RunLoop

RunLoop简单来说就是事件循环,保持APP一直处于存活方式的一种机制,让线程能随时处理事件但并不退出,下面有一篇超级棒的RunLoop文章给大家介绍一下,我这就不展开说了,了解RunLoop看那篇文章足够了。

超级棒的文章地址

简单说一下iOS的几种持续化存储方式

这个回答的就比较轻松了,下面随便列几个吧,大家有其他的可以补充一下:

  • NSUserDefaults
  • plist
  • NSKeyedArchiver
  • SQL
  • coredata
  • realm

WKWebView缓存和清理缓存的方法

WKWebView缓存的使用

主要通过NSURLCache对请求的数据进行缓存,具体实现可以去这个github上去查看~~~~

清楚缓存的方法

详情看这篇blog,这里简单陈述一下,其实在iOS9出了一个方法,调用一下就清除了:

NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
//// Date from
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
//// Execute
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
// Done
}];

WKWebview的cookie的使用

主要用到的类有NSHTTPCookie,大家同样也可以去大神的blog去查看~~~~

autorelease的实现,以及autorelease对象什么时候会被release

这个问题回答的不太好,说实话当时连续面试了两家,闹着已经有点蒙了,这里借用喵神翻译的一本书上的原话来描述一下:

autorelease它会将接受该消息的对象放到一个预先建立的自动释放池 (auto release pool) 中,并在 自动释放池收到 drain 消息时将这些对象的引用计数减一,然后将它们从池子中移除 (这一过程形象地称为“抽干池子”)。”

【摘录来自: 王巍 (onevcat). “Swifter - Swift 必备 Tips (第三版)”。 iBooks. 】

实现原理的话大概就是被autorelease标记的类会被加入一个池子,当这个池子drain时里面的引用计数会减1。

谈谈对swift中extension的理解

说实话其实这个问题当时比较吵,听的不是很清楚,问了好几遍问题,挺尴尬的,下面简单介绍一下吧,因为这个在swift中用的比较多:

  • 首先extensionswift中类似oc的类目,可以扩展方法,计算属性,不能添加存储属性;
  • 可以通过extension让类实现某个协议,一般这个用的也比较多,比如实现Comparable这个协议;
  • 还有一个很重要的,就是可以通过extension对协议进行扩展,添加默认实现,属于黑魔法吧,非常好用。

前两点在面试的时候都又提到,最后一点压根忘了,自己的面试发挥真不是一般的差- . -

swift写时复制,自己的结构体怎么实现写时复制

幸好之前看了喵神翻译的【Swift进阶】 ,受益颇深,下面同样借用喵神的话给大家简单描述一下,大家可以去买这本书,还是挺划算的,下面大量复制喵神书里面的内容,请见谅:

在 Swift 标准库中,像是 Array,Dictionary 和 Set 这样的集合类型是通过一种叫做写时复制 (copy-on-write) 的技术实现的。我们这里有一个整数数组:

var x = [1,2,3]
var y = x

如果我们创建了一个新的变量 y,并且把 x 赋值给它时,会发生复制,现在 x 和 y 含有的是独立的结构体。在内部,这些 Array 结构体含有指向某个内存的引用。这个内存就是数组中元素所存储的位置,它们位于堆 (heap) 上。在这个时候,两个数组的引用指向的是内存中同一个位置,这两个数组共享了它们的存储部分。不过,当我们改变 x 的时候,这个共享会被检测到,内存将会被复制。这样一来,我们得以独立地改变两个变量。昂贵的元素复制操作只在必要的时候发生,也就是我们改变这两个变量的时候发生复制:

x.append(5)
y.removeLast()
x // [1, 2, 3, 5]
y // [1, 2]

如果 Array 结构体中的引用在数组被改变的一瞬间时是唯一的话 (比如,没有声明 y),那么也不会有复制发生,内存的改变将在原地进行。这种行为就是写时复制,作为一个结构体的作者,你并不能免费获得这种特性,你需要自己进行实现。当你自己的类型内部含有一个或多个可变引用,同时你想要保持值语义,并且避免不必要的复制时,为你的类型实现写时复制是有意义的。

下面看看通过简单的例子看一下:

var array = [COWStruct()]
array[0].change() // No copy

var otherArray = [COWStruct()]
var x = array[0]
x.change() // Copy

然而自己去实现一个写时复制的话,首先你要判断引用的唯一性,不是唯一的话进行写时复制,唯一的话直接改变需要改变的值。

结构体和类以及它们的使用时机

结构体和类主要的区别就是一个是值类型,一个是引用类型;值类型是写时复制的,引用类型是不会发生写时复制的;当我们需要一个简单不需要继承、不多变的数据时候我们首选结构体,因为在数据结构上来说结构体的存取效率是高于类的,反之当我们需要一个数据结构比较大,需要继承,变化比较多的时候我们选择类,因为在变化的过程中结构体可能会发生写时复制,而类不会;下面举一个简单的例子:

ArrayNSMutableArray来说:

当有一个数组,数据量相对比较小,也不用去经常改变它,只是用来存数据和取数据,我们首先Array

当数组的数据量很大的时候,并且经常要去对他进行添加,删除等操作,并且经常赋值给其他变量的话就推荐使用NSMutableArray

让你实现一个数据存储的框架的具体思路

这个是一个比较开发性的问题了,我想到的是用策略模式的方式来简单实现一下,使用策略模式的好处是方便文件类型的扩展,下面我简单画个简单的UML图大家看一看吧:

当然这只是一个初步的模型,还有很多细节待考虑,比如文件缓存什么的,是存本地还是磁盘,这都得去考虑,小弟只是抛砖引玉给个简单的思路。

写完已经是深夜了,以上是我丁香园电话面试的一些问题,和之前面试一些回答不好的问题,最后面试完我问了一下丁香园的面试官对我感觉怎样,他说广度还行深度不够,我确实又有这点问题,想学的知识比较多,有时候也没来得及去看实现原理,只是简单的过一下,没深入研究透彻,这是我需要加强的地方,之后如果有二面的话我会在继续更新的,最后谢谢大家的阅读~~我是WCL,大家可以去我github关注一波