Resumo das perguntas da entrevista de desenvolvimento iOS Swift

Vantagens do Swift (em relação ao OC)

  • Swift é mais seguro e é uma linguagem type-safe
  • Menos código, sintaxe concisa, pode economizar muito código redundante
  • O Swift é mais rápido e tem maior desempenho de computação (a Apple otimiza especificamente o compilador)

A diferença entre classe (class) e estrutura (struct) no Swift e suas vantagens e desvantagens?

  • tipo:
    • tipo de referência
      • Ao realizar atribuição de variável, é por meio de cópia de ponteiro, que pertence à cópia rasa (cópia rasa)
      • O armazenamento de dados está no espaço de heap
    • Pode ser herdada (desde que a classe não seja modificada pela palavra-chave final), as subclasses podem usar as propriedades e métodos da classe pai
    • (Quando uma classe herda de Object, ela possui um mecanismo de tempo de execução) a conversão de tipo pode verificar e interpretar um objeto de instância em tempo de execução
    • Use deinit (destruidor) para liberar recursos semelhantes ao OC (dealloc)
    • O endereço do método da classe é incerto e o valor específico da chamada só pode ser determinado em tempo de execução
  • estrutura
    • tipo de valor
      • Quando a atribuição de variável é realizada, é uma cópia profunda (deep copy) e uma nova cópia é gerada
      • Os dados são armazenados no espaço da pilha (na maioria dos casos, não há necessidade de considerar o problema de vazamentos de memória, a característica do espaço da pilha é que ele será liberado quando for usado)
    • A estrutura chama o método e o valor de endereço específico do método pode ser determinado após a conclusão da compilação, para que possa ser chamado diretamente

Em resumo, a estrutura é preferida quando os requisitos do programa são atendidos


O que significa forte, fraco e sem dono em Swift? Qual é a diferença entre os dois? Quando usar sem dono?

O mecanismo de gerenciamento de memória do Swift é ARC (Automatic Reference Counting) como Objective-C. Seu princípio básico é que, quando um objeto não possui nenhuma referência forte a ele, a memória que ele ocupa será recuperada. Por outro lado, enquanto houver qualquer referência forte ao objeto, ele sempre existirá na memória.

  • strong Representa uma referência forte e é o atributo padrão. Quando um objeto é declarado strong como , significa que a hierarquia pai tem uma referência forte apontando para o objeto. Neste momento, a contagem de referência do objeto aumentará em 1.

  • weak 代表着弱引用。当对象被声明为 weak 时,父层级对此对象没有指向,该对象的引用计数不会增加1。它在对象释放后弱引用也随即消失。继续访问该对象,程序会得到 nil,不亏崩溃

  • unowned 与弱引用本质上一样。不同的是,unowned 无主引用 实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_unretained), 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)

  • weak unowned 只能用在 类实例上面

  • weakunowned 都能解决 循环引用,unowned 要比 weak 性能 稍高

    • 在生命周期中可能会 变成 nil 的使用 weak
    • 初始化赋值以后再也不会变成 nil 使用 unowned

Swift 中什么是可选类型?

  • Swift中可选类型为了表示 一个变量 允许为 空(nil)的情况
  • 类型名称后 加 ? 定义 可选项
  • 选项的本质是 枚举类型 

Swift 中什么 是 泛型?

  • 跟JS和Dart 类似,泛型 可以将类型 参数化,提高代码复用率,减少代码量
  • Swift泛型函数 并不会 在底层 生成 若干个 (匹配类型)函数 ,产生函数重载,而是: 在函数调用时,会将 参数的类型 传递给 目标函数
  • Swift泛型应用在协议上时,需要使用关联类型(associatedtype)

怎么理解 Swift中的泛型约束

泛型约束 可以 更精确的知道 参数 需要 遵循什么标准

//someT遵循的是某个class,someU遵循的是某个协议,这样在传参的时候明确参数类型
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}
复制代码

Swift 中 static 和 class 关键字的区别

在 Swift 中 static 和 class 都是表示「类型范围作用域」的关键字。

