IGListKit 源碼解析

IGListKit 是 Instagram 維護(hù)一個(gè) UI 框架茅逮,采用面向協(xié)議的思想,基于 UICollectionView 實(shí)現(xiàn)判哥,由數(shù)據(jù)驅(qū)動(dòng)的 UI 列表框架献雅。本文基于 IGListKit 源碼對(duì)其主要設(shè)計(jì)思想進(jìn)行分析。

分析前塌计,我們現(xiàn)看一下 IGListKit 的數(shù)據(jù)和 UI 對(duì)應(yīng)關(guān)系圖


image.png

可以看出 IGListKit 都是基于 IGListAdapter 進(jìn)行數(shù)據(jù)傳遞和 UI 刷新的操作挺身,接下來從 IGListAdapter 入手分析 IGListKit 具體做了哪些工作。

IGListAdapter

初始化:

- (instancetype)initWithUpdater:(id <IGListUpdatingDelegate>)updater
                 viewController:(UIViewController *)viewController
               workingRangeSize:(NSInteger)workingRangeSize {
    IGAssertMainThread();
    IGParameterAssert(updater);

    if (self = [super init]) {
        // objectLookupPointerFunctions 返回 hash 表計(jì)算 hash 以及比較 value 是否相同的設(shè)置
        NSPointerFunctions *keyFunctions = [updater objectLookupPointerFunctions];
        NSPointerFunctions *valueFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory];
        // table 是以 object 為 key锌仅,sectionController 為 value 的 map
        NSMapTable *table = [[NSMapTable alloc] initWithKeyPointerFunctions:keyFunctions valuePointerFunctions:valueFunctions capacity:0];
        _sectionMap = [[IGListSectionMap alloc] initWithMapTable:table];

        _displayHandler = [IGListDisplayHandler new];
        _workingRangeHandler = [[IGListWorkingRangeHandler alloc] initWithWorkingRangeSize:workingRangeSize];
        _updateListeners = [NSHashTable weakObjectsHashTable];

        // 將 cell 和 sectionController 映射
        _viewSectionControllerMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality | NSMapTableStrongMemory
                                                          valueOptions:NSMapTableStrongMemory];

        _updater = updater;
        _viewController = viewController;

        [IGListDebugger trackAdapter:self];
    }
    return self;
}

IGListSectionMap: 作用是映射 sectionController 和 collectionView 的 section 的對(duì)應(yīng)關(guān)系章钾,能在 O(1) 的時(shí)間復(fù)雜度根據(jù) section 獲取 sectionController墙贱。內(nèi)部實(shí)現(xiàn)結(jié)果如下圖:

graph LR
object -- objectToSectionControllerMap --> IGListSectionController
IGListSectionController -- objectToSectionControllerMap --> object
IGListSectionController -- sectionControllerToSectionMap --> section
section -- sectionControllerToSectionMap --> IGListSectionController

IGListDisplayHandler: 作用和對(duì)外暴露的 IGListAdapterPerformanceDelegate 類似,主要是對(duì) UICollectionViewCell 生命周日相關(guān)對(duì)調(diào)的處理(cell 顯示/消失/分區(qū)頭部贱傀、尾部顯示/消失)嫩痰,內(nèi)部會(huì)把事件傳給 IGListSectionController 的 displayDelegate;在 IGListAdapter+UICollectionView.m 文件中進(jìn)行調(diào)用窍箍。

IGListWorkingRangeHandler: 負(fù)責(zé) collectionView 每個(gè) section(sectionController) 的預(yù)加載的準(zhǔn)備工作。在 IGListAdapter+UICollectionView.m 文件中進(jìn)行調(diào)用丽旅,相關(guān)數(shù)據(jù)會(huì)保存起來椰棘,提供給 IGListAdapter 使用。

IGListAdapterUpdateListener: 代理集合榄笙,IGListAdapter 更新完數(shù)據(jù)后對(duì)集合的代理進(jìn)行通知

數(shù)據(jù)源:

IGListAdapter 會(huì)作為 UICollectionView 默認(rèn)的 dataSource邪狞。

- (void)setCollectionView:(UICollectionView *)collectionView {
    if (_collectionView != collectionView || _collectionView.dataSource != self) {
        static NSMapTable<UICollectionView *, IGListAdapter *> *globalCollectionViewAdapterMap = nil;
        if (globalCollectionViewAdapterMap == nil) {
            globalCollectionViewAdapterMap = [NSMapTable weakToWeakObjectsMapTable];
        }
        [globalCollectionViewAdapterMap removeObjectForKey:_collectionView];
        [[globalCollectionViewAdapterMap objectForKey:collectionView] setCollectionView:nil];
        [globalCollectionViewAdapterMap setObject:self forKey:collectionView];

        _registeredCellIdentifiers = [NSMutableSet new];
        _registeredNibNames = [NSMutableSet new];
        _registeredSupplementaryViewIdentifiers = [NSMutableSet new];
        _registeredSupplementaryViewNibNames = [NSMutableSet new];

        const BOOL settingFirstCollectionView = _collectionView == nil;

        _collectionView = collectionView;
        _collectionView.dataSource = self;

        if (@available(iOS 10.0, tvOS 10, *)) {
            _collectionView.prefetchingEnabled = NO;
        }

        [_collectionView.collectionViewLayout ig_hijackLayoutInteractiveReorderingMethodForAdapter:self];
        // 使當(dāng)前的布局失效,同時(shí)觸發(fā)布局更新
        [_collectionView.collectionViewLayout invalidateLayout];

        [self _updateCollectionViewDelegate];

        if (!IGListExperimentEnabled(self.experiments, IGListExperimentGetCollectionViewAtUpdate)
            || settingFirstCollectionView) {
            [self _updateAfterPublicSettingsChange];
        }
    }
}

