美文网首页iOS面试iOS面试iOS 面试
答《 卓同学的 Swift 面试题 》

答《 卓同学的 Swift 面试题 》

作者: PierceDark | 来源:发表于2018-03-07 14:49 被阅读303次

    原文链接 卓同学的 Swift 面试题

    class 和 struct 的区别

    class 为类, struct 为结构体, 类是引用类型, 结构体为值类型, 结构体不可以继承

    不通过继承,代码复用(共享)的方式有哪些

    扩展, 全局函数

    Set 独有的方法有哪些?

    // 定义一个 set
    let setA: Set<Int> = [0,1, 2, 3, 4, 4]// {1, 2, 3, 4}, 顺序可能不一致, 同一个元素只有一个值
    let setB: Set<Int> = [1, 3, 5, 7, 9]// {1, 3, 5, 7, 9}
    // 取并集 A | B
    let setUnion = setA.union(setB)// {0,1, 2, 3, 4, 5, 7, 9}
    // 取交集 A & B
    let setIntersect = setA.intersection(setB)// {1, 3}
    // 取差集 A - B
    let setRevers = setA.subtracting(setB) // {0,2, 4}
    // 取对称差集, A XOR B = A - B | B - A
    let setXor = setA.symmetricDifference(setB) //{0,2, 4, 5, 7, 9}
    
    

    实现一个 min 函数,返回两个元素较小的元素

    func getMin<T: Comparable>(_ a: T, _ b: T) -> T {
        return a < b ? a : b
    }
    getMin(1, 2)
    

    map、filter、reduce 的作用 && map 与 flatmap 的区别

    具体请参看我的博客 Swift 的高阶函数,map、flatMap、filter、reduce
    map 函数 -- 对数组中的每一个对象做一次运算

    let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
            
            let resultAry = stringArray.map { (element) -> Int? in
                if element.length > 0 {
                    return element.length
                }else{
                    return nil
                }
                
            }
            print(resultAry)
            //[Optional(11), Optional(5), Optional(6), Optional(5), Optional(1), nil]
    

    fiatMap 函数 -- 也是对每个对象做一次运算,但是有区别
    区别1 不会返回 nil ,会把 optional 类型解包

    let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
            let resultAry = stringArray.flatMap { (element) -> Int? in
                if element.length > 0 {
                    return element.length
                }else{
                    return nil
                }
                
            }
            print(resultAry)
            //[11, 5, 6, 5, 1]
    

    区别2 会把N 维数组变成成一个 1维 数组 返回

    let stringArray = [["Objective-C", "Swift"], ["Python", "HTML5", "C",""]]
            
            let resultAry = stringArray.map { $0 }
            print(resultAry)
            //[["Objective-C", "Swift"], ["Python", "HTML5", "C", ""]]
    
            //flatMap
            let stringArray = [["Objective-C", "Swift"], ["Python", "HTML5", "C",""]]
            
            let resultAry = stringArray.flatMap { $0 }
            print(resultAry)
            //["Objective-C", "Swift", "Python", "HTML5", "C", ""]
    

    filter 过滤,数组中的元素按照 闭包里面的规则过滤

    let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
            
            let resultAry = stringArray.filter { (element) -> Bool in
                //元素长度大于5的 取出
                return element.length >= 5
            }
            print(resultAry)
            //["Objective-C", "Swift", "Python", "HTML5"]
    

    reduce 计算,按顺序对数组中的元素进行操作,然后记录操作的值再进行下一步相同的操作,可以想象成累加。

    let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
            
            let resultStr = stringArray.reduce("Hi , I'm PierceDark,") { (element1, element2) -> String in
                return element1 + " ," + element2
            }
            print(resultStr)
            //Hi , I'm PierceDark, ,Objective-C ,Swift ,Python ,HTML5 ,C ,
    

    什么是 copy on write

    写时复制, 指的是 swift 中的值类型, 并不会在一开始赋值的时候就去复制, 只有在需要修改的时候, 才去复制.
    这里有详细的说明
    《Advanced Swift》笔记:在Swift结构体中实现写时复制

    如何获取当前代码的函数名和行号

    #file用于获取当前文件文件名
    #line用于获取当前行号
    #column用于获取当前列编号
    #function用于获取当前函数名

    如何声明一个只能被类 conform 的 protocol

    加一个 class

    protocol SomeClassProtocl: class {
        func someFunction()
    }
    

    guard 使用场景

    提前判断,如果表达式是假或者值绑定失败的时候, 会执行 else语句, 且在 else 语句中一定要停止函数调用
    使用场景如登录判断 账号密码 长度

    defer 使用场景

    defer 语句块中的代码, 会在当前作用域结束前调用, 常用场景如异常退出后, 关闭数据库连接 ,而且 defer是先加入后执行

    func someQuery() -> ([Result], [Result]){
        let db = DBOpen("xxx")
        defer {
            db.close()
        }
        guard results1 = db.query("query1") else {
            return nil
        }
        guard results2 = db.query("query2") else {
            return nil
        }
        return (results1, results2)
    }
    
    

    String 与 NSString 的关系与区别

    String是结构体,值类型,NSString是类,引用类型。可以相互转换

    怎么获取一个 String 的长度

    "test".count 
    

    如何截取 String 的某段字符串

    PS:Swift截取子串真的不好用= = ,Swift4不用substring:to , substring:from, substring:with了,但还是很难用 .还不如转成NSString再去截取。。
    来自Stack Overflow

    let newStr = str.substring(to: index) // Swift 3
    let newStr = String(str[..<index]) // Swift 4
    
    let newStr = str.substring(from: index) // Swift 3
    let newStr = String(str[index...]) // Swift 4 
    
    let range = firstIndex..<secondIndex // If you have a range
    let newStr = = str.substring(with: range) // Swift 3
    let newStr = String(str[range])  // Swift 4
    

    throws 和 rethrows 的用法与作用

    throws 用在函数上, 表示这个函数会抛出错误.
    有两种情况会抛出错误, 一种是直接使用 throw 抛出, 另一种是调用其他抛出异常的函数时, 直接使用 try xx 没有处理异常.

    enum DivideError: Error {
        case EqualZeroError;
    }
    func divide(_ a: Double, _ b: Double) throws -> Double {
        guard b != Double(0) else {
            throw DivideError.EqualZeroError
        }
        return a / b
    }
    func split(pieces: Int) throws -> Double {
        return try divide(1, Double(pieces))
    }
    
    

    rethrows 与 throws 类似, 不过只适用于参数中有函数, 且函数会抛出异常的情况, rethrows 可以用 throws 替换, 反过来不行

    func processNumber(a: Double, b: Double, function: (Double, Double) throws -> Double) rethrows -> Double {
        return try function(a, b)
    }
    

    try? 和 try!是什么意思

    这两个都用于处理可抛出异常的函数, 使用这两个关键字可以不用写 do catch.
    区别在于, try? 在用于处理可抛出异常函数时, 如果函数抛出异常, 则返回 nil, 否则返回函数返回值的可选值
    例子

    print(try? divide(3, 1))
    // Optional(3.0)
    print(try? divide(3, 0))
    // nil
    

    而 try! 则在函数抛出异常的时候崩溃, 否则则返会函数返回值, 相当于(try? xxx)!, 如:

    print(try! divide(3, 1))
    // 2.0
    print(try! divide(3, 0))
    // 崩溃
    

    associatedtype 的作用

    这里来自:yww的这篇回答

    protocol使用的泛型

    protocol ListProtcol {
        associatedtype Element
        func push(_ element:Element)
        func pop(_ element:Element) -> Element?
    }
    

    实现协议的时候, 可以使用 typealias 指定为特定的类型, 也可以自动推断, 如

    class IntList: ListProtcol {
        typealias Element = Int // 使用 typealias 指定为 Int
        var list = [Element]()
        func push(_ element: Element) {
            self.list.append(element)
        }
        func pop(_ element: Element) -> Element? {
            return self.list.popLast()
        }
    }
    class DoubleList: ListProtcol {
        var list = [Double]()
        func push(_ element: Double) {// 自动推断
            self.list.append(element)
        }
        func pop(_ element: Double) -> Double? {
            return self.list.popLast()
        }
    }
    
    

    使用泛型也可以

    class AnyList<T>: ListProtcol {
        var list = [T]()
        func push(_ element: T) {
            self.list.append(element)
        }
        func pop(_ element: T) -> T? {
            return self.list.popLast()
        }
    }
    
    

    可以使用 where 字句限定 Element 类型, 如:

    extension ListProtcol where Element == Int {
        func isInt() ->Bool {
            return true
        }
    }
    

    什么时候使用 final

    不能继承和重写,可用到 属性/函数/

    public 和 open 的区别

    这两个都用于在模块中声明需要对外界暴露的函数, 区别在于, public 修饰的类, 在模块外无法继承, 而 open 则可以任意继承, 公开度来说, public < open

    声明一个只有一个参数没有返回值闭包的别名

        typealias SomeClosuerType = (String) -> ()
        let someClosuer: SomeClosuerType = { (name: String) in
            print("hello,", name)
        }
    

    Self 的使用场景

    这里来自:yww的这篇回答
    Self 通常在协议中使用, 用来表示实现者或者实现者的子类类型

    protocol CopyProtocol {
        func copy() -> Self
    }
    

    如果是结构体去实现, 要将Self 换为具体的类型

    struct SomeStruct: CopyProtocol {
        let value: Int
        func copySelf() -> SomeStruct {
            return SomeStruct(value: self.value)
        }
    }
    

    如果是类去实现, 则有点复杂, 需要有一个 required 初始化方法, 具体可以看 这里

    class SomeCopyableClass: CopyProtocol {
        func copySelf() -> Self {
            return type(of: self).init()
        }
        required init(){}
    }
    

    dynamic 的作用

    动态化。因为Swift是一个静态语言,如果需要有和OC一样的动态化机制就需要加上dynamic
    应用场景有Runtime相关。参看Swift Runtime分析:还像OC Runtime一样吗?

    什么时候使用 @objc

    Swift4之后,继承NSObjectSwift类不会自动与 OC交互了,属性前面需要加上@objc
    另一个常用的地方为了在 Objective-CSwift 混编的时候, 能够正常调用 Swift代码. 可以用于修饰类, 协议, 方法, 属性.
    常用的地方是在定义 delegate协议中, 会将协议中的部分方法声明为可选方法, 需要用到

    @objc protocol OptionalProtocol {
        @objc optional func optionalFunc()
        func normalFunc()
    }
    class OptionProtocolClass: OptionalProtocol {
        func normalFunc() {
        }
    }
    let someOptionalDelegate: OptionalProtocol = OptionProtocolClass()
    someOptionalDelegate.optionalFunc?()
    
    

    Optional(可选型) 是用什么实现的

    枚举

    enum Optional<Wrapped> {
      case none
      case some(Wrapped)
    }
    

    如何自定义下标获取

    extension AnyList {
        subscript(index: Int) -> T{
            return self.list[index]
        }
        subscript(indexString: String) -> T?{
            guard let index = Int(indexString) else {
                return nil
            }
            return self.list[index]
        }
    }
    
    

    ?? 的作用

    当可选值为nil时,输出后面给定的值

    var test : String? = nil
    print(test ?? "optional = nil")
    //输出    optional = nil
    

    lazy 的作用

    用到的时候才初始化,懒加载

    一个类型表示选项,可以同时表示有几个选项选中(类似 UIViewAnimationOptions ),用什么类型表示

    这里来自:yww的这篇回答
    需要实现自OptionSet, 一般使用 struct 实现. 由于 OptionSet要求有一个不可失败的init(rawValue:) 构造器, 而 枚举无法做到这一点(枚举的原始值构造器是可失败的, 而且有些组合值, 是没办法用一个枚举值表示的)

    struct SomeOption: OptionSet {
        let rawValue: Int
        static let option1 = SomeOption(rawValue: 1 << 0)
        static let option2 =  SomeOption(rawValue:1 << 1)
        static let option3 =  SomeOption(rawValue:1 << 2)
    }
    let options: SomeOption = [.option1, .option2]
    
    

    inout 的作用

    输入输出参数,改变函数参数的值

    Error 如果要兼容 NSError 需要做什么操作

    这里来自:yww的这篇回答
    其实直接转换就可以, 例如 SomeError.someError as NSError 但是这样没有错误码, 描述等等, 如果想和 NSError 一样有这些东西, 只需要实现 LocalizedError 和 CustomNSError 协议, 有些方法有默认实现, 可以略过, 如:

    enum SomeError: Error, LocalizedError, CustomNSError {
        case error1, error2
        public var errorDescription: String? {
            switch self {
            case .error1:
                return "error description error1"
            case .error2:
                return "error description error2"
            }
        }
        var errorCode: Int {
            switch self {
            case .error1:
                return 1
            case .error2:
                return 2
            }
        }
        public static var errorDomain: String {
            return "error domain SomeError"
        }
        public var errorUserInfo: [String : Any] {
            switch self {
            case .error1:
                return ["info": "error1"]
            case .error2:
                return ["info": "error2"]
            }
        }
    }
    print(SomeError.error1 as NSError)
    // Error Domain=error domain SomeError Code=1 "error description error1" UserInfo={info=error1}
    
    

    下面的代码都用了哪些语法糖

    [1, 2, 3].map{ $0 * 2 }
    
    1. 快速创建数组
    2. 第一个参数用$0代替
    3. 闭包没有声明函数参数, 返回值类型, 数量, 依靠的是闭包类型的自动推断
    4. 闭包中语句只有一句时, 自动将这一句的结果作为返回值

    什么是高阶函数

    一个函数如果可以以某一个函数作为参数, 或者是返回值, 那么这个函数就称之为高阶函数, 如 map, reduce, filter

    如何解决引用循环

    1. 转换为值类型
    2. 注意各个强引用
      可以参考我的博客Swift 强引用的解决方案(unowned 、 weak 、隐式解析可选属性)

    下面的代码会不会崩溃,说出原因

    var mutableArray = [1,2,3]
    for _ in mutableArray {
      mutableArray.removeLast()
    }
    
    

    跑了一遍不会。。可能因为数组里面元素是连续的储蓄位置,然后删除掉之后不会置nil? 或者for in的时候复制了?

    给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明

    extension Set where Element == String {
        var isStringElement:Bool {
            return true
        }
    }
    

    定义静态方法时关键字 static 和 class 有什么区别

    static不能被继承 ,class可以

    一个 Sequence 的索引是不是一定从 0 开始?

    记得不是。但是想不起来例子。。。
    这里参考yww的这篇回答

    class Countdown: Sequence, IteratorProtocol {
        var count: Int
        init(count: Int) {
            self.count = count
        }
        func next() -> Int? {
           if count == 0 {
               return nil
           } else {
               defer { count -= 1 }
               return count
           }
       }
    }
    
    var countDown = Countdown(count: 5)
    print("begin for in 1")
    for c in countDown {
        print(c)
    }
    print("end for in 1")
    print("begin for in 2")
    for c in countDown {
        print(c)
    }
    print("end for in 2")
    
    

    最后输出的结果是

    begin for in 1
    5
    4
    3
    2
    1
    end for in 1
    begin for in 2
    end for in 2
    

    很明显, 第二次没有输出任何结果, 原因就是在第二次for in 的时候, 并没有将count 重置.

    数组都实现了哪些协议

    MutableCollection, 实现了可修改的数组, 如 a[1] = 2
    ExpressibleByArrayLiteral, 实现了数组可以从[1, 2, 3] 这种字面值初始化的能力

    如何自定义模式匹配

    不太懂
    http://swifter.tips/pattern-match/

    autoclosure 的作用

    自动闭包
    http://swifter.tips/autoclosure/

    编译选项 whole module optmization 优化了什么

    编译器可以跨文件优化编译代码, 不局限于一个文件.
    http://www.jianshu.com/p/8dbf2bb05a1c

    下面代码中 mutating 的作用是什么

    struct Person {
      var name: String {
          mutating get {
              return store
          }
      }
    }
    

    mutating表示有可能修改这个结构体,所以只有var 的对象才可以调用

    如何让自定义对象支持字面量初始化

    不太懂,参考yww的这篇回答
    有几个协议, 分别是
    ExpressibleByArrayLiteral 可以由数组形式初始化
    ExpressibleByDictionaryLiteral 可以由字典形式初始化
    ExpressibleByNilLiteral 可以由nil 值初始化
    ExpressibleByIntegerLiteral 可以由整数值初始化
    ExpressibleByFloatLiteral可以由浮点数初始化
    ExpressibleByBooleanLiteral 可以由布尔值初始化
    ExpressibleByUnicodeScalarLiteral
    ExpressibleByExtendedGraphemeClusterLiteral
    ExpressibleByStringLiteral
    这三种都是由字符串初始化, 上面两种包含有 Unicode 字符和特殊字符

    dynamic framework 和 static framework 的区别是什么

    静态库和动态库,
    静态库是每一个程序单独一份, 动态库多个程序之间共享
    个人只能打包静态库

    为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。

    数组的对象的储蓄地址是连续的,如果越界了,那取到的地址不一定可用,所以报错。毕竟还是需要有可以信任的部分的

    一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。

    泛型?

    func isNumber<T : SignedNumber>(number : T){
    print(" it is a number")
    }
    

    相关文章

      网友评论

      • 5adc1894f930:代码重复使用的应该还有protocol吧
      • 背锅_侠:var mutableArray = [1,2,3]
        for _ in mutableArray {
        mutableArray.removeLast()
        }

        这段代码不会崩溃的原因,我的理解是,for in 循环内部是一个Iterator的实现,array.makeIterator()返回了一个迭代器。所有for in 迭代的是一个迭代器,和原数组没有关系了,所以不会崩溃
      • 走停2015_iOS开发:好多啊 我竟然看完了
        PierceDark:@走停2015_iOS开发 :joy: 希望对你有用
      • 没故事的卓同学:答的非常好了。
        PierceDark:借卓大的题帮自己复习了

      本文标题:答《 卓同学的 Swift 面试题 》

      本文链接:https://www.haomeiwen.com/subject/jddjfftx.html