在所有类型(class[类]、struct、enum )中使用 

  • static 修饰都可以表示类方法类与属性(包括存储属性和计算属性)。
  • class 是专门用在 calss 类型中修饰类方法和类的计算属性(注意:无法使用 class 修饰存储属性)。

结构体只能用 static 修饰 类方法或属性

class类型 static class 的区别

在 class 类型中 static 和 class 都可以表示类型范围作用域,那区别是

  1. class 无法修饰存储属性,而 static 可以。
  2. 使用 static 修饰的类方法和类属性无法在子类中重载。也就是说 static 修饰的类方法和类属性包含了 final 关键字的特性。相当于 final class 。

一般在 protocol定义一个类方法或者类计算属性推荐使用 static 关键字来修饰。使用 protocol 时,在 struct 和 enum 中仍然使用 static,在 class 类型中 class 和 static 关键字都可以使用。


Swift 中的模式匹配?

模式 是用于 匹配的规则,比如 switch 的 case 、捕捉错误的 catch 、 if guard while for 语句条件等


Swift 中的访问控制?

Swift 提供了 5个 不同的访问级别,从高到低排列如下:

  • open
    • 允许在任意模块中访问、继承、重写
    • open 只能用在 类 、 类成员上
  • public
    • 允许在任意模块中访问
    • 修饰类时不允许其他模块进行继承、重写
  • internal - 默认
    • 只允许在定义实体的模块中访问,不允许在其他模块中访问
  • fileprivate
    • 只允许在定义实体的源文件中访问
  • private:
    • 只允许在定义实体的封闭声明中(作用域)访问

怎么理解 copy - on - write? 或者 理解Swift中的写时复制

值类型在复制时,复制对象 与 原对象 实际上在内存中指向同一个对象,**当且仅当 ** 修改复制的对象时,才会在内存中创建一个新的对象,

  • 为了提升性能,Struct, String、Array、Dictionary、Set采取了Copy On Write的技术
  • 比如仅当有“写”操作时,才会真正执行拷贝操作
  • 对于标准库值类型的赋值操作,Swift 能确保最佳性能,所有没必要为了保证最佳性能来避免赋值

原理

在结构体内部用一个引用类型来存储实际的数据,

  • 在不进行写入操作的普通传递过程中,都是将内部的reference的应用计数+1,
  • 当进行写入操作时,对内部的 reference 做一次 copy 操作用来存储新的数据;防止和之前的reference产生意外的数据共享。

swift中提供[isKnownUniquelyReferenced]函数,他能检查一个类的实例是不是唯一的引用,如果是,我们就不需要对结构体实例进行复制,如果不是,说明对象被不同的结构体共享,这时对它进行更改就需要进行复制。

Swift 为什么将 Array,String,Dictionary,Set,设计为值类型?

值类型 相比 引用类型的优点

  • 值类型和引用类型相比,最大优势可以高效的使用内存;
  • 值类型在栈上操作,引用类型在堆上操作;
  • 栈上操作仅仅是单个指针的移动,
  • 堆上操作牵涉到合并,位移,重链接

Swift 这样设计减少了堆上内存分配和回收次数,使用 copy-on-write将值传递与复制开销降到最低

String,Array,Dictionary设计成值类型,也是为了线程安全考虑。通过Swift的let设置,使得这些数据达到了真正意义上的“不变”,它也从根本上解决了多线程中内存访问和操作顺序的问题


什么是属性观察器?

属性观察是指在当前类型内对特性属性进行监测,并作出响应,属性观察是 swift 中的特性,具有2种, willset和 didset

  • willSet 传递新值 newValue

  • didSet 传递旧值 oldValue

  • 在初始化器中对属性初始化时,不会触发观察器

  • 属性观察器 只能用在 存储属性 ,不可用在 计算属性

  • 可以 为  lazy (即延迟存储属性)的 var 存储属性 设置 属性观察器

  • willSet 会传递新值,默认叫 newValue
  • didSet 会传递旧值,默认叫 oldValue

注意:

  • 在初始化器中设置属性值不会触发 属性观察器
    • 属性定义时设置初始值也不会出发 属性观察,原因是 
      • 属性定义时设置初始值,本质跟在 初始化器中设置值是一样的
  • 属性观察器 只能用在 存储属性 ,不可用在 计算属性
