美文网首页
Swift5复习(二)可选项、结构体、类、闭包

Swift5复习(二)可选项、结构体、类、闭包

作者: 默默_David | 来源:发表于2020-05-25 22:40 被阅读0次

    Swift复习(二)可选项、结构体、类、闭包

    一、 可选项(Optional)

    定义

    • 可选项,一般也叫可选类型,它允许将值设置为nil
    • 在类型名称后面加个问好?来定义一个可选项

    强制解包(Forced Unwrapping)

    • 如果要从可选项中取出被包装的数据,需要使用感叹号!进行强制解包
    • 如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误
    var a : String?
    a!//error: Fatal error: Unexpectedly found nil while unwrapping an Optional value
    

    可选值绑定(Optional Binding)

    • 可以使用可选项绑定类判断可选项是否包含之,如果包含就自动解包,把值给一个临时的常量(let)或者变量(var),并返回true,否则返回false
    • 可选绑定可以在if、while、switch-case、guard中
    if let number = Int("123"){
        print("字符串转换整形成功:\(number)") //字符串转换整形成功:123
    } else {
        print("字符串转化整形失败")
    }
    
    enum Season : Int{
        case Spring = 1, summer,autumn,winter
    }
    var season : Season? = .summer
    /*
     这里可以用var来接收,表示可修改
     可以使用同名变量接收
     */
    if var season = season {
        season = .winter
        print(season.rawValue)//4
    }
    

    多个可选绑定

    • 多个可选绑定可写在一排,用逗号间隔开,并且还可以加上逻辑表达式,它们的关系是一个逻辑与(&&)的关系
    if let first = Int("4"),let second = Int("5") , first < second,second < 10{
        print("成功了,first:\(first),second:\(second)")//成功了,first:4,second:5
    }
    

    空合运算符??(Nil-Coalescing Operator)

    public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

    • a ?? b 相当于 a != ni ? a! : b
    • a是可选项,b是可选项或者不是可选项
    • b跟a的存储类型必须相同
    • 如果a不为nil,就返回a!,如果a为nil,就返回b
    • 如果b是可选项,返回a时会自动解包

    guard语句

    guard 条件 else {
        //do something
        退出当前作用域
        //return、break、continue、throw error
    }
    
    • 当guard语句的条件为false时,就会执行大括号里面的代码
    • 当guard语句的条件为true时,就会跳过guard语句执行后面的语句
    • guard特别适合用来提前退出
    • 当使用guard语句进行可选绑定时,绑定的常量(let)、变量(var)如果绑定成功,可以在guard后面的语句中使用

    隐式解包(Implicitly Unwrapped Optional)

    • 在某些情况下,可选项一旦被设定值之后,就会一直拥有值
    • 在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
    • 可以在类型后面加个感叹号!定义一个隐式解包的可选项
    let num : Int! = 10
    print(num + 1)//11
    

    可选项字符串插值

    • 可选项在字符串插值或者直接打印时,编译器会发出警告
    var age : Int? = 18
    print("My age is \(age)")//String interpolation produces a debug description for an optional value; did you mean to make this explicit?
    
    • 以下三种方法可以消除警告
    print("My age is \(age!)")//My age is 18
    print("My age is \(String(describing: age))")//My age is Optional(18)
    print("My age is \(age ?? 0)")//My age is 18
    

    可选项的本质是枚举

    let a: Optional<Int> = 1
    

    如上代码所示,我们的点进源码的定义处,可以看到:

    @frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    
    /// The absence of a value.
    ///
    /// In code, the absence of a value is typically written using the `nil`
    /// literal rather than the explicit `.none` enumeration case.
    case none
    
    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)
    }
    

    所以,可选项的本质是枚举,它有两个case,分别是none和some,如果可选值为nil,它是none,如果有值,它是some。

    可选嵌套

    有如下代码

    let a: Int? = 1
    let b: Int?? = a
    let c: Int??? = b
    

    我们使用fr v -R查看其结构

    (lldb) fr v -R a
    (Swift.Optional<Swift.Int>) a = some {
      some = {
        _value = 1
      }
    }
    (lldb) fr v -R b
    (Swift.Optional<Swift.Optional<Swift.Int>>) b = some {
      some = some {
        some = {
          _value = 1
        }
      }
    }
    (lldb) fr v -R c
    (Swift.Optional<Swift.Optional<Swift.Int?>>) c = some {
      some = some {
        some = some {
          some = {
            _value = 1
          }
        }
      }
    }
    

    可以看出,它是一层层的可选值封装,就像下面的二叉树结构:


    从上图中我们可以看到,none可以出现在任意一层,那么在每一层的效果一样吗?
    我们看如下代码:

    let a: Int? = nil
    let b: Int?? = a
    let c: Int??? = b
    let d: Int??? = nil
    

    同样查看内存结构

    (lldb) fr v -R a
    (Swift.Optional<Swift.Int>) a = none {
      some = {
        _value = 0
      }
    }
    (lldb) fr v -R b
    (Swift.Optional<Swift.Optional<Swift.Int>>) b = some {
      some = none {
        some = {
          _value = 0
        }
      }
    }
    (lldb) fr v -R c
    (Swift.Optional<Swift.Optional<Swift.Int?>>) c = some {
      some = some {
        some = none {
          some = {
            _value = 0
          }
        }
      }
    }
    (lldb) fr v -R d
    (Swift.Optional<Swift.Optional<Swift.Int?>>) d = none {
      some = some {
        some = some {
          some = {
            _value = 0
          }
        }
      }
    }
    

    我们看到,b和c都是some,而d是none,拿c来说,c是一个Optional.some(Optional.some(Optional.none)),而d因为是直接赋值为nil,所以它是一个Optional.none.

    假如这个时候我们进行可选绑定

    let a: Int? = nil
    let b: Int?? = a
    let c: Int??? = b
    let d: Int??? = nil
    
    if let _ = a{
        print("a不为空")
    }
    if let _ = b{
        print("b不为空")
    }
    if let _ = c{
        print("c不为空")
    }
    if let _ = d{
        print("a不为空")
    }
    //打印结果
    b不为空
    c不为空
    

    我们再看这个例子

    let a: Int? = nil
    let b: Int?? = a
    let c: Int??? = b
    let d: Int??? = nil
    
    if a == b{
        print("a和b相等")
    }
    if b == c{
        print("b和c相等")
    }
    if a == c{
        print("a和c相等")
    }
    if c == d {
        print("c和d相等")
    }
    if a == d{
        print("a和d相等")
    }
    //打印结果
    a和b相等
    b和c相等
    a和c相等
    

    为什么结果是这样呢,我们查看关于可选值的==的定义

    extension Optional : Equatable where Wrapped : Equatable {
    
        /// Returns a Boolean value indicating whether two optional instances are
        /// equal.
        ///
        /// Use this equal-to operator (`==`) to compare any two optional instances of
        /// a type that conforms to the `Equatable` protocol. The comparison returns
        /// `true` if both arguments are `nil` or if the two arguments wrap values
        /// that are equal. Conversely, the comparison returns `false` if only one of
        /// the arguments is `nil` or if the two arguments wrap values that are not
        /// equal.
        ///
        ///     let group1 = [1, 2, 3, 4, 5]
        ///     let group2 = [1, 3, 5, 7, 9]
        ///     if group1.first == group2.first {
        ///         print("The two groups start the same.")
        ///     }
        ///     // Prints "The two groups start the same."
        ///
        /// You can also use this operator to compare a non-optional value to an
        /// optional that wraps the same type. The non-optional value is wrapped as an
        /// optional before the comparison is made. In the following example, the
        /// `numberToMatch` constant is wrapped as an optional before comparing to the
        /// optional `numberFromString`:
        ///
        ///     let numberToFind: Int = 23
        ///     let numberFromString: Int? = Int("23")      // Optional(23)
        ///     if numberToFind == numberFromString {
        ///         print("It's a match!")
        ///     }
        ///     // Prints "It's a match!"
        ///
        /// An instance that is expressed as a literal can also be used with this
        /// operator. In the next example, an integer literal is compared with the
        /// optional integer `numberFromString`. The literal `23` is inferred as an
        /// `Int` instance and then wrapped as an optional before the comparison is
        /// performed.
        ///
        ///     if 23 == numberFromString {
        ///         print("It's a match!")
        ///     }
        ///     // Prints "It's a match!"
        ///
        /// - Parameters:
        ///   - lhs: An optional value to compare.
        ///   - rhs: Another optional value to compare.
        @inlinable public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool
    }
    

    再看一段代码

    let a: Int? = 1
    let b: Int?? = a
    let c: Int??? = b
    let d: Int??? = 1
    
    if a == b{
        print("a和b相等")
    }
    if b == c{
        print("b和c相等")
    }
    if a == c{
        print("a和c相等")
    }
    if c == d {
        print("c和d相等")
    }
    if a == d{
        print("a和d相等")
    }
    //打印结果
    a和b相等
    b和c相等
    a和c相等
    c和d相等
    a和d相等
    

    ==的注释中有这样一段话:

    The comparison returns true if both arguments are nil or if the two arguments wrap values that are equal.

    翻译过来就是,要返回true,要么是两个都是nil,要么是warp值相等。
    按照我们上面的案例,a=1,d=1的时候,它们warp值都是1,所以相等。a=nil,d=nil的时候,a、b、c都是some,所以我们比较它里面的warp值,都是nil,所以相等,而d是none,所以d和所有其他的都不相等。
    我们可以测试一下:

    let a: Int? = nil
    let b: Int?? = a
    let c: Int??? = b
    let d: Int??? = Optional<Int>.none
    
    if a == b{
        print("a和b相等")
    }
    if b == c{
        print("b和c相等")
    }
    if a == c{
        print("a和c相等")
    }
    if c == d {
        print("c和d相等")
    }
    if a == d{
        print("a和d相等")
    }
    打印结果
    a和b相等
    b和c相等
    a和c相等
    c和d相等
    a和d相等
    

    结果也和我们的注释一样。

    结构体

    介绍

    • Swift标准库中,绝大多数的公开类型都是结构体,枚举和类只占很小一部分
    • Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体
    struct Date{
        var year: Int = 2020
        var month: Int
        var day: Int
    }
    var date = Date(year: 2020, month: 1, day: 20)
    date = Date(month: 2, day: 22)
    
    • 所有的结构体都会有编译器自动生成的初始化器(initializer),这个初始化器叫做逐一初始化器,它保证每个成员都会被初始化,若之前已经初始化的,逐一初始化器中可以不传

    自定义初始化器

    • 一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其它初始化器了
    struct Date{
        var year: Int = 2020
        var month: Int
        var day: Int
        init(year: Int,month: Int,day: Int) {
            self.year = year
            self.month = month
            self.day = day
        }
    }
    var date = Date(year: 2020, month: 1, day: 20)
    date = Date(month: 2, day: 22)//报错 Missing argument for parameter 'year' in call
    

    定义

    • 类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
    class Date{
        var year: Int = 2020
        var month: Int
        var day: Int
        init(year: Int,month: Int,day: Int) {
            self.year = year
            self.month = month
            self.day = day
        }
    }
    

    类的初始化器

    • 如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
    • 成员的初始化是在这个初始化器中完成的
    class Point {
        var x: Int = 10
        var y: Int = 20
    }
    let p1 = Point()
    
    class Point{
        var x: Int
        var y: Int
        init() {
            x = 10
            y = 20
        }
    }
    let p2 = Point()
    //上面两段代码是完全等效的
    

    结构体与类的本质区别

    • 结构体是值类型(枚举也是值类型),类是引用类型(指针类型)

    值类型

    • 值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
    • 类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
    • 在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术,仅有写操作时,才会真正执行拷贝操作

    引用类型

    • 引用类型赋值给var、let或者给函数传参,是将内存地址拷贝一份,指向的是同一个文件,属于浅拷贝(shallow copy)

    嵌套类型

    struct Poker{
        enum Suit : String {
            case spades,hearts,diamonds,clubs
        }
        enum Rank : Int {
            case tow = 2,three,four,five,six,seven,eight,nine,ten
            case jack,queen,king,ace
        }
    }
    print(Poker.Suit.hearts.rawValue)//hearts
    
    var rank = Poker.Rank.five
    rank = .king
    print(rank.rawValue)//13
    

    枚举、结构体、类都可以定义方法

    • 一般把定义在枚举、结构体、类内部的函数,叫做方法

    方法占用对象的内存吗?
    不占用
    方法的本质就是函数
    方法、函数都存放在代码段

    闭包

    闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
    相比OC的block,Swift的闭包有很多优化的地方:

    1. 可以根据上下文推断参数和返回值类型
    2. 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return)
    3. 可以使用简化参数名,如$0,$1(从0开始,表示第i个参数)
    4. 提供了尾随闭包语法(Trailing closure syntax)

    闭包表达式(Closure Expression)

    • 在Swift中,可以通过func定义一个函数,也可以闭包表达式定义一个函数,因为函数就是一个特殊的闭包
    func sum(_ v1: Int,_ v2: Int) -> Int { v1 + v2 }
    
    var fn = { (v1: Int,v2: Int) -> Int in
        v1 + v2
    }
    fn(10,20)//30
    
    {
        (参数列表) -> 返回值类型 in
        函数体代码
    }
    

    闭包表达式的简写

    func exec(v1: Int,v2: Int,fn: (Int,Int) -> Int){
        print(fn(v1,v2))
    }
    exec(v1: 10, v2: 20,fn: { (v1, v2) -> Int in
        v1 + v2
    })
    exec(v1: 10, v2: 20,fn : { v1, v2 in
        v1 + v2
    })
    exec(v1: 10, v2: 20,fn : {$0 + $1})
    exec(v1: 10, v2: 20,fn : +)
    

    尾随闭包

    • 如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
    • 尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
    func exec(v1: Int,v2: Int,fn: (Int,Int) -> Int){
        print(fn(v1,v2))
    }
    exec(v1: 10, v2: 20) {
        $0 + $1
    }
    
    • 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要再函数名后面写圆括号
    func exec(fn: (Int,Int) -> Int) {
        print(fn(1,2))
    }
    exec(fn: { $0 + $1 })
    exec(){ $0 + $1 }
    exec{ $0 + $1 }
    
    • 忽略参数
    func exec(fn: (Int,Int) -> Int) {
        print(fn(1,2))
    }
    exec{ _,_ in 10 }//10
    
    • 尾随闭包示例:数组的排序

    函数原型
    @inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows

    /*
     返回true:i1排在i2前面
     返回false:i1排在i2后面
     */
    func compare(i1: Int,i2: Int) -> Bool{
        //大的排在前面
        return i1 > i2
    }
    
    array.sort(by: compare)
    print(array) //[9, 8, 6, 4, 3, 1]
    
    array.sort { (i1, i2) -> Bool in
        i1 < i2
    }
    print(array)//[1, 3, 4, 6, 8, 9]
    
    //下方是逐步的简写
    //因为已知返回为Bool,可以省略
    array.sort { (i1, i2) in i1 < i2 }
    //已知参数类型,用$0,$1分别指代第一个第二个参数
    array.sort { $0 < $1 }
    //其它已知,直接给一个判断条件
    array.sort(by: <)
    
    

    闭包的定义

    一个函数和它所捕获的变量/常量环境组合起来,成为闭包

    • 一般指定义在函数内部的函数
    • 一般它捕获的是外层函数的局部变量/常量
    • 闭包的内存在堆空间
    • 闭包是引用类型(指针类型)

    注意
    如果返回值是函数类型,那么参数的修饰要保持统一

    func add(_ num: Int) -> (inout Int) -> Void{
        func plus(v: inout Int){
            v += num
        }
        return plus
    }
    

    捕获值

    闭包可以在其定义的上下文中捕获常量或变量
    即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内应用和修改这些值

    //案例一:
    func makeIncrementor(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementor() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementor
    }
    
    let incrementByTen = makeIncrementor(forIncrement: 10)
    // 返回的值为10
    print(incrementByTen())
    // 返回的值为20
    print(incrementByTen())
    // 返回的值为30
    print(incrementByTen())
    
    //案例二:
    var num = 10
    let ss = { () -> Int in
        num += 10
        return num
    }
    print(ss())//20
    print(num)//20
    print(ss())//30
    print(num)//30
    num = 100
    print(ss())//110
    print(num)//110
    print(ss())//120
    print(num)//120
    

    逃逸闭包(@escaping)

    当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。

    举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:

    //因为回调在函数返回后才返回,所以必须加上@escaping标记
    func download(_ completionHandler : @escaping ()->Void){
        DispatchQueue.global().async {
            Thread.sleep(forTimeInterval: 2)
            DispatchQueue.main.async {
                completionHandler()
            }
        }
    }
    

    自动闭包(@autoclosure)

    自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。

    我们经常会调用采用自动闭包的函数,但是很少去实现这样的函数。举个例子来说,assert(condition:message:file:line:) 函数接受自动闭包作为它的 condition 参数和 message 参数;它的 condition 参数仅会在 debug 模式下被求值,它的 message 参数仅当 condition 参数为 false 时被计算求值。

    自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。

    func returnSelfOrOther(_ num: Int,_ other: @autoclosure () -> Int) -> Int{
        num >= 0 ? num : other()
    }
    
    let result = returnSelfOrOther(-1) { 20 }
    print(result) // 20
    
    • 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行
    • @autoclosure会自动将20封装成闭包{20}
    • @autoclosure只支持()->T格式的参数
    • @autoclosure并非只支持最后一个参数
    • 空合运算符??使用了@autoclosure技术
    • 有@autoclosure,无@autoclosure,构成了函数重载

    注意
    过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的

    相关文章

      网友评论

          本文标题:Swift5复习(二)可选项、结构体、类、闭包

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