Swift的面试问题及答案-part1

译文:Swift Interview Questions and Answers

Swift面试问题及答案

原文链接 : 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

虽然swift才发布不到一年的时间,但它已经成为最流行的开发语言之一了。
事实上,Swift,是一种复杂的语言,同时面向对象和面向函数,并且它仍然还在不断推出新的版本。

Swift有很多东西,但是你怎么能测试你学了多少?在这篇文章中,raywenderlich.com团队和我一起列了一个列表-有关swift的面试问题。
你可以用这些问题来测试面试者的Swift知识,或者测试你自己的!如果你不知道答案,不要担心,每一个问题都有一个答案。

问题分成了两部分:

1.笔试题目(Written questions):可以通过电子邮件来进行编程测试,需要写一些代码。
2.口头提问(Verbal questions):可以很好的在电话或面对面的面试中询问,适用于口头回答。

另外,每部分都分为三个等级:

  • 初级:适合初学者阅读,已经读过一两本有关Swift的书籍,并已经在自己的应用程序使用了Swift。
  • 中级:适合某些对语言有浓厚兴趣的人,阅读过很多有关Swift的博客文章,并想进一步进行尝试。
  • 高级:适合顶尖的-专注同时享受在程序语言里探索,挑战自己,并使用尖端技术的人。

如果你想试着回答这些问题,建议你打开Playground。所有答案都已经在Xcode 7 Beta 6中测试过。
准备好了吗?Buckle up(系好安全带?),开始了!

注:特别感谢团队成员 Warren Burton, Greg Heo, Mikael Konutgan, Tim Mitra, Luke Parham, Rui Peres, and Ray Wenderlich

Written Questions(书面的问题)

Beginners(初学者)

你好,padowan(应该是指学徒,也就是我们。。)。我将开始检测你的基本知识。

问题#1-Swift 1.0 or later

想有一个更好的方式来写这里的循环范围?

for var i = 0; i < 5; i++ {
    print("Hello!")
}
答案:
for i in 0...4 {
  print("Hello!")
}

Swift实现了二元操作符、闭区间操作符(...)和半闭区间操作符(..<)。第一个的范围(...)包括所有的值。例如,下面包含所有的整数,从0到4:

0...4

半闭区间操作符(..<)不包含最后一个元素。以下产生相同的0至4整数的结果:

0..<5

问题#2-Swift 1.0 or later

思考如下代码:

struct Tutorial {
  var difficulty: Int = 1
}
var tutorial1 = Tutorial()
var tutorial2 = tutorial1
tutorial2.difficulty = 2

tutorial1.difficulty和tutorial2.difficulty的值是什么呢?如果是一个类,又是什么呢?为什么?

答案:

tutorial1.difficulty is 1,
tutorial2.difficulty is 2.
Swift的struct是值类型,它们被复制的是值,而不是引用。下面的代码是创建一份tutorial1的拷贝并将其分配到tutorial2:

var tutorial2 = tutorial1

从这段代码开始,对tutorial2任何改变都不会影响到tutorial1。
如果Tutorial是类,tutorial1.difficulty和tutorial2.difficulty都会是2。Swift的类是引用类型。对tutorial1属性的任何变化会反映到tutorial2,反之亦然。

问题#3-Swift 1.0 or later

view1是用var声明的,而view2是用let声明的。这里的区别是什么,最后一行又可以编译通过么?

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 // Error: view2 is immutable

但是,UIView是一个类的引用,所以你可以改变View2属性(这意味着最后一行可以编译通过):

let view2 = UIView()
view2.alpha = 0.5 // Yes!

问题#4-Swift 1.0 or later

下面这段代码将数组按字母进行了排序,看起来很复杂,请尽可能的简化它。

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 }

在单语句闭包中,返回关键字可以省略。最后一个语句的返回值就是闭包的返回值:

let sortedAnimals = animals.sort { $0 < $1 }

这已经很简单了,但还不够,不要停!
对于字符串,有一个比较函数定义如下:

func <(lhs: String, rhs: String) -> Bool

这个小功能使你的代码更整洁:

