迭代器模式 Iterator Pattern

迭代器模式(Iterator Pattern)属于行为型模式。Iterator pattern 提供了循环集合的标准方法。

Iterator pattern 包含以下两部分:

IteratorPatternUML.png
  1. IteratorProtocol:Swift 中的IterableProtocol协议定义了一个可以使用 for in 循环迭代的类型。
  2. Iterator Object:想要进行迭代的对象。一般,Iterator object 不直接遵守IteratorProtocol协议,而是遵守Sequence协议。Sequence协议遵守IteratorProtocol协议。通过遵守Sequence协议可以直接获得许多高级函数,例如,mapfilter等。

如果你对该协议不了解,可以查看IterableProtocolSequence文档。

在这篇文章中,将创建自定义 struct。该对象遵守Sequence协议,并使用Sequence协议中方法进行排序。

何时使用 Iterator Pattern

当拥有一组有序对象的类或结构,并且希望使用 for in 循环使其可迭代时,请使用迭代器模式。

示例

这篇文章将会创建 Queue 对象。Queue 是一个列表,只能从尾部插入对象,从头部移除对象。这样可以确保第一个入队的对象也是第一个出队的对象,先进先出(first come, first serve)。

创建Queue.swift文件,并添加以下代码:

import Foundation

public struct Queue<T> {
    // 数组元素可以为任意类型。
    private var array: [T?] = []
    
    // Queue 的头部是数组第一个元素
    private var head = 0
    
    // 确认数组是否为空
    public var isEmpty: Bool {
        return count == 0
    }
    
    // Queue 内对象数量
    public var count: Int {
        return array.count - head
    }
    
    // 添加对象到 Queue
    public mutating func enqueue(_ element: T) {
        array.append(element)
    }
    
    // 从 Queue 队列移除对象
    public mutating func dequeue() -> T? {
        guard head < array.count,
            let element = array[head] else {
                return nil
        }
        
        array[head] = nil
        head += 1
        
        let percentage = Double(head)/Double(array.count)
        
        if array.count > 50,
            percentage > 0.25 {
            array.removeFirst(head)
            head = 0
        }
        
        return element
    }
}

在上述代码中,enqueue()方法用于添加对象到 Queue;dequeue()方法用于从 Queue 队列移除对象。

创建Ticket.swift文件,并添加以下代码:

import Foundation

public struct Ticket {
    var description: String
    var priority: PriorityType
    
    enum PriorityType {
        case low
        case medium
        case high
    }
    
    init(description: String, priority: PriorityType) {
        self.description = description
        self.priority = priority
    }
}

进入ViewController.swift文件,在viewDidLoad()方法内添加以下代码:

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        // 初始化 Queue 对象,并添加四个对象。
        var queue = Queue<Ticket>()
        queue.enqueue(Ticket(description: "Wireframe Tinder for dogs app", priority: .low))
        queue.enqueue(Ticket(description: "Set up 4k monitor for Josh", priority: .medium))
        queue.enqueue(Ticket(description: "There is smoke coming out of my laptop", priority: .high))
        queue.enqueue(Ticket(description: "Put googly eyes on the Roomba", priority: .low))
        
        // 可以看到 dequeue 的是第一个添加的对象
        let element = queue.dequeue()
        print((element?.description ?? "No Description") + "\n")
    }

在实际应用中,肯定希望能够按照优先级对 Ticket 进行排序。根据现在的情况,只能编写多个 if 语句进行排序。使用 Swift 内置的排序函数能够节省大量时间。

如果现在尝试使用 for in 、sorted()进行排序,编译器会报错。因为使用这些方法前需要先遵守Sequence协议。为Queue添加以下 extension:

extension Queue: Sequence {
    public func makeIterator() -> IndexingIterator<ArraySlice<T?>> {
        // 只枚举非空对象
        let nonEmptyValues = array[head ..< array.count]
        return nonEmptyValues.makeIterator()
    }
}

遵守Sequence协议时,有两点需要注意:

  • 第一点为关联类型(associated type),也就是迭代器(Iterator)。在上述代码中,associated type 为IndexingIterator。如果集合没有声明自定义迭代器,则默认使用IndexingIterator
  • 第二点为Iterator协议,其为makeIterator函数不可缺少的。Iterator协议为 class 、struct 构造迭代器。

ViewController.swift文件中viewDidLoad()方法内添加以下代码:

    override func viewDidLoad() {
        ...
        
        // 枚举 queue 并输出
        print("List of Tickets in queue:")
        for ticket in queue {
            print(ticket?.description ?? "No Description")
        }
    }

输出如下:

List of Tickets in queue:
Set up 4k monitor for Josh
There is smoke coming out of my laptop
Put googly eyes on the Roomba

在使用排序函数前,为Ticket添加以下 extension:

extension Ticket {
    var sortIndex: Int {
        switch self.priority {
        case .low:
            return 0
        case .medium:
            return 1
        case .high:
            return 2
        }
    }
}

为优先级设置数字能够简化排序,排序时将使用sortIndex做为依据。在ViewController.swift文件中viewDidLoad()方法内添加以下代码:

    override func viewDidLoad() {
        ...
        
        // 进行排序
        let sortedTickets = queue.sorted {
            $0!.sortIndex > ($1?.sortIndex)!
        }
        
        // 循环 sortedTickets 数组,将元素添加到 sortedQueue
        var sortedQueue = Queue<Ticket>()
        for ticket in sortedTickets {
            sortedQueue.enqueue(ticket!)
        }
        
        // 输出 sotedQueue 内对象,可以看到 first in, firt out。
        print("\n")
        print("Tickets sorted by priority:")
        for ticket in sortedQueue {
            print(ticket?.description ?? "No Description")
        }

如此轻松地对集合进行排序是一项强大的功能。随着集合变大,排序功能将变得更有价值。

总结

通过遵守IteratorProtocol协议,可以自定义迭代对象的方式。例如,可以只实现一个next()方法,在该方法内返回迭代中的下一个对象。通常,极少需要直接遵守IteratorProtocol协议。即使需要自定义迭代器,遵守Sequence协议也是一个更好的选择。

以下是 Iterator Pattern 的关键点:

  • Iterator pattern 提供了使用 for in 语法循环遍历集合的标准方法。
  • 自定义对象最好遵守Sequence协议,而非直接遵守IteratorProtocol协议。
  • 通过遵守Sequence协议,能够直接获得更为高级的功能(如map、filter等)。

Demo名称:IteratorPattern
源码地址:https://github.com/pro648/BasicDemos-iOS/tree/master/IteratorPattern

参考资料:

  1. Design Patterns in Swift: Iterator Pattern
  2. Iterator pattern
  3. IteratorProtocol
  4. Sequence

欢迎更多指正:https://github.com/pro648/tips/wiki

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容