答卓同学的 Swift 面试题

发布于: 2017-03-15 18:09
阅读: 3981
评论: 0
喜欢: 44

作者:Enum

原文链接:http://posts.enumsblog.com/posts/17010

TAG: Swift iOS


引言

看到卓同学于今日又发了一篇面试题。出于自己把大把精力投入在 Swift 上然而还是找不到工作的原因,来答一下题。答案是凭借自己的掌握答的,并没有一题一题去查资料,如有答错答不全的情况欢迎到简书中交流。

题目地址在这里:「卓同学的 Swift 面试题」

本文首发在博客中,如需评论,简书评论地址在这里:「答卓同学的 Swift 面试题」

正文

开始答题。

初级

1、class 和 struct 的区别

  • 这个问题第一反应肯定是 class 能继承,而 struct 不能。
  • 再之,在 swift 中 struct 是值,而 class 是引用。所谓值,struct 一旦生成是不能变的,如果有一个方法改变了自己的属性,这个方法需要标为 mutating,修改时会产生一个新值去替代旧值(这一步可能会被编译器优化,并不一定会真的产生一个新 struct)。而 class 无论怎么传递都是同一个对象。
  • 值和引用的区别使 struct 不会有多线竞争的问题,class 会有。
  • 有些 protocol 只能被类遵守。
  • 总感觉还有但是一下子想不起来了。。。😅

2、不通过继承,代码复用(共享)的方式有哪些

  • 第一点毫无疑问,用 extension 追加方法和计算属性。
  • 运行时通过 runtime 添加方法。

3、Set 独有的方法有哪些?

  • 集合类型取交集、并集、差集和补集的方法是独有的。

4、实现一个 min 函数,返回两个元素较小的元素

  • 元素比较首先想到的是 Comparable 协议,实现 Comparable 协议即可:
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 列表的转换:
let objs = Array<MyObject>()
let jsonList = objs.map { $0.toJSON() }
  • filter: 传入一个返回值是布尔类型的函数作为过滤器,最后生成一个能通过这个过滤器(即返回值是真)的数组。例子:过滤:
var objs = Array<MyObject>()
objs = objs.filter { $0.value > 10 }
  • reduce: 传入一个初值,再传入一个函数,初值和数组中每一个元素都会被传入函数计算结果,最终返回一个值。例子:求和
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。
protocol Whatever: class { }

10、guard 使用场景

  • 守护关键变量,如有异常直接退出,避免出现大量if let的缩进。

11、defer 使用场景

  • 将代码块压如栈中,等程序执行完后弹出再执行。需要注意的是这个代码块不允许抛异常。以下两种情况用得比较多:
lock.lock()
defer {
    lock.unlock()
}
let file = fopen(...)
defer {
    fclose(...)
}

12、String 与 NSString 的关系与区别

  • 从表面上看,是两个不同的东西,API 不一样。
  • String 是 struct,值类型。NSString 是类,引用类型。
  • 没有了。OC用的比较少,不太清楚 NSString。。😂

13、怎么获取一个 String 的长度

  • Swift 3 还是哪个版本中 String 做了修改,引入了String.CharacterView类型来索引字符串的字符,第一种方法是直接去字符来做:
let count = str.characters.count
  • 取C语言那种字符数来取长度,做过一阵子C语言混编,所以用过。不过转过去之后长度需要减一,最后有一位是\0,做过C语言的都懂的:
print(str.cString(using: .utf8)!.count - 1)
  • 转成 Data 取长度(包括转成其他类型取长度的做法):
let str = "whatever"
let data = str.data(using: .utf8, allowLossyConversion: true)!
print(data.count)

14、如何截取 String 的某段字符串

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、声明一个只有一个参数没有返回值闭包的别名

let t: (Any) -> Void = { _ in }
  • 以上审题错误,别名看成变量了。正确答案是:
typealias t = (Any) -> Void

21、Self 的使用场景

  • 这个在我的一个开源库里用到了过,直接上几句代码吧。下面代码中的ret的类型是MyClass而不是Hello
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 的作用

30、Error 如果要兼容 NSError 需要做什么操作

  • 印象中 Error 和 NSError 是两个不同的东西。
  • 硬要兼容的话,我会尝试声明一个 enum 去携带 NSError。
  • 经指出,使用 extension CustomNSError 协议来继承 NSError 的一些参数即可。

31、下面的代码都用了哪些语法糖:[1, 2, 3].map{ $0 * 2 }

  • 函数参数最后一个是闭包,则可以提到括号外面。
  • 函数只有一个参数且是闭包,可以省略括号。
  • $0、$1、$2 表示第一、二、三个参数。
  • 以上说法来自斯坦福大学 iOS 开发公开课,2015年。
  • 最后一条省略了return,直接返回,这个我不太清楚官方说法是什么。

32、什么是高阶函数

  • 我的理解是,函数作为参数的函数,或者返回值是函数的函数。

33、如何解决引用循环

  • 使用weekunowned

34、下面的代码会不会崩溃,说出原因

var mutableArray = [1,2,3]
for _ in mutableArray {
  mutableArray.removeLast()
}
  • 这个测了一下,发现不会崩溃。原因不是很清楚。猜测跟 Array 是值类型有关。

35、给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明

  • 使用扩展协议 + where关键字,我一直是这样做的,看上去比较傻,不知道有没有更快捷的。
protocol IsString { }
extension String: IsString { }

extension Array where Element: IsString {
    func whatever() { }
}
["", ""].whatever()
  • 经过卓同学指出这样做真的太傻了,正确答案应该是:
extension Collection where Iterator.Element == String {
    func whatever() { }
}
["", ""].whatever()

高级

1、一个 Sequence 的索引是不是一定从 0 开始?

  • 不是。其实自己手写过 Sequence 的类的同学都知道,下标随便自己写的,爱从几开始都可以。

2、数组都实现了哪些协议

  • 这个完全没去背,Sequence、Collection 啥的,具体去看文档就好。

3、如何自定义模式匹配

  • 模式匹配只要实现~=这个运算符就可以了。

4、autoclosure 的作用

  • 如果一个函数有个闭包参数用 autoclosure 标记了,貌似是可以简化语法,传值进去当结果的,像这样:

func f(_ foo: @autoclosure ()->Bool) { }

f(true)

5、编译选项 whole module optmization 优化了什么

  • 区别于单文件优化,这个字面意思看是整个模块一起优化,多文件优化,具体不是很了解。

6、下面代码中 mutating 的作用是什么

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)都可以,要怎么表示。

  • 投机取巧。
func foo(_ a: NSNumber) { }

foo(2)
foo(2.33)
foo(0xABC)

结语

题目量很大,做了两个多小时。

初级部分基本上是需要掌握的知识点,自己做完后起到了查漏补缺的作用。高级部分我答得比较水,希望大神们继续补充。


Thanks for reading.

All the best wishes for you! 💕