UIViewController 中的集合视图可选展开崩溃

Posted

技术标签:

【中文标题】UIViewController 中的集合视图可选展开崩溃【英文标题】:Collection View in UIViewController optional unwrapping crash 【发布时间】:2017-02-16 10:51:05 【问题描述】:

对于 Swift 来说还是很陌生,并且已经阅读/了解了“最佳实践”,我正在尝试重构一些简单的代码,但无法将我的头脑围绕可选选项并将简单的 UICollectionView 放在 UIViewController 中。

我有什么,什么有效

class AddFriendsController: UIViewController 

    fileprivate let cellId = "cellId"

    override func viewDidLoad() 
        super.viewDidLoad()

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancel))

        let layout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
        layout.itemSize = CGSize(width: 111, height: 111)

        let collectionViewTest = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        collectionViewTest.delegate = self
        collectionViewTest.dataSource = self
        collectionViewTest.register(UserFriendCell.self, forCellWithReuseIdentifier: cellId)

        view.addSubview(collectionViewTest)
        collectionViewTest.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        collectionViewTest.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        collectionViewTest.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        collectionViewTest.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
    

    @objc private func handleCancel() 

        self.dismiss(animated: true, completion: nil)
    



extension AddFriendsController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    func numberOfSections(in collectionView: UICollectionView) -> Int 

        return 2
    

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 

        return 9
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! UserFriendCell

        return cell
    

我希望它是什么样的

class AddFriendsController: UIViewController 

    fileprivate let cellId = "cellId"
    private weak var collectionViewTest: UICollectionView?
    private weak var layout: UICollectionViewFlowLayout?

    override func viewDidLoad() 
        super.viewDidLoad()

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancel))

        layout = UICollectionViewFlowLayout()
        layout!.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
        layout!.itemSize = CGSize(width: 111, height: 111)

        collectionViewTest = UICollectionView(frame: self.view.frame, collectionViewLayout: layout!)
        collectionViewTest!.delegate = self
        collectionViewTest!.dataSource = self
        collectionViewTest!.register(UserFriendCell.self, forCellWithReuseIdentifier: cellId)

        view.addSubview(collectionViewTest!)
        collectionViewTest!.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        collectionViewTest!.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        collectionViewTest!.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        collectionViewTest!.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
    

    @objc private func handleCancel() 

        self.dismiss(animated: true, completion: nil)
    


extension AddFriendsController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    func numberOfSections(in collectionView: UICollectionView) -> Int 

        return 2
    

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 

        return 9
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! UserFriendCell

        return cell
    

这样做是因为我想在学习 Swift 时从一开始就尝试应用“最佳实践”,即根据我在这种情况下的理解,尽可能多地制作 privateweakify/"optionalize" 视图/outlets 以避免保留循环。

问题

由于 bang 在 layout!.sectionInset 级别展开 nil,应用程序崩溃。

哪位好心人愿意开导我?

提前致谢。

【问题讨论】:

为什么你的布局属性很弱?它是否保留了视图控制器?为什么不从声明中初始化它?所以你不需要强制解包:private let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() 【参考方案1】:

看起来你在声明你的布局很弱。如果您将其更改为强,即只需删除弱,那么您应该没问题。

您的 CollectionView 也是如此。

同样作为风格问题,您可以将布局和 collectionView 声明为隐式展开,而不是每次都展开。

比如private var layout: UICollectionViewFlowLayout!

【讨论】:

同时从collectionView中移除weak,你应该在制作IBOutlet的时候使用weak,因为这个view是被创建并保存在storyboard/xib中的! 工作正常@Chris,谢谢! @Vadim 好的,这是我的主要审问:weakify 视图实际上仅在从情节提要设计 UI 时才需要,但由于您刚才引用的原因而不是编程方式,即在我的情况下,当没有其他指向它时,它将被安全地释放?如果我在另一个控制器 controllerB 中有对 collectionViewTest 的引用怎么办:我是否应该在 weakify 这个引用在 controllerB 中避免在我解除 controllerB 时出现保留循环?【参考方案2】:

你的代码有问题。

如果您将变量作为可选变量,例如

 private weak var layout: UICollectionViewFlowLayout?

然后在强制解包之前,您应该确认这是非零。所以在这一行它会崩溃,因为你试图强行解开一个实际上是 nil 的变量。

layout!.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)

所以如果你想在不添加非零检查的情况下做喜欢它,那么你应该写。

layout?.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)

但是这里你的代码也是错误的,因为你不应该使用变量weak,因为弱引用只是一个指向对象的指针,它不能保护对象不被ARC释放

这里你希望 UICollectionViewFlowLayout 和 UICollectionView 贯穿 UIViewController 的生命周期,所以你不应该认为它很弱。

所以你应该将两者都定义为下。

    private var collectionViewTest: UICollectionView!
    private var layout: UICollectionViewFlowLayout!  

   private var collectionViewTest: UICollectionView?
    private var layout: UICollectionViewFlowLayout?

【讨论】:

感谢@Nikunj 提供的详细信息 这是我的荣幸 :)

以上是关于UIViewController 中的集合视图可选展开崩溃的主要内容,如果未能解决你的问题,请参考以下文章

一个 UIViewController 中的两个 UICollectionView

如何在 Swift 的 UIViewController 中添加多个集合视图?

UIview中的两个表视图致命错误

表格视图单元格中的集合视图

UIViewController xib 文件包含一个带有自定义单元格的集合视图

快速致命错误:在展开可选值时意外发现 nil