采访:斯威夫特。 问与答

Swift编程语言只有四岁了​​,但是它已经成为iOS的主要开发语言。 迅速发展到5.0版,Swift已成为一种既复杂又功能强大的语言,既满足面向对象又符合功能的范式。 在每个新版本中,它都添加了更多功能。

但是您对Swift的了解程度如何? 在本文中,您将找到有关Swift采访的示例问题。

您可以使用这些问题来采访候选人以测试他们的知识,也可以测试自己的知识。 如果您不知道答案,请不要担心:每个问题都有答案。

问题分为三组:

  • 初学者 :适合初学者。 您读了几本书,并在自己的应用程序中应用了Swift。
  • 中级 :适合对语言真正感兴趣的人。 您已经阅读了很多,并且经常尝试。
  • 进阶 :适合最进阶的开发人员-那些喜欢钻研语法并使用先进技术的人。

每个级别有两种类型的问题:

  • 书面的 :适合通过电子邮件进行测试,因为他们建议编写代码。
  • 口头的 :可以用电话或面谈时使用,因为有足够的口头回答。

在阅读本文时,请保持操场畅通,以便能够检查问题中的代码。 所有答案均已在Xcode 10.2Swift 5上进行了测试。

  • 初学者


    书面问题
    问题1
    考虑以下代码:

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

    tutorial1.difficultytutorial2.difficulty的值是什么? 如果本教程是一堂课,会有什么区别吗? 怎么了

    答案
    tutorial1.difficulty为1,tutorial2.difficulty为2。

    在Swift中,结构是值类型。 它们被复制,未被引用。 以下行复制了tutorial1并将其分配给tutorial2

     var tutorial2 = tutorial1 

    tutorial2的更改不会影响tutorial1

    如果Tutorial是一个类,则tutorial1.difficultytutorial2.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 // : view2 is immutable 

    但是,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
    此代码创建两个类: AddressPerson 。 还创建了Person类的两个实例( RayBrian )。

     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值的引用类型中可用。 值类型(例如intfloat )不具有此功能。
    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") // true areIntEqual(1, 1) // true 

    使用泛型,您可以将两个功能合而为一,同时确保类型安全:

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




  • 中级


    书面问题
    问题1
    nil.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() // Prints: "I love airplanes" 



    问题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]) //  3 


    将此函数重写为Array扩展,以便您可以像这样使用它:

     [1, 2, 3, 3].countUniques() //   3 


    译者注
    太痛苦了,这是一个可怕的功能。 为什么不呢?

     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
    这是一个将两个可选双精度数相除的函数。 必须满足三个条件:
    • 股息不应为零
    • 除数不应为零
    • 除数不得为0


     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() } // error: class method overrides a 'final' class method override static func illuminate() { super.illuminate() } } 



    问题3
    是否可以使用扩展名将 存储的属性添加到类型? 怎么或为什么不呢?

    答案
    不,这是不可能的。 我们可以使用扩展将新行为添加到现有类型,但是我们不能更改类型本身或其接口。 要存储新存储的属性,我们需要额外的内存,而扩展程序无法做到这一点。


    问题4
    Swift中的协议是什么?

    答案
    协议是一种定义方法,属性等概述的类型。 类,结构或枚举可以采用协议来实现所有这些。 协议本身并不实现功能,而是对其进行定义。



  • 进阶


    书面问题
    问题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 



    问题2
    Swift具有一组用于算术和逻辑运算的预定义运算符。 它还允许您创建自己的一元和二进制运算符。

    根据以下要求定义和实现自己的幂运算符(^^):
    • 以两个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
    以下代码定义了PizzaPizzeria协议的结构,并扩展了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中的循环引用? 如何解决?

    答案
    当两个实例相互包含一个强引用时,就会发生循环引用,由于这些实例都无法释放,因此导致内存泄漏。 当实例仍然有很强的引用时,无法释放该实例,但是一个实例持有另一个实例。

    这可以通过在一侧替换链接来解决,方法是指定关键字weakunowned


    问题4
    Swift允许您创建递归枚举。 这是一个这样的枚举示例,其中包含具有两个关联类型T和List的Node变量:

     enum List<T> { case node(T, List<T>) } 

    会有编译错误。 我们错过了什么?

    答案
    我们忘记了间接关键字,它允许类似的递归枚举选项:

     enum List<T> { indirect case node(T, List<T>) } 




Source: https://habr.com/ru/post/zh-CN449410/


All Articles