写点什么

【面试必备】Swift 面试题及其答案

用户头像
关注
发布于: 2021 年 01 月 25 日

问题 1- Swift 1.0 or later


什么是 optional 类型,它是用来解决什么问题的?


答案:optional 类型被用来表示任何类型的变量都可以表示缺少值。在 Objective-C 中,引用类型的变量是可以缺少值得,并且使用 nil 作为缺少值。基本的数据类型如 int 或者 float 没有这种功能。


Swift 用 optional 扩展了在基本数据类型和引用类型中缺少值的概念。一个 optional 类型的变量,在任何时候都可以保存一个值或者为 nil。


问题 2- Swift 1.0 or later


在 Swfit 中,什么时候用结构体,什么时候用类?


答案:一直都有这样的争论:到底是用类的做法优于用结构体,还是用结构体的做法优于类。函数式编程倾向于值类型,面向对象编程更喜欢类。


在 Swift 中,类和结构体有许多不同的特性。下面是两者不同的总结:


类支持继承,结构体不支持。


类是引用类型,结构体是值类型


并没有通用的规则决定结构体和类哪一个更好用。一般的建议是使用最小的工具来完成你的目标,但是有一个好的经验是多使用结构体,除非你用了继承和引用语义。


想要了解更多,点击这里


注意:在运行时,结构体的在性能方面更优于类,原因是结构体的方法调用是静态绑定,而类的方法调用是动态实现的。这就是尽可能得使用结构体代替类的又一个好的原因。


问题 3- Swift 1.0 or later


什么是泛型?泛型是用来解决什么问题的?


答案:泛型是用来使类型和算法安全的工作的一种类型。在 Swift 中,在函数和数据结构中都可以使用泛型,例如类、结构体和枚举。


泛型一般是用来解决代码复用的问题。常见的一种情况是,你有一个函数,它带有一个参数,参数类型是 A,然而当参数类型改变成 B 的时候,你不得不复制这个函数。


例如,下面的代码中第二个函数就是复制第一个函数——它仅仅是用 String 类型代替了 Integer 类型。


func areIntEqual(x: Int, _ y: Int) -> Bool {  return x == y}  func areStringsEqual(x: String, _ y: String) -> Bool {  return x == y}  areStringsEqual("ray", "ray") // trueareIntEqual(1, 1) // true
复制代码


Objective-C 开发人员可能想到用 NSObject 类来解决这个问题,代码如下:


import Foundation  func areTheyEqual(x: NSObject, _ y: NSObject) -> Bool {  return x == y}  areTheyEqual("ray", "ray") // trueareTheyEqual(1, 1) // true
复制代码


这个代码会按照预期的方式工作,但是它在编译时不安全。它允许字符串和整数相比较,像这样:


areTheyEqual(1, "ray")
复制代码


应用程序不会崩溃,但是允许字符串和整数相比较可能不是预想的结果。


通过采用泛型,可以合并这两个函数为一个并同时保持类型安全。下面是代码实现:


func areTheyEqual(x: T, _ y: T) -> Bool {  return x == y}  areTheyEqual("ray", "ray")areTheyEqual(1, 1)
复制代码


上面的例子是测试两个参数是否相等,这两个参数的类型受到约束都必须遵循 Equatable 协议。上面的代码达到预想的结果,并且防止了传递不同类型的参数。


问题 4- Swift 1.0 or later


哪些情况下你不得不使用隐式拆包?说明原因。


答案:对 optional 变量使用隐式拆包最常见的原因如下:


1、对象属性在初始化的时候不能 nil,否则不能被初始化。典型的例子是 Interface Builder outlet 类型的属性,它总是在它的拥有者初始化之后再初始化。在这种特定的情况下,假设它在 Interface Builder 中被正确的配置——outlet 被使用之前,保证它不为 nil。


2、解决强引用的循环问题——当两个实例对象相互引用,并且对引用的实例对象的值要求不能为 nil 时候。在这种情况下,引用的一方可以标记为 unowned,另一方使用隐式拆包。


建议:除非必要,不要对 option 类型使用隐式拆包。使用不当会增加运行时崩溃的可能性。在某些情况下,崩溃可能是有意的行为,但有更好的方法来达到相同的结果,例如,通过使用 fatalError( )函数。


问题 5- Swift 1.0 or later


对一个 optional 变量拆包有多少种方法?并在安全方面进行评价。


答案:  


  • 强制拆包 !操作符——不安全

  • 隐式拆包变量声明——大多数情况下不安全

  • 可选绑定——安全

  • 自判断链接(optional chaining)——安全

  • nil coalescing 运算符(空值合并运算符)——安全

  • Swift 2.0 的新特性 guard 语句——安全

  • Swift 2.0 的新特性 optional pattern(可选模式) ——安全(@Kametrixom 支持)



中级


问题 1- Swift 1.0 or later


Swift 是面向对象编程语言还是函数式编程语言?


答案:Swift 是一种混合编程语言,它包含这两种编程模式。它实现了面向对象的三个基本原则:


  • 封装

  • 继承

  • 多态


说道 Swift 作为一种函数式编程语言,我们就不得不说一下什么是函数式编程。有很多不同的方法去定义函数式编程语言,但是他们表达的意义相同。


最常见的定义来自维基百科:...它是一种编程规范…它把电脑运算当做数学函数计算,避免状态改变和数据改变。


很难说 Swift 是一个成熟的函数式语言,但是它已经具备了函数式语言的基础。


问题 2- Swift 1.0 or later


下面的功能特性都包含在 Swift 中吗?


1、泛型类


2、泛型结构体


3、泛型协议