globalCollectionViewAdapterMap: key 為 collectionView茅撞,value 為 IGListAdapter

通過 - (void)setCollectionView:(UICollectionView *)collectionView 關(guān)聯(lián) IGListAdapter 和 UICollectionView:

1. globalCollectionViewAdapterMap 先移除舊的 _collectionView 對(duì)應(yīng)的 IGListAdapter帆卓,就是代碼中的 self

2. 將新 collectionView 之前綁定的 IGListAdapter 取消對(duì) collectionView 綁定
3. 將新 collectionView 和當(dāng)前 IGListAdapter 綁定

dataSource 的方法實(shí)現(xiàn)再 IGListAdapter+UICollectionView.m 中,dataSource 的代理方法通過 IGSectionController 返回每個(gè) section 對(duì)應(yīng)的數(shù)據(jù)

// IGListAdapter+UICollectionView.m
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {...}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {...}
  
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {...}
  
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {...}
  
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath {
    const NSInteger sectionIndex = indexPath.section;
    const NSInteger itemIndex = indexPath.item;

    IGListSectionController *sectionController = [self sectionControllerForSection:sectionIndex];
    return [sectionController canMoveItemAtIndex:itemIndex];
}
  
- (void)collectionView:(UICollectionView *)collectionView
   moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath
           toIndexPath:(NSIndexPath *)destinationIndexPath {...}

數(shù)據(jù)源更新 <IGListUpdatingDelegate>:

IGListAdapter 提供以下幾種方法讓外部進(jìn)行數(shù)據(jù)更新:

- (void)performUpdatesAnimated:(BOOL)animated completion:(nullable IGListUpdaterCompletion)completion;

- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion;

- (void)reloadObjects:(NSArray *)objects;

我們先以 -reloadDataWithCompletion: 方法為例子米丘,分析數(shù)據(jù)更新的過程:

- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion {
    IGAssertMainThread();

    id<IGListAdapterDataSource> dataSource = self.dataSource;
    UICollectionView *collectionView = self.collectionView;
    if (dataSource == nil || collectionView == nil) {
        IGLKLog(@"Warning: Your call to %s is ignored as dataSource or collectionView haven't been set.", __PRETTY_FUNCTION__);
        if (completion) {
            completion(NO);
        }
        return;
    }

    // 重新讀取一次數(shù)據(jù)源代理方法剑令,數(shù)據(jù)根據(jù)diffIdentifier去重
    NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]);

    __weak __typeof__(self) weakSelf = self;
    [self.updater reloadDataWithCollectionViewBlock:[self _collectionViewBlock]
                                  reloadUpdateBlock:^{
                                        // 移除所有 section controllers 以便于重新生成
                                      [weakSelf.sectionMap reset];
                                        // 根據(jù)去重后的數(shù)據(jù)源重新生成 section controller
                                      [weakSelf _updateObjects:uniqueObjects dataSource:dataSource];
                                  } completion:^(BOOL finished) {
                                      [weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeReloadData animated:NO];
                                      if (completion) {
                                          completion(finished);
                                      }
                                  }];
}

刷新數(shù)據(jù)之前,會(huì)先將數(shù)據(jù)去重拄查,保證數(shù)據(jù)對(duì)應(yīng)的 diffIdentifier 是唯一的吁津。然后調(diào)用 IGListAdapterUpdater 的方法進(jìn)行刷新數(shù)據(jù)

- (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
                   reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock
                          completion:(nullable IGListUpdatingCompletion)completion {
    IGAssertMainThread();
    IGParameterAssert(collectionViewBlock != nil);
    IGParameterAssert(reloadUpdateBlock != nil);

    IGListUpdatingCompletion localCompletion = completion;
    if (localCompletion) {
        [self.completionBlocks addObject:localCompletion];
    }

    self.reloadUpdates = reloadUpdateBlock;
    self.queuedReloadData = YES;
    [self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
}
- (void)_queueUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
    IGAssertMainThread();
    
    __weak __typeof__(self) weakSelf = self;
    
    // dispatch_async 是為了執(zhí)行 -performBatchUpdatesWithCollectionViewBlock: 前提供更多時(shí)間來完成數(shù)據(jù)更新處理,減少在主線程上進(jìn)行差異化的操作
    dispatch_async(dispatch_get_main_queue(), ^{
        if (weakSelf.state != IGListBatchUpdateStateIdle
            || ![weakSelf hasChanges]) {
            return;
        }
        
        if (weakSelf.hasQueuedReloadData) {
            [weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock];
        } else {
            [weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock];
        }
    });
}

之后進(jìn)入條件判斷執(zhí)行 -performReloadDataWithCollectionViewBlock: 方法

- (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
    IGAssertMainThread();
    
    id<IGListAdapterUpdaterDelegate> delegate = self.delegate;
    void (^reloadUpdates)(void) = self.reloadUpdates;
    IGListBatchUpdates *batchUpdates = self.batchUpdates;
    NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy];

    // 清空相關(guān)狀態(tài)
    [self cleanStateBeforeUpdates];

    void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) {
        for (IGListUpdatingCompletion block in completionBlocks) {
            block(finished);
        }

        self.state = IGListBatchUpdateStateIdle;
    };

    // 防止 collectionView 被釋放導(dǎo)致崩潰
    UICollectionView *collectionView = collectionViewBlock();
    if (collectionView == nil) {
        [self _cleanStateAfterUpdates];
        executeCompletionBlocks(NO);
        return;
    }

    // 更新狀態(tài)堕扶,避免更新數(shù)據(jù)的過程中去通知視圖更新
    self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;

    // 通知外部移除所有 section controllers碍脏,然后重新生成
    if (reloadUpdates) {
        reloadUpdates();
    }

    // 即使我們只是調(diào)用reloadData,也要執(zhí)行所有存儲(chǔ)的 batchUpdates 任務(wù)
    // 實(shí)際效果所有 section 視圖的突變將被丟棄稍算,建議使用者也將其實(shí)際的數(shù)據(jù)更新也放入 batchUpdates 任務(wù)集合中典尾,因此,如果我們不執(zhí)行該塊糊探,則 batchUpdates 是不會(huì)被觸發(fā)
    for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) {
        itemUpdateBlock();
    }

    // add any completion blocks from item updates. added after item blocks are executed in order to capture any
    // re-entrant updates
    [completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks];

    self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;

    [self _cleanStateAfterUpdates];

    [delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView];
    [collectionView reloadData];
    [collectionView.collectionViewLayout invalidateLayout];
    [collectionView layoutIfNeeded];
    [delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView];

    executeCompletionBlocks(YES);
}

