Objective-C中Completion Handler触发EXC_BAD_ACCESS错误排查
Hey there! Let's break down this EXC_BAD_ACCESS crash with your completion handler—it’s a super common pitfall, so we’ll get to the bottom of it.
First, let’s start with the most likely culprit: memory management issues with your completion block. EXC_BAD_ACCESS here almost always means you’re trying to call a block that’s already been deallocated, or accessing an object inside the block that’s no longer in memory. Let’s walk through the key checks:
1. Check your completion handler’s method definition
If your .h file declares the completion parameter with __weak, that’s a red flag. For example:
// ❌ Bad: Weak reference lets the block be deallocated early - (void)GetPDFFileDataWithID:(NSString *)fileID completion:(void (^__weak)(NSData *, NSError *))completion;
In ARC, block parameters are strong by default, which ensures the method holds onto the block until it’s done executing. Remove the __weak modifier to fix this:
// ✅ Good: Strong reference keeps the block alive - (void)GetPDFFileDataWithID:(NSString *)fileID completion:(void (^)(NSData *, NSError *))completion;
2. Verify how you store/invoke the block in your .m implementation
If your method runs an asynchronous task (like fetching data from a server), you need to make sure the block is retained until the task finishes. Storing it as a copy property is safe (since blocks start on the stack, copying moves them to the heap):
// Inside your .m file's interface extension @property (nonatomic, copy) void (^completionHandler)(NSData *, NSError *); - (void)GetPDFFileDataWithID:(NSString *)fileID completion:(void (^)(NSData *, NSError *))completion { self.completionHandler = completion; // Run your async task (e.g., network call) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *pdfData = [self fetchPDFFromServer:fileID]; NSError *error = nil; // Always call completion handlers on the main thread if they touch UI dispatch_async(dispatch_get_main_queue(), ^{ // Check if the block still exists before calling it if (self.completionHandler) { self.completionHandler(pdfData, error); self.completionHandler = nil; // Clear to avoid accidental re-calls } }); }); }
Skipping the if (self.completionHandler) check could lead to calling a nil block (though that usually doesn’t crash, but better safe than sorry).
3. Check what’s inside your call-site block
If you’re referencing objects (like self or a local variable) inside the block you pass to GetPDFFileData, make sure those objects aren’t deallocated before the block runs. For example:
// ❌ Risky: If this ViewController is popped/dismissed before the task finishes... [self.pdfService GetPDFFileDataWithID:@"123" completion:^(NSData *data, NSError *error) { [self updateUIWithPDFData:data]; // ...self could be deallocated here }];
Fix this by using a weak reference to self inside the block (to avoid retain cycles and safely check if it’s alive):
// ✅ Safe: Weak self prevents retain cycles and lets you check existence __weak typeof(self) weakSelf = self; [self.pdfService GetPDFFileDataWithID:@"123" completion:^(NSData *data, NSError *error) { typeof(weakSelf) strongSelf = weakSelf; if (strongSelf) { [strongSelf updateUIWithPDFData:data]; } }];
4. Use your crash backtrace to narrow it down
If your backtrace points directly to the line where you call completion(pdfData, error), that confirms the block itself was deallocated. If it points to a line inside the block (like accessing self), then the issue is with an object the block references.
If you can share your exact .h definition, .m implementation, and call-site code, we can pinpoint this even faster—but based on what you’ve described, the completion handler’s memory management is almost certainly the issue.
内容的提问来源于stack exchange,提问作者user979331

