如何在performDrop的闭包中使用局部变量?CS193P课程技术问询
performDrop for CS193P Hey there! I remember wrestling with this exact part of CS193P early on—let's break down how to implement this properly using closure-captured local variables, just like the instructor suggested.
First, let's restate the core goal: when handling a drop action, you need to collect two critical pieces of data before adding an item to your model: the UIImage's aspect ratio, and the file URL of the image. The trick is using local variables in your performDrop method that get populated by the async data-loading closures.
Here's a step-by-step implementation with code examples tailored to CS193P's project structure:
1. Set up local variables to capture your data
Inside your performDrop method (part of your DropDelegate implementation), declare optional variables to hold the aspect ratio and URL. These will be captured and modified by the async load closures:
func performDrop(info: DropInfo) -> Bool { // Local variables to store our required data var imageAspectRatio: CGFloat? var imageFileURL: URL? // Use a DispatchGroup to wait for all async operations to finish let loadGroup = DispatchGroup() // ... rest of the logic goes here }
2. Process drop items and populate the variables
Loop through the item providers from the DropInfo, and use loadObject() to fetch both the UIImage (for aspect ratio) and the file URL. The closures here will capture your local variables and update them as data loads:
for provider in info.itemProviders { // Load UIImage to calculate aspect ratio if provider.canLoadObject(ofClass: UIImage.self) { loadGroup.enter() provider.loadObject(ofClass: UIImage.self) { [weak self] loadedObject, error in defer { loadGroup.leave() } guard let uiImage = loadedObject as? UIImage else { return } // Calculate and store the aspect ratio imageAspectRatio = uiImage.size.width / uiImage.size.height } } // Load the image's file URL if provider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) { loadGroup.enter() provider.loadObject(ofClass: URL.self) { loadedObject, error in defer { loadGroup.leave() } imageFileURL = loadedObject as? URL } } }
3. Wait for all data to load, then update your model
Use notify(queue:) on the DispatchGroup to run code once all async loads are complete. Here's where you validate that both pieces of data exist, then add the new item to your model:
loadGroup.notify(queue: .main) { [weak self] in guard let ratio = imageAspectRatio, let url = imageFileURL else { // Handle cases where we didn't get all required data (e.g., show an alert) return } // Update your model with the collected data! self?.model.addNewImageItem(url: url, aspectRatio: ratio) } return true // Confirm we've handled the drop action
Why this works
The local variables imageAspectRatio and imageFileURL are captured by the async load closures. Even though these closures run after the initial performDrop code proceeds, they can still modify these variables. The DispatchGroup ensures we don't try to update the model until both pieces of required data are ready.
A few quick notes to avoid pitfalls:
- Always use
[weak self]in the closures to prevent retain cycles with your view/model. - Add error handling for failed loads (the example skips this for brevity, but you should check the
errorparameter and handle missing data gracefully). - If your model uses
@Published, updating it on the main queue (as we do here) ensures SwiftUI picks up the changes immediately.
内容的提问来源于stack exchange,提问作者Eduard Lev

