【问题标题】:Bad Access on [UICollectionView setCollectionViewLayout:animated:][UICollectionView setCollectionViewLayout:animated:] 上的错误访问
【发布时间】:2013-08-13 21:38:14
【问题描述】:

我的 UICollectionView 发生了奇怪的崩溃。 崩溃的 UICollectionView 嵌入在另一个 UICollectionView 的 UICollectionView 单元格中。

我无法重现该问题,如果内部 UICollectionView 得到新初始化,有时似乎会发生,因为外部 CollectionView 正在重新加载它的单元格。

com.apple.main-thread 崩溃 0 libobjc.A.dylib objc_msgSend + 9 1 UIKit-[UICollectionViewData _setLayoutAttributes:atGlobalItemIndex:] + 60 2 UIKit __45-[UICollectionViewData validateLayoutInRect:]_block_invoke_0 + 668 3 UIKit-[UICollectionViewData validateLayoutInRect:] + 1408 4 UIKit-[UICollectionViewData layoutAttributesForElementsInRect:] + 82 5 UIKit-[UICollectionView setCollectionViewLayout:animated:] + 1644 6 MyApp BSCTopnewsCollectionView.m 第 52 行 -[BSCTopnewsCollectionView setupBSCTopnewsCollectionView] 7 MyApp BSCTopnewsCollectionView.m 第 27 行 -[BSCTopnewsCollectionView setWeakDelegatePointer:] 8 Myapp BSCFrontPageViewController.m 第 550 行-[BSCFrontPageViewController collectionView:cellForItemAtIndexPath:] 9 UIKit-[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:] + 252 10 UIKit-[UICollectionView _updateVisibleCellsNow:] + 2672 11 UIKit-[UICollectionView layoutSubviews] + 214 12 UIKit-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 258 13 QuartzCore -[CALayer layoutSublayers] + 214 14 QuartzCore CA::Layer::layout_if_needed(CA::Transaction*) + 460 15 QuartzCore CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 16 16 QuartzCore CA::Context::commit_transaction(CA::Transaction*) + 238 17 QuartzCore CA::Transaction::commit() + 316 18 QuartzCore CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 60 19 核心基础 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 20 25 UIKit UIApplicationMain + 1120 26 MyApp main.m 第 16 行 main 异常类型: EXC_BAD_ACCESS 代码: KERN_INVALID_ADDRESS 在 0x158848

我在 setupBSCTopnewsCollectionView 第 52 行所做的是

BSCInfiniteLayout *infiniteLayout = [[BSCInfiniteLayout alloc] init]; (第 52 行)self.collectionView.collectionViewLayout = 无限布局;



编辑:-[BSCFrontPageViewController collectionView:cellForItemAtIndexPath:]
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    if([collectionView isEqual:self.collectionView])
    {
        if(indexPath.row == 0) // Header Cell
        {
            BSCTopnewsCollectionView *cell = [collectionView dequeueReusableCellWithReuseIdentifier:BSCHeaderReuseIdentifier forIndexPath:indexPath];
            cell.dataSource = self;
            cell.weakDelegatePointer = self;

            self.topNewsCollectionView = cell;

            return cell;
        }
        else
        {
            //create normal cells
        }
    }
    else if ([collectionView isEqual:self.topNewsCollectionView.collectionView])
    {
        BSCTopNewsHeaderCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:BSCTopNewsCellReuseIdentifier forIndexPath:indexPath];
        BSCNews *topnews = [self.topNews objectAtIndex:indexPath.row];

        [cell setEntity:topnews];

        return cell;
    }
}

这里对方法调用的一些说明:

- (void)setWeakDelegatePointer:(BSCFrontPageViewController *)weakDelegatePointer
{
    _weakDelegatePointer = weakDelegatePointer;

    [self setupBSCTopnewsCollectionView];
    [self.collectionView reloadData];
}

- (void)setupBSCTopnewsCollectionView
{
    self.collectionView.delegate = self.weakDelegatePointer;
    self.collectionView.dataSource = self.weakDelegatePointer;

    BSCInfiniteLayout *infiniteLayout = [[BSCInfiniteLayout alloc] init];


    infiniteLayout.delegate = self;

    // Setup Layout
    self.collectionView.collectionViewLayout = infiniteLayout;
    self.collectionView.showsHorizontalScrollIndicator = NO;
    self.collectionView.pagingEnabled = YES;

    // Register Cells
    [self.collectionView registerNib:[UINib nibWithNibName:@"BSCTopNewsHeaderCell" bundle:nil] forCellWithReuseIdentifier:BSCTopNewsCellReuseIdentifier];
}



Edit3:崩溃似乎只发生在特殊场合。 如果应用程序在后台,但仍在内存中并且用户再次打开它。然后它会检查我们的 API 是否有新数据,如果发现某些东西会加载它们并重新加载整个 outer collectionView。那就是崩溃发生的时候。

如果在应用程序运行时重新加载 CollectionView 而不是一开始就在后台,一切都很好。


为了使设置更清晰。

【问题讨论】:

  • 你试过用僵尸吗?
  • 可以发BSCInfiniteLayout的相关方法吗?具体来说,-validateLayoutInRect:-layoutAttributesForElementsInRect:
  • @Macmade 我做到了,但是当调试器运行时我无法重现该问题。
  • @Caleb 这两种方法在我的实现中都没有被覆盖。
  • 我也有同样的问题。有人找到解决方案了吗?第一个答案只是一个不同的实现,但如果我们不使用 Xibs 怎么办?

