Swift 面试题
Swift语言至今诞生有一年多的时间了,已经成为当前最流行语言之一。虽然它的语法简单好用,但实际上Swift是一门非常复杂的语言。因为它不仅是面向对象的同时又是函数式编程语言。本文主要介绍Swift常见的一些面试问题,你可以用这些问题向面试者提问,也可以用来测试你自己目前所掌握的Swift知识,如果你不清楚问题答案的话也不用太担心,因为每个问题下面都有相应的答案。
问题主要分为两个部分,笔试题和口头问题。
笔试题:可以通过发送Email方式进行编程测试,问题可以包含一小段代码测试。
口头问题:可以通过手机等方式进行面对面的交流,因为这些问题更适合用语言进行交流。
而且每个部分又分为三个等级:
初级:适用于刚接触Swift的学习者,已经读过一本或者两本Swift相关书籍,开始准备在App中使用Swift语言。
中级:适用于对Swift语言概念非常感兴趣的学习者,已经读过许多Swift博客文章并且想进一步深入学习Swift语言。
高级:适用于高级学者,已经对Swift语言很熟悉,喜欢探寻语言,想进一步挑战自我,喜欢高级技术者。
如果你想知道这些问题的答案,建议你最好打开Playground,亲自实现编码。下面这些问题的答案都在Xcode 7.0 beta 6上测试过。
笔试题
初级
你好,现在开始基础测试。
问题 #1 - Swift1.0或者更高版本
用下面方法写一个for循环是最好的方法吗?
for var i = 0; i < 5; i++ {
print("Hello!")
}
答案:
for _ in 0...4 {
print("Hello!")
}
Swift实现了两种范围操作符,分别为闭区间操作符和半开区间操作符。前者包括范围内的所有元素。例如下面例子包含了0到4所有元素。
0...4
而半开区间操作符不会包括最后一个元素。下面的例子同样包括了0到4所有元素
0..<5
问题 #2 - Swift1.0或者更高版本
考虑以下这段代码
struct Tutorial {
var difficulty: Int = 1
}
var tutorial1 = Tutorial()
var tutorial2 = tutorial1
tutorial2.difficulty = 2
tutorial1.difficulty和tutorial2.difficulty的值有什么不同,如果Tutorial是一个类,又有什么不同?为什么?
答案:
tutorial1.difficulty的值是1,而tutorial2.difficulty的值是2
在Swift中,结构体是值类型而不是引用类型,它是值copy。
下面这行代码会首先创建一份tutorial1的copy,然后再赋值给tutorial2
var tutorial2 = tutorial1
从这行代码可以看出,tutorial2 的变化不会影响tutorial1
如果Tutorial是一个类,tutorial1.difficulty和tutorial2.difficulty的值都是2。
在Swift中,类是引用类型,tutorial1的变化会影响到tutorial2,反之亦然。
问题 #3 - Swift1.0或者更高版本
用var声明view1和用let声明view2,在下面的例子中有什么不同?,最后一行Code能否编译通过?
import UIKit
var view1 = UIView()
view1.alpha = 0.5
let view2 = UIView()
view2.alpha = 0.5 // Will this line compile?
答案:
因为view1是一个变量,所以可以用UIView的实例进行赋值,用let关键字只能赋值一次,所以下面代码不能编译成功。
view2 = view1 // 错误:view2是不可变的
但是,UIView是基于类引用的,所以view2的属性是可以改变的(最后一行代码可以编译通过)
let view2 = UIView()
view2.alpha = 0.5 // Yes!
问题 #4 - Swift1.0或者更高版本
下面Code是把数组按字母顺序进行排序,看起来有些复杂,你能用闭包简化它吗?
let animals = ["fish", "cat", "chicken", "dog"]
let sortedAnimals = animals.sort { (one: String, two: String) -> Bool in
return one < two
}
答案:
首先是可以简化闭包的参数,因为在闭包中,系统是可以通过类型推断方式推算出参数的类型。所以你可以去掉参数的类型:
let sortedAnimals = animals.sort { (one, two) -> Bool in return one < two }
返回类型也可以推算出来,所以可以去掉闭包的返回类型:
let sortedAnimals = animals.sort { (one, two) in return one < two }
可以用$i符号替换掉参数的名字,代码然后就变成这样:
let sortedAnimals = animals.sort { return $0 < $1 }
在单语句的闭包中,关键字return也可以省略。最后一条语句的返回值就变成闭包的返回值:
let sortedAnimals = animals.sort { $0 < $1 }
Oops, 到目前,是不是非常简单了,但实际上并非如此。
对字符串来说,有一个字符串比较函数,定义如下:
func <(lhs: String, rhs: String) -> Bool
使用这个函数可以让你的Code更加简洁, 如下:
let sortedAnimals = animals.sort(<)
注意:以上每一步的代码编译运行后都会输出同样的结果,你可以选择使用单字节的闭包。
问题 #5 - Swift1.0或者更高版本
下面代码定义Address和Person两个类,创建Ray和Brian两个实例。
class Address {
var fullAddress: String
var city: String
init(fullAddress: String, city: String) {
self.fullAddress = fullAddress
self.city = city
}
}
class Person {
var name: String
var address: Address
init(name: String, address: Address) {
self.name = name
self.address = address
}
}
var headquarters = Address(fullAddress: "123 Tutorial Street", city: "Appletown")
var ray = Person(name: "Ray", address: headquarters)
var brian = Person(name: "Brian", address: headquarters)
假如Brian要搬迁到新的地址居住,所以你会这样更新他的住址:
brian.address.fullAddress = "148 Tutorial Street"
这样做的话会发生什么?错在哪个地方?
答案:
Ray也会更新地址。因为类Address是引用类型,headquarters是同一个实例,不论你是修改ray的地址还是brian的地址,都会改变headquarters地址。
解决方法是为brian新创建一个地址。或者声明Address为结构体而不是类。
中级
下面提升一下难度的等级
问题 #1 - Swift2.0或者更高版本
思考一下以下代码:
var optional1: String? = nil
var optional2: String? = .None
nil和.None有什么不同?变量optional1和optional2有什么不同?
答案:
nil和.None是一样的。当可选变量没有值时,Optional.None(.None for short)是一般初始化可选变量的方式,而nil则是另一种写法。
事实上,下面条件语句输出是true:
nil == .None // On Swift 1.x this doesn't compile. You need Optional<Int>.None
记着下面代码说明enumeration是一个可选类型:
enum Optional<T> {
case None
case Some(T)
}
问题 #2 - Swift1.0或者更高版本
下面是用类和结构体实现温度计的例子:
public class ThermometerClass {
private(set) var temperature: Double = 0.0
public func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerClass = ThermometerClass()
thermometerClass.registerTemperature(56.0)
public struct ThermometerStruct {
private(set) var temperature: Double = 0.0
public mutating func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerStruct = ThermometerStruct()
thermometerStruct.registerTemperature(56.0)
这段代码在哪个地方会出现编译错误?为什么?
小提示:在Playground测试之前,请仔细阅读并思考代码。
答案:
在最后一行编译器会提示错误,结构体ThermometerStruct声明一个可变的函数修改内部变量 temperature,但是registerTemperature却被一个用let创建的实例所调用,用let定义的变量是不可变的,所以编译通不过。
在结构体中,改变内部状态的方法必须用mutating声明,而且不允许用不可变的实例调用它。
问题 #3 - Swift1.0或者更高版本
下面的代码打印输出是什么?为什么?
var thing = "cars"
let closure = { [thing] in
print("I love \(thing)")
}
thing = "airplanes"
closure()
答案:
结果会打印出“I love cars”。当声明闭包的时候,捕获列表会创建一份thing的copy,所以被捕获到的值是不会改变的,即使你改变thing的值。
如果你去掉闭包中的捕获列表,编译器会使用引用代替copy。在这种情况下,当闭包被调用时,变量的值是可以改变的。示例如下:
var thing = "cars"
let closure = {
print("I love \(thing)")
}
thing = "airplanes"
closure() // Prints "I love airplanes"
问题 #4 - Swift2.0或者更高版本
这是一个全局函数,用来记录数组中唯一值的数量。
func countUniques<T: Comparable>(array: Array<T>) -> Int {
let sorted = array.sort(<)
let initial: (T?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
它使用<和==操作符,所以T类型需要遵循Comparable协议。
可以像下面这样调用它:
countUniques([1, 2, 3, 3]) // result is 3
重写这个函数,让它作为数组Array的方法,使得可以这样调用:
[1, 2, 3, 3].countUniques() // should print 3
答案:
在Swift2.0中,泛型是可以被扩展的,但需要类型约束限制,如果泛型不满足约束,那么扩展也是不可见或者不可访问的。
因此,全局函数countUniques可以被重写为数组Array的扩展:
extension Array where Element: Comparable {
func countUniques() -> Int {
let sorted = sort(<)
let initial: (Element?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
}
注意这个被重写的方法只有当泛型的类型Element实现了Comparable协议才是有效的。例如,当你用装满UIView的数组调用这个方法时编译器会提示错误:
import UIKit
let a = [UIView(), UIView()]
a.countUniques() // compiler error here because UIView doesn't implement Comparable
问题 #5 - Swift2.0或者更高版本
下面是一个计算两个可选类型除法的函数。在计算之前满足以下三个条件:
- 被除数不能为空(not nil)
- 除数不能为空(not nil)
- 除数不能为0
func divide(dividend: Double?, by divisor: Double?) -> Double? {
if dividend == .None {
return .None
}
if divisor == .None {
return .None
}
if divisor == 0 {
return .None
}
return dividend! / divisor!
}
这段代码能够按照预期的那样进行工作,但同时存在两个问题:
- 前置条件,可以考虑利用guard关键字
- 使用了强制解包
改进这个函数,使用guard条件并且避免使用强制解包.
答案:
在Swift2.0中引用guard语句关键字,如果不满足时,guard会提供一个退出路径。这个关键字在判断预置条件时非常有用,能更加清晰表达条件,不需要采用金字塔似的多重嵌套if语句,下面是一个例子:
guard dividend != .None else { return .None }
它还可以用在可选绑定上,能够访问解包后的变量:
guard let dividend = dividend else { return .None }
因此重写后的divide函数如下:
func divide(dividend: Double?, by divisor: Double?) -> Double? {
guard let dividend = dividend else { return .None }
guard let divisor = divisor else { return .None }
guard divisor != 0 else { return .None }
return dividend / divisor
}
注意在最后一行没有隐士解包操作符,是因为dividend和divisor已经被解包存储,并且是非可选不可变的。
而且可以成组地监视guard条件,可以让函数变得更加简单:
func divide(dividend: Double?, by divisor: Double?) -> Double? {
guard let dividend = dividend, divisor = divisor where divisor != 0 else { return .None }
return dividend / divisor
}
现在只有两个guard条件,因为使用where语句判断解包后的变量divisor是否为0。
图片来自简书高级
问题 #1 - Swift1.0或者更高版本
思考以下用结构体定义的温度计:
public struct Thermometer {
public var temperature: Double
public init(temperature: Double) {
self.temperature = temperature
}
}
创建一个实例,可以用下面的方式:
var t: Thermometer = Thermometer(temperature:56.8)
但最好是使用下面方式进行初始化:
var thermometer: Thermometer = 56.8
这可以做到吗?应该怎样做?
答案:
Swift定义了以下协议,通过使用赋值操作符,用字面值直接初始化:
- NilLiteralConvertible
- BooleanLiteralConvertible
- IntegerLiteralConvertible
- FloatLiteralConvertible
- UnicodeScalarLiteralConvertible
- ExtendedGraphemeClusterLiteralConvertible
- StringLiteralConvertible
- ArrayLiteralConvertible
- DictionaryLiteralConvertible
采用相对应的协议,提供一个公有的构造器,允许字面值方式初始化它。在这个温度计这个例子中,需要实现FloatLiteralConvertible协议:
extension Thermometer : FloatLiteralConvertible {
public init(floatLiteral value: FloatLiteralType) {
self.init(temperature: value)
}
}
现在可以使用float值创建一个实例了:
var thermometer: Thermometer = 56.8
问题 #2 - Swift1.0或者更高版本
Swift中定义了运算操作符和逻辑操作符,用于操作不同的类型。当然,你可以自定义一些运算操作符,比如一元或者二元的。
定义一个^^幂操作符,并且同时满足以下要求:
- 用两个Int型整数作为参数。
- 返回幂函数的值,第一个参数为底数,第二个参数为指数。
- 实现潜在的溢出错误。
答案:
创建一个自定义运算符需要两步:声明和实现。
声明使用关键字operator,用于指定类型(一元或者二元),然后指定运算符的结合性和优先级。
在本例中,运算符是^^,类型是infix,结合性是右结合,优先级设置为155,鉴于乘法和除法的优先级是150。以下是运算符的声明:
infix operator ^^ { associativity right precedence 155 }
实现如下:
func ^^(lhs: Int, rhs: Int) -> Int {
let l = Double(lhs)
let r = Double(rhs)
let p = pow(l, r)
return Int(p)
}
注意一点,实现没有考虑溢出的情况,如果运算结果超过Int.max,将会产生运行时错误。
问题 #3 - Swift1.0或者更高版本
你能像下面这样用原始值定义枚举类型吗?为什么?
enum Edges : (Double, Double) {
case TopLeft = (0.0, 0.0)
case TopRight = (1.0, 0.0)
case BottomLeft = (0.0, 1.0)
case BottomRight = (1.0, 1.0)
}
答案:
不能,原始值的类型必须遵循以下条件:
- 实现Equatable协议
- 必须是以下文字转换的类型:
- Int
- String
- Character
在以上代码中,原始数值类型是元组类型,即使元组中的数值满足条件,也是不兼容的。
问题 #4 - Swift2.0或者更高版本
思考以下代码,定义了结构体Pizza,协议Pizzeria,在协议扩展中实现默认方法makeMargherita()。
struct Pizza {
let ingredients: [String]
}
protocol Pizzeria {
func makePizza(ingredients: [String]) -> Pizza
func makeMargherita() -> Pizza
}
extension Pizzeria {
func makeMargherita() -> Pizza {
return makePizza(["tomato", "mozzarella"])
}
}
然后定义一个餐馆Lombardis:
struct Lombardis: Pizzeria {
func makePizza(ingredients: [String]) -> Pizza {
return Pizza(ingredients: ingredients)
}
func makeMargherita() -> Pizza {
return makePizza(["tomato", "basil", "mozzarella"])
}
}
下面代码创建了Lombardis的两个实例。哪一个会使用“basil”来做披萨?
let lombardis1: Pizzeria = Lombardis()
let lombardis2: Lombardis = Lombardis()
lombardis1.makeMargherita()
lombardis2.makeMargherita()
答案:
两个都会。协议Pizzeria声明了makeMargherita()方法,并且提供了一个默认的实现。在Lombardis实现中,这个方法被重写了。因为这个方法在协议中声明了,在Runtime时能正确的被调用。
如果协议中没有声明makeMargherita()方法,但是在协议的扩展中又默认实现了 这个方法会怎样?
protocol Pizzeria {
func makePizza(ingredients: [String]) -> Pizza
}
extension Pizzeria {
func makeMargherita() -> Pizza {
return makePizza(["tomato", "mozzarella"])
}
}
在这种情况下,只有lombardis2能用“basil”来做披萨,而lombardis1则不会,因为它调用的是扩展中默认的方法。
问题 #5 - Swift2.0或者更高版本
下面代码会有编译错误,你能指出在哪个地方吗?为什么?
struct Kitten {
}
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
}
print(k)
}
提示:有三种方式可以fix它
答案:
在guard中else语句体中需要有退出路径,用return返回,或者抛出一个异常或者调用@noreturn。最简单的方式是return语句:
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
return
}
print(k)
}
下面版本是抛出异常:
enum KittenError: ErrorType {
case NoKitten
}
struct Kitten {
}
func showKitten(kitten: Kitten?) throws {
guard let k = kitten else {
print("There is no kitten")
throw KittenError.NoKitten
}
print(k)
}
try showKitten(nil)
最后一个方法是调用@noreturn中的一个函数fatalError():
struct Kitten {
}
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
fatalError()
}
print(k)
}
口头答疑
绝地战士初级
问题 #1 - Swift1.0或者更高版本
可选是什么?可以解决什么问题?
答案:
可选可以使得任何类型的变量都能表达缺省值。在Objective-C中,缺省值只适用于引用类型,通常被指定为nil。对于基础类型的变量(例如int, float)则没有这个功能,
而Swift把缺省值概念扩展到引用类型和值类型中。一个可选变量在任何时候可以有值或者为nil。
问题 #2 - Swift1.0或者更高版本
什么时候使用结构体?什么时候使用类?
答案:
目前关于使用类还是结构体这个问题,有许多的争论。在函数式编程倾向于使用值类型,而面向对象编程中更喜欢用类。
在Swift中,类和结构体有很多不相同的功能,主要有下面几点:
- 类支持继承,而结构体不支持。
- 类是引用类型,结构体是值类型
并没有统一的规则决定孰好孰坏。通常推荐使用最适合的工具完成特定的目标,在swift中比较好的做法是使用结构体,除非你需要用到继承或者引用的时候才使用类。
主要因为在运行时,结构体的性能优于类的,结构体的方法调用是静态绑定的,而类是在Runtime时动态解析的。
问题 #3 - Swift1.0或者更高版本
泛型是什么?用来解决什么问题?
答案:
泛型可以使某一个类型的算法能够更安全的工作。在Swift中泛型可以用在函数和数据类型上,如类,结构体和枚举类型。
泛型还能解决代码重复的问题。普遍现象是当你已经有一个带参数的方法,但你又不得不再重新写一遍有着类似类型的方法。
在下面的例子中,第二个函数就像是第一个函数的“clone”,它只是把传入参数的类型从字符串变为整型。
func areIntEqual(x: Int, _ y: Int) -> Bool {
return x == y
}
func areStringsEqual(x: String, _ y: String) -> Bool {
return x == y
}
areStringsEqual("ray", "ray") // true
areIntEqual(1, 1) // true
这时,Objective-C的开发者可能会想到用NSObject可以解决这个问题:
import Foundation
func areTheyEqual(x: NSObject, _ y: NSObject) -> Bool {
return x == y
}
areTheyEqual("ray", "ray") // true
areTheyEqual(1, 1) // true
虽然这种方式是能解决问题,但是在编译期间是不安全的。因为它会允许比较String类型和Integer类型,就像下面这样:
areTheyEqual(1, "ray")
虽然应用程序不会crash,但是允许字符串和整型进行比较,会出现让你想不到的结果。
使用泛型的话,可以把两个函数合成一个,同时又能保证类型是安全的。下面是一个具体实现:
func areTheyEqual<T: Equatable>(x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
这个例子是测试两个数是否相等,你可以限制传入参数类型是任意类型,只要这个类型实现了Equatable协议。这段代码可以得到你想要的结果并且能防止传入不同参数类型。
Two birds, one stone问题 #4 - Swift1.0或者更高版本
在某些情况下,你不得不使用隐式解包可选?什么场合?为什么?
答案:
下面情况下需要使用隐式解包可选:
- 当属性在初始化阶段不能为空的时候。一个典型的例子是IB的输出口,它总是要初始化的。使用它之前已经在IB中配置,outlet确保在使用之前值不为空。
- 解决强引用循环的问题,当两个实例相互引用,需要一个实例是非空引用。在这种情况下,一个实例可以标注unowned,另一个使用隐式解包可选。
提示:不要使用隐式解包可选,除非你必须使用时。如果使用它不当时,会增加Runtime时crash的机率。
问题 #5 - Swift1.0或者更高版本
解包可选类型方法是什么?它们的安全性怎样?
提示:有7种方法
答案:
- forced unwrapping ! operator -- unsafe
- implicitly unwrapped variable declaration -- unsafe in many cases
- optional binding -- safe
- optional chaining -- safe
- nil coalescing operator -- safe
- new Swift 2.0 guard statement -- safe
- new Swift 2.0 optional pattern -- safe
中级
问题 #1 - Swift1.0或者更高版本
Swift是面向对象语言还是函数式语言?
答案:
Swift是混合式语言,两种都支持。
它实现面向对象的三个基本特征:
- 封装
- 继承
- 多态
和函数式语言相比,Swift有一些不同,虽然它满足函数式语言基本要求,但并不是完全成熟的函数式编程语言。
问题 #2 - Swift1.0或者更高版本
下面功能,哪一个在Swift中有?
- 泛型类
- 泛型数据结构
- 泛型协议
答案:
1和2在Swift中有。泛型可以用在类,结构体,枚举类型,全局函数和方法中。
3可以通过关键字typealias部分实现。它并不是泛型,只是占位符名字而已。它常被看作是关联数据类型,采用协议时才会被定义。
问题 #3 - Swift1.0或者更高版本
在Objective-C中,一个常量可以像下面进行声明:
const int number = 0;
下面是Swift中的:
let number = 0
他们之间有什么不同吗?如果是的,你能解释一下吗?
答案:
const是一个变量在编译期间被初始化值或者在编译期间表达式的值。
通过let关键字创建常量是在Runtime时初始化的,它能够用用静态的或者动态表达式的结果初始化。注意它的值只能被初始化一次。
问题 #4 - Swift1.0或者更高版本
声明一个静态属性或者函数,你可以使用关键字static来修饰值类型。以下是一个结构体的例子:
struct Sun {
static func illuminate() {}
}
对于类来说,你可以使用static或者class修饰符。虽然它们完成同样的功能,但实际上是不同的。你能解释一下它们之间有什么不同吗?
答案:
使用static关键字,静态属性和静态函数是不能被重写的,但当使用class关键字,你可以重写属性和函数。
其实,对于类来说,static关键字是class final的别名而已。
例如,你编译下面这些code时,当你要重写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 - Swift1.0或者更高版本
使用extension可以增加存储属性吗?解释一下
答案:
是不能的。extension是用来给存在的类型添加新行为的,并不能改变类型或者接口本身。如果你增加存储属性,你需要额外的内存空间存储新的值。extension是不能管理这样的任务的。
高级
问题 #1 - Swift1.2
在Swift1.2中,你能解释一下用泛型声明枚举类型的问题吗?以两个泛型T和V的枚举类型Either为例,Left为关联T类型,Right关联V类型。
enum Either<T, V> {
case Left(T)
case Right(V)
}
提示:检验这个问题要用Xcode工程,不要在Playground中。注意这个问题和Swift1.2有关,你需要使用Xcode6.4版本。
答案:
编译的时候会提示以下问题:
unimplemented IR generation feature non-fixed multi-payload enum layout
问题在于不能提前知道T分配内存大小,主要取决于T类型本身。但是在枚举类型中需要知道固定大小。
最常用的解决方法是采用一个泛型Box,如下:
class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
enum Either<T, V> {
case Left(Box<T>)
case Right(Box<V>)
}
这个问题只会影响Swift1.0或之后版本,在Swift2.0中已经解决。
问题 #2 - Swift1.0或者更高版本
闭包是值类型还是引用类型?
答案:
闭包是引用类型。如果一个闭包赋值给一个变量,这个变量又复制一份copy给另一个变量,那么变量所引用的闭包和捕获的列表也会copy一份。
问题 #3 - Swift1.0或者更高版本
UInt类型用来存储无符号整数。它实现如下一个用有符号的整数构造器:
init(_ value: Int)
但是如果你提供一个负整数的话,下面代码会产生编译错误。
let myNegative = UInt(-1)
知道计算机负数是用二进制补码作为一个正数进行表示,你怎样把Int类型的负整数转为UInt数?
答案:
已经有一个初始化器可以完成:
UInt(bitPattern: Int)
问题 #4 - Swift1.0或者更高版本
你能描述一下在Swift中哪些地方会产生循环引用?有什么解决办法吗?
当两个实例之间相互进行强引用的时候,就会引起循环引用。两个实例都不会释放内存,就会造成内存泄露。可用weak或者unowned打破实例之间的强引用问题,这样两个实例才有机会释放内存空间。
问题 #5 - Swift1.0或者更高版本
Swift2.0引用了一个新关键字能产生递归枚举类型。下面是一个带有Node节点的枚举类型,Node关联值类型,T和list:
enum List<T> {
case Node(T, List<T>)
}
那个关键字是什么?
答案:
关键字indirect允许递归枚举类型,像下面这样:
enum List<T> {
indirect case Cons(T, List<T>)
}
结束语
所有的Swfit资源都来自于官方文档《The Swift Programming Language》,学习一门语言最好的方法是使用它。所以可以在Playground使用它或者在实际项目中运用它,Swift能够和Objective-C进行无缝对接。本文是翻译[Ray](1. http://www.raywenderlich.com/110982/swift-interview-questions-answers)的博客。