美文网首页
Swift进阶七: 函数1

Swift进阶七: 函数1

作者: Trigger_o | 来源:发表于2022-05-10 13:04 被阅读0次

    首先回顾基础,在Swift中:
    1.函数可以被赋值给变量,也可以作为另一个函数的输入参数,或者另一个函数的返回值来使用
    这点不必多说

    2.函数能够捕获存在于其局部作用域之外的变量

    func counterFunc() -> (Int) -> String { 
          var counter = 0
           func innerFunc(i: Int) -> String { 
                  counter += i // counter is captured return "running total: \(counter)" 
            } 
            return innerFunc
     }
    
    let f = counterFunc() 
    f(3) // running total: 3 
    f(4) // running total: 7
    
    let g = counterFunc() 
    g(2) // running total: 2 
    g(2) // running total: 4
    
    f(2) // running total: 9
    

    在结构体和类提到过,counter 将存在于堆上而非栈上。多次调用 innerFunc,并且看到 running total 的输出在增加
    可以看到f(3)和f(4)的count在持续累加,
    再次调用 counterFunc() 函数,将会生成并 “捕获” 新的 counter 变量,也就是g(2)
    并且不会影响第一个函数f(2)还可以继续累加

    将这些函数以及它们所捕获的变量想象为一个类的实例,这个类拥有一个单一的方法以及一些成员变量 (这里的被捕获的变量),这种一个函数和它所捕获的变量环境组合起来被称为闭包,也就是f和g.

    3.有两种方法可以创建函数,一种是使用 func 关键字,另一种是 { }。在 Swift 中,后一种被称为闭包表达式

    let doubler = { (i:Int) -> Int in return i * 2} //或者简化为let doubler = {$0 * 2}
    [1, 2, 3, 4].map(doubler)
    

    这就是闭包表达式,相对于func doubler(i:Int) -> Int
    { (i:Int) -> Int in i * 2}是函数的字面量,没有名字,不过被赋值给了变量doubler.
    这里两个doubler是等价的,包括命名空间.

    闭包指的是一个函数以及被它所捕获的所有变量的组合;而使用 { } 来创建的函数被称为闭包表达式

    一:通过优化排序方法深入理解闭包

    Swift一共有四个排序的方法:不可变版本的 sorted(by:) 和可变的 sort(by:),以及两者在待排序对象遵守Comparable 时进行升序排序的无参数版本。对于最常⻅的情况,这个无参数的sorted() 就是你所需要的。如果你需要用不同于默认升序的顺序进行排序的话,可以提供一个排序函数:

    let myArray = [3, 1, 2]
    myArray.sorted() // [1, 2, 3]
    myArray.sorted(by: >) // [3, 2, 1]
    

    就算待排序的元素不遵守 Comparable,但是只要有 < 操作符,你就可以使用这个方法来进行排序,比如可选值就是一个例子:

    var numberStrings = [(2, "two"), (1, "one"), (3, "three")] 
    numberStrings.sort(by: <) 
    numberStrings // [(1, "one"), (2, "two"), (3, "three")]
    

    如果用 Foundation 进行排序,会遇到一⻓串不同的选项,Foundation 中有接受 selector、block 或者函数指针作为比较方式的排序方法,或者你也可以传入一个排序描述符的数组。Swift 也可以使用任意的比较函数 (也就是那些返回NSComparisonResult 的函数) 来对集合进行排序.
    Foundation所有这些都提供了大量的灵活度和各式功能,但是代价是使排序变得相当复杂.
    没有一个选项可以让我能 “只需要基于默认顺序进行一个常规的排序”。Foundation 中那些接受 block 作为比较断言的方法,在实质上和 Swift 的 sorted(by:)方法是一样的;其他的像是接受排序描述符数组的方法,则利用了 Objective-C 的动态特性,它们是十分灵活和强大 (但是是弱类型) 的 API,它们不能被直接移植到 Swift 中,对于 selector 和动态派发的支持在 Swift 中依然有效,但是 Swift 标准库更倾向于使用基于函数的方式.

    为了对比Foundation,顶一个一个继承NSObject的Person类,
    @objcMembers作用是给所有的属性和方法增加@objc
    以及一个数组

    @objcMembers 
    final class Person: NSObject { 
        let frst: String 
        let last: String 
        let yearOfBirth: Int 
        init(frst: String, last: String, yearOfBirth: Int) { 
            self.frst = frst 
            self.last = last 
            self.yearOfBirth = yearOfBirth 
        } 
    }
    
    let people = [ Person(frst: "Emily", last: "Young", yearOfBirth: 2002), 
                          Person(frst: "David", last: "Gray", yearOfBirth: 1991), 
                          Person(frst: "Robert", last: "Barnes", yearOfBirth: 1985), 
                          Person(frst: "Ava", last: "Barnes", yearOfBirth: 2000), 
                          Person(frst: "Joanne", last: "Miller", yearOfBirth: 1994),
                          Person(frst: "Ava", last: "Barnes", yearOfBirth: 1998),
                         ]
    

    目标是对这个数组进行排序,规则是:
    先按照姓排序,再按照名排序,最后是出生年份。
    我们可以使用 NSSortDescriptor 对象来描述如何排序对象,通过它可以表达出各个排序标准 .
    使用 localizedStandardCompare 来进行遵循区域设置的排序,这样的话中文名字也可以排序了.

    首先按照三个规则分别创建排序描述符NSSortDescriptor

    let lastDescriptor = NSSortDescriptor(key: #keyPath(Person.last), ascending: true, selector: #selector(NSString.localizedStandardCompare(_:))) 
    let frstDescriptor = NSSortDescriptor(key: #keyPath(Person.frst), ascending: true, selector: #selector(NSString.localizedStandardCompare(_:))) 
    let yearDescriptor = NSSortDescriptor(key: #keyPath(Person.yearOfBirth), ascending: true)
    

    然后对数组进行排序,sortedArray(using:) 可以接受多个排序描述符,先使用第一个描述符,并检查其结果,如果两个
    元素在第一个描述符下相同,那么它将使用第二个描述符,以此类推

    let descriptors = [lastDescriptor, frstDescriptor, yearDescriptor] (people as NSArray).sortedArray(using: descriptors)
    /*[Ava Barnes (1998), Ava Barnes (2000), Robert Barnes (1985), David Gray (1991), Joanne Miller (1994), Emily Young (2002)] */
    

    这种方法用到了runtime的两个特性,第一是keypath,也就是属性链表,第二是kvc,也就是在运行时根据key查找对应的值,并且selector也是运行时向给定的方法发送消息,然后对比两个值.

    用Swift来实现单一规则的排序是很简单的

    let p1 = people.sorted{ $0.frst.localizedStandardCompare($1.frst) == .orderedAscending}
    

    但是如果要实现组合规则就有点麻烦了,标准库有一个lexicographicallyPrecedes方法,能够接受两个序列,依次从两个序列各取一个元素进行比较,如果第一个元素对比结果是相等,那就再用第二个元素,但是这个方法只能接收一个compare方法,比较名字就不能比较年龄

    people.sorted { p0, p1 in 
         let left = [p0.last, p0.frst]
         let right = [p1.last, p1.frst]
         return left.lexicographicallyPrecedes(right) { $0.localizedStandardCompare($1) == .orderedAscending } 
    }
    /*[Ava Barnes (2000), Ava Barnes (1998), Robert Barnes (1985), David Gray (1991), Joanne Miller (1994), Emily Young (2002)] */
    

    foundation的排序描述符的方式要清晰不少,但是它用到了运行时编程。而我们写的Swift函数没有使用运行时编程,不过它们不太容易写出来或者读懂。
    排序描述符其实是一种描述对象顺序的方式。我们现在不去将这些信息存储在类里,而是尝试定义一个函数来描述对象的顺序。最简单的定义是获取两个对象,当它们的顺序正确的时候返回 true。这个函数的类型正是标准库中 sort(by:) 和 sorted(by:) 所接受的参数的类型。我们通过定义一个泛型的 typealias 来表述排序描述符.
    也就是现在自己定义一个排序描述符:

    /// ⼀个排序断⾔,当且仅当第⼀个值应当排序在第⼆个值之前时,返回 `true` 
    typealias SortDescriptor<Value> = (Value, Value) -> Bool
    

    上面我们定义了一个闭包,它接受两个参数,返回一个bool值,现在来实现它:

    let sortByYear: SortDescriptor<Person> = { $0.yearOfBirth < $1.yearOfBirth } 
    let sortByLastName: SortDescriptor<Person> = { $0.last.localizedStandardCompare($1.last) == .orderedAscending }
    

    sortByYear和sortByLastName都是SortDescriptor类型,接下来实现真正的NSSortDescriptor,
    我们可以定义一个函数,它和 NSSortDescriptor 大体相似,但不涉及运行时编程的接口。这个函数接受一个键和一个比较函数,返回排序描述符 (这里的描述符将是函数,而非 NSSortDescriptor)。在这里,key 将不再是一个字符串,而它自身就是一个函数;给定正在排序的数组中的一个元素,它就会返回这个排序描述符所处理的属性的值。然后,我们使用 areInIncreasingOrder 函数来比较两个键。最后,返回的结果也是一个函数,只不过它被typealias 稍微包装了一下:

    func sortDescriptor<Value, Key>( key: @escaping (Value) -> Key, by areInIncreasingOrder: @escaping (Key, Key) -> Bool) -> SortDescriptor<Value> {
            return { areInIncreasingOrder(key($0), key($1)) }
        }
    

    首先来描述一个这个方法,有点绕,
    sortDescriptor定义两个泛型Value和Key
    最后返回SortDescriptor<Value>,因此Value将会是需要排序的对象,在这里是Person;
    key接收一个Value返回一个Key;
    return的是SortDescriptor,也就是说 { areInIncreasingOrder(key(0), key(1)) }这里的key(0)和key(1)是数值,那么Key就是Value的某个键的值.

    接下来使用一下

    let s : SortDescriptor<Person> = sortDescriptor { pp in
                pp.yearOfBirth
            } by: { k1, k2 in
                k1 < k2
      }
     let s1 = people.sorted(by: s)
    

    简化一下:

    let sortByYearAlt: SortDescriptor<Person> = sortDescriptor(key: { $0.yearOfBirth }, by: <) 
    people.sorted(by: sortByYearAlt)
    /*[Robert Barnes (1985), David Gray (1991), Joanne Miller (1994), Ava Barnes (1998), Ava Barnes (2000), Emily Young (2002)] */
    

    如果我们指定Key遵循Compare协议,就不需要第二个参数了

    func sortDescriptor<Value, Key>(key: @escaping (Value) -> Key) -> SortDescriptor<Value> where Key: Comparable {
            return { key($0) < key($1) }
    }
    
    let sortByYearAlt2: SortDescriptor<Person> = sortDescriptor(key: { $0.yearOfBirth })
    

    到目前为止,sortDescriptor返回的都是与布尔值相关的函数,而Foundation 中像是 localizedStandardCompare 这的 API,所返回的是一个包含 (升序,降序,相等) 三种值的 ComparisonResult 的结果,因此我们继续改造sortDescriptor

    func sortDescriptor<Value, Key>(key: @escaping (Value) -> Key, ascending: Bool = true, by comparator: @escaping (Key, Key) -> ComparisonResult) -> SortDescriptor<Value> {
            return { lhs, rhs in
                let order: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
                return comparator(key(lhs), key(rhs)) == order
            }
    }
    
    let sortByYearAlt3: SortDescriptor<Person> = sortDescriptor(key: {$0.frst}, ascending: true) { $0.localizedStandardCompare($1) }
    let p3 = people.sorted(by: sortByYearAlt3)
    

    SortDescriptor 现在与 NSSortDescriptor 拥有了同样地表达能力,不过它是类型安全的,而且不依赖于运行时编程。
    我们现在只能用一个单一的 SortDescriptor 函数对数组进行排序。如果你还记得,我们曾经用过NSArray.sortedArray(using:) 方法来用多个比较运算符对数组进行排序。我们可以很容易地为 Array,甚至是 Sequence 协议添加一个相似的方法。不过,我们需要添加两次:一次是可变版本,另一次是不可变版本。
    使用稍微不同的策略,就可以避免添加多次扩展。我们可以创建一个函数来将多个排序描述符合并为单个的排序描述符。它的工作方式和 sortedArray(using:) 类似:首先它会使用第一个描述符,并检查比较的结果。然后,当结果是相等的话,再使用第二个,第三个描述符,直到全部用完:

    func combine<Value>(sortDescriptors: [SortDescriptor<Value>]) -> SortDescriptor<Value> {
            return { lhs, rhs in
                for areInIncreasingOrder in sortDescriptors {
                    if areInIncreasingOrder(lhs, rhs) {
                        return true
                    }
                    if areInIncreasingOrder(rhs, lhs) {
                        return false
                    }
                }
                return false
            }
        }
    

    现在把一开始的例子的foundation写法换成SortDescriptor

    let sortByLastName: SortDescriptor<Person> = sortDescriptor(key: {$0.last}, ascending: true) { $0.localizedStandardCompare($1) }
    let sortByFirstName: SortDescriptor<Person> = sortDescriptor(key: {$0.frst}, ascending: true) { $0.localizedStandardCompare($1) }
    let sortByYear: SortDescriptor<Person> = sortDescriptor(key: { $0.yearOfBirth }, by: <)
    let combined: SortDescriptor<Person> = combine(sortDescriptors: [sortByLastName, sortByFirstName, sortByYear])
    let p4 = people.sorted(by: combined)
    

    最终,我们得到了一个与 Foundation 中的版本在行为和功能上等价的实现方法,但是我们的方式要更安全,也更符合 Swift 的语言习惯。因为 Swift 的版本不依赖于运行时,所以编译器有机会对它进行更好的优化。另外,我们也可以使用它来对结构体或是非 Objective-C 的对象进行排序。

    相关文章

      网友评论

          本文标题:Swift进阶七: 函数1

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