-performReloadDataWithCollectionViewBlock: 中也會(huì)觸發(fā)保存在 batchUpdates 中的更新任務(wù)钾埂,以便及時(shí)刷新數(shù)據(jù)/界面,然后通過代理通知外部 UICollectionView 刷新的前后事件科平。

可以看出 -reloadDataWithCompletion: 基本等同于強(qiáng)制刷新勃教,會(huì)把所有刷新任務(wù)全部執(zhí)行完之后,通知 UICollectionView 刷新界面匠抗。

-reloadDataWithCompletion: 不同的是故源,IGListAdapter 還有提供另外一個(gè)方法進(jìn)行數(shù)據(jù)刷新 - (void)performUpdatesAnimated:completion:

- (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletion)completion {
    // 略...
    [self _enterBatchUpdates];
    [self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock]
                                           fromObjects:fromObjects
                                        toObjectsBlock:toObjectsBlock
                                              animated:animated
                                 objectTransitionBlock:^(NSArray *toObjects) {
                                     // 重新捕獲一次 sectionMap,防止同時(shí)間有數(shù)據(jù)被刪除
                                     weakSelf.previousSectionMap = [weakSelf.sectionMap copy   
                                     // 更新 sectionMap 數(shù)據(jù)汞贸,刷新 collectiView 背景圖
                                     [weakSelf _updateObjects:toObjects dataSource:dataSource];
                                 } completion:^(BOOL finished) {
                                     // release the previous items
                                     weakSelf.previousSectionMap = nil;

                                     [weakSelf _notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated];
                                     if (completion) {
                                         completion(finished);
                                     }
                                     [weakSelf _exitBatchUpdates];
                                 }];
}

updater 會(huì)將更新數(shù)據(jù) sectionMap 的操作保存到 objectTransitionBlock 中

- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
                            fromObjects:(NSArray *)fromObjects
                         toObjectsBlock:(IGListToObjectBlock)toObjectsBlock
                               animated:(BOOL)animated
                  objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock
                             completion:(IGListUpdatingCompletion)completion {
    IGAssertMainThread();
    IGParameterAssert(collectionViewBlock != nil);
    IGParameterAssert(objectTransitionBlock != nil);

    // 正在執(zhí)行更新的過程中绳军,同一時(shí)間內(nèi)可能會(huì)有多個(gè)其他更新任務(wù)加入印机,
    // 執(zhí)行更新動(dòng)作的時(shí)候,是第一次加入的 fromObject 和 最后加入的 toObjects
    // 如果 self.fromObject == nil, 應(yīng)該有先使用之前加入并且還沒有執(zhí)行的 batch update 任務(wù)的終點(diǎn)數(shù)據(jù)源(toObjects)
    // 這樣做的目的是使整個(gè)數(shù)據(jù)變化可以串聯(lián)起來
    self.fromObjects = self.fromObjects ?: self.pendingTransitionToObjects ?: fromObjects;
    self.toObjectsBlock = toObjectsBlock;

    // disabled animations will always take priority
    // reset to YES in -cleanupState
    self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated;

    // 保證每次刷新使用最新的 objectTransitionBlock
    self.objectTransitionBlock = objectTransitionBlock;

    IGListUpdatingCompletion localCompletion = completion;
    if (localCompletion) {
        [self.completionBlocks addObject:localCompletion];
    }

    [self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
}

IGListUpdater 處理完傳入的 fromObjects 和 toObjects门驾,并保存數(shù)據(jù)轉(zhuǎn)化的閉包 objectTransitionBlock射赛,會(huì)調(diào)用 -_queueUpdateWithCollectionViewBlock: 方法,利用 dispatch_async 異步調(diào)用 -performBatchUpdatesWithCollectionViewBlock:

- (void)performBatchUpdatesWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
   IGAssertMainThread();
   IGAssert(self.state == IGListBatchUpdateStateIdle, @"Should not call batch updates when state isn't idle");

   // 創(chuàng)建局部變量奶是,以便我們可以立即清除狀態(tài)楣责,但將這些數(shù)據(jù)傳遞到批處理更新任務(wù)中
   id<IGListAdapterUpdaterDelegate> delegate = self.delegate;
   NSArray *fromObjects = [self.fromObjects copy];
   IGListToObjectBlock toObjectsBlock = [self.toObjectsBlock copy];
   NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy];
   void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy];
   const BOOL animated = self.queuedUpdateIsAnimated;
   IGListBatchUpdates *batchUpdates = self.batchUpdates;

   // 清理所有狀態(tài),以便在當(dāng)前更新進(jìn)行時(shí)可以合并新的更新
   [self cleanStateBeforeUpdates];

   // 初始化更新完成之后的回調(diào)
   void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) {
       self.applyingUpdateData = nil;
       self.state = IGListBatchUpdateStateIdle;

       for (IGListUpdatingCompletion block in completionBlocks) {
           block(finished);
       }
   };

   // collectionView 如果被銷毀聂沙,則結(jié)束更新恢復(fù)相關(guān)狀態(tài)
   UICollectionView *collectionView = collectionViewBlock();
   if (collectionView == nil) {
       [self _cleanStateAfterUpdates];
       executeCompletionBlocks(NO);
       return;
   }
   
   NSArray *toObjects = nil;
   if (toObjectsBlock != nil) {
       toObjects = objectsWithDuplicateIdentifiersRemoved(toObjectsBlock());
   }

   // 初始化數(shù)據(jù)刷新的閉包
   void (^executeUpdateBlocks)(void) = ^{
       self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;

       // 更新包括 IGListAdapter 的 sectionController 和 objects 的映射關(guān)系等數(shù)據(jù)
       // 保證執(zhí)行刷新前秆麸,數(shù)據(jù)已經(jīng)是最新的
       if (objectTransitionBlock != nil) {
           objectTransitionBlock(toObjects);
       }

       // 觸發(fā)批量刷新任務(wù)的數(shù)據(jù)更新閉包(包括插入、刪除及汉、刷新單個(gè) section 的數(shù)據(jù))
       // objectTransitionBlock 之后執(zhí)行是為了保證 section 級(jí)別的刷新在 item 級(jí)別刷新之前進(jìn)行
       for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) {
           itemUpdateBlock();
       }

       // 收集批量刷新完成的回調(diào)沮趣,后續(xù)所有操作完了之后一并處理
       [completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks];

       self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;
   };

   // 執(zhí)行全量的數(shù)據(jù)更新并刷新 UI
   void (^reloadDataFallback)(void) = ^{
       executeUpdateBlocks();
       [self _cleanStateAfterUpdates];
       [self _performBatchUpdatesItemBlockApplied];
       [collectionView reloadData];
       [collectionView layoutIfNeeded];

       executeCompletionBlocks(YES);
   };

   // 如果當(dāng)前 collection 沒有顯示,跳過差分/分批刷新
   const BOOL iOS83OrLater = (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_8_3);
   if (iOS83OrLater && self.allowsBackgroundReloading && collectionView.window == nil) {
       [self _beginPerformBatchUpdatesToObjects:toObjects];
       reloadDataFallback();
       return;
   }

   // 禁止同時(shí)執(zhí)行多個(gè) -performBatchUpdates:
   [self _beginPerformBatchUpdatesToObjects:toObjects];

   const IGListExperiment experiments = self.experiments;

   // 計(jì)算新舊數(shù)據(jù)源差分部分坷随,算法參考: https://dl.acm.org/citation.cfm?id=359467&dl=ACM&coll=DL
   IGListIndexSetResult *(^performDiff)(void) = ^{
       return IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, experiments);
   };

   // block executed in the first param block of -[UICollectionView performBatchUpdates:completion:]
   void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){
       // 更新數(shù)據(jù)
       executeUpdateBlocks();
       // 根據(jù)整理差分算法結(jié)果房铭,過濾相關(guān) section/item 數(shù)據(jù),把 item 級(jí)別的刷新轉(zhuǎn)換成 section 級(jí)別來規(guī)避 UICollectionView 的 bug温眉,并調(diào)用 collectionView reload/insert/delete/move 操作
       self.applyingUpdateData = [self _flushCollectionView:collectionView
                                             withDiffResult:result
                                               batchUpdates:self.batchUpdates
                                                fromObjects:fromObjects];
       
       // 更新相關(guān)數(shù)據(jù)狀態(tài), 清空批量更新任務(wù)和等待更新的數(shù)據(jù)
       [self _cleanStateAfterUpdates];
       [self _performBatchUpdatesItemBlockApplied];
   };

   // block used as the second param of -[UICollectionView performBatchUpdates:completion:]
   void (^batchUpdatesCompletionBlock)(BOOL) = ^(BOOL finished) {
       IGListBatchUpdateData *oldApplyingUpdateData = self.applyingUpdateData;
       executeCompletionBlocks(finished);

       [delegate listAdapterUpdater:self didPerformBatchUpdates:oldApplyingUpdateData collectionView:collectionView];

       // queue another update in case something changed during batch updates. this method will bail next runloop if
       // there are no changes
       // 如果 batch update 任務(wù)執(zhí)行的過程中尤其比那話缸匪,則異步在下一個(gè) runloop 周期執(zhí)行相關(guān)更新動(dòng)作
       [self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
   };

   // block that executes the batch update and exception handling
   void (^performUpdate)(IGListIndexSetResult *) = ^(IGListIndexSetResult *result){
       [collectionView layoutIfNeeded];

       @try {
           // 對(duì)外通知即將進(jìn)行 batch update
           [delegate  listAdapterUpdater:self
willPerformBatchUpdatesWithCollectionView:collectionView
                             fromObjects:fromObjects
                               toObjects:toObjects
                      listIndexSetResult:result];

           if (collectionView.dataSource == nil) {
               // 如果數(shù)據(jù)源為空則不再刷新的 UICollectionview
               batchUpdatesCompletionBlock(NO);
           } else if (result.changeCount > 100 && IGListExperimentEnabled(experiments, IGListExperimentReloadDataFallback)) {
               // 差分變化數(shù)量超過100,進(jìn)行全量刷新
               reloadDataFallback();
           } else if (animated) {
               // 執(zhí)行差分更新的批量動(dòng)畫
               [collectionView performBatchUpdates:^{
                   batchUpdatesBlock(result);
               } completion:batchUpdatesCompletionBlock];
           } else {
               [CATransaction begin];
               [CATransaction setDisableActions:YES];
               [collectionView performBatchUpdates:^{
                   batchUpdatesBlock(result);
               } completion:^(BOOL finished) {
                   [CATransaction commit];
                   batchUpdatesCompletionBlock(finished);
               }];
           }
       } @catch (NSException *exception) {
           // 異常對(duì)外通知
           [delegate listAdapterUpdater:self
                         collectionView:collectionView
                 willCrashWithException:exception
                            fromObjects:fromObjects
                              toObjects:toObjects
                             diffResult:result
                                updates:(id)self.applyingUpdateData];
           @throw exception;
       }
   };

   if (IGListExperimentEnabled(experiments, IGListExperimentBackgroundDiffing)) {
       dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
           // 計(jì)算完差分部分
           IGListIndexSetResult *result = performDiff();
           dispatch_async(dispatch_get_main_queue(), ^{
               //根據(jù)差分結(jié)果刷新 UICollectionView
               performUpdate(result);
           });
       });
   } else {
       IGListIndexSetResult *result = performDiff();
       performUpdate(result);
   }
}