标签: ios objective-c uikit uicollectionview exc-bad-access


【解决方案1】:

首先,将UICollectionView拖放到XIB中的ViewController,将Delegate,数据源连接到ViewController(这只是主ViewController)

不要为 1 个 CollectionView 使用 2 个不同的 nib 单元格,因为您只能注册 1 个 Nib。更好地使用装饰视图作为 HeaderView。创建新类 HeaderView:UICollectionReusableView。这个 UICollectionReusableView 是 UIView 的子类,可以与 UICollectionViewCell 一起在 UICollectionView 内部重用。现在您可以注册两种类型:

[self.collectionView registerNib:[UINib nibWithNibName:@"MyCell" bundle:nil] forCellWithReuseIdentifier:@"CELL"];
[self.collectionView registerNib:[UINib nibWithNibName:@"HeaderView" bundle:nil] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderCell"];

接下来,将另一个 UICollectionView 拖到这个 HeaderView 上,在 HeaderView.h 中连接 IBOutlet。这里最好将Delegate、DataSource设置为这个类进行控制。还要注册这个 CollectionView 将使用的 Nib Cell。在 awakeFromNib 中执行此操作,因为您之前注册了 Nib

- (void)awakeFromNib{
    [self.topCollectionView registerNib:[UINib nibWithNibName:@"TopCell" bundle:nil] forCellWithReuseIdentifier:@"TopCell"];

    [self.topCollectionView setDataSource:self];
    [self.topCollectionView setDelegate:self];
}

它会很好用,如果您将数据源存储在外部,只需创建另一个属性并分配给它,然后在此处用于返回内部数据源。

如果您想知道何时单击 headerView 内的 Cell,请使用 customDelegate 协议在单击 HeaderCell 时将委托发送给 ViewController。

这是我的代码,希望你能理解并在这里应用你的数据:

https://github.com/lequysang/gitfiles02/blob/master/CollectionViewWithDecorationView.zip

【讨论】:

  • 此时 HeaderView 不再只是一个视图,它就像一个 ViewController。创造这样的设计并不好。 ViewController 管理模型和视图之间的数据。这对 MVC 无效。您的方法更像是 MVCC,这不是最好的方法。
  • 这是一个装饰视图,标题是 UICollectonView 的一部分,现在是一个 ViewController。我的代码中只有 1ViewController。你检查了吗? @mariusLAN
  • 是的,我检查了代码,但 HeaderView 不仅仅充当视图。它更像是一个控制器。它不符合 MVC。
  • 查看我的编辑。查看有关 DecorationView 的 Apple 文档和 WWDC 2012 视频
  • Delegate 和 Datasource 是一个协议,你可以将它设置为一个类(可以是 UIView 的子类)。并不总是将其设置为 ViewController
【解决方案2】:

看起来您的委托或内部集合视图已死(setWeakDelegatePointer: 做什么?)。在模拟器上尝试僵尸仪器,如果是僵尸,它应该标记。还为 xCode 中的所有异常设置“异常断点”(当应用程序从 xCode 而不是仪器运行时将有助于您的调试)。还要检查您的 -[BSCFrontPageViewController collectionView:cellForItemAtIndexPath:] 实现,它可能会在重用时释放该内部集合视图。

编辑: 为什么不将标题单元格添加为标题而不是单元格 0(集合视图中的标题示例 here)?当您为外部集合视图创建普通单元格时,还要检查(只是为了确保它不是崩溃的原因)您的重用标识符。在模拟器上调试时也会定期发送 mem 警告。

另外,为什么要为标题使用另一个集合视图?你可以使用类似于 iCarousel 的东西来进行这样的布局。

【讨论】:

  • 我添加了一些代码。我已经尝试使用僵尸调试代码,但在调试器运行时无法重现错误。
  • 尝试向模拟器发送内存警告(从顶部菜单)。也许这就是重新创建顶级集合视图的原因,并且(可能)由于旧的代表没有被清除,我们尝试访问已释放的集合视图。
  • 还要确保如果您在某处添加观察者,则在解除分配时将其删除。
【解决方案3】:

您可以尝试以下操作,滚动主 uicollectionview 以使标题不显示...(更好)然后在模拟器上尝试执行内存警告...使用硬件->模拟内存警告...

这可能应该创建一个单元格回收和删除(uicollectionview 应该销毁任何现在不重要的东西......如果发生这种情况,标题将被释放并且你仍然持有对它的弱引用......所以任何东西你所做的事情会因为访问不正确而发生......由于缺乏内存警告(只有你以显式方式创建的警告),这也是“不可能”在模拟器上重现的......

视图看起来很重,您可以尝试这样做并发布结果吗?

【讨论】:

    【解决方案4】:

    从 cmets 中提取我们的“答案”。

    遗憾的是,没有一个答案有帮助。我最终重构了整个事情,然后问题就消失了。我什至与一位也不知道该怎么做的苹果 DTS 工程师进行了非常详细的交谈。所以我们只是重构。对不起:/

    【讨论】:

    • 我遇到了类似的问题,我的集合视图在设置布局时没有超级视图并导致崩溃。可能与您的无关,但希望它对某人有所帮助。干杯。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-05
    • 2015-04-02
    • 1970-01-01
    • 2014-10-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多