问题一:
下面代码中变量 tutorial1.difficulty 和 tutorial2.difficulty 的值分别是什么? 如果 Tutorial 是一个类,会有什么不同吗?为什么?
struct Tutorial {
var difficulty: Int = 1
}
var tutorial1 = Tutorial()
var tutorial2 = tutorial1
tutorial2.difficulty = 2
回答:
tutorial1.difficulty 等于 1, tutorial2.difficulty 等于 2.
swift中的结构体是值类型。是按值类型而不是引用类型复制值的。
创建了一个tutorial1的副本,并将其分配给tutorial2:
var tutorial2 = tutorial1
“ tutorial1 ”中未反映对“ tutorial12”的更改
如果 Tutorial 是一个类,那么 tutorial1 和 tutorial2 都等于 2.
swift中的类是引用类型。当你更改Tutorial1的属性时,你将看到它反映在Tutorial2中,反之亦然。
问题二:
你用var声明了view1,用let声明了view2。有什么区别,最后一行会编译通过吗?
import UIKit
var view1 = UIView()
view1.alpha = 0.5
let view2 = UIView()
view2.alpha = 0.5 // 此行是否编译?
回答:
是的,最后一行可以编译。view1 是一个变量,可以给它重新分配一个 UIView 类型的新实例。使用let,只能分配一次值,因此不会编译以下代码:
view2 = view1 // Error: view2 is immutable
UIView是一个具有引用语义的类,因此你可以改变view2的属性-这意味着最后一行将编译:
let view2 = UIView()
view2.alpha = 0.5 // Yes!
问题三:
这段复杂的代码按字母顺序对名称数组进行排序。尽可能简化闭包。
var animals = ["fish", "cat", "chicken", "dog"]
animals.sort { (one: String, two: String) -> Bool in
return one < two
}
print(animals)
回答:
类型推断系统会自动判断闭包中参数的类型和返回类型,就可以去掉类型:
animals.sort { (one, two) -> Bool in
return one < two
}
可以用$I符号替换参数名:
animals.sort { return $0 < $1 }
在单语句闭包中,可以省略返回关键字。最后一条语句的值将成为闭包的返回值:
animals.sort { $0 < $1 }
后,由于Swift知道数组的元素符合equatable,因此可以简单地编写:
animals.sort(by: <)
问题四:
此代码创建两个类:Address 和 Person 。然后它创建两个 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 的地址,他也搬到了新大楼。
print (ray.address.fullAddress)
这是怎么回事?你如何解决这个问题?
回答:
Address 是一个类,是引用类型,不管是通过Ray还是Brian访问,都是相同的实例。更改 headquarters 地址将同时更改两个人的地址。你能想象如果 Brian 收到Ray的邮件会发生什么情况吗,反之亦然?
解决方案是创建一个新 Address 对象来分配给Brian,或者将 Address 声明为结构体。
口述问题
什么是可选的,可选可以解决哪些问题?
使用可选类型(optionals)来处理值可能缺失的情况。在objective-c中,只有在使用nil特殊值的引用类型中才可以表示值缺失。值类型(如int或float)不具有此功能。
Swift将缺乏值概念扩展到引用类型和值类型。可选变量可以包含值或零,表示是否缺少值。
总结结构体和类之间的主要区别。
差异总结为:
- 类支持继承;结构不支持。
- 类是引用类型;结构体是值类型。
什么是通用类型,它们解决了什么问题?
在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
通过采用泛型,可以将这两个函数合并为一个函数,同时保持类型安全。下面是通用实现:
func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
由于在本例中测试的是相等性,所以将参数限制为遵守 Equatable 协议的任何类型。此代码实现了预期的结果,并防止传递不同类型的参数。
在某些情况下,你无法避免使用隐式展开的选项。什么时候?为什么?
使用隐式展开选项的最常见原因是:
- 如果在实例化时无法初始化非本质的属性。
一个典型的例子是Interface Builder出口,它总是在它的所有者之后初始化。在这种特定情况下 - 假设它在Interface Builder中正确配置 - 你在使用它之前保证outlet是非零的。 - 解决强引用循环问题,即两个实例相互引用并且需要对另一个实例的非空引用。在这种情况下,将一侧标记为无主引用,而另一侧使用隐式解包可选。
打开可选项的各种方法有哪些?他们如何评价安全性?
var x : String? = "Test"
提示:有七种方法。
- 强行打开 - 不安全。
let a: String = x!
- 隐式解包变量声明 - 在许多情况下不安全。
var a = x!
- 可选绑定 - 安全。
if let a = x {
print("x was successfully unwrapped and is = \(a)")
}
- 可选链接 - 安全。
let a = x?.count
- 无合并操作员 - 安全。
let a = x ?? ""
- 警卫声明 - 安全。
guard let a = x else {
return
}
- 可选模式 - 安全。
if case let a? = x {
print(a)
}
中级面试题
问题一:
nil 和 .none有什么区别?
没有区别,因为Optional.none(简称.none)和nil是等价的。
实际上,下面的等式输出为真:
nil == .none
使用nil更常见,推荐使用。
这是 thermometer 作为类和结构的模型。编译器在最后一行报错。为什么编译失败?
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)
使用可变函数正确声明ThermometerStruct以更改其内部变量temperature。编译器报错是因为你在通过 let 创建的实例上调用了registerTemperature,因为它是不可变的。将let改为var通过编译。
对于结构体,必须将内部状态更改为mutating的方法标记,但不能从不可变变量中调用它们。
这段代码会打印什么?为什么?
var thing = "cars"
let closure = { [thing] in
print("I love \(thing)")
}
thing = "airplanes"
closure()
它会打印:I love cars。声明闭包时,捕获列表会创建一个 thing 副本。这意味着即使为 thing 分配新值,捕获的值也不会改变。
如果省略闭包中的捕获列表,则编译器使用引用而不是副本。因此,当调用闭包时,它会反映对变量的任何更改。可以在以下代码中看到:
var thing = "cars"
let closure = {
print("I love \(thing)")
}
thing = "airplanes"
closure() // Prints: "I love airplanes"
问题四
这是一个全局函数,用于计算数组中唯一值的数量:
func countUniques<T: Comparable>(_ array: Array<T>) -> Int {
let sorted = array.sorted()
let initial: (T?, Int) = (.none, 0)
let reduced = sorted.reduce(initial) {
($1, $0.0 == $1 ? $0.1 : $0.1 + 1)
}
return reduced.1
}
它使用sorted方法,因此它将T限制为符合Comparable协议的类型。
你这样调用它:
countUniques([1, 2, 3, 3]) // result is 3
问题: 将此函数重写为Array上的扩展方法,以便你可以编写如下内容:
[1, 2, 3, 3].countUniques() // should print 3
你可以将全局countUniques(_ :)重写为Array扩展:
extension Array where Element: Comparable {
func countUniques() -> Int {
let sortedValues = sorted()
let initial: (Element?, Int) = (.none, 0)
let reduced = sortedValues.reduce(initial) {
($1, $0.0 == $1 ? $0.1 : $0.1 + 1)
}
return reduced.1
}
}
请注意,仅当泛型 Element 符合Comparable时,新方法才可用。
问题五
这是 divide 对象的两个可选 Double 类型变量的方法。在执行实际解析之前,有三个先决条件需要验证:
- 变量 dividend 必须包含非空的值
- 变量 divisor 必须包含非空的值
- 变量 divisor 不能等于零
func divide(_ dividend: Double?, by divisor: Double?) -> Double? {
if dividend == nil {
return nil
}
if divisor == nil {
return nil
}
if divisor == 0 {
return nil
}
return dividend! / divisor!
}
使用guard语句并且不使用强制解包来改进此功能。
func divide(_ dividend: Double?, by divisor: Double?) -> Double? {
guard let dividend = dividend, let divisor = divisor, divisor != 0 else {
return nil
}
return dividend / divisor
}
问题六
使用 if let 重写第五题
func divide(_ dividend: Double?, by divisor: Double?) -> Double? {
if let dividend = dividend, let divisor = divisor, divisor != 0 {
return dividend / divisor
} else {
return nil
}
}
中级口述问题
第一题
在Objective-C中,声明一个这样的常量:
const int number = 0;
Swift对应的写法:
let number = 0
const是在编译时使用值或表达式初始化的变量,必须在编译时解析。
使用let创建的不可变是在运行时确定的常量。你可以使用静态或动态表达式对其进行初始化。这允许声明如下:
let higherNumber = number + 5
请注意,只能分配一次值。
第二题
声明一个 static 修饰的属性或函数,请在值类型上使用static修饰符。这是一个结构的例子:你用 static 修饰值类型。这是一个结构体的例子:
struct Sun {
static func illuminate() {}
}
对于类,可以使用static或class修饰符。他们实现了相同的目标,但方式不同。你能解释一下它们有何不同?
static 使属性或方法为静态并不可重写。使用class可以重写属性或方法。
应用于类时,static将成为class final的别名。
例如,在此代码中,当你尝试重写 illuminate() 时,编译器会抱错:
class Star {
class func spin() {}
static func illuminate() {}
}
class Sun : Star {
override class func spin() {
super.spin()
}
// error: class method overrides a 'final' class method
override static func illuminate() {
super.illuminate()
}
}
问题三
你可以在 extension 里添加存储属性吗?为什么行或不行呢?
不,这是不可能的。你可以使用扩展来向现有类型添加新行为,但不能更改类型本身或其接口。如果添加存储的属性,则需要额外的内存来存储新值。扩展程序无法管理此类任务。
问题四
Swift中的协议是什么?
协议是一种定义方法,属性和其他要求蓝图的类型。然后,类,结构或枚举可以遵守协议来实现这些要求。
遵守协议需要实现该协议的要求。该协议本身不实现任何功能,而是定义功能。可以扩展协议以提供某些要求的默认实现或符合类型可以利用的其他功能。
高级书面问题
第一题
思考以下 thermometer 模型结构体:
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定义了一些协议,使你可以使用赋值运算符初始化具有文字值的类型。
采用相应的协议并提供公共初始化器允许特定类型的文字初始化。在 Thermometer 的情况下,实现ExpressibleByFloatLiteral如下:
extension Thermometer: ExpressibleByFloatLiteral {
public init(floatLiteral value: FloatLiteralType) {
self.init(temperature: value)
}
}
现在,可以使用float创建实例。
var thermometer: Thermometer = 56.8