我有一个显示在UITableView上的项目的列表/数组。每个项目内部都包含一个状态机,该状态机使用Futures和Promise机制执行一些异步任务,并且其状态显示在相应的UITableViewCell上。目前仅适用于一种型号。
但是,我需要按批处理顺序执行此操作。例如,阵列中可以有15个模型,但是在特定情况下,我只能启动3个模型,一旦模型完成或失败,我应该手动触发第4个模型来启动它的任务。注意:我无法启动所有15种模型操作,只能等待回调,因为它受到硬件的限制,在这种情况下会立即失败。
如果上面的内容不清楚,下面是两个示例: 我的问题陈述与iPhone的 App Store 应用程序中更新选项卡下的 全部更新 功能完全匹配。如果您有20个应用程序更新并点击“全部更新”按钮,则它将显示17个应用程序处于等待状态,并且随时仅在3个应用程序上运行更新下载。应用更新完成后,它将移至下一个更新。这是我的问题陈述的精确副本,但有一点点改动。
Twist:我的操作是通过蓝牙进行的与硬件相关的操作。假设您有20台可穿戴设备,要通过蓝牙写入一些数据进行配置。硬件限制是,您一次最多可以连接3-4个设备。因此,一旦设备/外围设备操作成功或失败,我应该尝试逐步连接4个设备,然后依次连接n个,直到全部完成。还有重试功能,可以将失败的功能排回队列。
我的问题是我应该如何构造以维护它并进行监视。我对并发有一个大致的了解,但是并没有做太多的工作。我目前的感觉是使用包装在Manager类中的Queue和计数器来监视状态。希望对如何解决这个问题有所帮助。另外,我不需要代码,只是数据结构的概念性解决方案。
答案 0 :(得分:1)
使用OperationQueue
,对于每种操作,您都有子类Operation
。使用操作,您可以添加对其他操作的依赖关系以完成操作。因此,例如,如果您要等到第3个操作完成才开始第4、5和6个操作,则只需将第3个操作添加为它们的依赖项。
编辑:因此,可以将操作分组在一起,可以为其创建一个单独的类。我在下面添加了代码示例。 add(dependency: OperationGroup)
函数告诉其他组在原始组中的操作完成执行后立即开始执行操作。
//Make a subclass for each kind of operation.
class BluetoothOperation: Operation
{
let number: Int
init(number: Int)
{
self.number = number
}
override func main()
{
print("Executed bluetooth operation number \(number)")
}
}
class OperationGroup
{
var operationCounter: Int = 0
var operations: [Operation]
let operationQueue: OperationQueue = OperationQueue()
init(operations: [Operation])
{
self.operations = operations
}
func executeAllOperations()
{
operationQueue.addOperations(operations, waitUntilFinished: true)
}
//This in essence is popping the "Stack" of operations you have.
func pop() -> Operation?
{
guard operationCounter < operations.count else { return nil }
let operation = operations[operationCounter]
operationCounter += 1
return operation
}
func add(dependency: OperationGroup)
{
dependency.operations.forEach(
{
$0.completionBlock =
{
if let op = self.pop()
{
dependency.operationQueue.addOperation(op)
}
}
})
}
}
let firstOperationGroup = OperationGroup(operations: [BluetoothOperation(number: 1), BluetoothOperation(number: 2), BluetoothOperation(number: 3)])
let secondOperationGroup = OperationGroup(operations: [BluetoothOperation(number: 4), BluetoothOperation(number: 5), BluetoothOperation(number: 6)])
secondOperationGroup.add(dependency: firstOperationGroup)
firstOperationGroup.executeAllOperations()
答案 1 :(得分:1)
我认为您的反应很好。我使用ReactiveKit整理了一个简单的示例,以便您查看。反应套件非常简单,在这种情况下已绰绰有余。您也可以使用任何其他反应式库。希望对您有所帮助。
ReactiveKit:https://github.com/DeclarativeHub/ReactiveKit 债券:https://github.com/DeclarativeHub/Bond
安装reactKit依赖项后,您可以在工作区中运行以下代码:
import UIKit
import Bond
import ReactiveKit
class ViewController: UIViewController {
var jobHandler : JobHandler!
var jobs = [Job(name: "One", state: nil), Job(name: "Two", state: nil), Job(name: "Three", state: nil), Job(name: "Four", state: nil), Job(name: "Five", state: nil)]
override func viewDidLoad() {
super.viewDidLoad()
self.jobHandler = JobHandler()
self.run()
}
func run() {
// Initialize jobs with queue state
_ = self.jobs.map({$0.state.value = .queue})
self.jobHandler.jobs.insert(contentsOf: jobs, at: 0)
self.jobHandler.queueJobs(limit: 2) // Limit of how many jobs you can start with
}
}
// Job state, I added a few states just as test cases, change as required
public enum State {
case queue, running, completed, fail
}
class Job : Equatable {
// Initialize state as a Reactive property
var state = Property<State?>(nil)
var name : String!
init(name: String, state: State?) {
self.state.value = state
self.name = name
}
// This runs the current job
typealias jobCompletion = (State) -> Void
func runJob (completion: @escaping jobCompletion) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.state.value = .completed
completion(self.state.value ?? .fail)
return
}
self.state.value = .running
completion(.running)
}
// To find the index of current job
static func == (lhs: Job, rhs: Job) -> Bool {
return lhs.name == rhs.name
}
}
class JobHandler {
// The array of jobs in an observable form, so you can see event on the collection
var jobs = MutableObservableArray<Job>([])
// Completed jobs, you can add failed jobs as well so you can queue them again
var completedJobs = [Job]()
func queueJobs (limit: Int) {
// Observe the events in the datasource
_ = self.jobs.observeNext { (collection) in
let jobsToRun = collection.collection.filter({$0.state.value == .queue})
self.startJob(jobs: Array(jobsToRun.prefix(limit)))
}.dispose()
}
func startJob (jobs: [Job?]) {
// Starts a job thrown by the datasource event
jobs.forEach { (job) in
guard let job = job else { return }
job.runJob { (state) in
switch state {
case .completed:
if !self.jobs.collection.isEmpty {
guard let index = self.jobs.collection.indexes(ofItemsEqualTo: job).first else { return }
print("Completed " + job.name)
self.jobs.remove(at: index)
self.completedJobs.append(job)
self.queueJobs(limit: 1)
}
case .queue:
print("Queue")
case .running:
print("Running " + job.name)
case .fail:
print("Fail")
}
}
}
}
}
extension Array where Element: Equatable {
func indexes(ofItemsEqualTo item: Element) -> [Int] {
return enumerated().compactMap { $0.element == item ? $0.offset : nil }
}
}