該數(shù)據(jù)更新過程調(diào)用鏈大概是:

|---performUpdatesAnimated:completion:
    |---performUpdateWithCollectionViewBlock:fromObjects:toObjectsBlock:animated:objectTransitionBlock:completion:
        |---_queueUpdateWithCollectionViewBlock:
            |---performBatchUpdatesWithCollectionViewBlock:

整個(gè) performUpdates 的大部分邏輯都是由 IGListUpdater 完成类溢,重中之重都幾種放 -performBatchUpdatesWithCollectionViewBlock:方法:

1. 判斷 collectionView 是否在顯示豪嗽,若不在屏幕窗口上顯示,直接全量刷新數(shù)據(jù)和視圖豌骏;反之繼續(xù)步驟2
2. 子線程調(diào)用 IGListDiffExperiment龟梦,計(jì)算數(shù)據(jù)的差分變化,計(jì)算完畢之后在主線程觸發(fā)界面刷新邏輯
3. 通過代理對(duì)外通知即將進(jìn)行 batch update 批量更新
4. 如果 collectionView 的 dataSource 為 nil窃躲,結(jié)束更新過程计贰;反之繼續(xù)
5. 差分變化的數(shù)據(jù)個(gè)數(shù)超過100,直接調(diào)用 reloadData 全量刷新數(shù)據(jù)/視圖蒂窒;若變化數(shù)據(jù)小于100躁倒,則調(diào)用 `-[UICollectionView performBatchUpdates:completion:]` 批量刷新數(shù)據(jù)/視圖,刷新過程中會(huì)調(diào)用 `-_flushCollectionView:withDiffResult:batchUpdates:fromObjects:` 將數(shù)據(jù)源提供的數(shù)據(jù)和 diff 結(jié)果包裝成批量更新的數(shù)據(jù)類型 IGListBatchUpdateData 以便 UICollectionView 進(jìn)行讀取

視圖管理 <IGListAdapterPerformanceDelegate>:

IGListAdapter 會(huì)作為 collectionView 屬性的默認(rèn)代理

@protocol IGListCollectionViewDelegateLayout <UICollectionViewDelegateFlowLayout>
  
@interface IGListAdapter (UICollectionView)
<
UICollectionViewDataSource,
IGListCollectionViewDelegateLayout
>

IGListAdapter 會(huì)實(shí)現(xiàn)相關(guān)代理方法洒琢,進(jìn)行對(duì) cell 級(jí)別的視圖管理秧秉,包含視圖 UICollectionView 滾動(dòng),cell 大小衰抑、cell 顯示等事件象迎,并通過 IGListAdapterPerformanceDelegate 對(duì)外通知

- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  //...略
  [performanceDelegate listAdapter:self didCallSizeOnSectionController:sectionController atIndex:indexPath.item];
  //...略
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
    [performanceDelegate listAdapterWillCallScroll:self];

    //...略

    [performanceDelegate listAdapter:self didCallScroll:scrollView];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
  [performanceDelegate listAdapterWillCallDequeueCell:self];
  //...略
  [performanceDelegate listAdapter:self didCallDequeueCell:cell onSectionController:sectionController atIndex:indexPath.item];
  //...略
}

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
    id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
    [performanceDelegate listAdapterWillCallDisplayCell:self];
    // ...略
    [performanceDelegate listAdapter:self didCallDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item];
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
    id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
    [performanceDelegate listAdapterWillCallEndDisplayCell:self];

    // ...略

    [performanceDelegate listAdapter:self didCallEndDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item];
}

視圖交互:

cell 的拖動(dòng)會(huì)首先觸發(fā) UICollectionView 的代理方法 -collectionView:moveItemAtIndexPath:toIndexPath 。在這個(gè)方法中會(huì)判斷拖動(dòng)開始/結(jié)束位置,根據(jù)不同的情況進(jìn)行數(shù)據(jù)刷新

