UIPageViewController滚动时动态增删VC解决内存泄漏问题
UIPageViewController 内存泄漏解决方案
问题背景
使用UIPageViewController时遭遇严重内存泄漏,滚动页面时内存急剧增长。需加载350+个TestVC实例,一次性将所有VC加入数组会直接导致内存爆炸,希望通过viewControllerBefore和viewControllerAfter方法动态维护VC数组,始终只保留当前页前后少量VC(例如初始保留[vc1,vc2,vc3],滚动到下一页时更新为[vc2,vc3,vc4],移除旧VC并添加新VC)。
现有代码
TestVC 代码
var TodayDate: String? var data1 : String? , data2: String? var data3: String? , data4: String? var data5: String? , data6: String? var imagee: UIImage?
UIPageViewController 现有实现
let vc1 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC vc1.TodayDate = "06.09.2022" vc1.data1 = "welcome" vc1.data2 = "The first data..." vc1.data3 = "The first data..." vc1.data4 = "The first data..." vc1.data5 = "The first data..." vc1.data6 = "The first data..." vc1.imagee = UIImage(named: "0001") let vc2 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC vc2.TodayDate = "07.09.2022" vc2.data1 = "welcome" vc2.data2 = "The second data..." vc2.data3 = "The second data..." vc2.data4 = "The second data..." vc2.data5 = "The second data..." vc2.data6 = "The second data..." vc2.imagee = UIImage(named: "0002") let vc3 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC vc3.TodayDate = "08.09.2022" vc3.data1 = "welcome" vc3.data2 = "The third data..." vc3.data3 = "The third data..." vc3.data4 = "The third data..." vc3.data5 = "The third data..." vc3.data6 = "The third data..." vc3.imagee = UIImage(named: "0003") let vc4 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC vc4.TodayDate = "08.09.2022" vc4.data1 = "welcome" vc4.data2 = "The fourth data..." vc4.data3 = "The fourth data..." vc4.data4 = "The fourth data..." vc4.data5 = "The fourth data..." vc4.data6 = "The fourth data..." vc4.imagee = UIImage(named: "0004") let vc5 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC vc3.TodayDate = "08.09.2022" vc3.data1 = "welcome" vc3.data2 = "The fifth data..." vc3.data3 = "The fifth data..." vc3.data4 = "The fifth data..." vc3.data5 = "The fifth data..." vc3.data6 = "The fifth data..." vc3.imagee = UIImage(named: "0005") arraypage1.append(contentsOf:[vc1, vc2, vc3]) // 期望滚动到下一页时添加vc4并移除vc1 func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let currentIndex = arraypage2.firstIndex(of: viewController) else { return nil } UserDefaults.standard.removeObject(forKey: "emptyView1") let previousIndex = currentIndex - 1 guard previousIndex >= 0 else { return nil } return arraypage2[previousIndex] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let currentIndex = arraypage2.firstIndex(of: viewController) else { return nil } let afterIndex = currentIndex + 1 let afterIndex3 = currentIndex + 2 if afterIndex3 == arraypage2.count { UserDefaults.standard.set("next", forKey: "emptyView1") } guard afterIndex < arraypage2.count else { return nil } return arraypage2[afterIndex] }
解决方案
核心思路:不预加载所有VC,分离数据与VC实例,根据当前页码动态创建/销毁VC,仅维护当前页前后有限数量的活跃VC。
1. 重构数据模型,分离页面数据与VC
先将每个页面的抽成独立数据模型,避免直接绑定VC实例:
struct PageData { let date: String let data1: String let data2: String let data3: String let data4: String let data5: String let data6: String let imageName: String } // 初始化所有350+条页面数据 let allPageData: [PageData] = [ PageData(date: "06.09.2022", data1: "welcome", data2: "The first data...", data3: "The first data...", data4: "The first data...", data5: "The first data...", data6: "The first data...", imageName: "0001"), PageData(date: "07.09.2022", data1: "welcome", data2: "The second data...", data3: "The second data...", data4: "The second data...", data5: "The second data...", data6: "The second data...", imageName: "0002"), // ... 剩余350+条数据 ]
2. 修改TestVC,添加页码标识属性
给TestVC增加pageIndex属性,用于跟踪该VC对应的页面索引:
class TestPageVC: UIViewController { var pageIndex: Int? // 新增页码标识 var TodayDate: String? var data1 : String? , data2: String? var data3: String? , data4: String? var data5: String? , data6: String? var imagee: UIImage? }
3. 重写UIPageViewController逻辑,动态维护活跃VC
在UIPageViewController中,仅维护当前页、前一页、后一页的VC实例,并在页面滚动时更新数组:
class YourPageVC: UIPageViewController { // 所有页面数据 private let allPageData: [PageData] = // 这里传入初始化好的350+条数据 // 当前显示的页码 private var currentPageIndex = 0 // 活跃VC数组,始终保留当前页±1的VC(最多3个) private var activeViewControllers: [TestPageVC] = [] override func viewDidLoad() { super.viewDidLoad() dataSource = self // 初始化初始活跃VC组 setupInitialActiveVCs() } // 初始化第一组活跃VC private func setupInitialActiveVCs() { let initialVCs = [0,1,2].compactMap { index in guard index < allPageData.count else { return nil } let vc = createVC(with: allPageData[index]) vc.pageIndex = index return vc } activeViewControllers = initialVCs // 设置初始显示页面 if let firstVC = initialVCs.first { setViewControllers([firstVC], direction: .forward, animated: false) } } // 根据数据创建VC的工具方法 private func createVC(with data: PageData) -> TestPageVC { let vc = storyboard?.instantiateViewController(withIdentifier: "page1") as! TestPageVC vc.TodayDate = data.date vc.data1 = data.data1 vc.data2 = data.data2 vc.data3 = data.data3 vc.data4 = data.data4 vc.data5 = data.data5 vc.data6 = data.data6 vc.imagee = UIImage(named: data.imageName) return vc } // 更新活跃VC数组,清理超出范围的旧VC,创建需要的新VC private func updateActiveVCs(for currentIndex: Int) { // 移除当前页±1范围外的旧VC activeViewControllers.removeAll { vc in guard let vcIndex = vc.pageIndex else { return true } return vcIndex < currentIndex - 1 || vcIndex > currentIndex + 1 } // 检查并创建前一页的VC(如果不存在且索引有效) let prevIndex = currentIndex - 1 if prevIndex >= 0, !activeViewControllers.contains(where: { $0.pageIndex == prevIndex }) { let prevVC = createVC(with: allPageData[prevIndex]) prevVC.pageIndex = prevIndex activeViewControllers.append(prevVC) } // 检查并创建后一页的VC(如果不存在且索引有效) let nextIndex = currentIndex + 1 if nextIndex < allPageData.count, !activeViewControllers.contains(where: { $0.pageIndex == nextIndex }) { let nextVC = createVC(with: allPageData[nextIndex]) nextVC.pageIndex = nextIndex activeViewControllers.append(nextVC) } } // 监听页面滚动完成,更新当前页码和活跃VC数组 override func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed, let currentVC = pageViewController.viewControllers?.first as? TestPageVC, let index = currentVC.pageIndex else { return } currentPageIndex = index updateActiveVCs(for: index) } }
4. 实现PageViewController数据源方法
修改viewControllerBefore和viewControllerAfter,直接根据页码获取或创建对应VC:
extension YourPageVC: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let currentVC = viewController as? TestPageVC, let currentIndex = currentVC.pageIndex else { return nil } let prevIndex = currentIndex - 1 guard prevIndex >= 0 else { return nil } // 优先从活跃数组取,取不到则创建并加入数组 if let prevVC = activeViewControllers.first(where: { $0.pageIndex == prevIndex }) { return prevVC } else { let prevVC = createVC(with: allPageData[prevIndex]) prevVC.pageIndex = prevIndex activeViewControllers.append(prevVC) return prevVC } } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let currentVC = viewController as? TestPageVC, let currentIndex = currentVC.pageIndex else { return nil } let nextIndex = currentIndex + 1 guard nextIndex < allPageData.count else { return nil } // 优先从活跃数组取,取不到则创建并加入数组 if let nextVC = activeViewControllers.first(where: { $0.pageIndex == nextIndex }) { return nextVC } else { let nextVC = createVC(with: allPageData[nextIndex]) nextVC.pageIndex = nextIndex activeViewControllers.append(nextVC) return nextVC } } }
额外内存优化
- 图片加载优化:避免使用
UIImage(named:)(会缓存图片到内存),改用UIImage(contentsOfFile:)加载,或使用第三方图片库(如Kingfisher)自动管理图片缓存与释放。 - TestVC内存清理:在
viewDidDisappear中清空所有数据引用,帮助ARC快速回收内存:
override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) imagee = nil TodayDate = nil data1 = nil data2 = nil data3 = nil data4 = nil data5 = nil data6 = nil }
内容的提问来源于stack exchange,提问作者A clear
相关产品推荐
相关产品推荐