struct Cicle {
    /// 存储属性
    var radius :Double {
        willSet {
            print("willSet -- ",newValue,"radius == ",radius)
        }
        didSet {
            print("didSet ++ ",oldValue,"radius == ",radius)
        }
    }
    /*
     上述代码 跟下面等价,不推荐
     var radius :Double {
         willSet(jk_newValue) {
             print("willSet -- ",jk_newValue,"radius == ",radius)
         }
         didSet(jk_oldValue) {
             print("didSet ++ ",jk_oldValue,"radius == ",radius)
         }
     }
     */
}

var circle = Cicle.init(radius: 10.0)

circle.radius = 20.0
/*
 willSet --  20.0 radius ==  10.0
 didSet ++  10.0 radius ==  20.0
 */

print("result == ",circle.radius)
//result ==  20.0
复制代码

拓展:
●属性观察器,计算属性这两个功能,同样可以应用在全局变量/局部变量
●属性观察器,计算属性 不可以同时 应用在同一个类(不包括继承)的属性中

Swift 异常捕获

do - try - catch 机制

  • Swift中可以通过 Error 协议自定义运行时的错误信息
  • 函数内部通过throw 抛出 自定义  Error , 抛出 Error 的函数必须加上 throws 声明(逻辑通过不会抛出,反之可能抛出)
  • 需要使用 try 调用 可能会 抛出 的Error 函数
  • 通过 try 尝试调用 函数 抛出的异常 必须要 处理异常;否在会编译报错; 反之 运行时 在 top level (main) 报错,闪退

defer 的用法

  • 使用defer代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。


如何将Swift中协议 部分方法 设计成可选?

  • 方案一(不推荐,除非需要暴露给OC用)
    • 在协议和方法前面添加 @objc,然后在方法前面添加 optional关键字,改方式实际上是将协议转为了OC的方式
@objc protocol someProtocol {
  @objc  optional func test()
}
复制代码

协议 可以 用来定义 属性 方法 下标 的 声明,协议 可以被  类 结构体 枚举 遵守(多个协议之间用, 隔开)

protocol Drawable {
    func draw()
    var x:Int { get set }
    var y:Int { get }
    subscript(index:Int) -> Int {get set}
}

protocol Test1 {}
protocol Test2 {}
protocol Test3 {}

class TestClass: Test1,Test2,Test3 {
    
}
复制代码

注意:

  • 协议中定义的方法,不能有默认参数
  • 默认情况下,协议中定义的内容需要全部实现

Swift和OC中的 protocol 有什么不同?

  • 相同点,两者都是用作代理
  • 不同点
    • Swift
      • Swift中的 protocol 还可以对接口进行抽象,可以实现面向协议,从而大大提高编程效率
      • Swift中的 protocol 可以用于值类型,结构体,枚举;

比较Swift 和OC中的初始化方法 (init) 有什么不同?

swift 的初始化方法,因为引入了两段式初始化和安全检查因此更加严格和准确,

swift初始化方法需要保证所有的非optional的成员变量都完成初始化,

同时 swfit 新增了convenience和 required两个修饰初始化器的关键字

  • convenience只提供一种方便的初始化器,必须通过一个指定初始化器来完成初始化
  • required是强制子类重写父类中所修饰的初始化方法

Swift 和OC 中的自省 有什么区别?

  • OC
    • 自省在OC中就是判断某一对象是否属于某一个类的操作,有以下2中方式
      • [obj iskinOfClass:[SomeClass class]] : obj 必须是 SomeClass 的 对象或 其子类对象;return YES;
      • [obj isMemberOfClass:[SomeClass class]] : obj 必须是 SomeClass 的 对象;return YES;
  • Swift
    •  Swift 中由于很多 class 并非继承自 NSObject, 故而 Swift 使用 is 来判断是否属于某一类型, is 不仅可以作用于class, 还可以作用于enum和struct

Swift 与 OC 如何相互调用

  • Swift -> OC 
    • 需要创建一个 Target-BriBridging-Header.h (默认在OC项目中,会提示自动创建)的桥文件,在该文件中,导入需要调用的OC代码的头文件即可
  • OC -> Swift
    • 直接导入Target-Swift.h(该文件是Xcode自动创建) Swift如何需要被OC调用,需要使用 @objc 对方法或属性进行修饰

