答卓同学的 Swift 面试题

发布于:2017-03-15 18:09,阅读数:6023,点赞数:45


# 引言 看到卓同学于今日又发了一篇面试题。出于自己把大把精力投入在 Swift 上然而还是找不到工作的原因,来答一下题。答案是凭借自己的掌握答的,并没有一题一题去查资料,如有答错答不全的情况欢迎到简书中交流。 题目地址在这里:[「卓同学的 Swift 面试题」](http://www.jianshu.com/p/7c7f4b4e4efe) 本文首发在博客中,如需评论,简书评论地址在这里:[「答卓同学的 Swift 面试题」](http://www.jianshu.com/p/032ab437dd93) # 正文 开始答题。 ## 初级 ### 1、class 和 struct 的区别 - 这个问题第一反应肯定是 class 能继承,而 struct 不能。 - 再之,在 swift 中 struct 是值,而 class 是引用。所谓值,struct 一旦生成是不能变的,如果有一个方法改变了自己的属性,这个方法需要标为 mutating,修改时会产生一个新值去替代旧值(这一步可能会被编译器优化,并不一定会真的产生一个新 struct)。而 class 无论怎么传递都是同一个对象。 - 值和引用的区别使 struct 不会有多线竞争的问题,class 会有。 - 有些 protocol 只能被类遵守。 - 总感觉还有但是一下子想不起来了。。。😅 ### 2、不通过继承,代码复用(共享)的方式有哪些 - 第一点毫无疑问,用 extension 追加方法和计算属性。 - 运行时通过 runtime 添加方法。 ### 3、Set 独有的方法有哪些? - 集合类型取交集、并集、差集和补集的方法是独有的。 ### 4、实现一个 min 函数,返回两个元素较小的元素 - 元素比较首先想到的是 Comparable 协议,实现 Comparable 协议即可: ```swift struct A: Comparable, Equatable { var value: Int } func ==(lhs: A, rhs: A) -> Bool { return lhs.value == rhs.value } func <(lhs: A, rhs: A) -> Bool { return lhs.value < rhs.value } func <=(lhs: A, rhs: A) -> Bool { return lhs.value <= rhs.value } func >=(lhs: A, rhs: A) -> Bool { return lhs.value >= rhs.value } func >(lhs: A, rhs: A) -> Bool { return lhs.value > rhs.value } let a = A.init(value: 1) let b = A.init(value: 2) func myMin<T: Comparable>(_ a: T, _ b: T) -> T { return a > b ? b : a } print(myMin(a, b)) ``` ### 5、map、filter、reduce 的作用 - 这三个是集合的实例方法,学函数式编程时最先学的函数,下面以数组为例。 - `map`: 传入一个函数,数组中每一个元素都执行这个函数,最后生成一个新数组。例子:模型列表到 JSON 列表的转换: ```swift let objs = Array<MyObject>() let jsonList = objs.map { $0.toJSON() } ``` - `filter`: 传入一个返回值是布尔类型的函数作为过滤器,最后生成一个能通过这个过滤器(即返回值是真)的数组。例子:过滤: ```swift var objs = Array<MyObject>() objs = objs.filter { $0.value > 10 } ``` - `reduce`: 传入一个初值,再传入一个函数,初值和数组中每一个元素都会被传入函数计算结果,最终返回一个值。例子:求和 ```swift let nums = [1, 2, 3] let sum = nums.reduce(0) { $0 + $1 } ``` ### 6、map 与 flatmap 的区别 - flatMap 主要用于展开多层数组,例如二维数组转成一维。 - 另一个区别是,flatMap 传入的函数可以返回 nil,如果返回 nil,它将不会出现在结果中,因此也可以用来过滤。 ### 7、什么是 copy on write - struct 是值类型,因此传递是深拷贝的值传递。但这个拷贝并不会立刻发生,只有在引用不唯一且正在修改这个结构体的时候才会发生。如果是单一引用,这个拷贝也有可能会被编译器优化。这点在 inout 关键字的用法中的拷贝同样适用。 ### 8、如何获取当前代码的函数名和行号 - 函数名: 用`#function` - 行号: 用`#line` ### 9、如何声明一个只能被类 conform 的 protocol - 协议名后面加 class。 ```swift protocol Whatever: class { } ``` ### 10、guard 使用场景 - 守护关键变量,如有异常直接退出,避免出现大量`if let`的缩进。 ### 11、defer 使用场景 - 将代码块压如栈中,等程序执行完后弹出再执行。需要注意的是这个代码块不允许抛异常。以下两种情况用得比较多: ```swift lock.lock() defer { lock.unlock() } ``` ```swift let file = fopen(...) defer { fclose(...) } ``` ### 12、String 与 NSString 的关系与区别 - 从表面上看,是两个不同的东西,API 不一样。 - String 是 struct,值类型。NSString 是类,引用类型。 - 没有了。OC用的比较少,不太清楚 NSString。。😂 ### 13、怎么获取一个 String 的长度 - Swift 3 还是哪个版本中 String 做了修改,引入了`String.CharacterView`类型来索引字符串的字符,第一种方法是直接去字符来做: ```swift let count = str.characters.count ``` - 取C语言那种字符数来取长度,做过一阵子C语言混编,所以用过。不过转过去之后长度需要减一,最后有一位是`\0`,做过C语言的都懂的: ```swift print(str.cString(using: .utf8)!.count - 1) ``` - 转成 Data 取长度(包括转成其他类型取长度的做法): ```swift let str = "whatever" let data = str.data(using: .utf8, allowLossyConversion: true)! print(data.count) ``` ### 14、如何截取 String 的某段字符串 - Swift 引入了`String.Index`来操作字符串,通过字符串开头和结尾的偏移量来索引字符。具体可以看我这篇文章下面关于截取 HTML 字符串中有用的 JSON 字符串的代码:[「使用 Swift 3.0 爬取“简书”的文章模型」](http://posts.enumsblog.com/posts/17002) ### 15、throws 和 rethrows 的用法与作用 - throws: 用于标记函数会抛出异常。 - retrhows: 似乎是用于标记闭包会抛出异常,不太确定。 ### 16、try? 和 try!是什么意思 - try?: 表示忽略异常,如果后面的表达式有异常抛出,会忽略,并且返回 nil。 - try!: 断言这里不会抛异常。如果后面表达式有异常抛出,直接崩溃。 ### 17、associatedtype 的作用 - 这个怎么形容呢。一个 protocol,里面有几个泛型方法,要求去具体实现这几个方法的泛型类型一样,可以用 associatedtype。 ### 18、什么时候使用 final - 类不能被继承时候。这个我没仔细了解,不知道 Swift 里还有没有别的用处。 ### 19、public 和 open 的区别 - 像构造函数等,只能用 public 不能用 open。 - enum、protocol 的声明也只能用 public。 ### 20、声明一个只有一个参数没有返回值闭包的别名 ```swift let t: (Any) -> Void = { _ in } ``` - 以上审题错误,别名看成变量了。正确答案是: ```swift typealias t = (Any) -> Void ``` ### 21、Self 的使用场景 - 这个在我的一个开源库里用到了过,直接上几句代码吧。下面代码中的`ret`的类型是`MyClass`而不是`Hello`。 ```swift protocol Hello { func hello() -> Self } class MyClass: Hello { func hello() -> Self { return self } } let ret = MyClass.init().hello() ``` ### 22、dynamic 的作用 - 需要使用 runtime 动态性时标记,如果类继承于`NSObject`就不用了。 ### 23、什么时候使用 @objc - 同上。这两条我不是很清楚,似乎跟 CoreData 有关。 ### 24、Optional(可选型) 是用什么实现的 - 可选类型是一个枚举,一个选项是`some`,一个选项是`nil`。 - 通关泛型来兼容所有类型。 ### 25、如何自定义下标获取 - 加入`subscript`方法。 ### 26、?? 的作用 - 设置默认值。如果前面的表达式是 nil,则使用后面的默认值。 ### 27、lazy 的作用 - `lazy`: 懒加载,当变量在用的时候才加载变量。 - 需要注意的是,全局变量不用 lazy 也是懒加载的。 ### 28、一个类型表示选项,可以同时表示有几个选项选中(类似 UIViewAnimationOptions ),用什么类型表示 - 这个我还真遇到过,用`OptionSet`。 ### 29、inout 的作用 - 具体可以看我这篇文章中关于`inout`的内容:[「Swift 3.0 从 ++ 的实现到 inout 和 defer 的小细节」](http://posts.enumsblog.com/posts/17004) ### 30、Error 如果要兼容 NSError 需要做什么操作 - 印象中 Error 和 NSError 是两个不同的东西。 - <del>硬要兼容的话,我会尝试声明一个 enum 去携带 NSError。</del> - 经指出,使用 extension `CustomNSError` 协议来继承 NSError 的一些参数即可。 ### 31、下面的代码都用了哪些语法糖:[1, 2, 3].map{ $0 * 2 } - 函数参数最后一个是闭包,则可以提到括号外面。 - 函数只有一个参数且是闭包,可以省略括号。 - $0、$1、$2 表示第一、二、三个参数。 - 以上说法来自斯坦福大学 iOS 开发公开课,2015年。 - 最后一条省略了`return`,直接返回,这个我不太清楚官方说法是什么。 ### 32、什么是高阶函数 - 我的理解是,函数作为参数的函数,或者返回值是函数的函数。 ### 33、如何解决引用循环 - 使用`week`和`unowned` ### 34、下面的代码会不会崩溃,说出原因 ```swift var mutableArray = [1,2,3] for _ in mutableArray { mutableArray.removeLast() } ``` - 这个测了一下,发现不会崩溃。原因不是很清楚。猜测跟 Array 是值类型有关。 ### 35、给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明 - <del>使用扩展协议 + `where`关键字,我一直是这样做的,看上去比较傻,不知道有没有更快捷的。</del> ```swift protocol IsString { } extension String: IsString { } extension Array where Element: IsString { func whatever() { } } ["", ""].whatever() ``` - 经过卓同学指出这样做真的太傻了,正确答案应该是: ```swift extension Collection where Iterator.Element == String { func whatever() { } } ["", ""].whatever() ``` ## 高级 ### 1、一个 Sequence 的索引是不是一定从 0 开始? - 不是。其实自己手写过 Sequence 的类的同学都知道,下标随便自己写的,爱从几开始都可以。 ### 2、数组都实现了哪些协议 - 这个完全没去背,Sequence、Collection 啥的,具体去看文档就好。 ### 3、如何自定义模式匹配 - 模式匹配只要实现`~=`这个运算符就可以了。 ### 4、autoclosure 的作用 - 如果一个函数有个闭包参数用 autoclosure 标记了,貌似是可以简化语法,传值进去当结果的,像这样: ```swift func f(_ foo: @autoclosure ()->Bool) { } f(true) ``` ### 5、编译选项 whole module optmization 优化了什么 - 区别于单文件优化,这个字面意思看是整个模块一起优化,多文件优化,具体不是很了解。 ### 6、下面代码中 mutating 的作用是什么 ```swift struct Person { var name: String { mutating get { return store } } } ``` - 上面提到过,值类型的结构体在改变自身时需要用 mutating 标记。 - 但是计算属性的 getter 也需要用 mutating 不知道是什么原因,求大神解答。 ### 7、如何让自定义对象支持字面量初始化 - 这个在分析`SwiftyJSON`时接触过。 - 有一吨的`ExpressibleByXXXXLiteral`协议。实现这些协议就好了。 ### 8、dynamic framework 和 static framework 的区别是什么 - 我从 C++ 角度回答这个问题,不太清楚 iOS 这边有没有区别。 - 静态库: 链接时函数、数据等会被打包进程序。 - 动态库: 运行时加载库,供程序调用调用。 ## 哲学 ### 1、为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。 - 一款语言必须要有一些基础的可以完全信任的东西,否则就会陷入循环验证的悖论。所以第一题的答案就是,数组取下标被设计为可以完全信任的操作。 ### 2、一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。 - 投机取巧。 ```swift func foo(_ a: NSNumber) { } foo(2) foo(2.33) foo(0xABC) ``` # 结语 题目量很大,做了两个多小时。 初级部分基本上是需要掌握的知识点,自己做完后起到了查漏补缺的作用。高级部分我答得比较水,希望大神们继续补充。


评论:0条


返回列表

返回主页