Swift面试集锦

作者: 小和大大 | 来源:发表于2022-12-30 14:49 被阅读0次

    一、基础

    1、final关键词的用法
    final关键词的作用:它修饰的类、方法、变量是不能被继承或重写的,编译器会报错。另外,通过它可以显示的指定函数的派发机制
    
    
    2、常见的数据类型
    常见的数据类型
    枚举(enum) Optional
    值类型(value type) 结构体(struct) Bool、Int、Float、Double、Character
    String、Array、Dictionary、Set
    引用类型(reference type) 类(class)
    3、函数重载
    1. 规则
    函数名相同
    参数个数不同 || 参数类型不同 || 参数标签不同
    
    2.注意
    返回值类型与函数重载无关
    默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(c++会报错)
    可变参数、省略函数标签、函数重载一起使用产生二义性时,编译器有可能会报错
    
    
    4、内联函数

    如果开启了编译器优化(Release模式默认会开启优化),编译器会自动将某些函数变成内联函数
    将函数调用展开成函数体

    1、哪些函数不会被自动内联?

    • 函数体比较长
    • 包含递归调用
    • 包含动态派发
    // 永远不会被内联(即使开启了编译器优化)
    @inline(never) func test() {
    print("test")
    }
    
    // 开启编译器优化后,即使代码很长,也会被内联(递归调用函数、动态派发的函数除外)
    @inline(__always) func test() {
    print("test")
    }
    
    

    在Release模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用

    5、递归枚举

    递归枚举是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 indirect 来表示该成员可递归

    enum ArithmeticExpression {
        case number(Int)
        indirect case addition(ArithmeticExpression, ArithmeticExpression)
        indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    
    // 你也可以在枚举类型开头加上 indirect 关键字来表明它的所有成员都是可递归的:
    // 定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员 addition 和 multiplication 的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。
    indirect enum ArithmeticExpression {
        case number(Int)
        case addition(ArithmeticExpression, ArithmeticExpression)
        case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    
    // 使用 ArithmeticExpression 这个递归枚举创建表达式 (5 + 4) * 2
    let five = ArithmeticExpression.number(5)
    let four = ArithmeticExpression.number(4)
    let sum = ArithmeticExpression.addition(five, four)
    let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
    
    // 算术表达式求值的函数
    // 该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法运算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。
    func evaluate(_ expression: ArithmeticExpression) -> Int {
        switch expression {
        case let .number(value):
            return value
        case let .addition(left, right):
            return evaluate(left) + evaluate(right)
        case let .multiplication(left, right):
            return evaluate(left) * evaluate(right)
        }
    }
    print(evaluate(product)) // 打印“18”
    
    
    6、MemoryLayout
    /// 用法一
    MemoryLayout<password>.stride // 分配的占用内存
    MemoryLayout<password>.size // 实际的内存大小
    MemoryLayout<password>.alignment // 对齐参数
    
    /// 用法二
    let pwd = password.number(1, 1, 1, 1)        
    MemoryLayout.stride(ofValue: pwd)
    MemoryLayout.size(ofValue: pwd)
    MemoryLayout.alignment(ofValue: pwd)
    
    
    7、关联值跟原始值的区别
    - 枚举类型变量会占用1个字节 不论枚举变量类型是Int还是String 都不会根据定义类型来计算字节大小
    - 关联值跟原始值的区别
     1、关联类型的枚举,其实是会存储对应的关联类型的值的,关联类型占用多少个字节就影响枚举的内存
     关联值会占用枚举变量的内存,会根据外界传值类型计算大小
     2、原始值不允许你自定义,也不会根据枚举类型计算内存大小
     原始值不会占用枚举变量的内存,只会占用1个字节,用来标记枚举类型
    
    
    8、类跟结构体的区别
    本质区别:结构体(枚举)是值类型,类是引用类型(指针类型)
    结构体:
    1.编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始值
    2.一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器
    
    类:
    1.类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
    2.如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
    3.成员的初始化是在这个初始化器中完成的
    
    
    9、值类型与引用类型
    值类型
    值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
    类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
    
    引用类型
    引用赋值给var、let或者给函数传参,是将内存地址拷贝一份
    类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝(shallow copy)
    
    
    10、闭包(引用类型)

    https://wenku.baidu.com/view/c69d9f4e26c52cc58bd63186bceb19e8b9f6ec56.html

    1、闭包

    是我们最常用的闭包的一种,是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
    
    1.闭包捕获值的本质是在堆区开辟内存然后存储其在上下文中捕获到的值
    2.修改值也是修改的堆空间的值
    3.闭包是一个引用类型
    4.闭包的底层结构是一个结构体
            - 首先存储闭包的地址
            - 加上捕获值的地址
    5.在捕获的值中,会对定义的变量和函数中的参数分开存储
    6.存储的时候内部会有一个HeapObject结构,用于管理内存,引用计数
    7.函数是特殊的闭包,只不过函数不捕获值,所以在闭包结构体中只存储函数地址,不存储指向捕获值的指针
    
    使用闭包有很多好处:
    1.可以利用上下文自动推断参数和返回值的类型
    2.隐士返回单表达式闭包,也就是但表达式的时候可以省略return关键字
    3.参数名称可以简写或者不写,使用$0表示第一个参数
    4.尾随闭包表达式
    
    

    2、自动闭包

    - 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
    - 这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
    - 这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
    - 自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。
    - 延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。
    
    

    3、尾随闭包

    11、逃逸闭包&非逃逸闭包

    1、非逃逸闭包的特性

    Swift 3.0之后,系统默认闭包参数就是被@noescapeing修饰的
    执行时机:在函数体内执行
    声明周期:在函数执行完后,闭包也就消失了
    
    

    2、逃逸闭包

    逃逸闭包的定义:当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式参数前些@escapeing来明确闭包是允许逃逸的。
    另外使用@escapeing修饰的闭包在其闭包体内需要显示的使用self,对于显示的使用self主要是希望开发者注意循环引用。
    
    逃逸闭包的使用时机分为以下两种情况:
    保存在一个函数外部定义的变量中
    延迟调用
    
    

    总结

    关于逃逸闭包和非逃逸闭包基本就说完了,下面稍作总结:
    1.首先两者都是作为函数参数
    2.非逃逸闭包在函数中调用,并且是函数结束前就调用完成
    3.非逃逸闭包不会产生循环引用,因为作用域在函数内,函数执行完毕会释放函数内的所有对象
    4.针对非逃逸闭包编译器会优化内存管理的调用
    5.根据官方文档的说明,非逃逸闭包会保存在栈上(未验证出来)
    6.逃逸闭包在函数返回后才会调用,也就是闭包逃离出了函数的作用域
    7.逃逸闭包中可能会产生循环引用
    8.逃逸闭包中需要显示的引用self,主要作用是提醒开发者注意循环引用
    
    
    12、解决循环引用

    1、weak也是我们OC中的一种解决循环引用的方式,在Swift中使用weak修饰的实例变量默认为可选类型,所以在使用weak后p对象会成为可选类型,在使用的时候需要使用?或者!

    func test() {
        var p = Person()
        p.myClourse = { [weak p] in
            p?.age += 1
        }
    }
    
    

    2、unowned也是Swift中独有的一种解决循环引用的方式,相较于weak,使用unowned修饰后的对象不是可选类型,也不可置为nil。unowned的意思是假定当前对象有值,所以就可能存在野指针的情况,所以在使用的过程中要注意使用的位置。而weak修饰的对象一旦为nil的时候,由于swift特性,可选值为nil就不会执行后续的代码,相较于unowned比较安全

    func test() {
        var p = Person()
        p.myClourse = { [unowned p] in
            p.age += 1
        }
    }
    
    
    13、捕获列表

    在上文中我们提到的[weak p]和[unowned p]在swift中被称为捕获列表
    捕获列表:

    定义在闭包的参数列表之前

    • 使用[]括起来
    • 即使是省略参数和返回值的时候也要使用in关键字
    • [weak p]和[unowned p]是分别使用weak和unowned关键字修饰p
    • 如果不需要修饰,我们可以写成[p]
    对于闭包中捕获列表中的常量,闭包会捕获外部相同名称的变量或常量来初始化捕获列表中定义的常量,其有如下特点:
    
    1.捕获列表中的是常量,不可修改
    2.该常量是值拷贝,相当于复制了一份外部变量或常量
    3.既然是常量就是不可修改的
    
    

    14、属性

    Swift中跟实例相关的属性可以分为2大类
    
    1.存储属性(Stored Property)
    类似于成员变量这个概念
    存储在实例的内存中
    结构体、类可以定义存储属性
    枚举不可以定义存储属性
    
    2.计算属性(Computed Property)
    本质就是方法(函数)
    不占用实例的内存
    枚举、结构体、类都可以定义计算属性
    
    
    15、inout
    1.如果实参有物理内存地址,且没有设置属性观察器
    直接将实参的内存地址传入函数(实参进行引用传递)
    
    2.如果实参是计算属性或者设置了属性观察器
    采取了Copy In Copy Out的做法
      - 调用该函数时,先复制实参的值,产生副本【get】
      - 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
      - 函数返回后,再将副本的值覆盖实参的值【set】
    
    3.总结:inout的本质就是引用传递(地址传递)
    
    
    16、类型属性
    严格来说,属性可以分为
    实例属性(Instance Property):只能通过实例去访问
    ✔️存储实例属性(Stored Instance Property):存储在实例的内存中,每个实例都有1份
    ✔️计算实例属性(Computed Instance Property)
    
    类型属性(Type Property):只能通过类型去访问
    ✔️存储类型属性(Stored Type Property):整个程序运行过程中,就只有1份内存(类似于全局变量)
    ✔️计算类型属性(Computed Type Property)
    
    可以通过static定义类型属性
    如果是类,也可以用关键字class
    
    
    类型属性细节
    1.不同于存储实例属性,你必须给存储类型属性设定初始值
    因为类型没有像实例那样的init初始化器来初始化存储属性
    
    2.存储类型属性默认就是lazy,会在第一次使用的时候才初始化
    就算被多个线程同时访问,保证只会初始化一次
    存储类型属性可以是let
    
    3.枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
    
    
    17、方法
    枚举、结构体、类都可以定义实例方法、类型方法
    实例方法(Instance Method):通过实例对象调用
    类型方法(Type Method):通过类型调用,用static或者class关键字定义
    
    self
    在实例方法中代表实例对象
    在类型方法中代表类型
    
    
    18、mutating
    结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改
    在func关键字前加mutating可以允许这种修改行为
    
    只有将协议中的实例方法标记为mutating
    才允许结构体、枚举的具体实现修改自身内存
    类在实现方法时不用加mutating,枚举、结构体才需要加mutating
    
    
    19、@discardableResult
    在func前面加个@discardableResult,可以消除:函数调用后返回值未被使用的警告⚠
    
    
    20、重写类型方法、下标
    被class修饰的类型方法、下标,允许被子类重写
    被static修饰的类型方法、下标,不允许被子类重写
    
    
    21、重写属性
    1、子类可以将父类的属性(存储、计算)重写为计算属性
    2、子类不可以将父类属性重写为存储属性
    3、只能重写var属性,不能重写let属性
    4、重写时,属性名、类型要一致
    5、子类重写后的属性权限不能小于父类属性的权限
    - 如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
    - 如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的
    
    
    22、重写类型属性
    被class修饰的计算类型属性,可以被子类重写
    被static修饰的类型属性(存储、计算),不可以被子类重写
    
    
    23、final
    被final修饰的方法、下标、属性,禁止被重写
    被final修饰的类,禁止被继承
    
    
    24、两段式初始化
    Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查
    
    *两段式初始化
    第1阶段:初始化所有存储属性
    ① 外层调用指定\便捷初始化器
    ② 分配内存给实例,但未初始化
    ③ 指定初始化器确保当前类定义的存储属性都初始化
    ④ 指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链
    
    第2阶段:设置新的存储属性值
    ① 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例
    ② 初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)
    ③ 最终,链中任何便捷初始化器都有机会定制实例以及使用self
    
    
    25、安全检查
    ① 指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
    ② 指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
    ③ 便捷初始化器必须先调用同类中的其它初始化器,然后再为任意属性设置新值
    ④ 初始化器在第1阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self
    ⑤ 直到第1阶段结束,实例才算完全合法
    
    
    26、初始化器

    单独init修饰的为指定初始化器
    convenience修饰的为便捷初始化器

    // 指定初始化器
    //init(parametets) {
    //    statements
    //}
    
    // 便捷初始化器
    //convenience init(parametets) {
    //    statements
    //}
    
    
    27、重写
    ① 当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)
    
    ② 如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override
      因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器
    
    
    28、自动继承
    ① 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
    
    ② 如果子类提供了父类所有指定初始化器的实现(要么通过方式①继承,要么重写)
    子类自动继承所有的父类便捷初始化器
    
    ③ 就算子类添加了更多的便捷初始化器,这些规则仍然适用
    
    ④ 子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则②的一部分
    
    
    29、required
    用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
    如果子类重写了required初始化器,也必须加上required,不用加override
    
    
    30、属性观察器

    父类的属性在它自己的初始化器中赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器

    31、可失败初始化器
    类、结构体、枚举都可以使用init?定义可失败初始化器
    
    不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器
    
    可以用init!定义隐式解包的可失败初始化器
    
    可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包
    
    如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
    
    可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的
    
    
    32、反初始化器(deinit)
    deinit叫做反初始化器,类似于C++的析构函数、OC中的dealloc方法
    
    当类的实例对象被释放内存时,就会调用实例对象的deinit方法
    class Person {
        deinit {
          print("Person对象销毁了")
        }
    }
    
    deinit不接受任何参数,不能写小括号,不能自行调用
    
    父类的deinit能被子类继承
    
    子类的deinit实现执行完毕后会调用父类的deinit
    
    
    33、可选链
    ① 如果可选项为nil,调用方法、下标、属性失败,结果为nil
    ② 如果可选项不为nil,调用方法、下标、属性成功,结果会被包装成可选项
    ③ 如果结果本来就是可选项,不会进行再次包装
    
    多个?可以链接在一起
    如果链中任何一个节点是nil,那么整个链就会调用失败
    
    
    34、协议(procotol)
    协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
    
    协议中定义方法时不能有默认参数值
    默认情况下,协议中定义的内容必须全部都实现
    
    
    35、协议中的属性
    协议中定义属性时必须用var关键字
    实现协议时的属性权限要不小于协议中定义的属性权限
    
    协议定义get、set,用var存储属性或get、set计算属性去实现
    
    协议定义get,用任何属性都可以实现
    
    为了保证通用,协议中必须用static定义类型方法、类型属性、类型下标
    
    
    36、协议中的init、init?、init!
    协议中还可以定义初始化器init
    非final类实现时必须加上required
    
    如果从协议实现的初始化器,刚好是重写了父类的指定初始化器
    那么这个初始化必须同时加required、override
    
    协议中定义的init?、init!,可以用init、init?、init!去实现
    协议中定义的init,可以用init、init!去实现
    
    
    37、CaseIterable
    让枚举遵守CaseIterable协议,可以实现遍历枚举值
    
    
    38、CustomStringConvertible
    遵守CustomStringConvertible、CustomDebugStringConvertible协议,都可以自定义实例的打印字符串
    
    print调用的是CustomStringConvertible协议的description
    debugPrint、po调用的是CustomDebugStringConvertible协议的debugDescription
    
    
    38、Any、AnyObject
    Swift提供了2种特殊的类型:Any、AnyObject
    Any:可以代表任意类型(枚举、结构体、类,也包括函数类型)
    AnyObject:可以代表任意类类型(在协议后面写上: AnyObject代表只有类能遵守这个协议)
    在协议后面写上: class也代表只有类能遵守这个协议
    
    
    39、rethrows
    rethrows表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
    
    
    40、defer
    defer语句:用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
    
    defer语句将延迟至当前作用域结束之前执行
    
    defer语句的执行顺序与定义顺序相反
    
    
    41、局部作用域

    可以使用do 实现局部作用域

    42、关联类型(Associated Type)
    关联类型的作用:给协议中用到的类型定义一个占位名称
    协议中可以拥有多个关联类型
    
    
    43、 some
    使用some关键字声明一个不透明类型
    some限制只能返回一种类型
    some除了用在返回值类型上,一般还可以用在属性类型上
    
    
    44、运算符重载
    类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载
    
    操作符预定义 prefix infix postfix
    prefix: 运算符在运算值的前方; postfix:运算符在运算值的后方;infix:运算符在运算值之间
    precedencegroup:定义运算符的其它属性
    
    
    45、Equatable
    要想得知2个实例是否等价,一般做法是遵守Equatable 协议,重载== 运算符
    与此同时,等价于重载了!= 运算符
    引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符=== 、!==
    
    
    46、Comparable
    要想比较2个实例的大小,一般做法是:
    遵守Comparable 协议
    重载相应的运算符
    
    
    47、自定义运算符(Custom Operator)
    可以自定义新的运算符:在全局作用域使用operator进行声明
    
    prefix operator 前缀运算符
    postfix operator 后缀运算符
    infix operator 中缀运算符: 优先级组
    
    precedencegroup 优先级组{
      associativity: 结合性(left\right\none)
      higherThan: 比谁的优先级高
      lowerThan: 比谁的优先级低
      assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级
    }
    
    prefix operator +++
    infix operator +- : PlusMinusPrecedence
    precedencegroup PlusMinusPrecedence {
      associativity: none
      higherThan: AdditionPrecedence
      lowerThan: MultiplicationPrecedence
      assignment: true
    }
    
    

    Apple文档参考:
    https://developer.apple.com/documentation/swift/swift_stadard_library/operator_declarations
    https://docs.swift.org/swiftbook/ReferenceManual/Declarations.html#ID380

    48、扩展
    Swift中的扩展,有点类似于OC中的分类(Category)
    扩展可以为枚举、结构体、类、协议添加新功能
    可以添加方法、计算属性、下标、(便捷)初始化器、嵌套类型、协议等等
    
    扩展不能办到的事情
    不能覆盖原有的功能
    不能添加存储属性,不能向已有的属性添加属性观察器
    不能添加父类
    不能添加指定初始化器,不能添加反初始化器
    
    
    49、访问控制(Access Control)
    在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排列, 实体指被访问级别修饰的内容)
    open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
    public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
    internal:只允许在定义实体的模块中访问,不允许在其他模块中访问
    fileprivate:只允许在定义实体的源文件中访问
    private:只允许在定义实体的封闭声明中访问
    
    绝大部分实体默认都是internal级别
    
    
    访问级别的使用准则
    
    一个实体不可以被更低访问级别的实体定义,比如
    变量\常量类型≥ 变量\常量
    参数类型、返回值类型≥ 函数
    父类≥ 子类
    父协议≥ 子协议
    原类型≥ typealias
    原始值类型、关联值类型≥ 枚举类型
    定义类型A时用到的其他类型≥ 类型A
    
    
    元组类型
    元组类型的访问级别是所有成员类型最低的那个
    
    泛型类型
    泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个
    
    成员、嵌套类型
    类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
    一般情况下,类型为private或fileprivate,那么成员\嵌套类型默认也是private或fileprivate
    一般情况下,类型为internal或public,那么成员\嵌套类型默认是internal
    
    成员的重写
    子类重写成员的访问级别必须≥ 子类的访问级别,或者≥ 父类被重写成员的访问级别
    父类的成员不能被成员作用域外定义的子类重写
    
    直接在全局作用域下定义的private等价于fileprivate
    
    getter、setter
    getter、setter默认自动接收它们所属环境的访问级别
    可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限
    fileprivate(set) public var num = 10
    internal(set) public subscript(index: Int) -> Int { return 0 }
    
    初始化器
    如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器
    因为public类的默认初始化器是internal级别
    required初始化器≥ 它的默认访问级别
    如果结构体有private\fileprivate的存储实例属性,那么它的成员初始化器也是private\fileprivate
    否则默认就是internal
    
    枚举类型的case
    不能给enum的每个case单独设置访问级别
    每个case自动接收enum的访问级别
    public enum定义的case也是public
    
    协议
    协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
    public协议定义的要求也是public
    协议实现的访问级别必须≥ 类型的访问级别,或者≥ 协议的访问级别
    
    扩展
    如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
    如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
    可以单独给扩展添加的成员设置访问级别
    不能给用于遵守协议的扩展显式设置扩展的访问级别
    
    
    50、内存管理
    Swift的ARC中有3种引用
    强引用(strong reference):默认情况下,引用都是强引用
    
    弱引用(weak reference):通过weak定义弱引用
    必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil
    ARC自动给弱引用设置nil时,不会触发属性观察器
    
    无主引用(unowned reference):通过unowned定义无主引用
    不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中unsafe_unretained)
    试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
    • Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated
    
    
    51、weak、unowned的使用限制
    weak、unowned只能用在类实例上面
    
    protocol Livable : AnyObject {}
    class Person {}
    
    weak var p0: Person?
    weak var p1: AnyObject?
    weak var p2: Livable?
    
    unowned var p10: Person?
    unowned var p11: AnyObject?
    unowned var p12: Livable?
    
    
    52、循环引用(Reference Cycle)
    weak、unowned 都能解决循环引用的问题,unowned 要比weak 少一些性能消耗
    在生命周期中可能会变为nil 的使用weak
    初始化赋值后再也不会变为nil 的使用unowned
    
    
    53、闭包的循环引用
    如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的(因为在实例初始化完毕之后才能引用self)
    如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)
    
    
    54、@escaping
    非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
    非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
    逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明
    
    逃逸闭包不可以捕获inout参数
    
    
    55、指针
    Swift中也有专门的指针类型,这些都被定性为“Unsafe”(不安全的),常见的有以下4种类型
    UnsafePointer<Pointee> 类似于const Pointee *
    UnsafeMutablePointer<Pointee> 类似于Pointee *
    UnsafeRawPointer 类似于const void *
    UnsafeMutableRawPointer 类似于void *
    
    
    56、字面量
    Swift自带的绝大部分类型,都支持直接通过字面量进行初始化
    Bool、Int、Float、Double、String、Array、Dictionary、Set、Optional等
    
    常见字面量的默认类型
    public typealias IntegerLiteralType = Int
    public typealias FloatLiteralType = Double
    public typealias BooleanLiteralType = Bool
    public typealias StringLiteralType = String
    
    
    57、字面量协议
    Swift自带类型之所以能够通过字面量初始化,是因为它们遵守了对应的协议
    Bool : ExpressibleByBooleanLiteral
    Int : ExpressibleByIntegerLiteral
    Float、Double : ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral
    Dictionary : ExpressibleByDictionaryLiteral
    String : ExpressibleByStringLiteral
    Array、Set : ExpressibleByArrayLiteral
    Optional : ExpressibleByNilLiteral
    
    
    58、模式(Pattern)
    什么是模式?
    模式是用于匹配的规则,比如switch的case、捕捉错误的catch、if\guard\while\for语句的条件等
    
    Swift中的模式有
    通配符模式(Wildcard Pattern)
    标识符模式(Identifier Pattern)
    值绑定模式(Value-Binding Pattern)
    元组模式(Tuple Pattern)
    枚举Case模式(Enumeration Case Pattern)
    可选模式(Optional Pattern)
    类型转换模式(Type-Casting Pattern)
    表达式模式(Expression Pattern)
    
    
    59、MARK、TODO、FIXME
    // MARK: 类似于OC中的#pragma mark
    // MARK: - 类似于OC中的#pragma mark -
    // TODO: 用于标记未完成的任务
    // FIXME: 用于标记待修复的问题
    
    
    60、条件编译
    // 操作系统:macOS\iOS\tvOS\watchOS\Linux\Android\Windows\FreeBSD
    #if os(macOS) || os(iOS)
    // CPU架构:i386\x86_64\arm\arm64
    #elseif arch(x86_64) || arch(arm64)
    // swift版本
    #elseif swift(<5) && swift(>=3)
    // 模拟器
    #elseif targetEnvironment(simulator)
    // 可以导入某模块
    #elseif canImport(Foundation)
    #else
    #endif
    
    
    61、打印
    func log<T>(_ msg: T,
                file: NSString = #file,
                line: Int = #line,
                fn: String = #function) {
    #if DEBUG
        let prefix = "\(file.lastPathComponent)_\(line)_\(fn):"
        print(prefix, msg)
    #endif
    }
    
    
    62、dynamic
    被@objc dynamic 修饰的内容会具有动态性,比如调用方法会走runtime那一套流程
    
    
    63、关联对象(Associated Object)
    在Swift中,class依然可以使用关联对象
    默认情况,extension不可以增加存储属性
    借助关联对象,可以实现类似extension为class增加存储属性的效果
    class Person {}
    extension Person {
        private static var AGE_KEY: Void?
        var age: Int {
            get {
                (objc_getAssociatedObject(self, &Self.AGE_KEY) as? Int) ?? 0
            }
            set {
                objc_setAssociatedObject(self,
                                         &Self.AGE_KEY,
                                         newValue,
                                         .OBJC_ASSOCIATION_ASSIGN)
            }
        }
    }
    
    
    64、资源名管理

    https://github.com/mac-cain13/R.swift
    https://github.com/SwiftGen/SwiftGen

    65、RxSwift

    源码:https://github.com/ReactiveX/RxSwift
    中文文档: https://beeth0ven.github.io/RxSwift-Chinese-Documentation/

    模块说明
    RxSwift:Rx标准API的Swift实现,不包括任何iOS相关的内容
    RxCocoa:基于RxSwift,给iOS UI控件扩展了很多Rx特性

    66、Swift

    Swift于2015年正式开源,github地址: https://github.com/apple/swift
    标准库源码位置:https://github.com/apple/swift/tree/master/stdlib/public/core

    几个可能会经常看的目录
    docs:一些文档
    stdlib:Swift源码
    lib:C++源码
    include:C++头文件
    
    

    Array分析
    map、filter
    https://github.com/apple/swift/blob/master/stdlib/public/core/Sequence.swift

    flatMap、compactMap、reduce
    https://github.com/apple/swift/blob/master/stdlib/public/core/SequenceAlgorithms.swift

    Substring分析
    append、lowercased、uppercased
    https://github.com/apple/swift/blob/master/stdlib/public/core/Substring.swift

    Optional分析
    map、flatMap、==、??
    https://github.com/apple/swift/blob/master/stdlib/public/core/Optional.swift

    Metadata分析
    文档: https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst

    其他参考
    https://github.com/apple/swift/blob/master/include/swift/ABI/Metadata.h
    https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataKind.def
    https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataValues.h
    https://github.com/apple/swift/blob/master/include/swift/Reflection/Records.h

    原文链接:https://www.jianshu.com/p/600e379eb44a

    相关文章

      网友评论

        本文标题:Swift面试集锦

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