Weston Mitchell
Weston Mitchell

Reputation: 321

How do I properly return a CollectionViewCell with reuseIdentifier if weak self is nil in RxDataSource function?

I have an issue where [unowned self] was changed to [weak self] within the dataSource function used for a CollectionView using RxDataSource due to a memory leak. I now received a crash from returning a blank collectionViewCell that doesn't have a reuseIdentifier. I understand that I need to return a cell with a reuseID.

What changes are suggested to deal with this properly?

Someone suggested making collectionView.dataSource = nil in viewDidLoad() would fix this...

I was thinking instead of returning CanvasItemCollectionViewCell() in the 'guard' check, I return collectionView.dequeueReusableCell(for: indexPath, cellType: CanvasItemCollectionViewCell.self), but if self = self fails wouldn't that mean the collectionView is garbage?

This is a difficult problem to debug because this crash doesn't happen consistently.

Here are some screenshots to portray what I am looking at.

RxDataSource code:

func dataSource()
        -> RxCollectionViewSectionedAnimatedDataSource<CanvasSectionModel> {
        RxCollectionViewSectionedAnimatedDataSource<CanvasSectionModel>(
            animationConfiguration: AnimationConfiguration(
                insertAnimation: .fade,
                reloadAnimation: .fade,
                deleteAnimation: .fade
            ),
            configureCell: { [weak self] dataSource, collectionView, indexPath, _ in
                guard let self = self else { return CanvasItemCollectionViewCell() }
                
                switch dataSource[indexPath] {
                case let .CellModel(model):
                    let cell = collectionView
                        .dequeueReusableCell(
                            for: indexPath,
                            cellType: CanvasItemCollectionViewCell.self
                        )

                    cell.model = model

                    cell.onDeleteHandler = { _ in
                        self.presentDeleteConfirmation { deleteConfirmed in
                            guard deleteConfirmed else { return }
                            self.viewModel.inputs.deletePage(withProofID: model.id)
                        }
                    }

                    return cell
                }
            }

Crash:

enter image description here

Upvotes: 1

Views: 290

Answers (1)

Daniel T.
Daniel T.

Reputation: 33967

Ultimately, the problem is here:

cell.onDeleteHandler = { _ in
    self.presentDeleteConfirmation { deleteConfirmed in
        guard deleteConfirmed else { return }
        self.viewModel.inputs.deletePage(withProofID: model.id)
    }
}

Don't use self and you won't have a problem with self references so you won't need to import a weak self and then worry about the guard let self...

  • For the first self reference, replace it with UIViewController.top() (see below for implementation.)
  • For the second self reference, capture viewModel instead.
extension UIViewController {
    static func top() -> UIViewController {
        guard let rootViewController = UIApplication.shared.delegate?.window??.rootViewController else { fatalError("No view controller present in app?") }
        var result = rootViewController
        while let vc = result.presentedViewController, !vc.isBeingDismissed {
            result = vc
        }
        return result
    }
}

Upvotes: 1

Related Questions