let sortedAnimals = animals.sort(<)

注意,这里的每一步都是能编译和输出相同的结果的,并且你做了一个字符的闭包!

问题#5-Swift 1.0 or later

这段代码创建了2个类、Address和Person,并且创建了2个实例来表示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.fullAddress也跟着改变了!Address是一个类,并且具有引用的语义。headquarters是同一个实例,无论您访问它通过Ray或Brian,改变headquarters的Address将改变它的两个。
解决方案是重新分配给Brian一个新的Address,或设置Address为struct而不是class。

Intermediate(中级)

现在,开始挑战困难点的问题。你准备好了吗?

问题#1-Swift 2.0 or later

思考下列代码:

var optional1: String? = nil
var optional2: String? = .None

nil和.None有什么区别?optional1和optional1变量有何不同?

答案:

其实没有区别。Optional.None(.None是缩写)是初始化一个可选变量(没有值)的正确写法,而不只是语法糖.None。
下面的验证代码的输出是true:

nil == .None // On Swift 1.x this doesn't compile. You need Optional<Int>.None

请牢记,在底层下,optional是枚举类型:

enum Optional<T> {
  case None
  case Some(T)
}

问题#2-Swift 1.0 or later

这是温度计的模型,一个class和一个struct:

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声明与变异函数来改变其内部的温度变化,但编译器报错是因为registertemperature创建实例是通过let的,因此它是不变的。
在结构体中,函数改变内部状态必须得是可变数据类型,即用var声明的。

问题#3-Swift 1.0 or later

下面这段代码会print出什么?为什么?

var thing = "cars"
let closure = { [thing] in
  print("I love \\(thing)")
}
thing = "airplanes"
closure()
答案:

它会打印“I love cars”。当闭包被声明的时候,参数列表也会创建一份拷贝,所以参数的值不会改变,即使将一个新值赋值给一个。
如果在闭包中省略了参数列表([thing]),则编译器将使用一个引用而不是复制。在这种情况下,当被调用时该变量的任何变化都会产生变化。如下面的代码所示:
看不太懂就直接看代码吧= =

var thing = "cars"
let closure = {    
  print("I love \\(thing)")
}
thing = "airplanes"
closure() // Prints "I love airplanes"

问题#4-Swift 2.0 or later

这里有一个全局函数来计算数组中 “值是唯一” 的数目:

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
}

你可以这样使用这个方法:

countUniques([1, 2, 3, 3]) // result is 3

请把这个函数重写为数组的扩展方法,使得你可以按照下面代码那样使用:

[1, 2, 3, 3].countUniques() // should print 3
答案:

在Swift2.0中,泛型可以通过强制类型约束来进行扩展。如果泛型不满足约束,则该扩展既不可见也不可访问。

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
  }
}

注意新方法只有在当前的数据类型实现了Comparable的协议时才可用。比如,如果你这样调用的话编译器会报错:

import UIKit
let a = [UIView(), UIView()]
a.countUniques() // compiler error here because UIView doesn't implement Comparable

问题#5-Swift 2.0 or later

这里有一个函数来计算给定的两个double的可选数据类型的除法。在执行除法之前,需要满足三个条件:

  • dividend不为空
  • divisor不为空
  • divisor不为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!
}

代码虽然能工作,但有2个问题:

  • 它的条件可以guard语句
  • 它使用了强制拆包(那个!)不安全

请使用guard语句改进这个函数,避免使用强制拆包。

答案:

如果条件不符合,新的guard语句在Swift2.0提供了返回值。检查条件是非常有用的,因为它提供了一个清晰的方式表达方式--如果不需要对语句进行嵌套的话。这里就是一个例子:

guard dividend != .None else { return .None }

也可以结合可选数据类型,使得访问变量在guard检查之后:

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
}

另外,你还可以合并guard语句,使函数看起来更简单:

func divide(dividend: Double?, by divisor: Double?) -> Double? {
  guard let dividend = dividend, divisor = divisor where divisor != 0 else { return .None }
  return dividend / divisor
}

Advanced(高级)

