A simple Queue for iOS Swift

Posted in software by Christopher R. Wirz on Wed May 17 2017



At the time of writing this, iOS Swift does not provide a queue structure - though they do provide a walk-through. A queue is actually pretty simple. You enqueue (add an item) and dequeue (remove and return) an item (or push and pop for a stack).

Note: There are many other suggested implementations for an iOS Swift Queue. Check those out as well.

A custom queue can have many more features that allow for collection management operations. Using generics, these capabilities can be provided for all nullable types.

The file Queue.swift could be created as follows:


//
//  Queue.swift
//
//  Created by Christopher Wirz on 5/15/17.
//  Copyright © 2017. All rights reserved.
//

import Foundation

public struct Queue<T>{

    private var _list = [T?]()
    private var _head : Int = 0
    private let _queue = DispatchQueue(label: "ThreadSafeCollection.queue", attributes: .concurrent)
    private var _gc_size : Int = 32

    public var count : Int {
        return _list.count - _head
    }

    public var isEmpty : Bool {
        return self.count < 1
    }

    public mutating func enqueue(_ element: T) {
        _queue.sync(flags: .barrier) {
            _list.append(element)
        }
    }

    public mutating func dequeue() ->T? {
        guard _head < _list.count, let element = _list[_head] else { return nil }

        _queue.sync(flags: .barrier) {
            self._list[self._head] = nil // Removes the reference
            self._head += 1

            if self._head >= _gc_size {
                self._list.removeFirst(self._head)
                self._head = 0
            }
        }

        return element
    }

    public func peek() ->T? {
        if isEmpty {
            return nil
        } else {
            var ret : T? = nil
            _queue.sync {
                ret = _list[_head]
            }
            return ret
        }
    }

    subscript(index: Int) ->T? {
        get {
            let i = index + _head
            if i >= _list.count {return nil}
            return _list[i]
        }
        set(newValue) {
            let i = index + _head
            if i >= _list.count {return}

            _queue.sync(flags: .barrier) {
                _list[i] = newValue
            }
        }
    }

    public mutating func dropWhere(_ pred:(T)->Bool) ->[T] {
        var dropped = [T]()
        var remaining = [T]()
        _queue.sync(flags: .barrier) {
            for index in _head..<_list.count {
                if let item = _list[index] {
                    if pred(item) == true {
                        dropped.append(item)
                    } else {
                        remaining.append(item)
                    }
                }
            }
            _list = remaining
            _head = 0
        }
        return dropped
    }


    public var garbageCollectSize : Int {
        get {
            return _gc_size
        }
        set {
            if newValue >= 1 {
                _gc_size = newValue
            }
        }
    }

    public func any(_ pred:(T)->Bool) -> Bool {
        for index in _head..<_list.count {
            if let item = _list[index] {
                return pred(item) == true
            }
        }
        return false
    }
}

This simple queue has many use cases. The primary use case is to reduce network usage by processing one call to the server at a time. In the following example, the web API only allows us to delete one message or thread at a time. Therefore, clearing out my inbox, it will be far more efficient on memory and network to have one network session active with the server at a time.


//
//  MessageDeleter.swift
//
//  Created by Christopher Wirz on 5/16/17.
//  Copyright © 2017. All rights reserved.
//

import Foundation
public class MessageDeleter {

    private var _queue = Queue<Message>()
    private var _isRunning : Bool = false

    public var isRunning : Bool {
        return _isRunning
    }

    public static let instance = MessageDeleter()  // Singleton pattern

    private init(){}

    func deleteMessage(_ message: Message? = nil){
        if let m = message {
            if !_queue.any({m in
                return m.messageId == message?.messageId}) {
                _queue.enqueue(m)
            }
        }
        self.runMessages()
    }

    private func runMessages(){
        if _isRunning {return}
        _isRunning = true
        guard let message = _queue.dequeue() else {_isRunning = false; return}

        var dict = ["message_id" : message.messageId]
        if message.messageThreadString != "" {
            dict["message_thread_string"] = message.messageThreadString
        }
        WebApi.instance.Message.delete(params: dict){_ in
            self._isRunning = false
            self.runMessages()
        }
    }
}