用于任务调度机制的类型擦除

时间:2018-06-01 12:43:47

标签: swift type-erasure

我正在尝试构建一个轻量级的任务调度系统。基本上我可以在Task中包含一些异步工作,并用TaskRunner发送它。目前,跑步者非常简单,只需在start上调用Task并保留它,以便在它运行时不会取消分配。任务看起来像这样:

enum TaskNotification<T> {
    case start
    case cancel
    case complete(T)
    case progress(TaskProgress)
}

typealias TaskNotificationHandler<TaskResponseType> = (TaskNotification<TaskResponseType>) -> Void

protocol Task: AnyObject {

    associatedtype Response

    var onChange: TaskNotificationHandler<Response> { get set }

    func start()
    func cancel()
}

所以我喜欢这样一个事实:我得到了LinkedType Response的强类型。但是,当需要构建TaskRunner时,我会得到经典的"contains Self or AssociatedType requirements"编译器错误,因此我使用T: Task或创建一个名为AnyTask的类型擦除包装。

class TaskRunner {

    var currentTask: AnyTask<???>

    func run<T: Task>(task: T) {
        let boxed = AnyTask<???>(with: task)
        currentTask = task
        task.start()
    }
}

但即使使用类型擦除(仍然不清楚什么是“擦除”和“不擦除”),它仍然需要我决定通用参数<???>的单个具体类型。

最后我想要的是能够使用协议的关联类型中的强类型构建Task的具体实现,但允许TaskRunner管理任意Task只使用它关心的功能。 (例如,它不关心强类型响应)

我已经查看了泛型,类型擦除,甚至使用了类层次结构,但还没有找到合适的解决方案。

1 个答案:

答案 0 :(得分:1)

我假设TaskRunner的目标是跟踪所有正在运行的任务,直到他们发出.complete.cancel。如果这是目标,那么基本块(() -> Void)就是你需要的所有类型擦除。

class TaskRunner {
    // An increasing identifier just to keep track of things that aren't equatable
    var nextTaskId = 0

    var inProgressTasks: [Int: () -> Void] = [:]

    func run<T: Task>(task: T) {
        // Get an id
        let taskId = nextTaskId
        nextTaskId += 1

        // This allows you easily write a `TaskRunner.cancelAll()` method, so
        // it's useful. But it's real point is to retain `task` until it 
        // completes, while type-erasing it so it can be stored in inProgressTasks
        let cancel: () -> Void = {
            task.cancel()
        }

        // Extend the onChange handler to remove this task when it completes.
        // This intentionally retains self so the TaskRunner cannot go away
        // until all Tasks complete
        let oldChange = task.onChange
        task.onChange = { [self] notification in
            oldChange(notification)

            switch notification {
            case .complete, .cancel: self.inProgressTasks.removeValue(forKey: taskId)
            case .start, .progress: break
            }
        }

        // Retain the task via its canceller
        inProgressTasks[taskId] = cancel

        // Run it!
        task.start()
    }
}