swift 4.0> 进阶知识点全面梳理(二)

1,reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

从语境中推断类型:

因排序闭包为实际参数来传递给函数,故 Swift 能推断它的形式参数类型和返回类型。 sorted(by:) 方法期望它的第二个形式参数是一个 (String, String) -> Bool 类型的函数。这意味着 (String, String)和 Bool 类型不需要被写成闭包表达式定义中的一部分,因为所有的类型都能被推断,返回箭头 ( ->) 和围绕在形式参数名周围的括号也能被省略:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

从单表达式闭包隐式返回:

单表达式闭包能够通过从它们的声明中删掉 return 关键字来隐式返回它们单个表达式的结果,前面的栗子可以写作:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

sorted(by:) 方法的第二个实际参数的函数类型已经明确必须通过闭包返回一个 Bool 值。因为闭包的结构包涵返回 Bool 值的单一表达式 (s1 > s2),因此没有歧义,并且 return 关键字能够被省略。

简写的实际参数名:Swift 自动对行内闭包提供简写实际参数名,你也可以通过 $0 , $1 , $2 等名字来引用闭包的实际参数值;

reversedNames = names.sorted(by: { $0 > $1 } )

$0 和 $1 分别是闭包的第一个和第二个 String 实际参数。

运算符函数:

Swift 的 String 类型定义了关于大于号( >)的特定字符串实现,让其作为一个有两个 String 类型形式参数的函数并返回一个 Bool 类型的值。这正好与  sorted(by:) 方法的第二个形式参数需要的函数相匹配。因此,你能简单地传递一个大于号,并且 Swift 将推断你想使用大于号特殊字符串函数实现:

reversedNames = names.sorted(by: >)

尾随闭包:reversedNames = names.sorted() { $0 > $1 }

如果闭包表达式作为函数的唯一实际参数传入,而你又使用了尾随闭包的语法,那你就不需要在函数名后边写圆括号了:

reversedNames = names.sorted { $0 > $1 }

2,捕获值:在 Swift 中,一个能够捕获值的闭包最简单的模型是[表情]内嵌函数,即被书写在另一个函数的内部。一个内嵌函数能够捕获外部函数的实际参数并且能够捕获任何在外部函数的内部定义了的常量与变量。

3,逃逸闭包:

当闭包作为一个实际参数传递给一个函数的时候,我们就说这个闭包逃逸了,因为它可以在函数返回之后被调用。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的;闭包可以逃逸的一种方法是被储存在定义于函数外的变量里;

举例:var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {

    completionHandlers.append(completionHandler)

}

如果你不标记函数的形式参数为 @escaping ,你就会遇到编译时错误。

让闭包 @escaping 意味着你必须在闭包中显式地引用 self ,比如说,下面的代码中,传给 someFunctionWithEscapingClosure(_:) 的闭包是一个逃逸闭包,也就是说它需要显式地引用 self 。相反,传给 someFunctionWithNonescapingClosure(_:) 的闭包是非逃逸闭包,也就是说它可以隐式地引用 self 。

个人见解: 逃逸闭包需要显示引用self,是因为逃逸闭包是被存储于函数外的变量里,用self来声明此闭包的归属对象,以便在其他类中,此闭包调用时,能够很好的区分来源

4,自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包;它不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。这个语法的好处在于通过写普通表达式代替显式闭包而使你省略包围函数形式参数的括号。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

let customerProvider = { customersInLine.remove(at: 0) }//自动闭包

尽管 customersInLine 数组的第一个元素以闭包的一部分被移除了,但任务并没有执行直到闭包被实际调用。如果闭包永远不被调用,那么闭包里边的表达式就永远不会求值。[表情]注意 customerProvider 的类型不是 String 而是  () -> String ——一个不接受实际参数并且返回一个字符串的函数;

当你传一个闭包作为实际参数到函数的时候,你会得到与延迟处理相同的行为。

func serve(customer customerProvider: () -> String) {

    print("Now serving \(customerProvider())!")

}

serve(customer: { customersInLine.remove(at: 0) } )

