CKInvalidChangesetInfo CKIsValidChangesetForState()

in ComponentKit/TransactionalDataSources/Common/Internal/CKDataSourceChangesetVerification.mm [34:192]


CKInvalidChangesetInfo CKIsValidChangesetForState(CKDataSourceChangeset *changeset,
                                                  CKDataSourceState *state,
                                                  NSArray<id<CKDataSourceStateModifying>> *pendingAsynchronousModifications)
{
  /*
   "Fold" any pending asynchronous modifications into the supplied state and compute the number of items in each section.
   This process ensures that the modified state represents the state the changeset will be eventually applied to.
   */
  NSMutableArray<NSNumber *> *sectionCounts = [sectionCountsWithModificationsFoldedIntoState(state, pendingAsynchronousModifications) mutableCopy];
  NSMutableArray<NSNumber *> *originalSectionCounts = [sectionCounts mutableCopy];
  __block BOOL invalidChangeFound = NO;
  __block NSInteger invalidSection = -1;
  __block NSInteger invalidItem = -1;
  // Updated items
  [changeset.updatedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull fromIndexPath, id _Nonnull model, BOOL * _Nonnull stop) {
    const NSInteger section = fromIndexPath.section;
    const NSInteger item = fromIndexPath.item;
    if (section >= originalSectionCounts.count
        || item >= [originalSectionCounts[section] integerValue]
        || section < 0
        || item < 0) {
      invalidChangeFound = YES;
      invalidSection = section;
      invalidItem = item;
      *stop = YES;
    }
  }];
  if (invalidChangeFound) {
    return { CKInvalidChangesetOperationTypeUpdate, invalidSection, invalidItem };
  }
  /*
   Removed items
   Section counts may not immediately reflect removals as order is not guaranteed and may result in a false positive.
   As long as each item is located within the bounds of its section the changeset is valid.
   */
  NSMutableDictionary<NSNumber *, NSMutableIndexSet *> *itemsToRemove = [NSMutableDictionary new];
  [changeset.removedItems enumerateObjectsUsingBlock:^(NSIndexPath *_Nonnull fromIndexPath, BOOL * _Nonnull stop) {
    const NSInteger section = fromIndexPath.section;
    const NSInteger item = fromIndexPath.item;
    if (section >= sectionCounts.count
        || section < 0
        || item >= [originalSectionCounts[section] integerValue]) {
      invalidChangeFound = YES;
      invalidSection = section;
      invalidItem = item;
      *stop = YES;
    } else {
      if (!itemsToRemove[@(section)]) {
        itemsToRemove[@(section)] = [NSMutableIndexSet indexSet];
      }
      [itemsToRemove[@(section)] addIndex:item];
    }
  }];
  if (invalidChangeFound) {
    return { CKInvalidChangesetOperationTypeRemoveRow, invalidSection, invalidItem };
  } else {
    [itemsToRemove enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull section, NSMutableIndexSet * _Nonnull indexSet, BOOL * _Nonnull stop) {
      sectionCounts[[section integerValue]] = @([sectionCounts[[section integerValue]] integerValue] - [indexSet count]);
    }];
  }
  /*
   Removed sections
   Section counts may not immediately reflect removals as order is not guaranteed and may result in a false positive.
   As long as each section is located within the bounds of all sections the changeset is valid.
   */
  NSMutableIndexSet *sectionsToRemove = [NSMutableIndexSet indexSet];
  [changeset.removedSections enumerateIndexesUsingBlock:^(NSUInteger fromSection, BOOL * _Nonnull stop) {
    if (fromSection >= originalSectionCounts.count) {
      invalidChangeFound = YES;
      invalidSection = fromSection;
      *stop = YES;
    } else {
      [sectionsToRemove addIndex:fromSection];
    }
  }];
  if (invalidChangeFound) {
    return { CKInvalidChangesetOperationTypeRemoveSection, invalidSection, invalidItem };
  } else {
    [sectionCounts removeObjectsAtIndexes:sectionsToRemove];
  }
  /*
   Inserted sections
   Section counts may immediately reflect insertions as they are guaranteed to be contiguous by virtue of NSIndexSet.
   As long as each section is located within the bounds of all sections the changeset is valid.
   */
  [changeset.insertedSections enumerateIndexesUsingBlock:^(NSUInteger toSection, BOOL * _Nonnull stop) {
    if (toSection > sectionCounts.count) {
      invalidChangeFound = YES;
      invalidSection = toSection;
      *stop = YES;
    } else {
      [sectionCounts insertObject:@0 atIndex:toSection];
    }
  }];
  if (invalidChangeFound) {
    return { CKInvalidChangesetOperationTypeInsertSection, invalidSection, invalidItem };
  }
  /*
   Inserted items
   Section counts may immediately reflect insertions as they are guaranteed to be contiguous by virtue of sorting the index paths.
   As long as each item is located within the bounds of its section the changeset is valid.
   */
  [sortedIndexPaths(changeset.insertedItems.allKeys) enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull toIndexPath, NSUInteger index, BOOL * _Nonnull stop) {
    const NSInteger section = toIndexPath.section;
    const NSInteger item = toIndexPath.item;
    if (section >= sectionCounts.count
        || section < 0
        || item > [sectionCounts[section] integerValue]) {
      invalidChangeFound = YES;
      invalidSection = section;
      invalidItem = item;
      *stop = YES;
    } else {
      sectionCounts[section] = @([sectionCounts[section] integerValue] + 1);
    }
  }];
  if (invalidChangeFound) {
    return { CKInvalidChangesetOperationTypeInsertRow, invalidSection, invalidItem };
  }
  // Moved items
  const auto sectionIdxTransform =
  CK::makeCompositeIndexTransform(CK::RemovalIndexTransform(changeset.removedSections),
                                  CK::InsertionIndexTransform(changeset.insertedSections));
  
  [changeset.movedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull fromIndexPath, NSIndexPath * _Nonnull toIndexPath, BOOL * _Nonnull stop) {
    const BOOL fromIndexPathSectionInvalid = fromIndexPath.section >= originalSectionCounts.count;
    const BOOL toIndexPathSectionInvalid = toIndexPath.section >= sectionCounts.count;
    if (fromIndexPathSectionInvalid || toIndexPathSectionInvalid) {
      invalidChangeFound = YES;
      invalidSection = fromIndexPathSectionInvalid ? fromIndexPath.section : toIndexPath.section;
      *stop = YES;
    } else {
      const BOOL fromIndexPathItemInvalid = fromIndexPath.item >= [originalSectionCounts[fromIndexPath.section] integerValue];
      originalSectionCounts[fromIndexPath.section] = @([originalSectionCounts[fromIndexPath.section] integerValue] - 1);
      const auto fromSectionIdxAfterUpdate = sectionIdxTransform.applyToIndex(fromIndexPath.section);
      if (fromSectionIdxAfterUpdate != NSNotFound) {
        sectionCounts[fromSectionIdxAfterUpdate] = @([sectionCounts[fromSectionIdxAfterUpdate] integerValue] - 1);
      }
      const auto originalSectionIdx = sectionIdxTransform.applyInverseToIndex(toIndexPath.section);
      const auto movingToJustInsertedSection = (originalSectionIdx == NSNotFound);
      if (!movingToJustInsertedSection) {
        originalSectionCounts[originalSectionIdx] = @([originalSectionCounts[originalSectionIdx] integerValue] + 1);
      }
      const auto toIndexPathItemInvalid = toIndexPath.item > [sectionCounts[toIndexPath.section] integerValue];
      sectionCounts[toIndexPath.section] = @([sectionCounts[toIndexPath.section] integerValue] + 1);
      if (fromIndexPathItemInvalid || toIndexPathItemInvalid) {
        invalidSection = fromIndexPathItemInvalid ? fromIndexPath.section : toIndexPath.section;
        invalidItem = fromIndexPathItemInvalid ? fromIndexPath.row : toIndexPath.row;
        invalidChangeFound = YES;
        *stop = YES;
      }
    }
  }];
  return {
    invalidChangeFound ? CKInvalidChangesetOperationTypeMoveRow : CKInvalidChangesetOperationTypeNone,
    invalidSection,
    invalidItem
  };
}