Swift中如何用Core Data存储视频?含离线下载分步教程(零基础)
Hey there! No worries if you're new to Core Data—let's break this down into simple, actionable steps to store video metadata (and track offline downloads) using Core Data, plus implement the actual video download logic. I'll walk you through everything from setup to playing offline videos.
1. Set Up Core Data in Your Project
- New Project: When creating your iOS project in Xcode, check the "Use Core Data" box. Xcode automatically generates a Core Data stack (including a
PersistentContainer) in yourAppDelegate(orSceneDelegatefor older iOS versions) and creates a.xcdatamodeldmodel file. - Existing Project: Add a new Core Data model file (File > New > File > Core Data > Data Model), then create a
PersistentContainerin a dedicated manager class (we'll build this next) to handle Core Data operations.
2. Create Your Video Entity
Open the .xcdatamodeld file and follow these steps:
- Click the "+" button in the bottom-left to add an Entity named
Video. - Add these attributes to the
Videoentity (adjust types as needed):id: String (mark as Unique in the Constraints section to avoid duplicate records)title: String (optional, for displaying video names)remoteURL: String (stores the online video URL)localFilePath: String (optional, will hold the path to the downloaded offline video)isDownloaded: Bool (default value:false, tracks if the video is available offline)downloadProgress: Double (default value:0.0, tracks download percentage)
- Save the model file.
3. Generate NSManagedObject Subclass
To work with typed code instead of raw KVC calls:
- Select the
Videoentity in the model file. - Go to Editor > Create NSManagedObject Subclass and follow the prompts. This generates a
Video.swiftfile with a strongly typed class for your entity.
4. Build a Core Data Manager (Singleton)
Create a singleton class to handle all Core Data operations—this keeps your code organized and ensures you're using a single Core Data context throughout your app.
import CoreData class CoreDataManager { static let shared = CoreDataManager() private init() {} // Load your Core Data model (replace "YourModelName" with your actual model filename) lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "YourModelName") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Core Data load failed: \(error), \(error.userInfo)") } }) return container }() // Access the main context for UI-related operations var context: NSManagedObjectContext { return persistentContainer.viewContext } // Save changes to Core Data func saveContext() { if context.hasChanges { do { try context.save() } catch { let nserror = error as NSError print("Core Data save error: \(nserror), \(nserror.userInfo)") } } } // Fetch all stored videos func fetchVideos() -> [Video] { let fetchRequest: NSFetchRequest<Video> = Video.fetchRequest() do { return try context.fetch(fetchRequest) } catch { print("Fetch error: \(error)") return [] } } // Add or update a video record (upsert) func upsertVideo(id: String, title: String, remoteURL: String, localFilePath: String? = nil, isDownloaded: Bool = false, downloadProgress: Double = 0.0) { // Check if the video already exists let fetchRequest: NSFetchRequest<Video> = Video.fetchRequest() fetchRequest.predicate = NSPredicate(format: "id == %@", id) do { let existingVideos = try context.fetch(fetchRequest) let video: Video if let existingVideo = existingVideos.first { video = existingVideo // Update existing record } else { video = Video(context: context) // Create new record video.id = id } video.title = title video.remoteURL = remoteURL video.localFilePath = localFilePath video.isDownloaded = isDownloaded video.downloadProgress = downloadProgress saveContext() } catch { print("Upsert error: \(error)") } } // Delete a video (and its local file if it exists) func deleteVideo(_ video: Video) { // Delete local file first if it exists if let localFilePath = video.localFilePath { let fileURL = URL(fileURLWithPath: localFilePath) do { try FileManager.default.removeItem(at: fileURL) } catch { print("Failed to delete local video file: \(error)") } } context.delete(video) saveContext() } }
5. Implement Video Download Logic
Create a VideoDownloadManager to handle downloading videos, tracking progress, and updating Core Data when downloads complete.
import Foundation class VideoDownloadManager { static let shared = VideoDownloadManager() private var downloadTasks: [String: URLSessionDownloadTask] = [:] // Map video IDs to active tasks private let session = URLSession.shared // Start downloading a video func startDownload(for video: Video, progressHandler: @escaping (Double) -> Void, completionHandler: @escaping (Error?) -> Void) { guard let remoteURL = URL(string: video.remoteURL), !video.isDownloaded else { completionHandler(nil) return } // Cancel existing task for this video if it exists downloadTasks[video.id!]?.cancel() let task = session.downloadTask(with: remoteURL) { [weak self] tempURL, response, error in guard let self = self else { return } self.downloadTasks.removeValue(forKey: video.id!) if let error = error { completionHandler(error) return } guard let tempURL = tempURL, let response = response as? HTTPURLResponse, response.statusCode == 200 else { completionHandler(NSError(domain: "DownloadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid download response"])) return } // Move the temporary download file to the app's Documents directory let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let fileExtension = remoteURL.pathExtension let fileName = "\(video.id!).\(fileExtension)" let destinationURL = documentsDir.appendingPathComponent(fileName) do { // Delete existing file if it exists if FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) } try FileManager.default.moveItem(at: tempURL, to: destinationURL) // Update Core Data to mark the video as downloaded CoreDataManager.shared.upsertVideo( id: video.id!, title: video.title!, remoteURL: video.remoteURL, localFilePath: destinationURL.path, isDownloaded: true, downloadProgress: 1.0 ) completionHandler(nil) } catch { completionHandler(error) } } // Track download progress task.progress.addObserver(self, forKeyPath: "fractionCompleted", options: .new, context: nil) downloadTasks[video.id!] = task task.resume() } // Observe progress updates override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "fractionCompleted", let progress = object as? Progress { // Find which video this progress belongs to for (videoId, task) in downloadTasks { if task.progress === progress { if let video = CoreDataManager.shared.fetchVideos().first(where: { $0.id == videoId }) { let progressValue = progress.fractionCompleted CoreDataManager.shared.upsertVideo( id: videoId, title: video.title!, remoteURL: video.remoteURL, downloadProgress: progressValue ) // Pass progress to the UI (use Combine/delegate if needed) // progressHandler(progressValue) } break } } } } // Cancel a download func cancelDownload(for videoId: String) { downloadTasks[videoId]?.cancel() downloadTasks.removeValue(forKey: videoId) } }
6. Play Offline Videos
Once a video is downloaded, fetch it from Core Data and play it using AVPlayer:
import AVKit func playOfflineVideo(_ video: Video) { guard video.isDownloaded, let localFilePath = video.localFilePath else { // Handle case where video isn't downloaded print("Video not available offline") return } let fileURL = URL(fileURLWithPath: localFilePath) let player = AVPlayer(url: fileURL) let playerVC = AVPlayerViewController() playerVC.player = player // Present the player (adjust based on your UI setup) if let topVC = UIApplication.shared.windows.first?.rootViewController { topVC.present(playerVC, animated: true) { player.play() } } }
7. Key Error Handling Tips
- Wrap all Core Data operations in
do-catchblocks to handle save/fetch errors. - Check network connectivity before starting downloads (use
NWPathMonitoror similar). - Validate file paths and existence before attempting to play offline videos.
- Handle download task cancellations and failures gracefully in your UI.
内容的提问来源于stack exchange,提问作者stalin athavan