答案:


  • Swift 包含 1 和 2 特性。泛型可以在类、结构体、枚举、全局函数或者方法中使用。

  • 3 是通过 typealias 部分实现的。typealias 不是一个泛型类型,它只是一个占位符的名字。它通常是作为关联类型被引用,只有协议被一个类型引用的时候它才被定义。


问题 3- Swift 1.0 or later


在 Objective-C 中,一个常量可以这样定义:


const int number = 0;


类似的 Swift 是这样定义的:


let number = 0


两者之间有什么不同吗?如果有,请说明原因。


答案:const 常量是一个在编译时或者编译解析时被初始化的变量。通过 let 创建的是一个运行时常量,是不可变得。它可以使用 stattic 或者 dynamic 关键字来初始化。谨记它的的值只能被分配一次。


问题 4- Swift 1.0 or later


声明一个静态属性或者函数,我们常常使用值类型的 static 修饰符。下面就是一个结构体的例子:


struct Sun {


static func illuminate() {}


}


对类来说,使用 static 或者 class 修饰符,都是可以的。它们使用后的效果是一样的,但是本质上是不同的。能解释一下为什么不同吗?


答案:


static 修饰的属性或者修饰的函数都不可以重写。但是使用 class 修饰符,你可以重写属性或者函数。


当 static 在类中应用的时候,static 就成为 class final 的一个别名。


例如,在下面的代码中,当你尝试重写 illuminate()函数时,编译器就会报错:


class Star {  class func spin() {}  static func illuminate() {}}  class Sun : Star {  override class func spin() {    super.spin()  }  override static func illuminate() { // error: class method overrides a ‘final‘ class method    super.illuminate()  }}
复制代码


问题 5- Swift 1.0 or later


你能通过 extension(扩展)保存一个属性吗?请解释一下原因。


答案:不能。扩展可以给当前的类型添加新的行为,但是不能改变本身的类型或者本身的接口。如果你添加一个新的可存储的属性,你需要额外的内存来存储新的值。扩展并不能实现这样的任务。


高级


问题 1- Swift 1.2


在 Swift1.2 版本中,你能解释一下用泛型来声明枚举的问题吗?拿下面代码中 Either 枚举来举例说明吧,它有两个泛型类型的参数 T 和 V,参数 T 在关联值类型为 left 情况下使用,参数 V 在关联值为 rihgt 情况下使用,代码如下:


enum Either{  case Left(T)  case Right(V)}
复制代码


提示:验证上面的条件,需要在 Xcode 工程里面,而不是在 Playgroud 中。同时注意,这个问题跟 Swift1.2 相关,所以 Xcode 的版本必须是 6.4 以上。


答案:上面的代码会出现编译错误:


unimplemented IR generation feature non-fixed multi-payload enum layout
复制代码


问题是 T 的内存大小不能确定前期,因为它依赖于 T 类型本身,但 enum 情况下需要一个固定大小的有效载荷。


最常用的解决方法是讲泛类型用引用类型包装起来,通常称为 box,代码如下:


class Box{  let value: T  init(_ value: T) {    self.value = value  }}  enum Either{  case Left(Box)  case Right(Box)}
复制代码


这个问题在 Swift1.0 及之后的版本出现,但是 Swift2.0 的时候,被解决了。


问题 2- Swift 1.0 or later


闭包是引用类型吗?


答案:闭包是引用类型。如果一个闭包被分配给一个变量,这个变量复制给另一个变量,那么他们引用的是同一个闭包,他们的捕捉列表也会被复制。


问题 3- Swift 1.0 or later


UInt 类型是用来存储无符号整型的。下面的代码实现了一个有符号整型转换的初始化方法:


init(_ value: Int)
复制代码


然而,在下面的代码中,当你给一个负值的时候,它会产生一个编译时错误:


let myNegative = UInt(-1)
复制代码


我们知道负数的内部结构是使用二进制补码的正数,在保持这个负数内存地址不变的情况下,如何把一个负整数转换成一个无符号的整数?


答案:使用下面的初始化方法:


UInt(bitPattern: Int)
复制代码


问题 4- Swift 1.0 or later


描述一种在 Swift 中出现循环引用的情况,并说明怎么解决。


答案:循环引用出现在当两个实例对象相互拥有强引用关系的时候,这会造成内存泄露,原因是这两个对像都不会被释放。只要一个对象被另一个对象强引用,那么该对象就不能被释放,由于强引用的存在,每个对象都会保持对方存在。


解决这个问题的方法是,用 weak 或者 unowned 引用代替其中一个的强引用,来打破循环引用。


问题 5- Swift 2.0 or later


Swift2.0 增加了一个新的关键字来实现递归枚举。下面的例子是一个枚举类型,它在 Node 条件下有两个相关联的值类型 T 和 List:


enum List{
case Node(T, List)
}
复制代码


什么关键字可以实现递归枚举?


答案:indirect 关键值可以允许递归枚举,代码如下:


enum List{
indirect case Cons(T, List)
}
复制代码


Where To Go From Here?


恭喜你到了文章的最后,如果你不知道所有问题的答案,也不要感到沮丧。


因为上面中得有些问题还是比较复杂的,并且 Swift 是一门富有表现力的语言,还有很多需要我们学。此外,苹果公司一直改善 Swift 的新特性,所以即使学的最好的人也不可能知道所有的一切。


同时在平时工作之余,我也创建了一个 iOS 技术交流群:642363427,平时大家都会在里面探讨心得,不管你是大牛还是小白都欢迎入驻 ,分享 BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!


image


用户头像

关注

你的努力没人会看到,可成功会让人羡慕。 2020.12.08 加入

iOS交流群:642363427 公众号:iOS进阶宝典 抖音:iOS 普拉斯 视频学习:https://space.bilibili.com/107521719 感谢支持与关注

评论

发布
暂无评论
【面试必备】Swift 面试题及其答案