Swift 中特殊的标记

imagepng

Swift调用OC
●新建一个桥接文件,文件格式默认为:{targetName}-Bridging-Header.h;(一般在OC项目中,创建Swift文件,Xcode会自动提示生成该文件,仅需点击确认即可)

imagepng

●在{targetName}-Bridging-Header.h 文件中 #import OC 需要 暴露 给 Swift的内容

OC 调用 Swift
●Xcode 已经默认 生成 一个 用于 OC 调用 Swift的头文件,文件名格式是: {targetName}-Swift.h

imagepng

●Swift 暴露给 OC的 类 一定要继承 NSObject

●使用 @objc 修饰 需要暴露 给 OC的成员

●使用@objcMembers 修饰类
○代表 默认所有的 成员 都会 暴露给 OC(包括扩展中定义的成员)
○最终 是否成功 暴露,还需要考虑 成员自身的 访问级别

拓展
●为什么Swift 暴露给 OC 的类 要最终 继承 NSObject?
因为 OC 中的方法调用 是通过 Runtime 机制,需要通过 isa 指针 去完成 一些列消息的发送等, 而 只有继承自 NSObject 的类 才具有 isa 指针,才具备 Runtime 消息 发送的能力
●p.run() 底层是如何调用的? 反过来,OC调用Swift 又是如何调用?
○JKPerson 是 OC 的类,以及OC 中定义的初始化 和 run 方法
○在Swift中 调用 JKPerson 对象的 run 方法 ,底层是如何调用的?

Swift复制代码

var p = JKPerson(age: 10,name:"Jack")
p.run()
复制代码

答:走 Runtime 运行时机制, 反过来 OC 调用 Swift中的类 跟 问题一 一样,也是通过 Runtime 机制
●car.run() 底层是如何调用的?

swift

答 : (虽然 Car 类 被暴露给 OC使用)在Swift中 car.run(),底层依然是 通过 类似 C++ 的虚表 机制 来调用的;

拓展:
如果想要 Swift中的方法 调用 也使用 Runtime 机制,需要在方法名称前面 加上 dynamic关键字


Swift定义常量 和 OC定义常量的区别?

//OC:
const int price = 0;
//Swift:
let price = 0
复制代码
  • OC中 const 常量类型和数值是在编译时确定的
  • Swift 中 let 常量(只能赋值一次),其类型和值既可以是静态的,也可以是一个动态的计算方法,它们在运行时确定的。

Swift 中的函数重载

构成函数重载的规则

  • 函数名相同
  • 参数个数不同 || 参数类型不同 || 参数标签不同

注意: 返回值类型 与函数重载无关;返回值类型不同时,函数重载会报错:

func overloadsum(v1 : Int,v2:Int) -> Int {
    v2 + v1
}

// 参数个数不同
func overloadsum(v1 : Int,v2:Int,v3:Int) -> Int {
    v2 + v1 + v3
}

// 参数类型不同
func overloadsum(v1 : Int,v2:Double) -> Double {
    v2 + Double(v1)
}

// 参数标签不同
func overloadsum(_ v1 : Int,_ v2:Int) -> Int {
    v2 + v1
}

func overloadsum(a : Int,_ b:Int) -> Int {
    a + b
}

/**
 返回值类型不同时,在函数重载时,会报错:
 Ambiguous use of 'overloadsum(v1:v2:)'
 
 func overloadsum(v1 : Int,v2:Int) {
 }
 */

public func overloadtest() {
    let result1 = overloadsum(v1: 10, v2: 20)
    let result2 = overloadsum(v1: 10, v2: 20, v3: 30)
    let result3 = overloadsum(v1: 10, v2: 20)
    let result4 = overloadsum(10, 20)
    let resutt4_1 = overloadsum(a: 10, 20)
    
    print(result1,result2,result3,result4,resutt4_1)
    //30 60 30 30 30
}

复制代码