上边的函数 serve(customer:) 接收一个明确的返回下一个客户名称的闭包。下边的另一个版本的 serve(customer:) 执行相同的任务但是不使用明确的闭包而是通过 @autoclosure 标志标记它的形式参数使用了自动闭包。现在你可以调用函数就像它接收了一个 String 实际参数而不是闭包。实际参数自动地转换为闭包,[表情]因为 customerProvider 形式参数的类型被标记为 @autoclosure 标记。实际如此:serve(customer customerProvider: @autoclosure () -> String)

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

func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {

    customerProviders.append(customerProvider)

}

6,在 Swift 中,为不同类型产品条码定义枚举大概是这种姿势:

enum Barcode {

    case upc(Int, Int, Int, Int)

    case qrCode(String)

}

switch productBarcode {

case .upc(let numberSystem, let manufacturer, let product, let check):

    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")

case .qrCode(let productCode):

    print("QR code: \(productCode).")

}

switch productBarcode {

case let .upc(numberSystem, manufacturer, product, check):

    print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")

case let .qrCode(productCode):

    print("QR code: \(productCode).")

}

7,枚举:enum CompassPoint: String {

    case north, south, east, west

}可以用 rawValue属性来访问一个枚举成员的原始值:let sunsetDirection = CompassPoint.west.rawValue;

递归枚举:

递归枚举是拥有另一个枚举作为枚举成员关联值的枚举;当编译器操作递归枚举时[表情]必须插入间接寻址层,你可以在声明枚举成员之前使用 indirect关键字来明确它是递归的;

例如:储存简单数学运算表达式的枚举:

enum ArithmeticExpression {

    case number(Int)

    indirect case addition(ArithmeticExpression, ArithmeticExpression)

    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)

}

你同样可以在枚举之前写 indirect 来让整个枚举成员在需要时可以递归:

indirect enum ArithmeticExpression {

    case number(Int)

    case addition(ArithmeticExpression, ArithmeticExpression)

    case multiplication(ArithmeticExpression, ArithmeticExpression)

}

示例:let five = ArithmeticExpression.number(5)

let four = ArithmeticExpression.number(4)

let sum = ArithmeticExpression.addition(five, four)

let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

8,struct Resolution {

      var width = 0

      var height = 0

  }存储属性是绑定并储存在类或者结构体中的常量或者变量。这两个属性因以值 0 来初始化,所以它们的类型被推断为 Int 。

9,所有的结构体都有一个自动生成的成员初始化器,你可以使用它来初始化新结构体实例的成员属性。

struct Resolution {

      var width = 0

      var height = 0

  }

let vga = Resolution(width: 640, height: 480)

10,结构体和枚举是值类型;实际上,Swift 中所有的基本类型——整数,浮点数,布尔量,字符串,数组和字典——都是值类型,并且都以结构体的形式在后台实现。Swift 中所有的结构体和枚举都是值类型,这意味着你所创建的任何结构体和枚举实例——和实例作为属性所包含的任意值类型——在代码传递中总是被拷贝的。

11,有时候找出两个常量或者变量是否引用自同一个类实例非常有用,为了允许这样,Swift提供了两个特点运算符:

相同于 ( ===)

不相同于( !==)

利用这两个恒等运算符来检查两个常量或者变量是否引用相同的实例:

结构体实例总是通过值来传递,而类实例总是通过引用来传递,

按照通用准则,当符合以下一条或多条情形时应考虑创建一个结构体:

[表情]结构体的主要目的是为了封装一些相关的简单数据值;

当你在赋予或者传递结构实例时,有理由需要封装的数据值被拷贝而不是引用;

任何存储在结构体中的属性是值类型,也将被拷贝而不是被引用;

结构体不需要从一个已存在类型继承属性或者行为。

12,存储属性会存储常量或变量作为实例的一部分,反之计算属性会计算(而不是存储)值。计算属性可以由类、结构体和枚举定义。存储属性只能由类和结构体定义。

结构体是值类型。当一个值类型的实例被标记为常量时,该实例的其他属性也均为常量。

类来说则不同,它是引用类型。如果你给一个常量赋值引用类型实例,你仍然可以修改那个实例的变量属性。

13,延迟存储属性:通过在其声明前标注 lazy 修饰语来表示一个延迟存储属性。

