You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:35:23