问题#1-Swift 1.0 or later

思考下面的结构体,一个温度计的模型:

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
    采用相应的协议,提供一个公共的初始化方法用来实现特定类型的文字初始化。在Thermometer下,实现FloatLiteralConvertible协议如下:
extension Thermometer : FloatLiteralConvertible {
  public init(floatLiteral value: FloatLiteralType) {
    self.init(temperature: value)
  }
}

现在你可以用一个简单的浮点数据来创建一个实例。

var thermometer: Thermometer = 56.8

问题#2-Swift 1.0 or later

Swift拥有一组预定义的运算符,执行不同类型的操作,例如算术或逻辑。它还允许创建自定义操作,一元或二元。

按照以下要求自定义并实现一个 ^^ 幂运算符:

  • 以两个整数作为参数
  • 返回第一个参数与第二个参数的幂运算
  • 不用考虑潜在溢出错误
答案:

创建一个新的自定义操作符需要两步:声明和实现。
这部分我不太会翻译,是关于自定义操作符的,这里有Swift的相关资料(http://www.yiibai.com/swift/custom_operators.html
这是声明:

infix operator ^^ { associativity left precedence 155 }

实现的代码如下:

import Foundation
func ^^(lhs: Int, rhs: Int) -> Int {
  let l = Double(lhs)
  let r = Double(rhs)
  let p = pow(l, r)
  return Int(p)
}

请注意,它没有考虑溢出情况;如果操作产生的结果不能用int代表。比如大于int.max,会发生运行时错误。

问题#3-Swift 1.0 or later

你能用这样的原始值来定义一个枚举吗?为什么?

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)
}
答案:

不能。一个原始值类型必须:

  • 符合合理的协议
  • 从下列任一个类型中转换的:
    • Int
    • String
    • Character

在上面的代码中,原始类型是一个元组,是不符合条件的。

问题#4-Swift 1.0 or later

考虑下面的代码定义的Pizza结构体和Pizza协议,以及扩展,包含有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的实例。哪一个会成功调用margherita做出basil(一种面)?

let lombardis1: Pizzeria = Lombardis()
let lombardis2: Lombardis = Lombardis()
lombardis1.makeMargherita()
lombardis2.makeMargherita()
答案:

都能。Pizzeria协议声明makemargherita()方法提供了一个默认的实现。该方法是在lombardis实现重写。在这两种情况下声明的方法,在运行时都能正确执行。
如果协议没有声明makemargherita()方法,但在扩展中还提供了一个默认的实现呢?

protocol Pizzeria {
  func makePizza(ingredients: [String]) -> Pizza
} 
extension Pizzeria {
  func makeMargherita() -> Pizza {
    return makePizza(["tomato", "mozzarella"])
  }
}

在这种情况下,只有lombardis2做出Pizza,而lombardis1没有做出Pizza,因为lombardis1会去使用在扩展中定义的makeMargherita方法方法。它没有声明makeMargherita方法,就不会去调用结构体里面的那个makeMargherita方法。

问题#5-Swift 2.0 or later

以下代码编译时有错误。你能发现哪里出错了么,为什么会发生错误?

struct Kitten {
} 
func showKitten(kitten: Kitten?) {
  guard let k = kitten else {
    print("There is no kitten")
  }
  print(k)
}

Hint: There are three ways to fix it.

答案:

else里面需要退出路径,使用return,或者抛出一个异常或声明这是一个@noreturn的方法。最简单的解决方案是添加返回语句。

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)

最后一个方法,调用fatalerror()方法,表明这是一个@noreturn方法。

struct Kitten {
} 
func showKitten(kitten: Kitten?) {
  guard let k = kitten else {
    print("There is no kitten")
    fatalError()
  }
  print(k)
}

Verbal questions part2部分待译。


第一次翻译这么长的文章,也许会有翻译错误的地方,大家可以在评论中帮忙指出。

另外,我在管理一个微信公众号SwiftTips,每天发布一些Swift的文章什么的,欢迎关注

qrcode_for_gh_e36203f1d8d6_258.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容