Swift 中的枚举,关联值 和 原始值的区分?

    • 将 枚举的成员值 跟 其他类型的值 关联 存储在一起
    • 存储在枚举变量中,占用枚举变量内存
enum Score {
    case points(Int)
    case grade(Character)
}
复制代码
    • 枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做:原始值
    • 不会存储在 枚举变量中,不占用枚举变量内存
enum PokerSuit : Character {
    case spade = "♠"
    case heart = "♥"
    case diamond = "♦"
    case club = "♣"
}
复制代码

闭包是引用类型吗?

闭包和函数都是是引用类型。如果一个闭包被分配给一个变量,这个变量复制给另一个变量,那么他们引用的是同一个闭包,他们的捕捉列表也会被复制。

swift 中的闭包结构是什么样子的?

{
    (参数列表) -> 返回值类型 in 函数体代码
}
复制代码

什么是尾随闭包

  • 尾随闭包 是一个被 书写在 函数调用 括号 后面的 闭包表达式

基本定义
●Swift中可通过 func 定义一个函数,也可以通过 闭包表达式 定义一个函数

闭包表达式的格式:

{
    (参数列表) -> 返回值类型  in
    函数体代码
}
复制代码

闭包表达式与定义函数的语法相对比,有区别如下:
1没有func
2没有函数名
3返回值类型添加了关键字in

let fn1 = {
    (v1 : Int,v2 : Int) -> Int in
    return v1 + v2
}

let result1 = fn1(10,5)

let result2 = {
    (v1:Int,v2:Int) -> Int in
    return v2 + v1
}(10,6)

print(result1,result2) // 15 16
复制代码

闭包表达式的简写

func exec(v1:Int,v2:Int,fn:(Int,Int)->Int) {
    print(fn(v1,v2))
}

闭包表达式的简写
private func test2() {
    // 1: 没有简写
    exec(v1:10, v2:20, fn: {
        (v1:Int,v2:Int) -> Int in
        return v1 + v2
    })
    
    // 2: 简写1
    exec(v1: 2, v2: 3, fn: {
        v1,v2 in return v1 + v2
    })
    
    // 3:简写 2
    exec(v1: 3, v2: 4, fn: {
        v1,v2 in v1 + v2
    })
    
    // 4: 简写3
    exec(v1: 5, v2: 6, fn: {$0 + $1})
    
    // 5: 简写4
    exec(v1: 7, v2: 8, fn: +)
}
复制代码

尾随闭包

  • 将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强代码的可读性
    • 尾随闭包 是一个被 书写在 函数调用 括号 后面的 闭包表达式
func test3() {
    
    exec(v1: 8, v2: 7) { a, b in
        a + b
    }
    
    // or     { 书写在 函数调用 括号 后面的 闭包表达式}
    exec(v1: 9, v2: 10) {
        $0 + $1
    }
}
复制代码
  • 如果 闭包表达式 是函数的唯一 实参,且使用了尾随闭包的 语法,则在函数名后面的 () 可省略
// fn:就是尾随闭包
func exec1(fn:(Int,Int)->Int) {
    print(fn(1,2))
}

exec1(fn: {$0 + $1})
exec1() {$0 + $1}
exec1{$0 + $1}
复制代码

什么是逃逸闭包

  • 闭包有可能在函数结束后调用,闭包调用 逃离了函数的作用域,需要通过@escaping 声明

注意:逃逸闭包 不可以 捕获 inout 参数

原因是: 逃逸闭包不确定 何时开始执行,有可能 在执行逃逸闭包时,可变参数已经被程序回收,造成野指针访问

什么是自动闭包

自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包。

它不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。

这个语法的好处在于通过写普通表达式代替显式闭包而使你省略包围函数形式参数的括号。

func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(10, 20)
复制代码
  • 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行
  • @autoclosure 会自动将 20 封装成闭包 { 20 }
  • @autoclosure 只支持 () -> T 格式的参数
  • @autoclosure 并非只支持最后1个参数
  • 有@autoclosure、无@autoclosure,构成了函数重载

如果你想要自动闭包允许逃逸,就同时使用 @autoclosure 和 @escaping 标志。


Swift 中的存储属性与计算属性

