译文:Swift Interview Questions and Answers
Swift面试问题及答案-part2
原文链接 : Swift Interview Questions and Answers
译文发布地址:
part1: http://www.jianshu.com/p/e98d7dc625ff
part2:http://www.jianshu.com/p/0b9bdffc2523
原文作者 : Antonio Bello
译者 : lfb_CD
欢迎关注我的微博:http://weibo.com/lfbWb
写在前面的话:
译文中有许多链接和代码是我为方便读者阅读添加的---我可是一名有情怀的译者
Verbal questions 口头的提问
到达这一步你已经很优秀了,但是你还不能自称为绝地武士(意思是还不能认为自己很厉害了)任何人只要够努力都可以解决上面那些代码,但是你如何处理下面这些不断出现的理论知识和实践问题呢
回答这些问题仍然需要你在Playground里实际操作
初级
Question #1 - Swift 1.0 or later
什么是可选数据类型?它解决了什么问题?
答案:
可选数据类型是用来表示一个数据类型缺少具体值。在Objective-C中,只有引用类型才允许没有具体值,用的是nil
来表示。比如float
数据类型没有这样的特性
Swift用可选数据类型扩展了值类型和引用数据类型。可选数据类型任何时候都允许拥有具体值或者为
nil
Question #2 - Swift 1.0 or later
什么时候使用结构体,我们什么时候又应该使用类呢?
答案:
有一个正在进行的讨论,关于过度使用类超过结构体这究竟是好还是坏。
函数式编程倾向于多使用值数据类型,而面向对象编程更倾向于使用类。
在Swift中,类和结构体有很多不同的特性。可以得出下面这样一份总结:
- 类支持继承,结构体不行
- 类是引用类型,结构体是值类型
我们并没有一个规则来判定使用哪一个是最好。一般的建议是在能够达到你目标的前提下且使用到的代价最小(结构体比类节省内存空间)。除非你需要用到继承或者是引用的语法,否则那就采用结构体。
更多关于类和结构体的细节问题请查阅这篇文章:
detailed post on the matter.(我们还在翻译中..)
注意:在运行时,结构体比类具有更高性能的因为结构体的方法调用是静态绑定的,而类的方法调用是在运行时动态解析。这是另一个很好的理由来使用结构体,而不是使用类。
Question #3 - Swift 1.0 or later
什么是泛型,它们又解决了什么问题?
答案:
泛型是用来使代码能安全工作。在Swift中,泛型可以在函数数据类型和普通数据类型中使用,例如类、结构体或枚举。
泛型解决了代码复用的问题。有一种常见的情况,你有一个方法,需要一个类型的参数,你为了适应另一种类型的参数还得重新再写一遍这个方法。
比如,在下面的代码中,第二个方法是第一个方法的“克隆体”:
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
>这段代码能达到了目的,但是编译的时候并不安全。它允许一个字符串和一个整型数据进行比较:
>
areTheyEqual(1, "ray")
程序可能不会崩溃,但是允许一个字符串和一个整型数据进行比较可能不会得到想要的结果。
采用泛型的话,你可以将上面两个方法合并为一个,并同时还保证了数据类型安全。这是实现代码:
>
func areTheyEqual<T: Equatable>(x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
### Question #4 - Swift 1.0 or later
偶尔你也会不可避免地使用隐式可选类型。那请问什么时候我们需要这么做?为什么需要这么做?
>#### 答案:
>最常见的情况是:
>>1. 不为nil的才能初始化它的值。一个典型的例子是一个界面生成器的出口(Interface Builder outlet),它总是在它的本体初始化后初始化。在这种情况下,如果它在界面构建器(Interface Builder)中正确地配置了,就能够保证在使用前outlet不为nil的。
>>
>>2.为解决强引用循环问题(不知道循环引用是什么的可以看我们翻译的Swift官方文档[自动引用计数篇](http://wiki.jikexueyuan.com/project/swift/chapter2/16_Automatic_Reference_Counting.html))。当2个实例互相引用时,就需要一个不为nil的引用指向另一个实例。引用的一边可以修饰为unowned,另一边使用隐式可选类型,便可解决循环引用问题。
>>为方便大家理解我贴段代码上来(原文是没用的)
>>```
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
理解得不太清楚可以点开上面链接查看
小贴士:尽量不要使用隐式可选类型。使用它们会增加运行时崩溃的几率。在某些情况下,出现崩溃也许是程序员需要这么做,这里也有一个更好的方法来达到同样的效果,例如,使用fatalerror()。
Question #5 - Swift 1.0 or later
你知道有哪些解包的方式?它们是否是安全解包的?
答案:
ps:下面代码为译者本人为方便读者阅读而添加,如还有不理解的地方可以根据关键字搜索相关文档
强制用!展开 -- 操作不安全
声明隐式可选类型变量 -- 在许多情况下是不安全的
(var implicitlyUnwrappedString: String!)
>* optional binding -- 安全
>
> ```
var count: Int?
count = 100
if let validCount = count {
"count is " + String(validCount) //count is 100
} else {
"nil"
}
新的Swift2 guard声明 -- 安全
自判断链接--安全
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has (roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
>* nil -- 安全
## 中级
渐渐地你挑战了这里。也许你之前问题解决得很好,但是让我们看看你是否能很好地通过下面这些问题。
### Question #1 - Swift 1.0 or later
Swift是一种面向对象的语言还是一种面向函数的语言?
>#### 答案:
Swift是一种混合语言,同时支持这两种范式。
>它实现了三种面向对象的基本原则
>
* 封装
* 继承
* 多态
>
当Swift作为一种面向函数的语言来理解时,会有不同却相似的方式来定义它。
其中一种是较为常见的维基百科上的:"…a programming paradigm [...] that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data."
“……编程范式[……],将数学函数作为一种值,不需要考虑其中的状态变化和数据变化。”
>你要觉得Swift是一个门成熟的面向函数的编程语言是比较牵强的,但它确实也具有很多面向函数编程的基本要素。
### Question #2 - Swift 1.0 or later
下列哪些特性是Swift含有的?
1. 泛型类
2. 泛型结构
3. 泛型接口
>#### 答案:
>* Swift中包括了上述的1和2。泛型可以在类,结构体,枚举全局方法或者普通方法中使用。
>* 3用typealias实现了。它本身不是一个泛型类型,它是一个占位符名称。它通常被称为关联类型,当一个协议声明时使用。如果有不明白的可查看[喵神的文章](http://swifter.tips/typealias/)
### Question #3 - Swift 1.0 or later
在Objective-C语言中,常量可以被定义如下:
const int number = 0;
这是Swift对应的代码:
let number = 0
请问它们有什么区别么?如果有,你能解释下它们之间的区别么?
>#### 答案:
`const` 是一个变量在编译时初始化的值或着是在编译时解决初始化的。
>
`let`声明的常数是在运行的时候创建,它最终可以被初始化为静态或者动态的表达式。注意它的值只能被分配一次。
### Question #4 - Swift 1.0 or later
Swift声明一个静态属性或静态函数可以使用static来修饰。这是一个结构体的例子:
struct Sun {
static func illuminate() {}
}
对于类,可以使用static或class来修饰。他们可以达到同样的目标,但实际上他们是不同的。你能解释他们有什么不同吗?
>#### 答案:
使用static声明的一个静态属性或者方法并不可被覆盖override(子类覆盖父类的方法)。
使用class就可以覆盖。
>
当用在类里的时候,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()
}
}
### Question #5 - Swift 1.0 or later
可以使用扩展添加存储属性吗?
>#### 答案:
no,这是不可能的。扩展可以为已经存在的数据类型添加新的行为,但是不允许改变类型本身或它的接口。
如果您添加了存储的属性,您需要额外的内存来存储新的值。扩展不能完成这样的任务。
##高级
噢,孩子,你是个聪明的人,对吗?那就一步一步向上攀爬吧。
### Question #1 - Swift 1.2 or later
在Swift1.2中,你可以解释一下声明一个枚举类型的泛型的问题么?
以一个含有两个泛型参数`T`和`V`的枚举`Either`为例,用`T`作为`Left`的相关值类型,`V`作为`Right`的相关值类型:
enum Either<T, V> {
case Left(T)
case Right(V)
}
小贴士:检查这种情况应该在一个Xcode中的项目中,不是在Playground上。还得注意,这个问题是Swift 1.2相关的,所以你需要Xcode 6.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>)
}
这个问题只在Swift 1.0或者以上出现,但是2.0已经解决了。
### Question #2 - Swift 1.0 or later
闭包是值类型还是引用类型的?
>#### 答案:
闭包是引用类型。如果一个闭包被分配给一个变量,该变量被复制到另一个变量,它们实际是引用的相同一个闭包并且它里面的参数列表也同样会被复制。
### Question #3 - Swift 1.0 or later
`UInt`数据类型用于存储整数。它实现了从一个带符号的整数转化为`UInt`的初始化方式:
init(_ value: Int)
然而,如果您提供了一个负值,例如下面的代码会产生一个编译错误:
let myNegative = UInt(-1)
我们知道负数在内部是使用补码表示,你怎么才能把一个负数的`Int`转化为`UInt`,同时保持它在内存中的表示形式?
>#### 答案:
这儿有一个初始化的方式:
>
UInt(bitPattern: Int)
### Question #4 - Swift 1.0 or later
你能描述一下你用Swift时遇到的循环引用么?你是怎么解决的?
>#### 答案:
循环引用是指两个实例彼此强引用,导致内存泄漏,因为两个实例都不会被收回。原因是只要有一个强引用,实例就不会被回收。你可以通过 `weak` 或者 `unowned` 引用 替换其中一个强引用,从而打破强引用循环
>
想了解更多可以查看我们翻译的Swift官方文档里的有关章节[自动引用计数篇](http://wiki.jikexueyuan.com/project/swift/chapter2/16_Automatic_Reference_Counting.html)
### Question #5 - Swift 2.0 or later
Swift2.0增添了一个新关键字实现递归枚举。这里有一个枚举包含一个`Node`,`Node`有两个的相关的值类型,`T`和`List`:
enum List<T> {
case Node(T, List<T>)
}
请问那个可以实现递归枚举的关键字是什么?
>#### 答案:
>
`indirect`
>
代码如下:
>
enum List<T> {
indirect case Cons(T, List<T>)
}
# 之后的方向?
恭喜你看到了文章末尾,如果你确实不太清楚那些答案,希望你也不要感到沮丧!
其中的一些问题很复杂,Swift是一个非常丰富、且富有表现力的语言。我们还有很多需要学。此外,苹果不断改善Swift与添加新的功能,所以非常有可能还存在一些非常好用的但是我们并不知道的(言外之意就是让我们多研究)。
To get to know Swift or build upon what you already know, be sure to check out our in-depth, tutorial-rich book, [Swift by Tutorials](http://www.raywenderlich.com/store/swift-by-tutorials), or sign up for our hands-on tutorial conference [RWDevCon](http://www.rwdevcon.com/)!
当然,最根本的资料当然还是由苹果公司写撰写的[The Swift Programming Language](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/) (ps:肯定还有我们翻译的[Swift中文版](http://wiki.jikexueyuan.com/project/swift/)撒!)
最后,使用一门语言才是学习语言最好的方式。只需要你在Playground上写写或在一个真正的项目使用Swift。Swift几乎可以与objective-c无缝地混合,所以建立一个你已经非常熟悉的现有项目是一个很好的方法来学习Swift的来龙去脉。
感谢您的阅读和解决以上这些问题所做出的努力!你也可以在评论中留下你的问题或者一些你的发现。我也不介意你提出一些你项目中遇到的问题。我们可以互相学习。咱们论坛上见!
***
第一次翻译这么长的文章,也许会有翻译错误的地方,大家可以在评论中帮忙指出。
另外,我在管理一个微信公众号SwiftTips,每天发布一些Swift的文章什么的,欢迎关注
![qrcode_for_gh_e36203f1d8d6_258.jpg](http://upload-images.jianshu.io/upload_images/40785-18a32b6e9b5e5eb6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)