Monday, 20 May 2024

Video Task Manager System design in iOS(Download, Upload, Cancel Multiple videos)

 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

  1. MVC or MVVM Pattern: Use Model-View-Controller (MVC) or Model-View-ViewModel (MVVM) design pattern to separate concerns and facilitate testability and maintainability.

  2. Background Task Management: Utilize iOS background task capabilities such as URLSession, BackgroundTasks, and OperationQueue for handling concurrent operations efficiently.

  3. Persistence: CoreData or Realm can be used for local storage to keep track of video tasks' states (e.g., pending, in-progress, completed, failed).

  4. Notifications and Delegates: Use notifications and delegate patterns to communicate between components.

Core Modules

  1. Task Manager: Central manager to handle all tasks.
  2. Download Manager: Handles video download tasks.
  3. Upload Manager: Manages video upload tasks.
  4. Task Queue: Manages the order and concurrency of tasks.
  5. Persistence Layer: Stores task states and information.
  6. 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.
  • 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 URLSession with background configurations to handle tasks even when the app is in the background.
  • OperationQueue: Use OperationQueue for managing concurrent execution of tasks with dependencies.
  • DispatchQueue: Utilize DispatchQueue for thread-safe operations on shared resources.

Example Workflow

  1. User Initiates a Download:

    • The user selects a video to download.
    • TaskManager creates a VideoTask and adds it to TaskQueue.
    • DownloadManager starts the download using URLSession.
    • PersistenceLayer saves the task state.
  2. User Cancels a Task:

    • The user selects a task to cancel.
    • TaskManager invokes cancelTask on the respective manager (DownloadManager or UploadManager).
    • The manager cancels the operation and updates the task state.
    • TaskQueue is updated to remove the task.
  3. App Launches:

    • PersistenceLayer loads task states.
    • TaskManager reinitializes 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.

Setting Up Multiple App Targets in Xcode from a Single Codebase

 To create two different apps (like "Light" and "Regular") from the same codebase in Xcode, you can follow these steps b...