存储属性(Stored Property)

  • 类似于成员变量这个概念
  • 存储在实例对象的内存中
  • 结构体、类可以定义存储属性
  • 枚举不可以定义存储属性

关于 存储属性, Swift 中有个明确的规定

  • 在创建 类 或者 结构体 的实例时,必须为所有的存储属性设置一个合适的初始值
    • 可以在初始化器里为存储属性设置一个初始值
    • 可以分配一个默认的属性值作为属性定义的一部分

计算属性(Computed Property)

  • 本质就是方法(函数)
  • 不占用实例对象的内存
  • 枚举、结构体、类都可以定义计算属性

计算属性(Computed Property)
○本质就是方法(函数)
○不占用实例的内存
○枚举、结构体、类 都可以定义计算属性

理解计算属性与存储属性:

如果两个属性之间存在一定的逻辑关系,使用计算属性,原因如下:
●如果都用存储属性的话,其逻辑对应关系可能有误
●而使用计算属性,则可以准确的描述 这种逻辑关系
具体案例参考 下面的 Cicle 中的 radius(半径) 和 diameter(直径) 的逻辑关系

同时因为计算属性不占用实例的内存,可以有效的节省实例的内存空间

●set 传入的新值 默认叫做 newValue,也可以自定义

struct Cicle {
    /// 存储属性
    var radius :Double
    /// 计算属性
    var diameter: Double {
        get {
            radius * 2
        }
        set (jkNewValue){
            radius = jkNewValue / 2.0
        }
    }
}
复制代码
  • 只读计算属性:只有 get , 没有 set
struct Cicle {
    /// 存储属性
    var radius :Double
    /// 计算属性
    /*
     var diameter: Double {
         get {
             radius * 2
         }
     }
     */

    // 上述代码与下面的代码等价
    var diameter: Double {radius * 2}
}

var cicle = Cicle.init(radius: 12)

print(cicle.radius)//12.0
print(cicle.diameter)//24.0

// cicle.diameter = 10.0 //Cannot assign to property: 'diameter' is a get-only property
复制代码
  • 定义计算属性只能用 var,不可以是 let
    • let 表示常量
    • 计算属性的值是可能会发生变化的(包括只读计算属性)

什么是[延迟存储属性](Lazy Stored Property)

  • 使用 lazy 可以定义一个 延迟存储属性,在第一次用到属性的时候才会进行初始化(类似 OC 中的懒加载)

注意点:

  • lazy 属性 必须是 var 
    • 因为,let 属性 必须在实例的初始化方法 完成之前 就拥有值
  • 如果多条线程同时第一次访问 lazy 属性,无法保证 属性 只被 初始化 一次 (即线程不是安全的)

  • 使用 lazy 可以定义一个 延迟存储属性,在第一次用到属性的时候才会进行初始化(类似 OC 中的懒加载)

注意点:

  • lazy 属性 必须是 var 
    • 因为Swift 规定:let 属性 必须在实例的初始化方法 完成之前 就拥有值
  • 如果多条线程同时第一次访问 lazy 属性,无法保证 属性 只被 初始化 一次 (即线程不是安全的)
class Car {
    init() {
        print("car has init")
    }

    func run() {
        print("car running")
    }
}

class Person {
    lazy var car = Car.init()

    init() {
        print("person has init")
    }

    func go_out() {
        car.run()
    }
}

let p = Person.init() //person has init
print("*******")
p.go_out()//  car has init ---->   car running
复制代码
  • 当结构体 包含 一个延迟存储属性时,只有 var 才能访问延迟 存储属性
    • 因为延迟属性 初始化时 要改变 结构体的内存
struct Point {
    var x = 0
    var y = 0
    lazy var z = 0
}

let p = Point.init()
print(p.z)//Cannot use mutating getter on immutable value: 'p' is a 'let' constant
复制代码

什么是 类型 属性?

  • 类型属性(Type Property) :通过类型去访问
    • 存储类型属性(Stored Type Property):整个程序运行过程中,就只有1份内存(类似于全局变量,底层采用了 gcd_once 操作)
    • 计算类型数据(Computed Type Property):

