Swift编程语言只有四岁了,但是它已经成为iOS的主要开发语言。 迅速发展到5.0版,Swift已成为一种既复杂又功能强大的语言,既满足面向对象又符合功能的范式。 在每个新版本中,它都添加了更多功能。
但是您对Swift的了解程度如何? 在本文中,您将找到有关Swift采访的示例问题。
您可以使用这些问题来采访候选人以测试他们的知识,也可以测试自己的知识。 如果您不知道答案,请不要担心:每个问题都有答案。
问题分为三组:
- 初学者 :适合初学者。 您读了几本书,并在自己的应用程序中应用了Swift。
- 中级 :适合对语言真正感兴趣的人。 您已经阅读了很多,并且经常尝试。
- 进阶 :适合最进阶的开发人员-那些喜欢钻研语法并使用先进技术的人。
每个级别有两种类型的问题:
- 书面的 :适合通过电子邮件进行测试,因为他们建议编写代码。
- 口头的 :可以用电话或面谈时使用,因为有足够的口头回答。
在阅读本文时,请保持
操场畅通,以便能够检查问题中的代码。 所有答案均已在
Xcode 10.2和
Swift 5上进行了测试。
初学者
书面问题问题1考虑以下代码:
struct Tutorial { var difficulty: Int = 1 } var tutorial1 = Tutorial() var tutorial2 = tutorial1 tutorial2.difficulty = 2
tutorial1.difficulty和
tutorial2.difficulty的值是什么? 如果本
教程是一堂课,会有什么区别吗? 怎么了
答案tutorial1.difficulty为1,tutorial2.difficulty为2。
在Swift中,结构是值类型。 它们被复制,未被引用。 以下行复制了
tutorial1并将其分配给
tutorial2 :
var tutorial2 = tutorial1
对
tutorial2的更改不会影响
tutorial1 。
如果Tutorial是一个类,则
tutorial1.difficulty和
tutorial2.difficulty等于2。Swift中的类是引用类型。 更改tutorial1属性时,您将看到对tutorial2所做的相同更改,反之亦然。
问题2您使用
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
但是,UIView是具有引用语义的类,因此您可以更改view2的属性-这意味着代码将进行
编译 。
问题3此代码按字母顺序对数组进行排序。 尽可能简化闭合。
var animals = ["fish", "cat", "chicken", "dog"] animals.sort { (one: String, two: String) -> Bool in return one < two } print(animals)
答案Swift会
自动确定闭包参数
的类型和返回类型,因此您可以
删除它们 :
animals.sort { (one, two) in return one < two }
您可以使用
$ i表示法替换参数名称:
animals.sort { return $0 < $1 }
由单个语句组成的闭包可能不包含
return关键字。 最后执行的语句的值成为闭包的返回结果:
animals.sort { $0 < $1 }
最后,由于Swift知道数组的元素符合
Equatable协议,因此您可以简单地编写:
animals.sort(by: <)
更新:
hummingbirddj进一步简化了:
在这种情况下,您甚至可以缩短: animals.sort()
-升序排序,适用于实现Comparable的类型。
问题4此代码创建两个类:
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和brian共享的类的同一实例。 更改总部将更改两者的地址。
要解决此问题,您可以创建Address类的新实例并将其分配给Brian,或者将Address声明为结构而不是class 。
口头问题问题1什么是
可选的 ,它们可以解决什么问题?
答案可选允许任何类型的变量呈现“ 无值 ”情况。 在Objective-C中,“ no value”仅在使用特殊nil值的引用类型中可用。 值类型(例如int或float )不具有此功能。
Swift已将“无值”的概念扩展到值类型。 可选变量可以包含value或nil ,指示不存在值。
问题2简要列出
结构和
类之间的主要区别。
答案类支持继承,但结构不支持。
类是引用类型,结构是值类型。
问题3什么是
泛型 ?它们的用途是什么?
答案在Swift中,您可以在类,结构和枚举中使用
泛型 。
泛型解决了代码重复的问题。 如果您有一个接受一种类型参数的方法,则有时您必须复制代码才能使用另一种类型的参数。
例如,在此代码中,第二个函数是第一个函数的“克隆”,除了它具有字符串参数而不是整数。
func areIntEqual(_ x: Int, _ y: Int) -> Bool { return x == y } func areStringsEqual(_ x: String, _ y: String) -> Bool { return x == y } areStringsEqual("ray", "ray")
使用泛型,您可以将两个功能合而为一,同时确保类型安全:
func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y } areTheyEqual("ray", "ray") areTheyEqual(1, 1)
由于您正在测试相等性,因此将类型限制为符合
Equatable协议的类型。 该代码可提供所需的结果,并防止错误类型的参数的传递。
问题4在某些情况下,将无法避免隐式
展开的可选。 什么时候,为什么?
答案使用隐式展开的可选变量的最常见原因是:
- 当您无法初始化创建时不为nil的属性时。 一个典型的示例是Interface Builder的插座 ,该插座始终在其所有者之后初始化。 在这种特殊情况下,如果在Interface Builder中正确配置了所有内容,则可以确保在使用前,插座为非零。
- 为了解决强引用循环的问题,当类的两个实例相互引用并且需要对另一个实例的非null引用时。 在这种情况下,您可以将链接的一侧标记为未拥有 ,而另一侧则使用隐式可选扩展。
问题5有哪些部署可选的方法? 根据安全性对它们进行评分。
var x : String? = "Test"
提示:只有7种方法。
答案强制展开是不安全的。
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语句很安全。
guard let a = x else { return }
可选模式 -安全。
if case let a? = x { print(a) }
中级
书面问题问题1nil和
.none有什么
区别 ?
答案没什么区别,
Optional.none (
简写为.none )和
nil是等效的。
实际上,以下语句将返回
true :
nil == .none
普遍建议使用
nil 。
问题2这是类和结构形式的温度计模型。 编译器抱怨最后一行。 那里怎么了
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 ,以更改内部变量。 编译器抱怨您正在调用用let创建的实例的registerTemperature方法,因此该实例是不可变的。 将let更改为var将修复编译错误。
在结构中,应将修改内部变量的方法标记为mutating ,但不能使用不可变实例调用这些方法。
问题3此代码将输出什么,为什么?
var thing = "cars" let closure = { [thing] in print("I love \(thing)") } thing = "airplanes" closure()
答案它将打印:我爱汽车。 当声明闭包时,捕获列表将创建变量的副本。 这意味着即使分配了新值,捕获的变量也不会更改其值。
如果在闭包中省略捕获列表,则编译器将使用链接,而不是副本。 闭包调用将反映变量中的更改:
var thing = "cars" let closure = { print("I love \(thing)") } thing = "airplanes" closure()
问题4此函数可计算数组中唯一值的数量:
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 }
它使用排序,因此仅使用符合Comparable协议的类型。
您可以这样称呼它:
countUniques([1, 2, 3, 3])
将此函数重写为Array扩展,以便您可以像这样使用它:
[1, 2, 3, 3].countUniques()
译者注太痛苦了,这是一个可怕的功能。 为什么不呢?
func countUniques<T: Hashable>(_ array: Array<T>) -> Int { return Set(array).count }
答案 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 } }
问题5这是一个将两个可选双精度数相除的函数。 必须满足三个条件:
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语句而不是强制展开来重写此函数。
答案如果不满足条件,则Swift 2.0中引入的
guard语句将提供退出。 这是一个例子:
guard dividend != nil else { return nil }
您还可以将guard语句用于
可选绑定 ,此后,扩展变量将
在guard语句之外可用:
guard let dividend = dividend else { return .none }
因此,您可以这样重写函数:
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend else { return nil } guard let divisor = divisor else { return nil } guard divisor != 0 else { return nil } return dividend / divisor }
请注意没有强制拆包,因为我们已经拆包了除数和除数,并将它们放入了非可选的不可变变量中。
还要注意,在guard语句之外也可以使用guard语句中解压缩的可选结果。
您可以通过对保护语句进行分组来进一步简化功能:
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend, let divisor = divisor, divisor != 0 else { return nil } return dividend / divisor }
问题6使用
if let语句重写问题5中的方法。
答案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 } }
口头问题问题1在Objective-C中,您可以这样声明一个常量:
const int number = 0;
在Swift中:
let number = 0
有什么区别?
答案在
Objective-C中,常量
在编译时使用此时应该知道
的值进行初始化。
用
let创建的不可变值是
在运行时定义的常数。 您可以使用静态或动态表达式对其进行初始化。 因此,我们可以这样做:
let higherNumber = number + 5
请注意,这样的分配只能完成一次。
问题2要为值类型声明静态属性或函数,请使用
static修饰符。 这是结构的示例:
struct Sun { static func illuminate() {} }
对于类,可以使用
静态或
类修饰符。 结果相同,但是有所不同。 描述一下。
答案static使属性或函数为static和
non-overlapping 。 使用类
将覆盖属性或函数。
在这里,编译器会发誓尝试覆盖
illuminate() :
class Star { class func spin() {} static func illuminate() {} } class Sun : Star { override class func spin() { super.spin() }
问题3是否可以使用
扩展名将 存储的属性添加到类型? 怎么或为什么不呢?
答案不,这是不可能的。 我们可以使用扩展将新行为添加到现有类型,但是我们不能更改类型本身或其接口。 要存储新存储的属性,我们需要额外的内存,而扩展程序无法做到这一点。
问题4Swift中的协议是什么?
答案协议是一种定义方法,属性等概述的类型。 类,结构或枚举可以采用协议来实现所有这些。 协议本身并不实现功能,而是对其进行定义。
进阶
书面问题问题1假设我们有一个定义温度计模型的结构:
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定义了协议,使您可以通过赋值使用文字来初始化类型。 应用适当的协议并提供公共初始化程序将允许使用文字进行初始化。 对于温度计,我们实现
ExpressibleByFloatLiteral :
extension Thermometer: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(temperature: value) } }
现在我们可以创建一个这样的实例:
var thermometer: Thermometer = 56.8
问题2Swift具有一组用于算术和逻辑运算的预定义运算符。 它还允许您创建自己的一元和二进制运算符。
根据以下要求定义和实现自己的幂运算符(^^):
- 以两个int作为参数
- 返回将第一个参数提高为第二个幂的结果
- 正确处理代数运算的顺序
- 忽略可能的溢出错误
答案创建新的操作员分为两个阶段:公告和实施。
该声明使用
operator关键字指定类型(一元或二进制),指定新运算符的字符序列,其关联性和执行优先级。
此处的运算符为^^,其类型为中缀(二进制)。 关联性是正确的。
Swift没有预定义的取幂等级。 在代数中,必须在乘/除之前计算乘幂。 因此,我们通过将乘幂高于乘法来创建自定义执行顺序。
本公告:
precedencegroup ExponentPrecedence { higherThan: MultiplicationPrecedence associativity: right } infix operator ^^: ExponentPrecedence
这是实现:
func ^^(base: Int, exponent: Int) -> Int { let l = Double(base) let r = Double(exponent) let p = pow(l, r) return Int(p) }
问题3以下代码定义了
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的两个实例。 他们中的哪一个使玛格丽塔与罗勒?
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可以搭配罗勒披萨,而lombardis1则可以没有披萨,因为它将使用扩展中定义的方法。
问题4以下代码无法编译。 你能解释一下他怎么了吗? 建议解决方案。
struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") } print(k) }
提示:有三种方法可以修复该错误。
答案Guard语句的
else块需要一个exit选项,可以使用
return ,抛出异常或调用
@noreturn 。 最简单的解决方案是添加
return语句 。
func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") return } print(k) }
此解决方案将引发异常:
enum KittenError: Error { 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(kitten: nil)
最后,这是对
@noreturn函数
fatalError()的调用。
struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") fatalError() } print(k) }
口头问题问题1闭包是引用类型还是值类型?
答案闭包是参考类型。 如果将闭包分配给变量,然后将其复制到另一个变量,则将链接复制到相同的闭包及其捕获列表。
问题2您可以使用
UInt类型存储无符号整数。 它实现了一个带有符号的从整体转换的初始化器:
init(_ value: Int)
但是,如果您指定负值,则以下代码不会编译:
let myNegative = UInt(-1)
根据定义,带符号整数不能为负。 但是,可以使用内存中负数的表示形式将其转换为无符号数。
如何在将负整数保留在内存中的同时将负整数转换为UInt?
答案为此有一个初始化程序:
UInt(bitPattern: Int)
并使用:
let myNegative = UInt(bitPattern: -1)
问题3描述Swift中的循环引用? 如何解决?
答案当两个实例相互包含一个强引用时,就会发生循环引用,由于这些实例都无法释放,因此导致内存泄漏。 当实例仍然有很强的引用时,无法释放该实例,但是一个实例持有另一个实例。
这可以通过在一侧替换链接来解决,方法是指定关键字weak或unowned 。
问题4Swift允许您创建递归枚举。 这是一个这样的枚举示例,其中包含具有两个关联类型T和List的Node变量:
enum List<T> { case node(T, List<T>) }
会有编译错误。 我们错过了什么?
答案我们忘记了
间接关键字,它允许类似的递归枚举选项:
enum List<T> { indirect case node(T, List<T>) }