选择照片后UIImagePickerController出现内存泄漏问题
巧了,我之前做项目也碰到过一模一样的问题!折腾了好几天才找到根源,其实UIImagePickerController的内存泄漏大多和代理引用逻辑、对象生命周期管理脱不了干系,给你几个针对性的排查和解决方向:
检查代理的内存引用方式
很多人踩坑是因为没注意代理的弱引用规则。不管是Swift还是OC,UIImagePickerController的delegate属性本身就是弱引用设计,但如果你在自己的控制器里强持有了picker实例,或者代理声明时不小心用了强引用,就会触发循环引用。举个Swift的正确写法:
class YourVC: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { // 不需要强持有picker!用局部变量更安全 func openPhotoPicker() { let picker = UIImagePickerController() picker.delegate = self picker.sourceType = .photoLibrary present(picker, animated: true) } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { // 处理图片逻辑 if let selectedImage = info[.originalImage] as? UIImage { // 比如赋值给UI展示 self.avatarImageView.image = selectedImage } // 直接dismiss,不用额外持有picker picker.dismiss(animated: true) } }如果是Objective-C,一定要把picker的属性声明为
weak:@interface YourVC () <UIImagePickerControllerDelegate, UINavigationControllerDelegate> @property (nonatomic, weak) UIImagePickerController *imagePicker; @end避免不必要的强持有
很多开发者习惯把picker实例存为控制器的强引用属性,但其实present之后系统已经持有了picker,完全可以用局部变量。dismiss后局部变量会被自动释放,从根源避免泄漏。如果必须存为属性,记得在dismiss完成后手动置空:picker.dismiss(animated: true) { [weak self] in self?.imagePicker = nil }排查图片处理环节的隐式引用
有时候泄漏不是picker本身的问题,而是你在didFinishPickingMediaWithInfo里处理图片时,不小心用了强引用闭包。比如异步处理图片时,一定要用[weak self]打破循环:func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let image = info[.originalImage] as? UIImage else { picker.dismiss(animated: true) return } picker.dismiss(animated: true) { [weak self] in // 异步压缩图片等操作 self?.compressAndSaveImage(image) } }考虑替换为更现代的PHPickerViewController
如果你适配的是iOS 14及以上版本,完全可以用苹果推出的PHPickerViewController替代旧的UIImagePickerController。这个新框架不仅内存管理更稳定,还避免了很多旧框架的已知bug,用法也大同小异。用Leaks Instrument精准定位
上面的方法都试了还没解决的话,直接用Xcode的Leaks工具排查。运行app后打开Instrument选择Leaks,操作一遍选图dismiss的流程,就能看到具体是哪个对象没被释放——如果是你的控制器实例没释放,那肯定是循环引用;如果是picker本身没释放,就找哪里还在强持有它。
我之前就是因为强持有了picker实例又没及时置空导致的泄漏,按照上面的方法调整后就解决了,你可以逐个排查试试。
内容的提问来源于stack exchange,提问作者Max