属性可分为
●实例属性(Instance Property):通过实例去访问
○存储实例属性(Stored Instance Property):存储在实例的内存中,每个实例都有1分
○计算实例属性(Computed Instance Property):不占用实例的内存,本质是方法

●类型属性(Type Property) :通过类型去访问
○存储类型属性(Stored Type Property):整个程序运行过程中,就只有1份内存(类似于全局变量,底层采用了 gcd_once 操作,保证只初始化一次)
○计算类型数据(Computed Type Property):

注意:
存储类型属性不会 占用 实例对象 的内存,整个程序运行过程中,就只有1份内存

存储类型属性 本质就是全局变量(该全局变量加了一些类型控制,只能通过类型去访问),

可以通过 static 定义类型属性
如果 是类, 也可以使用 关键字 class
结构体 就只能使用 关键字 static

struct Car {
    static var count:Int = 0
    init(){
        Car.count += 1
    }
}

let c1 = Car.init()
let c2 = Car.init()
let c3 = Car.init()
print(Car.count)//3
复制代码

类型属性的细节:
●不同于 存储实例属性 ,必须给 存储类型属性 设定初始值
○因为类型没有想实例那样的 init 初始化器来初始化 存储属性
●存储类型属性 默认就是 lazy。会在第一次使用的时候 初始化
○就算 被 多个线程 同时访问,保证只会被 初始化 一次,线程是安全的
○存储类型 属性 也可以是 let
●枚举类型 也可以 定义 类型属性(存储类型属性,计算类型属性)


Swift 中如何定义单例模式

可以通过类型属性+let+private 来写单例; 代码如下如下:

// 方式一
public class SingleManager{
    public static let shared = SingleManager()
    private init(){}
}

// 方式二
public class SingleManager{
    public static let shared = {
        //...
        //...
        return SingleManager()
    }()
    private init(){}
}

// 上述两个方法等价,一般推荐 方式二
复制代码

Swift 中 下标是什么?

  • 使用subscript 可以给任意类型(枚举,结构体,类) 增加下标的功能

subscript 的语法 类似于 实例方法、计算属性,本质就是方法(函数)

func xiabiaoTest() {
    class Point {
        var x = 0.0
        var y = 0.0
        
        subscript (index:Int) -> Double {
            set{
                if index == 0 {
                    x = newValue
                } else if index == 1 {
                    y = newValue
                }
            }
            get{
                if index == 0 {
                    return x
                } else if index == 1 {
                    return y
                }
                return 0
            }
        }
    }
    
    let p = Point()
    p[0] = 11.1 // 调用subscript
    p[1] = 22.2 // 调用subscript
    print(p.x)//11.1 不会调用 subscript
    print(p.y)//22/2 不会调用 subscript
    print(p[0])//11.1 // 调用subscript
    print(p[1])//22.2 // 调用subscript
}
复制代码

简要说明Swift中的初始化器?

一图胜千言 针对类

  • 结构体会默认生成 含有参数的初始化器,一旦自定义初始化器,默认的初始化器则不可用
  • 类默认只会生成无参的指定初始化器

11

  • 类、结构体、枚举都可以定义初始化器
  • 类有2种初始化器: 指定初始化器(designated initializer)、便捷初始化器(convenience initializer)

什么是可选链?

可选链是一个调用和查询可选属性、方法和下标的过程,它可能为 nil 。

  • 如果可选项包含值,属性、方法或者下标的调用成功;
  • 如果可选项是 nil ,属性、方法或者下标的调用会返回 nil 。
  • 多个查询可以链接在一起,如果链中任何一个节点是 nil ,那么整个链就会得体地失败。

可选链(Optional Chaining)

如果 可选项 不会nil ,调用 方法 ,下标,属性成功,结果会被包装成 可选项,反之调用失败,返回nil

class Car { var price = 0}
class Dog { var weight = 0}
class Person {
    var name:String = ""
    var dog :Dog = Dog()
    var car :Car? = Car()
    func age() -> Int { 18 }
    func eat() {print("Person Eat")}
    subscript (index:Int) ->Int {index}
}


var person :Person? = Person()
var age1 = person!.age() //Int
var age2 = person?.age() // Int?
var name = person?.name // String?
var index = person?[6] // Int?


