mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 19:30:29 +00:00
[ASTableViewThrashTests] Initial commit
This commit is contained in:
parent
6dac29a16f
commit
fcf2db79f8
@ -545,6 +545,7 @@
|
||||
CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; };
|
||||
CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; };
|
||||
CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */; };
|
||||
CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */; };
|
||||
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; };
|
||||
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; };
|
||||
@ -935,6 +936,7 @@
|
||||
CC3B20881C3F7A5400798563 /* ASWeakSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSet.m; sourceTree = "<group>"; };
|
||||
CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSetTests.m; sourceTree = "<group>"; };
|
||||
CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBridgedPropertiesTests.mm; sourceTree = "<group>"; };
|
||||
CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewThrashTests.m; sourceTree = "<group>"; };
|
||||
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = "<group>"; };
|
||||
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = "<group>"; };
|
||||
CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = "<group>"; };
|
||||
@ -1201,6 +1203,7 @@
|
||||
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */,
|
||||
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */,
|
||||
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */,
|
||||
CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */,
|
||||
058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */,
|
||||
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */,
|
||||
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */,
|
||||
@ -2171,6 +2174,7 @@
|
||||
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */,
|
||||
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
|
||||
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */,
|
||||
CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */,
|
||||
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */,
|
||||
056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */,
|
||||
AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */,
|
||||
|
||||
@ -41,7 +41,7 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray
|
||||
}
|
||||
}
|
||||
|
||||
static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res)
|
||||
static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray <NSIndexPath *>*res)
|
||||
{
|
||||
if (![obj isKindOfClass:[NSArray class]]) {
|
||||
[res addObject:curIndexPath];
|
||||
|
||||
391
AsyncDisplayKitTests/ASTableViewThrashTests.m
Normal file
391
AsyncDisplayKitTests/ASTableViewThrashTests.m
Normal file
@ -0,0 +1,391 @@
|
||||
//
|
||||
// ASTableViewThrashTests.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 6/21/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
@import XCTest;
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
typedef NS_ENUM(NSUInteger, ASThrashChangeType) {
|
||||
ASThrashReplaceItem,
|
||||
ASThrashReplaceSection,
|
||||
ASThrashDeleteItem,
|
||||
ASThrashDeleteSection,
|
||||
ASThrashInsertItem,
|
||||
ASThrashInsertSection
|
||||
};
|
||||
|
||||
#define USE_UIKIT_REFERENCE 1
|
||||
#define kInitialSectionCount 6
|
||||
#define kInitialItemCount 6
|
||||
|
||||
#if USE_UIKIT_REFERENCE
|
||||
#define kCellReuseID @"ASThrashTestCellReuseID"
|
||||
#endif
|
||||
|
||||
static NSString *ASThrashArrayDescription(NSArray *array) {
|
||||
NSMutableString *str = [NSMutableString stringWithString:@"(\n"];
|
||||
NSInteger i = 0;
|
||||
for (id obj in array) {
|
||||
[str appendFormat:@"\t[%ld]: \"%@\",\n", i, obj];
|
||||
i += 1;
|
||||
}
|
||||
[str appendString:@")"];
|
||||
return str;
|
||||
}
|
||||
@interface ASThrashTestItem: NSObject
|
||||
#if USE_UIKIT_REFERENCE
|
||||
/// This is used to identify the row with the table view (UIKit only).
|
||||
@property (nonatomic, readonly) CGFloat rowHeight;
|
||||
#endif
|
||||
@end
|
||||
|
||||
@implementation ASThrashTestItem
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
#if USE_UIKIT_REFERENCE
|
||||
_rowHeight = arc4random_uniform(500);
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (NSArray <ASThrashTestItem *> *)itemsWithCount:(NSInteger)count {
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
|
||||
for (NSInteger i = 0; i < count; i += 1) {
|
||||
[result addObject:[[ASThrashTestItem alloc] init]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
#if USE_UIKIT_REFERENCE
|
||||
return [NSString stringWithFormat:@"<Item: rowHeight=%lu>", (unsigned long)self.rowHeight];
|
||||
#else
|
||||
return [NSString stringWithFormat:@"<Item: %p>", self];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASThrashTestSection: NSObject
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *items;
|
||||
/// This is used to identify the section with the table view.
|
||||
@property (nonatomic, readonly) CGFloat headerHeight;
|
||||
@end
|
||||
|
||||
@implementation ASThrashTestSection
|
||||
|
||||
- (instancetype)initWithCount:(NSInteger)count {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_items = [NSMutableArray arrayWithCapacity:count];
|
||||
_headerHeight = arc4random_uniform(500) + 1;
|
||||
for (NSInteger i = 0; i < count; i++) {
|
||||
[_items addObject:[ASThrashTestItem new]];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithCount:0];
|
||||
}
|
||||
|
||||
+ (NSMutableArray <ASThrashTestSection *> *)sectionsWithCount:(NSInteger)count {
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
|
||||
for (NSInteger i = 0; i < count; i += 1) {
|
||||
[result addObject:[[ASThrashTestSection alloc] initWithCount:kInitialItemCount]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<Section: headerHeight=%lu, itemCount=%lu>", (unsigned long)self.headerHeight, (unsigned long)self.items.count];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#if !USE_UIKIT_REFERENCE
|
||||
@interface ASThrashTestNode: ASCellNode
|
||||
@property (nonatomic, strong) ASThrashTestItem *item;
|
||||
@end
|
||||
|
||||
@implementation ASThrashTestNode
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@interface ASThrashDataSource: NSObject
|
||||
#if USE_UIKIT_REFERENCE
|
||||
<UITableViewDataSource, UITableViewDelegate>
|
||||
#else
|
||||
<ASTableDataSource, ASTableDelegate>
|
||||
#endif
|
||||
@property (nonatomic, strong, readonly) NSMutableArray <ASThrashTestSection *> *data;
|
||||
@end
|
||||
|
||||
|
||||
@implementation ASThrashDataSource
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.data[section].items.count;
|
||||
}
|
||||
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.data.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
return self.data[section].headerHeight;
|
||||
}
|
||||
|
||||
#if USE_UIKIT_REFERENCE
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return [tableView dequeueReusableCellWithIdentifier:kCellReuseID forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
ASThrashTestItem *item = self.data[indexPath.section].items[indexPath.item];
|
||||
return item.rowHeight;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
ASThrashTestItem *item = self.data[indexPath.section].items[indexPath.item];
|
||||
return ^{
|
||||
ASThrashTestNode *tableNode = [[ASThrashTestNode alloc] init];
|
||||
tableNode.item = item;
|
||||
return tableNode;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSIndexSet (ASThrashHelpers)
|
||||
|
||||
- (NSArray <NSIndexPath *> *)indexPathsInSection:(NSInteger)section {
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count];
|
||||
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[result addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableViewThrashTests: XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASTableViewThrashTests {
|
||||
CGRect screenBounds;
|
||||
ASThrashDataSource *ds;
|
||||
UIWindow *window;
|
||||
#if USE_UIKIT_REFERENCE
|
||||
UITableView *tableView;
|
||||
#else
|
||||
ASTableNode *tableNode;
|
||||
ASTableView *tableView;
|
||||
#endif
|
||||
|
||||
NSInteger minimumItemCount;
|
||||
NSInteger minimumSectionCount;
|
||||
float fickleness;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
minimumItemCount = 5;
|
||||
minimumSectionCount = 3;
|
||||
fickleness = 0.1;
|
||||
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
ds = [[ASThrashDataSource alloc] init];
|
||||
#if USE_UIKIT_REFERENCE
|
||||
tableView = [[UITableView alloc] initWithFrame:window.bounds style:UITableViewStyleGrouped];
|
||||
[window addSubview:tableView];
|
||||
tableView.dataSource = ds;
|
||||
tableView.delegate = ds;
|
||||
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellReuseID];
|
||||
[window layoutIfNeeded];
|
||||
#else
|
||||
tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStyleGrouped];
|
||||
tableNode.frame = window.bounds;
|
||||
[window addSubnode:tableNode];
|
||||
tableNode.dataSource = ds;
|
||||
tableNode.delegate = ds;
|
||||
[tableView reloadDataImmediately];
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (void)testInitialDataRead {
|
||||
[self verifyTableStateWithHierarchy];
|
||||
}
|
||||
|
||||
- (void)testThrashingWildly {
|
||||
for (NSInteger i = 0; i < 100; i++) {
|
||||
[self _testThrashingWildly];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_testThrashingWildly {
|
||||
NSLog(@"Old data: %@", ASThrashArrayDescription(ds.data));
|
||||
NSMutableArray <NSMutableIndexSet *> *deletedItems = [NSMutableArray array];
|
||||
NSMutableArray <NSMutableIndexSet *> *replacedItems = [NSMutableArray array];
|
||||
NSMutableArray <NSMutableIndexSet *> *insertedItems = [NSMutableArray array];
|
||||
NSInteger i = 0;
|
||||
|
||||
// Randomly reload some items
|
||||
for (ASThrashTestSection *section in ds.data) {
|
||||
NSMutableIndexSet *indexes = [self randomIndexesLessThan:section.items.count probability:fickleness insertMode:NO];
|
||||
NSArray *newItems = [ASThrashTestItem itemsWithCount:indexes.count];
|
||||
[section.items replaceObjectsAtIndexes:indexes withObjects:newItems];
|
||||
[replacedItems addObject:indexes];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Randomly replace some sections
|
||||
NSMutableIndexSet *replacedSections = [self randomIndexesLessThan:ds.data.count probability:fickleness insertMode:NO];
|
||||
NSArray *replacingSections = [ASThrashTestSection sectionsWithCount:replacedSections.count];
|
||||
[ds.data replaceObjectsAtIndexes:replacedSections withObjects:replacingSections];
|
||||
|
||||
// Randomly delete some items
|
||||
i = 0;
|
||||
for (ASThrashTestSection *section in ds.data) {
|
||||
if (section.items.count >= minimumItemCount) {
|
||||
NSMutableIndexSet *indexes = [self randomIndexesLessThan:section.items.count probability:fickleness insertMode:NO];
|
||||
|
||||
/// Cannot reload & delete the same item.
|
||||
[indexes removeIndexes:replacedItems[i]];
|
||||
|
||||
[section.items removeObjectsAtIndexes:indexes];
|
||||
[deletedItems addObject:indexes];
|
||||
} else {
|
||||
[deletedItems addObject:[NSMutableIndexSet indexSet]];
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Randomly delete some sections
|
||||
NSMutableIndexSet *deletedSections = nil;
|
||||
if (ds.data.count >= minimumSectionCount) {
|
||||
deletedSections = [self randomIndexesLessThan:ds.data.count probability:fickleness insertMode:NO];
|
||||
|
||||
// Cannot reload & delete the same section.
|
||||
[deletedSections removeIndexes:replacedSections];
|
||||
} else {
|
||||
deletedSections = [NSMutableIndexSet indexSet];
|
||||
}
|
||||
[ds.data removeObjectsAtIndexes:deletedSections];
|
||||
|
||||
// Randomly insert some sections
|
||||
NSMutableIndexSet *insertedSections = [self randomIndexesLessThan:(ds.data.count + 1) probability:fickleness insertMode:YES];
|
||||
NSArray *newSections = [ASThrashTestSection sectionsWithCount:insertedSections.count];
|
||||
[ds.data insertObjects:newSections atIndexes:insertedSections];
|
||||
|
||||
// Randomly insert some items
|
||||
i = 0;
|
||||
for (ASThrashTestSection *section in ds.data) {
|
||||
NSMutableIndexSet *indexes = [self randomIndexesLessThan:(section.items.count + 1) probability:fickleness insertMode:YES];
|
||||
NSArray *newItems = [ASThrashTestItem itemsWithCount:indexes.count];
|
||||
[section.items insertObjects:newItems atIndexes:indexes];
|
||||
[insertedItems addObject:indexes];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
NSLog(@"Deleted items: %@\nDeleted sections: %@\nReplaced items: %@\nReplaced sections: %@\nInserted items: %@\nInserted sections: %@\nNew data: %@", ASThrashArrayDescription(deletedItems), deletedSections, ASThrashArrayDescription(replacedItems), replacedSections, ASThrashArrayDescription(insertedItems), insertedSections, ASThrashArrayDescription(ds.data));
|
||||
|
||||
// TODO: Submit changes in random order, randomly chunked up
|
||||
|
||||
[tableView beginUpdates];
|
||||
i = 0;
|
||||
for (NSIndexSet *indexes in insertedItems) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:i];
|
||||
NSLog(@"Requested to insert rows: %@", indexPaths);
|
||||
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
[tableView insertSections:insertedSections withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
[tableView deleteSections:deletedSections withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
i = 0;
|
||||
for (NSIndexSet *indexes in deletedItems) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:i];
|
||||
NSLog(@"Requested to delete rows: %@", indexPaths);
|
||||
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (NSIndexSet *indexes in replacedItems) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:i];
|
||||
NSLog(@"Requested to reload rows: %@", indexPaths);
|
||||
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
[tableView endUpdates];
|
||||
#if !USE_UIKIT_REFERENCE
|
||||
[tableView waitUntilAllUpdatesAreCommitted];
|
||||
#endif
|
||||
[self verifyTableStateWithHierarchy];
|
||||
}
|
||||
|
||||
/// `insertMode` means that for each index selected, the max goes up by one.
|
||||
- (NSMutableIndexSet *)randomIndexesLessThan:(NSInteger)max probability:(float)probability insertMode:(BOOL)insertMode {
|
||||
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
|
||||
u_int32_t cutoff = probability * 100;
|
||||
for (NSInteger i = 0; i < max; i++) {
|
||||
if (arc4random_uniform(100) < cutoff) {
|
||||
[indexes addIndex:i];
|
||||
if (insertMode) {
|
||||
max += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
|
||||
- (void)verifyTableStateWithHierarchy {
|
||||
NSArray <ASThrashTestSection *> *data = [ds data];
|
||||
XCTAssertEqual(data.count, tableView.numberOfSections);
|
||||
for (NSInteger i = 0; i < tableView.numberOfSections; i++) {
|
||||
XCTAssertEqual([tableView numberOfRowsInSection:i], data[i].items.count);
|
||||
XCTAssertEqual([tableView rectForHeaderInSection:i].size.height, data[i].headerHeight);
|
||||
|
||||
for (NSInteger j = 0; j < [tableView numberOfRowsInSection:i]; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
||||
ASThrashTestItem *item = data[i].items[j];
|
||||
#if USE_UIKIT_REFERENCE
|
||||
XCTAssertEqual([tableView rectForRowAtIndexPath:indexPath].size.height, item.rowHeight);
|
||||
#else
|
||||
ASThrashTestNode *node = (ASThrashTestNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
||||
XCTAssertEqual(node.item, item);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Loading…
x
Reference in New Issue
Block a user