- (void)collectionView:(UICollectionView *)collectionView
   moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath
           toIndexPath:(NSIndexPath *)destinationIndexPath {

    if (@available(iOS 9.0, *)) {
        const NSInteger sourceSectionIndex = sourceIndexPath.section;
        const NSInteger destinationSectionIndex = destinationIndexPath.section;
        const NSInteger sourceItemIndex = sourceIndexPath.item;
        const NSInteger destinationItemIndex = destinationIndexPath.item;

        IGListSectionController *sourceSectionController = [self sectionControllerForSection:sourceSectionIndex];
        IGListSectionController *destinationSectionController = [self sectionControllerForSection:destinationSectionIndex];

        if (sourceSectionController == destinationSectionController) {

            if ([sourceSectionController canMoveItemAtIndex:sourceItemIndex toIndex:destinationItemIndex]) {
                // 同一個(gè) section 內(nèi)的挪動(dòng)
                [self moveInSectionControllerInteractive:sourceSectionController
                                               fromIndex:sourceItemIndex
                                                 toIndex:destinationItemIndex];
            } else {
                // 撤銷修改
                [self revertInvalidInteractiveMoveFromIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
            }
            return;
        }

        // 跨 section 移動(dòng)砾淌, 如果 section 的 item 數(shù)目為1
        if ([sourceSectionController numberOfItems] == 1 && [destinationSectionController numberOfItems] == 1) {

            [self moveSectionControllerInteractive:sourceSectionController
                                         fromIndex:sourceSectionIndex
                                           toIndex:destinationSectionIndex];
            return;
        }

        // 撤銷修改
        [self revertInvalidInteractiveMoveFromIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
    }
}

成功拖動(dòng)之后會(huì)觸發(fā) IGListUpdater 的 -moveInSectionControllerInteractive 或者 -moveSectionControllerInteractive:fromIndex:toIndex啦撮,在同一個(gè) UICollectionView section 中拖動(dòng)則觸發(fā)前者,跨 section 之間則后者

- (void)moveInSectionControllerInteractive:(IGListSectionController *)sectionController
                                 fromIndex:(NSInteger)fromIndex
                                   toIndex:(NSInteger)toIndex NS_AVAILABLE_IOS(9_0) {
    //... 略
    [sectionController moveObjectFromIndex:fromIndex toIndex:toIndex];
}

在同一個(gè) section 中拖動(dòng) UICollectionViewCell 比較簡單汪厨,實(shí)現(xiàn)中回去調(diào)用對(duì)應(yīng) sectionController 的 -moveObjectFromIndex:toIndex:赃春,使用者在自定義的 sectionController 中實(shí)現(xiàn)該代理方法,進(jìn)行對(duì)應(yīng)的數(shù)據(jù)刷新更新即可

- (void)moveSectionControllerInteractive:(IGListSectionController *)sectionController
                               fromIndex:(NSInteger)fromIndex
                                 toIndex:(NSInteger)toIndex NS_AVAILABLE_IOS(9_0) {
    // ... 略
    if (fromIndex != toIndex) {
        id<IGListAdapterDataSource> dataSource = self.dataSource;

        NSArray *previousObjects = [self.sectionMap objects];

        if (self.isLastInteractiveMoveToLastSectionIndex) {
            // 如果 item 是被移動(dòng)到 UICollectionView 最底部
            self.isLastInteractiveMoveToLastSectionIndex = NO;
        }
        else if (fromIndex < toIndex) {
            toIndex -= 1;
        }

        NSMutableArray *mutObjects = [previousObjects mutableCopy];
        id object = [previousObjects objectAtIndex:fromIndex];
        [mutObjects removeObjectAtIndex:fromIndex];
        [mutObjects insertObject:object atIndex:toIndex];

        NSArray *objects = [mutObjects copy];

        // inform the data source to update its model
        [self.moveDelegate listAdapter:self moveObject:object from:previousObjects to:objects];

        // update our model based on that provided by the data source
        NSArray<id<IGListDiffable>> *updatedObjects = [dataSource objectsForListAdapter:self];
        [self _updateObjects:updatedObjects dataSource:dataSource];
    }

    // 刷新 UI
    // 這里 from index 和 to index 可能是相同的, 但是實(shí)際上可能是以 section 的方式向上/下移動(dòng)了一個(gè) section
    [self.updater moveSectionInCollectionView:collectionView fromIndex:fromIndex toIndex:toIndex];
}

跨 UICollectionView section 間拖動(dòng) UICollectionViewCell 需要對(duì)原始/目標(biāo) section 的位置/ item 數(shù)目進(jìn)行相關(guān)判斷劫乱,最后執(zhí)行 IGListUpdater 的 -moveSectionInCollectionView:fromIndex:toIndex: 方法

- (void)moveSectionInCollectionView:(UICollectionView *)collectionView
                          fromIndex:(NSInteger)fromIndex
                            toIndex:(NSInteger)toIndex {
    // iOS 移動(dòng)是以 item 為移動(dòng)單位的拖動(dòng)
    // 如果 originating section 中的 item 數(shù)量是1织中,將這個(gè) item 拖動(dòng)到 item 數(shù)目同樣為1的 target section
    // 拖動(dòng)之后 target section 的 item 數(shù)目為2, originating section 的數(shù)目為 0
    // 基于這種情況必須使用 reloadData
    [collectionView reloadData];

    // 似乎在 UICollectionVie 的 -moveItemAtIndexPath 代理方法調(diào)用期間調(diào)用的 -reloadData 不會(huì)按預(yù)期重新加載所有單元格衷戈,
    // 因此狭吼,這里進(jìn)一步重新加載了所有可見部分,以確保沒有任何 item 上的數(shù)據(jù)與 dataSource 不同步脱惰。
    id<IGListAdapterUpdaterDelegate> delegate = self.delegate;
    
    NSMutableIndexSet *visibleSections = [NSMutableIndexSet new];
    NSArray *visibleIndexPaths = [collectionView indexPathsForVisibleItems];
    for (NSIndexPath *visibleIndexPath in visibleIndexPaths) {
        [visibleSections addIndex:visibleIndexPath.section];
    }
    
    [delegate listAdapterUpdater:self willReloadSections:visibleSections collectionView:collectionView];
    
    // prevent double-animation from reloadData + reloadSections
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [collectionView performBatchUpdates:^{
        [collectionView reloadSections:visibleSections];
    } completion:^(BOOL finished) {
        [CATransaction commit];
    }];
}

-moveSectionInCollectionView:fromIndex:toIndex: 方法會(huì)現(xiàn)調(diào)用 -[UICollectionView reloadDate] 來規(guī)避 origin section item 數(shù)目為0的情況,之后還會(huì)對(duì)應(yīng)當(dāng)前屏幕顯示區(qū)域進(jìn)行 batch update 來規(guī)避 UICollectionView 不能及時(shí)刷新的 bug窿春。

整個(gè) UICollectionViewCell 拖動(dòng)的調(diào)用棧大概為:

|---collectionView:moveItemAtIndexPath:toIndexPath:
        |---moveInSectionControllerInteractive:fromIndex:toIndex: # section 內(nèi)拖動(dòng)
                |---moveObjectFromIndex:toIndex:
        |---moveSectionControllerInteractive:fromIndex:toIndex: # section 間拖動(dòng)
                |---_updateObjects:dataSource
                |---moveSectionInCollectionView:fromIndex:toIndex # updater
                        |---performBatchUpdates:completion: # UICollectionView

總結(jié)來說拉一,整個(gè) IGListKit 結(jié)構(gòu)可以用下圖來概括:

image.png

可以看出來,IGListAdapter 負(fù)責(zé)不同功能的屬性都是通過面向協(xié)議來進(jìn)行開發(fā)旧乞,不同的功能模塊粒度都比較小蔚润,避免模塊之間的循環(huán)依賴,實(shí)現(xiàn)數(shù)據(jù)跟視圖的有效解耦尺栖。

不僅如此,IGListKit 通過 IGListDiffable 協(xié)議加上 diff 算法,對(duì)外隱藏?cái)?shù)據(jù)更新的細(xì)節(jié)沫勿,用戶只需關(guān)注業(yè)務(wù)數(shù)據(jù)薛窥,減輕了數(shù)據(jù)更新的操作。