必须把延迟存储属性声明为变量(使用 var 关键字),因为它的初始值可能在实例初始化完成之前无法取得。常量属性则必须在初始化完成之前有值,因此不能声明为延迟。如果被标记为 lazy 修饰符的属性同时被多个线程访问并且属性还没有被初始化,则无法保证属性只初始化一次。

14,简写设置器(setter)声明:如果一个计算属性的设置器没有为将要被设置的值定义一个名字,那么他将被默认命名为 newValue;以下示例,自定义为newCenter;

struct Rect {

    var origin = Point()

    var size = Size()

    var center: Point {

        get {

            let centerX = origin.x + (size.width / 2)

            let centerY = origin.y + (size.height / 2)

            return Point(x: centerX, y: centerY)

        }

        set(newCenter) {

            origin.x = newCenter.x - (size.width / 2)

            origin.y = newCenter.y - (size.height / 2)

        }

    }

}

15,只读计算属性:一个有读取器但是没有设置器的计算属性就是所谓的只读计算属性。只读计算属性返回一个值,也可以通过点语法访问,但是不能被修改为另一个值。

通过去掉 get 关键字和他的大扩号来简化只读计算属性的声明:

struct Cuboid {

    var width = 0.0, height = 0.0, depth = 0.0

    var volume: Double {

        return width * height * depth

    }

}

16,必须用 var 关键字定义计算属性——包括只读计算属性——为变量属性,因为它们的值不是固定的。

属性观察者:父类属性的 willSet 和 didSet 观察者会在子类初始化器中设置时被调用。它们不会在类的父类初始化器调用中设置其自身属性时被调用;

class StepCounter {

    var totalSteps: Int = 0 {

        willSet(newTotalSteps) {

            print("About to set totalSteps to \(newTotalSteps)")

        }

        didSet {

            if totalSteps > oldValue  {

                print("Added \(totalSteps - oldValue) steps")

            }

        }

    }

}

let stepCounter = StepCounter()

stepCounter.totalSteps = 200

// About to set totalSteps to 200

// Added 200 steps

stepCounter.totalSteps = 360

// About to set totalSteps to 360

// Added 160 steps

17,类型属性语法:

对于类类型的计算类型属性,你可以使用 class 关键字来允许子类重写父类的实现;

class SomeClass {

    static var storedTypeProperty = "Some value."

    static var computedTypeProperty: Int {

        return 27

    }

    class var overrideableComputedTypeProperty: Int {

        return 107

    }

}

18,结构体和枚举是值类型。默认情况下,值类型属性不能被自身的实例方法修改。

总之,如果你需要在特定的方法中修改结构体或者枚举的属性,你可以选择将这个方法异变。然后这个方法就可以在方法中[表情]异变(嗯,改变)它的属性了,并且任何改变在方法结束的时候[表情]都会写入到原始的结构体中。方法同样可以指定一个全新的实例给它隐含的 self属性,并且这个新的实例将会在方法结束的时候[表情]替换掉现存的这个实例。

你可以选择在[表情] func关键字前放一个 mutating关键字来使用这个行为,

struct Point {

    var x = 0.0, y = 0.0

    mutating func moveBy(x deltaX: Double, y deltaY: Double) {

        x += deltaX

        y += deltaY

    }

}

var somePoint = Point(x: 1.0, y: 1.0)

somePoint.moveBy(x: 2.0, y: 3.0)

print("The point is now at (\(somePoint.x), \(somePoint.y))")

在异变方法里指定自身:

异变方法可以指定整个实例给隐含的 self属性。上文中那个 Point的栗子可以用下边的代码代替;

struct Point {

    var x = 0.0, y = 0.0

    mutating func moveBy(x deltaX: Double, y deltaY: Double) {

        self = Point(x: x + deltaX, y: y + deltaY)

    }

}

枚举的异变方法可以设置隐含的 self属性为相同枚举里的不同成员:

enum TriStateSwitch {

    case off, low, high

    mutating func next() {

        switch self {

        case .off:

            self = .low

        case .low:

            self = .high

        case .high:

            self = .off

        }

    }

}

var ovenLight = TriStateSwitch.low

ovenLight.next()

你可能感兴趣的:(swift 4.0> 进阶知识点全面梳理(二))