func getName() -> String {"jackie"}

/*
 如果 person 对象 是 nil  ,将不会调用 getName() 方法
 */
person?.name = getName()

复制代码
  • 如果结果本来就是可选项,不会进行再次包装
if let _ = person?.eat() {
    /*
    Person Eat
	eat success
    */
    print("eat success")
} else {
    print("eat failure")
}
复制代码
  • 多个  可以连接在一起,其中任何一个节点 如果为 nil,那么整条链就会 调用失败
var dog = person?.dog // Dog?
var weight = person?.dog.weight // Int?
var price = person?.car?.price // Int?
复制代码

什么是运算符重载?

类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载

struct Point {
    var x: Int,y: Int
    static func + (p1: Point,p2: Point) -> Point {
        Point(x: p1.x + p2.x ,y: p1.y + p2.y)
    }
}
let p = Point(x:10,y: 20) + Point(x: 30,y: 40)
print(p) //Point(x: 40, y: 60)
复制代码

Swift中函数的柯里化

将一个 接受 多个参数的 函数,变成 只接受 一个参数的一系列 操作

示例

func add(_ v1: Int,_ v2: Int) -> Int {
    v1 + v2
}

func difference(_ v1: Int,_ v2: Int) -> Int {
    v1 - v2
}

func add2(_ v1: Int,_ v2: Int,_ v3 :Int ,_ v4 :Int) -> Int {
    v1 + v2 - v3 + v4
}
复制代码
  • 伪柯里化
func currying_add(_ v1:Int) -> (Int) -> Int {
    return {$0 + v1}
}

/*
 将任意一个 接受两个 参数的函数 柯里化
 */
func curring_fun_tow_params1(_ f1 :@escaping (Int,Int) -> Int, _ v1 :Int) -> (Int) -> Int {
//    return {
//        f1($0,v1)
//    }
    return { (v2) in
        return f1(v1 , v2)
    }
}

print(add(10, 20)) // 30
print(currying_add(10)(20)) // 30 // 30
print(curring_fun_tow_params1(add, 10)(20))
复制代码
  • 正宗柯里化
func curring_fun_two_params2<A,B,C>(_ f1: @escaping (A,B) -> C) -> (A) -> ((B) -> C) {
    /*
     return {
         (a) in  // a = 3
         return {
             (b) in  // b = 8
             return  f1(a,b)
         }
     }
     */
    
    { a in { b in f1(a, b)} }
    
}

let result = curring_fun_two_params2(add)(3)(5)
print("result == ",result) //8
复制代码
  • 柯里化拓展

/*
 -> (A) -> (B) -> (C) -> (D) -> E
 
 实际是 一连串 闭包的 组合  如下所示:
 
 -> (A) -> (  (B) ->    ((C)  ->   ((D) -> E))  )
 
 
 传入 A   >  一个 闭包    (B)   ->   (  (C) -> ((D) -> E)  )

 
 传入 B   >>  一个 闭包   (C)   ->   (  (D) -> E  )
 
 
 传入 C   >>  一个闭包    (D) -> E
 
 
 */


//func curring_fun_more_params<A,B,C,D,E>(_ fn: @escaping (A,B,C,D) -> (E)) -> (A) -> ((B) -> ((C) -> ((D) -> E))) {
  func curring_fun_more_params<A,B,C,D,E>(_ fn: @escaping (A,B,C,D) -> (E)) -> (A) -> (B) -> (C) -> (D) -> E {
   /*
    return {
        (a) in
        return {
            (b) in
            return {
                (c) in
                return {
                    d in
                    return fn(a,b,c,d)
                }
            }
        }
    }
    */
    
    {a in { b in { c in { d in fn(a,b,c,d)}}}}
}

let resutl2 = curring_fun_more_params(add2)(10)(20)(30)(40)
print(resutl2) // 40

let resutl2_func = curring_fun_more_params(add2)
let resutl2_func_value = resutl2_func(10)(20)(30)(40)
print(resutl2_func_value) // 40
复制代码

Acho que você gosta

Origin juejin.im/post/7229877486171930684
Recomendado
Clasificación