其他

IGListKit 中還用到一些平時(shí)沒有注意到的特性

NSCountedSet

插入 NSCountedSet 對(duì)象的每個(gè)不同的對(duì)象都有一個(gè)與之相關(guān)的計(jì)數(shù)器挫以,同一個(gè)對(duì)象每加入一次 NSCountedSet 集合中者蠕,對(duì)應(yīng)的 count 就會(huì)加1

- (void)_willDisplayReusableView:(UICollectionReusableView *)view
                 forListAdapter:(IGListAdapter *)listAdapter
              sectionController:(IGListSectionController *)sectionController
                         object:(id)object
                      indexPath:(NSIndexPath *)indexPath {
    IGParameterAssert(view != nil);
    IGParameterAssert(listAdapter != nil);
    IGParameterAssert(object != nil);
    IGParameterAssert(indexPath != nil);

    [self.visibleViewObjectMap setObject:object forKey:view];
    NSCountedSet *visibleListSections = self.visibleListSections;
    if ([visibleListSections countForObject:sectionController] == 0) {
        [sectionController.displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController];
        [listAdapter.delegate listAdapter:listAdapter willDisplayObject:object atIndex:indexPath.section];
    }
    [visibleListSections addObject:sectionController];
}

IGListKit 中的 IGListDisplayHandler 利用 NSCountedSet 記錄 UICollectionView section 的顯示狀態(tài),旨在通知外部每個(gè) section 的顯示/消失事件掐松。

prefetchingEnabled

當(dāng)調(diào)用 collectionView:didEndDisplayingCell:forItemAtIndexPath: 后踱侣,cell 不會(huì)立刻進(jìn)入復(fù)用隊(duì)列,系統(tǒng)會(huì)keeps it around for a bit大磺。相當(dāng)于會(huì)緩存該 cell 一小段時(shí)間抡句,在這段時(shí)間內(nèi)如果該 cell 再次回到屏幕中,便不會(huì)重新調(diào)用 cellForItemAtIndexPath:杠愧,而是直接顯示待榔。

至于系統(tǒng)會(huì)緩存多久,官方并沒有給出明確的時(shí)間流济,感覺跟程序運(yùn)行時(shí)開銷有關(guān)究抓。

如果想關(guān)閉該功能猾担,需要設(shè)置 collectionView.prefetchingEnabled = NO;

UICollectionViewLayoutInvalidationContext

當(dāng)改變 UICollectionView item 的時(shí)候刺下,通過調(diào)用 -invalidateLayout 方法讓 UICollectionView 布局失效绑嘹,通過 Invalidation Context 聲明了在布局失效時(shí)布局的哪些部分需要被更新,布局對(duì)象就可以根據(jù)該信息減小重新計(jì)算的數(shù)據(jù)量橘茉。

IGListKit 提供了自定義的 IGListCollectionViewLayout 類來優(yōu)化 UICollectionView 的刷新工腋,IGListCollectionViewLayout 實(shí)現(xiàn)和 UICollectionViewLayoutInvalidationContext 相關(guān)的方法

@interface IGListCollectionViewLayoutInvalidationContext : UICollectionViewLayoutInvalidationContext
// 追加視圖
@property (nonatomic, assign) BOOL ig_invalidateSupplementaryAttributes;
@property (nonatomic, assign) BOOL ig_invalidateAllAttributes;
@end

IGListCollectionViewLayoutInvalidationContext 類繼承了 UICollectionViewLayoutInvalidationContext,用于記錄刷新布局相關(guān)邏輯

