Designing a high-level architecture for a task manager that handles downloading, deleting, uploading, and canceling tasks for multiple videos simultaneously on iOS involves several key components. This includes defining the system architecture, identifying core modules and their interactions, and ensuring concurrency management. Here's an outline of the design:
System Architecture
MVC or MVVM Pattern: Use Model-View-Controller (MVC) or Model-View-ViewModel (MVVM) design pattern to separate concerns and facilitate testability and maintainability.
Background Task Management: Utilize iOS background task capabilities such as
URLSession,BackgroundTasks, andOperationQueuefor handling concurrent operations efficiently.Persistence: CoreData or Realm can be used for local storage to keep track of video tasks' states (e.g., pending, in-progress, completed, failed).
Notifications and Delegates: Use notifications and delegate patterns to communicate between components.
Core Modules
- Task Manager: Central manager to handle all tasks.
- Download Manager: Handles video download tasks.
- Upload Manager: Manages video upload tasks.
- Task Queue: Manages the order and concurrency of tasks.
- Persistence Layer: Stores task states and information.
- UI Layer: Displays task status and controls to the user.
Detailed Design
TaskManager
Responsibilities:
- Coordinate between different task managers (download, upload, delete).
- Maintain a list of current tasks and their statuses.
- Provide methods to start, cancel, pause, and resume tasks.
Key Methods:
startTask(task: VideoTask)cancelTask(task: VideoTask)deleteTask(task: VideoTask)uploadTask(task: VideoTask)pauseTask(task: VideoTask)resumeTask(task: VideoTask)
DownloadManager
Responsibilities:
- Handle the downloading of videos using
URLSession. - Manage download progress and states.
- Handle the downloading of videos using
Key Methods:
startDownload(video: Video)pauseDownload(video: Video)resumeDownload(video: Video)cancelDownload(video: Video)
UploadManager
Responsibilities:
- Handle the uploading of videos.
- Manage upload progress and states.
Key Methods:
startUpload(video: Video)pauseUpload(video: Video)resumeUpload(video: Video)cancelUpload(video: Video)
TaskQueue
Responsibilities:
- Manage the execution order of tasks.
- Ensure tasks are executed concurrently within allowed limits.
Key Methods:
addTask(task: VideoTask)removeTask(task: VideoTask)prioritizeTask(task: VideoTask)executeNextTask()
PersistenceLayer
Responsibilities:
- Persist task information and state.
- Retrieve task information on app launch.
Key Methods:
saveTaskState(task: VideoTask)loadTaskStates() -> [VideoTask]
UILayer
Responsibilities:
- Provide a user interface to display task statuses.
- Allow user interactions to start, pause, resume, cancel, and delete tasks.
Components:
- Task List View: Displays all tasks with their current status.
- Task Detail View: Shows detailed progress and options for a selected task.
Concurrency Management
- URLSession: For download and upload tasks, use
URLSessionwith background configurations to handle tasks even when the app is in the background. - OperationQueue: Use
OperationQueuefor managing concurrent execution of tasks with dependencies. - DispatchQueue: Utilize
DispatchQueuefor thread-safe operations on shared resources.
Example Workflow
User Initiates a Download:
- The user selects a video to download.
TaskManagercreates aVideoTaskand adds it toTaskQueue.DownloadManagerstarts the download usingURLSession.PersistenceLayersaves the task state.
User Cancels a Task:
- The user selects a task to cancel.
TaskManagerinvokescancelTaskon the respective manager (DownloadManagerorUploadManager).- The manager cancels the operation and updates the task state.
TaskQueueis updated to remove the task.
App Launches:
PersistenceLayerloads task states.TaskManagerreinitializes tasks based on their last known state.
Key Considerations
- Error Handling: Ensure robust error handling and retries for failed tasks.
- Data Consistency: Maintain consistency between the UI, task manager, and persistence layer.
- User Feedback: Provide clear feedback to users about the status of their tasks, especially for long-running operations.
This high-level design outlines the major components and their interactions to create a robust task manager for handling multiple video operations simultaneously on iOS.
Creating a comprehensive example of the entire system is quite extensive, but I'll provide a simplified yet practical example focusing on key aspects: downloading videos using URLSession, managing tasks with OperationQueue, and providing real-time updates with a delegate pattern.
Step 1: Define Models
First, define the VideoTask model to represent tasks.
import Foundation
enum TaskState { case pending, inProgress, completed, failed, cancelled } struct VideoTask { let id: String let url: URL var state: TaskState var progress: Float }
Step 2: Task Manager
Next, create the TaskManager to manage video tasks.
import Foundation class TaskManager { static let shared = TaskManager() private var tasks: [String: VideoTask] = [:] private let queue = OperationQueue() weak var delegate: TaskManagerDelegate? private init() { queue.maxConcurrentOperationCount = 3 loadTasks() } func startDownload(task: VideoTask) { var task = task task.state = .inProgress tasks[task.id] = task PersistenceManager.shared.saveTask(task) let operation = DownloadOperation(task: task) { [weak self] updatedTask in self?.tasks[updatedTask.id] = updatedTask PersistenceManager.shared.updateTask(updatedTask) self?.delegate?.taskDidUpdate(updatedTask) } queue.addOperation(operation) } func cancelTask(taskId: String) { if var task = tasks[taskId] { task.state = .cancelled PersistenceManager.shared.updateTask(task) queue.operations .compactMap { $0 as? DownloadOperation } .first { $0.task.id == taskId }? .cancel() delegate?.taskDidUpdate(task) } } func getTask(taskId: String) -> VideoTask? { return tasks[taskId] } func getAllTasks() -> [VideoTask] { return Array(tasks.values) } private func loadTasks() { let savedTasks = PersistenceManager.shared.fetchTasks() for task in savedTasks { tasks[task.id] = task if task.state == .inProgress { startDownload(task: task) } } } }
Step 3: Download Operation
Create a custom Operation for downloading videos.
import Foundation class DownloadOperation: Operation { private var task: VideoTask private let updateHandler: (VideoTask) -> Void private var urlSession: URLSessionDataTask? init(task: VideoTask, updateHandler: @escaping (VideoTask) -> Void) { self.task = task self.updateHandler = updateHandler } override func main() { guard !isCancelled else { task.state = .cancelled updateHandler(task) return } let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) urlSession = session.dataTask(with: task.url) urlSession?.resume() task.state = .inProgress updateHandler(task) while !isCancelled && task.state == .inProgress { Thread.sleep(forTimeInterval: 1) } if isCancelled { task.state = .cancelled updateHandler(task) } } override func cancel() { super.cancel() urlSession?.cancel() } } extension DownloadOperation: URLSessionDataDelegate { func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { // Handle data received task.progress += Float(data.count) / Float(dataTask.countOfBytesExpectedToReceive) updateHandler(task) } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { print("Download failed: \(error.localizedDescription)") self.task.state = .failed } else { self.task.state = .completed } updateHandler(self.task) } }
Step 4: UI Layer
Lastly, implement a simple UI layer to display and manage tasks. Here's a basic view controller using UITableView to list the tasks.
import UIKit class TaskViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() tableView = UITableView(frame: view.bounds) tableView.delegate = self tableView.dataSource = self view.addSubview(tableView) TaskManager.shared.delegate = self } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return TaskManager.shared.getAllTasks().count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil) let task = TaskManager.shared.getAllTasks()[indexPath.row] cell.textLabel?.text = task.url.lastPathComponent cell.detailTextLabel?.text = "\(task.state) - \(Int(task.progress * 100))%" return cell } } extension TaskViewController: TaskManagerDelegate { func taskDidUpdate(_ task: VideoTask) { DispatchQueue.main.async { self.tableView.reloadData() } } }
Adding a Task
To add a download task, you could add a button that triggers the following:
let videoURL = URL(string: "https://example.com/video.mp4")! let newTask = VideoTask(id: UUID().uuidString, url: videoURL, state: .pending, progress: 0.0) TaskManager.shared.startDownload(task: newTask)
To include a persistence layer, we'll use CoreData to store the states of tasks so that they can be restored when the app is relaunched. This involves defining a CoreData model, creating the persistence manager, and updating the TaskManager to interact with the persistence layer.
Step 5: Setting Up CoreData
First, create a new CoreData model (.xcdatamodeld) file and define an entity VideoTaskEntity with the following attributes:
id(String)url(String)state(String)progress(Float)
Step 2: Persistence Manager
Create a persistence manager to handle saving and loading tasks.
import CoreData import UIKit class PersistenceManager { static let shared = PersistenceManager() private init() {} lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "VideoTaskModel") container.loadPersistentStores { _, error in if let error = error { fatalError("Unresolved error \(error)") } } return container }() var context: NSManagedObjectContext { return persistentContainer.viewContext } func saveContext() { if context.hasChanges { do { try context.save() } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } func fetchTasks() -> [VideoTask] { let request: NSFetchRequest<VideoTaskEntity> = VideoTaskEntity.fetchRequest() do { let result = try context.fetch(request) return result.map { VideoTask(id: $0.id!, url: URL(string: $0.url!)!, state: TaskState(rawValue: $0.state!)!, progress: $0.progress) } } catch { print("Failed to fetch tasks: \(error)") return [] } } func saveTask(_ task: VideoTask) { let entity = VideoTaskEntity(context: context) entity.id = task.id entity.url = task.url.absoluteString entity.state = task.state.rawValue entity.progress = task.progress saveContext() } func updateTask(_ task: VideoTask) { let request: NSFetchRequest<VideoTaskEntity> = VideoTaskEntity.fetchRequest() request.predicate = NSPredicate(format: "id == %@", task.id) do { let result = try context.fetch(request) if let entity = result.first { entity.state = task.state.rawValue entity.progress = task.progress saveContext() } } catch { print("Failed to update task: \(error)") } } func deleteTask(_ task: VideoTask) { let request: NSFetchRequest<VideoTaskEntity> = VideoTaskEntity.fetchRequest() request.predicate = NSPredicate(format: "id == %@", task.id) do { let result = try context.fetch(request) if let entity = result.first { context.delete(entity) saveContext() } } catch { print("Failed to delete task: \(error)") } } }
Conclusion
This example demonstrates the basics of managing video tasks in an iOS app. The TaskManager handles the coordination, DownloadOperation manages the downloading process, and the view controller displays task status updates in real-time. To expand this further, you could add similar structures for uploading and deleting videos, enhance error handling, and improve UI feedback. With the addition of the PersistenceManager, the task manager can now save and load tasks, maintaining state across app launches. This example integrates CoreData to persist task information, ensuring continuity of tasks and progress tracking even after the app is closed and reopened.
No comments:
Post a Comment