// -[UICollectionView setFrame:] / -[UICollectionView setBounds:] 會(huì)觸發(fā)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds {
    const CGRect oldBounds = self.collectionView.bounds;
    
    IGListCollectionViewLayoutInvalidationContext *context =
    (IGListCollectionViewLayoutInvalidationContext *)[super invalidationContextForBoundsChange:newBounds];
    // 每次都需要刷新 追加視圖
    context.ig_invalidateSupplementaryAttributes = YES;
    if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) {
        // size 改變之后畅卓,必須進(jìn)行全量刷新
        context.ig_invalidateAllAttributes = YES;
    }
    return context;
}

-invalidationContextForBoundsChange: 當(dāng) UICollectionView 發(fā)生變化的時(shí)候(比如視圖 frame 發(fā)生改變)擅腰,在進(jìn)行視圖刷新之前,會(huì)觸發(fā)該方法返回 UICollectionViewLayoutInvalidationContext 對(duì)象來告訴UICollectionView 布局刷新的相關(guān)信息翁潘。

// 根據(jù) context 中的信息重新計(jì)算布局改變的部分趁冈。
// -[UICollectionView setDataSource:] / -[UICollectionView setFrame:] 會(huì)觸發(fā)該方法
// 也可以主動(dòng)調(diào)用,強(qiáng)制刷新
- (void)invalidateLayoutWithContext:(IGListCollectionViewLayoutInvalidationContext *)context {
    BOOL hasInvalidatedItemIndexPaths = NO;
    if ([context respondsToSelector:@selector(invalidatedItemIndexPaths)]) {
        hasInvalidatedItemIndexPaths = [context invalidatedItemIndexPaths].count > 0;
    }
    
    // _minimumInvalidatedSection 用來記錄指定從哪個(gè) section 開始的布局失效拜马,需要重新布局
    if (hasInvalidatedItemIndexPaths
        || [context invalidateEverything]
        || context.ig_invalidateAllAttributes) {
        // invalidates all
        _minimumInvalidatedSection = 0;
    } else if ([context invalidateDataSourceCounts] && _minimumInvalidatedSection == NSNotFound) {
        // invalidateDataSourceCounts 標(biāo)記 layout 需要重新從 UICollectionView 查詢 section 和 item 數(shù)目
        // UICollectionView 調(diào)用 -reloadData 或者插入/刪除 item 的時(shí)候 invalidateDataSourceCounts = YES
        // 如果 layout 需要重新 UICollectionView 的信息或者沒有找到重新刷新的 section 啟動(dòng)渗勘,則刷新起點(diǎn) section 默認(rèn)為0
        _minimumInvalidatedSection = 0;
    }
    
    if (context.ig_invalidateSupplementaryAttributes) {
        // 清空追加視圖的布局信息緩存
        [self _resetSupplementaryAttributesCache];
    }
    
    [super invalidateLayoutWithContext:context];
}

-invalidateLayoutWithContext: 方法在 UICollectionView 布局信息發(fā)生變化會(huì)被系統(tǒng)調(diào)用,IGListCollectionViewLayout 實(shí)現(xiàn)了該方法俩莽,在調(diào)用的過程中會(huì)對(duì)一些布局緩存進(jìn)行更新(主要是緩存 UICollectionViewLayoutAttributes 對(duì)象)旺坠,具體細(xì)節(jié)不再展開。

除此之外扮超,UICollectionViewLayoutInvalidationContext 本身提供了幾個(gè)方法取刃,用戶可以主動(dòng)調(diào)用來進(jìn)行局部 UI 刷新

// 調(diào)用此方法以標(biāo)識(shí)布局中需要更新的特定單元格。 
// 指定的更新的所有 indexPath 對(duì)象將添加到屬性 invalidatedItemIndexPaths 中出刷。
- (void)invalidateItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths API_AVAILABLE(ios(8.0));

// 重新計(jì)算一個(gè)或者多個(gè)追加視圖的布局
- (void)invalidateSupplementaryElementsOfKind:(NSString *)elementKind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths API_AVAILABLE(ios(8.0));

// 重新計(jì)算一個(gè)或者多個(gè)裝飾視圖的布局
- (void)invalidateDecorationElementsOfKind:(NSString *)elementKind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths API_AVAILABLE(ios(8.0));
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末璧疗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子馁龟,更是在濱河造成了極大的恐慌病毡,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屁柏,死亡現(xiàn)場離奇詭異啦膜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)淌喻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門僧家,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裸删,你說我怎么就攤上這事八拱。” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵肌稻,是天一觀的道長清蚀。 經(jīng)常有香客問我,道長爹谭,這世上最難降的妖魔是什么枷邪? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮诺凡,結(jié)果婚禮上东揣,老公的妹妹穿的比我還像新娘。我一直安慰自己腹泌,他們只是感情好嘶卧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凉袱,像睡著了一般芥吟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上专甩,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天钟鸵,我揣著相機(jī)與錄音,去河邊找鬼配深。 笑死携添,一個(gè)胖子當(dāng)著我的面吹牛嫁盲,可吹牛的內(nèi)容都是我干的篓叶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼羞秤,長吁一口氣:“原來是場噩夢啊……” “哼缸托!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瘾蛋,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤俐镐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后哺哼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佩抹,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年取董,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棍苹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茵汰,死狀恐怖枢里,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤栏豺,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布彬碱,位于F島的核電站,受9級(jí)特大地震影響奥洼,放射性物質(zhì)發(fā)生泄漏巷疼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一溉卓、第九天 我趴在偏房一處隱蔽的房頂上張望皮迟。 院中可真熱鬧,春花似錦桑寨、人聲如沸伏尼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爆阶。三九已至,卻和暖如春沙咏,著一層夾襖步出監(jiān)牢的瞬間辨图,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工肢藐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留故河,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓吆豹,卻偏偏與公主長得像鱼的,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痘煤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354