diff --git a/Classes/Network/FLEXMITMDataSource.h b/Classes/Network/FLEXMITMDataSource.h new file mode 100644 index 0000000000..cfd7e45bb7 --- /dev/null +++ b/Classes/Network/FLEXMITMDataSource.h @@ -0,0 +1,34 @@ +// +// FLEXMITMDataSource.h +// FLEX +// +// Created by Tanner Bennett on 8/22/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FLEXMITMDataSource<__covariant TransactionType> : NSObject + ++ (instancetype)dataSourceWithProvider:(NSArray *(^)())future; + +@property (nonatomic, readonly) NSArray *transactions; +@property (nonatomic, readonly) NSArray *allTransactions; +/// Equal to \c allTransactions if not filtered +@property (nonatomic, readonly) NSArray *filteredTransactions; + +/// Use this instead of either of the other two as it updates based on whether we have a filter or not +@property (nonatomic) NSInteger bytesReceived; +@property (nonatomic) NSInteger totalBytesReceived; +/// Equal to \c totalBytesReceived if not filtered +@property (nonatomic) NSInteger filteredBytesReceived; + +- (void)reloadByteCounts; +- (void)reloadData:(void (^_Nullable)(FLEXMITMDataSource *dataSource))completion; +- (void)filter:(NSString *)searchString completion:(void(^_Nullable)(FLEXMITMDataSource *dataSource))completion; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/Classes/Network/FLEXMITMDataSource.m b/Classes/Network/FLEXMITMDataSource.m new file mode 100644 index 0000000000..ac557d8acc --- /dev/null +++ b/Classes/Network/FLEXMITMDataSource.m @@ -0,0 +1,102 @@ +// +// FLEXMITMDataSource.m +// FLEX +// +// Created by Tanner Bennett on 8/22/21. +// + +#import "FLEXMITMDataSource.h" +#import "FLEXNetworkTransaction.h" +#import "FLEXUtility.h" + +@interface FLEXMITMDataSource () +@property (nonatomic, readonly) NSArray *(^dataProvider)(); +@property (nonatomic) NSString *filterString; +@end + +@implementation FLEXMITMDataSource + ++ (instancetype)dataSourceWithProvider:(NSArray *(^)())future { + FLEXMITMDataSource *ds = [self new]; + ds->_dataProvider = future; + [ds reloadData:nil]; + + return ds; +} + +- (NSArray *)transactions { + return _filteredTransactions; +} + +- (NSInteger)bytesReceived { + return _filteredBytesReceived; +} + +- (void)reloadByteCounts { + [self updateBytesReceived]; + [self updateFilteredBytesReceived]; +} + +- (void)reloadData:(void (^)(FLEXMITMDataSource *dataSource))completion { + self.allTransactions = self.dataProvider(); + [self filter:self.filterString completion:completion]; +} + +- (void)filter:(NSString *)searchString completion:(void (^)(FLEXMITMDataSource *dataSource))completion { + self.filterString = searchString; + + if (!searchString.length) { + self.filteredTransactions = self.allTransactions; + if (completion) completion(self); + } else { + [self onBackgroundQueue:^NSArray *{ + return [self.allTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) { + return [entry.request.URL.absoluteString localizedCaseInsensitiveContainsString:searchString]; + }]; + } thenOnMainQueue:^(NSArray *filteredNetworkTransactions) { + if ([self.filterString isEqual:searchString]) { + self.filteredTransactions = filteredNetworkTransactions; + if (completion) completion(self); + } + }]; + } +} + +- (void)setAllTransactions:(NSArray *)transactions { + _allTransactions = transactions; + [self updateBytesReceived]; +} + +- (void)setFilteredTransactions:(NSArray *)filteredTransactions { + _filteredTransactions = filteredTransactions; + [self updateFilteredBytesReceived]; +} + +- (void)updateBytesReceived { + NSInteger bytesReceived = 0; + for (FLEXNetworkTransaction *transaction in self.transactions) { + bytesReceived += transaction.receivedDataLength; + } + + self.bytesReceived = bytesReceived; +} + +- (void)updateFilteredBytesReceived { + NSInteger filteredBytesReceived = 0; + for (FLEXNetworkTransaction *transaction in self.filteredTransactions) { + filteredBytesReceived += transaction.receivedDataLength; + } + + self.filteredBytesReceived = filteredBytesReceived; +} + +- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSArray *items = backgroundBlock(); + dispatch_async(dispatch_get_main_queue(), ^{ + mainBlock(items); + }); + }); +} + +@end diff --git a/Classes/Network/FLEXNetworkMITMViewController.h b/Classes/Network/FLEXNetworkMITMViewController.h index aef8fd412e..3db0089513 100644 --- a/Classes/Network/FLEXNetworkMITMViewController.h +++ b/Classes/Network/FLEXNetworkMITMViewController.h @@ -9,6 +9,7 @@ #import "FLEXTableViewController.h" #import "FLEXGlobalsEntry.h" +/// The main screen for the network observer, which displays a list of network transactions. @interface FLEXNetworkMITMViewController : FLEXTableViewController @end diff --git a/Classes/Network/FLEXNetworkMITMViewController.m b/Classes/Network/FLEXNetworkMITMViewController.m index 68c54c7090..83b414e6ed 100644 --- a/Classes/Network/FLEXNetworkMITMViewController.m +++ b/Classes/Network/FLEXNetworkMITMViewController.m @@ -8,29 +8,34 @@ #import "FLEXColor.h" #import "FLEXUtility.h" +#import "FLEXMITMDataSource.h" #import "FLEXNetworkMITMViewController.h" #import "FLEXNetworkTransaction.h" #import "FLEXNetworkRecorder.h" #import "FLEXNetworkObserver.h" #import "FLEXNetworkTransactionCell.h" -#import "FLEXNetworkTransactionDetailController.h" +#import "FLEXHTTPTransactionDetailController.h" #import "FLEXNetworkSettingsController.h" +#import "FLEXObjectExplorerFactory.h" #import "FLEXGlobalsViewController.h" +#import "FLEXWebViewController.h" #import "UIBarButtonItem+FLEX.h" #import "FLEXResources.h" -@interface FLEXNetworkMITMViewController () +typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) { + FLEXNetworkObserverModeREST = 0, + FLEXNetworkObserverModeWebsockets, +}; -/// Backing model -@property (nonatomic, copy) NSArray *networkTransactions; -@property (nonatomic) long long bytesReceived; -@property (nonatomic, copy) NSArray *filteredNetworkTransactions; -@property (nonatomic) long long filteredBytesReceived; +@interface FLEXNetworkMITMViewController () -@property (nonatomic) BOOL rowInsertInProgress; -@property (nonatomic) BOOL isPresentingSearch; +@property (nonatomic) BOOL updateInProgress; @property (nonatomic) BOOL pendingReload; +@property (nonatomic, readonly) FLEXMITMDataSource *dataSource; +@property (nonatomic, readonly) FLEXMITMDataSource *HTTPDataSource; +@property (nonatomic, readonly) FLEXMITMDataSource *websocketDataSource; + @end @implementation FLEXNetworkMITMViewController @@ -47,6 +52,18 @@ - (void)viewDidLoad { self.showsSearchBar = YES; self.showSearchBarInitially = NO; + _HTTPDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * { + return FLEXNetworkRecorder.defaultRecorder.HTTPTransactions; + }]; + + if (@available(iOS 13.0, *)) { + self.searchController.searchBar.showsScopeBar = YES; + self.searchController.searchBar.scopeButtonTitles = @[@"REST", @"Websockets"]; + _websocketDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * { + return FLEXNetworkRecorder.defaultRecorder.websocketTransactions; + }]; + } + [self addToolbarItems:@[ [UIBarButtonItem flex_itemWithImage:FLEXResources.gearIcon @@ -61,14 +78,14 @@ - (void)viewDidLoad { ]]; [self.tableView - registerClass:[FLEXNetworkTransactionCell class] - forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier + registerClass:FLEXNetworkTransactionCell.class + forCellReuseIdentifier:FLEXNetworkTransactionCell.reuseID ]; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; self.tableView.rowHeight = FLEXNetworkTransactionCell.preferredCellHeight; [self registerForNotifications]; - [self updateTransactions]; + [self updateTransactions:nil]; } - (void)viewWillAppear:(BOOL)animated { @@ -137,44 +154,32 @@ - (void)settingsViewControllerDoneTapped:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; } -#pragma mark Transactions - -- (void)updateTransactions { - self.networkTransactions = [FLEXNetworkRecorder.defaultRecorder networkTransactions]; -} -- (void)setNetworkTransactions:(NSArray *)networkTransactions { - if (![_networkTransactions isEqual:networkTransactions]) { - _networkTransactions = networkTransactions; - [self updateBytesReceived]; - [self updateFilteredBytesReceived]; - } -} +#pragma mark Transactions -- (void)updateBytesReceived { - long long bytesReceived = 0; - for (FLEXNetworkTransaction *transaction in self.networkTransactions) { - bytesReceived += transaction.receivedDataLength; +- (FLEXMITMDataSource *)dataSource { + switch (self.searchController.searchBar.selectedScopeButtonIndex) { + case FLEXNetworkObserverModeREST: + return self.HTTPDataSource; + case FLEXNetworkObserverModeWebsockets: + return self.websocketDataSource; + + default: + @throw NSInternalInconsistencyException; } - self.bytesReceived = bytesReceived; - [self updateFirstSectionHeader]; } -- (void)setFilteredNetworkTransactions:(NSArray *)networkTransactions { - if (![_filteredNetworkTransactions isEqual:networkTransactions]) { - _filteredNetworkTransactions = networkTransactions; - [self updateFilteredBytesReceived]; - } +- (void)updateTransactions:(void(^)())callback { + id completion = ^(FLEXMITMDataSource *dataSource) { + // Update byte count + [self updateFirstSectionHeader]; + if (callback && dataSource == self.dataSource) callback(); + }; + + [self.HTTPDataSource reloadData:completion]; + [self.websocketDataSource reloadData:completion]; } -- (void)updateFilteredBytesReceived { - long long filteredBytesReceived = 0; - for (FLEXNetworkTransaction *transaction in self.filteredNetworkTransactions) { - filteredBytesReceived += transaction.receivedDataLength; - } - self.filteredBytesReceived = filteredBytesReceived; - [self updateFirstSectionHeader]; -} #pragma mark Header @@ -191,11 +196,11 @@ - (NSString *)headerText { long long bytesReceived = 0; NSInteger totalRequests = 0; if (self.searchController.isActive) { - bytesReceived = self.filteredBytesReceived; - totalRequests = self.filteredNetworkTransactions.count; + bytesReceived = self.dataSource.filteredBytesReceived; + totalRequests = self.dataSource.transactions.count; } else { - bytesReceived = self.bytesReceived; - totalRequests = self.networkTransactions.count; + bytesReceived = self.dataSource.bytesReceived; + totalRequests = self.dataSource.transactions.count; } NSString *byteCountText = [NSByteCountFormatter @@ -253,7 +258,7 @@ - (void)handleNewTransactionRecordedNotification:(NSNotification *)notification - (void)tryUpdateTransactions { // Don't do any view updating if we aren't in the view hierarchy if (!self.viewIfLoaded.window) { - [self updateTransactions]; + [self updateTransactions:nil]; self.pendingReload = YES; return; } @@ -261,57 +266,71 @@ - (void)tryUpdateTransactions { // Let the previous row insert animation finish before starting a new one to avoid stomping. // We'll try calling the method again when the insertion completes, // and we properly no-op if there haven't been changes. - if (self.rowInsertInProgress) { + if (self.updateInProgress) { return; } - if (self.searchController.isActive) { - [self updateTransactions]; - [self updateSearchResults:self.searchText]; - return; - } + self.updateInProgress = YES; - NSInteger existingRowCount = self.networkTransactions.count; - [self updateTransactions]; - NSInteger newRowCount = self.networkTransactions.count; - NSInteger addedRowCount = newRowCount - existingRowCount; + // Get state before update + NSString *currentFilter = self.searchText; + FLEXNetworkObserverMode currentMode = self.searchController.searchBar.selectedScopeButtonIndex; + NSInteger existingRowCount = self.dataSource.transactions.count; + + [self updateTransactions:^{ + // Compare to state after update + NSString *newFilter = self.searchText; + FLEXNetworkObserverMode newMode = self.searchController.searchBar.selectedScopeButtonIndex; + NSInteger newRowCount = self.dataSource.transactions.count; + NSInteger rowCountDiff = newRowCount - existingRowCount; + + // Abort if the observation mode changed, or if the search field text changed + if (newMode != currentMode || ![currentFilter isEqualToString:newFilter]) { + self.updateInProgress = NO; + return; + } + + if (rowCountDiff) { + // Insert animation if we're at the top. + if (self.tableView.contentOffset.y <= 0.0 && rowCountDiff > 0) { + [CATransaction begin]; + + [CATransaction setCompletionBlock:^{ + self.updateInProgress = NO; + // This isn't an infinite loop, it won't run a third time + // if there were no new transactions the second time + [self tryUpdateTransactions]; + }]; + + NSMutableArray *indexPathsToReload = [NSMutableArray new]; + for (NSInteger row = 0; row < rowCountDiff; row++) { + [indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]]; + } - if (addedRowCount != 0 && !self.isPresentingSearch) { - // Insert animation if we're at the top. - if (self.tableView.contentOffset.y <= 0.0 && addedRowCount > 0) { - [CATransaction begin]; - - self.rowInsertInProgress = YES; - [CATransaction setCompletionBlock:^{ - self.rowInsertInProgress = NO; - [self tryUpdateTransactions]; - }]; - - NSMutableArray *indexPathsToReload = [NSMutableArray new]; - for (NSInteger row = 0; row < addedRowCount; row++) { - [indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]]; + [self.tableView insertRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationAutomatic]; + [CATransaction commit]; + } else { + // Maintain the user's position if they've scrolled down. + CGSize existingContentSize = self.tableView.contentSize; + [self.tableView reloadData]; + CGFloat contentHeightChange = self.tableView.contentSize.height - existingContentSize.height; + self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + contentHeightChange); + self.updateInProgress = NO; } - [self.tableView insertRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationAutomatic]; - - [CATransaction commit]; } else { - // Maintain the user's position if they've scrolled down. - CGSize existingContentSize = self.tableView.contentSize; - [self.tableView reloadData]; - CGFloat contentHeightChange = self.tableView.contentSize.height - existingContentSize.height; - self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + contentHeightChange); + self.updateInProgress = NO; } - } + }]; } - (void)handleTransactionUpdatedNotification:(NSNotification *)notification { - [self updateBytesReceived]; - [self updateFilteredBytesReceived]; + [self.HTTPDataSource reloadByteCounts]; + [self.websocketDataSource reloadByteCounts]; FLEXNetworkTransaction *transaction = notification.userInfo[kFLEXNetworkRecorderUserInfoTransactionKey]; // Update both the main table view and search table view if needed. - for (FLEXNetworkTransactionCell *cell in [self.tableView visibleCells]) { + for (FLEXNetworkTransactionCell *cell in self.tableView.visibleCells) { if ([cell.transaction isEqual:transaction]) { // Using -[UITableView reloadRowsAtIndexPaths:withRowAnimation:] is overkill here and kicks off a lot of // work that can make the table view somewhat unresponsive when lots of updates are streaming in. @@ -320,12 +339,14 @@ - (void)handleTransactionUpdatedNotification:(NSNotification *)notification { break; } } + [self updateFirstSectionHeader]; } - (void)handleTransactionsClearedNotification:(NSNotification *)notification { - [self updateTransactions]; - [self.tableView reloadData]; + [self updateTransactions:^{ + [self.tableView reloadData]; + }]; } - (void)handleNetworkObserverEnabledStateChangedNotification:(NSNotification *)notification { @@ -337,7 +358,7 @@ - (void)handleNetworkObserverEnabledStateChangedNotification:(NSNotification *)n #pragma mark - Table view data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return self.searchController.isActive ? self.filteredNetworkTransactions.count : self.networkTransactions.count; + return self.dataSource.transactions.count; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { @@ -352,7 +373,11 @@ - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - FLEXNetworkTransactionCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath]; + FLEXNetworkTransactionCell *cell = [tableView + dequeueReusableCellWithIdentifier:FLEXNetworkTransactionCell.reuseID + forIndexPath:indexPath + ]; + cell.transaction = [self transactionAtIndexPath:indexPath]; // Since we insert from the top, assign background colors bottom up to keep them consistent for each transaction. @@ -367,9 +392,33 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - FLEXNetworkTransactionDetailController *detailViewController = [FLEXNetworkTransactionDetailController new]; - detailViewController.transaction = [self transactionAtIndexPath:indexPath]; - [self.navigationController pushViewController:detailViewController animated:YES]; + switch (self.searchController.searchBar.selectedScopeButtonIndex) { + case FLEXNetworkObserverModeREST: { + FLEXHTTPTransaction *transaction = [self HTTPTransactionAtIndexPath:indexPath]; + UIViewController *details = [FLEXHTTPTransactionDetailController withTransaction:transaction]; + [self.navigationController pushViewController:details animated:YES]; + break; + } + + case FLEXNetworkObserverModeWebsockets: { + if (@available(iOS 13.0, *)) { // This check will never fail + FLEXWebsocketTransaction *transaction = [self websocketTransactionAtIndexPath:indexPath]; + + UIViewController *details = nil; + if (transaction.message.type == NSURLSessionWebSocketMessageTypeData) { + details = [FLEXObjectExplorerFactory explorerViewControllerForObject:transaction.message.data]; + } else { + details = [[FLEXWebViewController alloc] initWithText:transaction.message.string]; + } + + [self.navigationController pushViewController:details animated:YES]; + } + break; + } + + default: + @throw NSInternalInconsistencyException; + } } @@ -397,7 +446,7 @@ - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuCo previewProvider:nil actionProvider:^UIMenu *(NSArray *suggestedActions) { UIAction *copy = [UIAction - actionWithTitle:@"Copy" + actionWithTitle:@"Copy URL" image:nil identifier:nil handler:^(__kindof UIAction *action) { @@ -426,39 +475,34 @@ - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuCo } - (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath { - return self.searchController.isActive ? self.filteredNetworkTransactions[indexPath.row] : self.networkTransactions[indexPath.row]; + return self.dataSource.transactions[indexPath.row]; } +- (FLEXHTTPTransaction *)HTTPTransactionAtIndexPath:(NSIndexPath *)indexPath { + return self.HTTPDataSource.transactions[indexPath.row]; +} -#pragma mark - Search Bar - -- (void)updateSearchResults:(NSString *)searchString { - if (!searchString.length) { - self.filteredNetworkTransactions = self.networkTransactions; - [self.tableView reloadData]; - } else { - [self onBackgroundQueue:^NSArray *{ - return [self.networkTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) { - return [entry.request.URL.absoluteString localizedCaseInsensitiveContainsString:searchString]; - }]; - } thenOnMainQueue:^(NSArray *filteredNetworkTransactions) { - if ([self.searchText isEqual:searchString]) { - self.filteredNetworkTransactions = filteredNetworkTransactions; - [self.tableView reloadData]; - } - }]; - } +- (FLEXWebsocketTransaction *)websocketTransactionAtIndexPath:(NSIndexPath *)indexPath { + return self.websocketDataSource.transactions[indexPath.row]; } -#pragma mark UISearchControllerDelegate +#pragma mark - Search Bar -- (void)willPresentSearchController:(UISearchController *)searchController { - self.isPresentingSearch = YES; +- (void)updateSearchResults:(NSString *)searchString { + id callback = ^(FLEXMITMDataSource *dataSource) { + if (self.dataSource == dataSource) { + [self.tableView reloadData]; + } + }; + + [self.HTTPDataSource filter:searchString completion:callback]; + [self.websocketDataSource filter:searchString completion:callback]; } -- (void)didPresentSearchController:(UISearchController *)searchController { - self.isPresentingSearch = NO; +- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope { + [self updateFirstSectionHeader]; + [self.tableView reloadData]; } - (void)willDismissSearchController:(UISearchController *)searchController { diff --git a/FLEX.xcodeproj/project.pbxproj b/FLEX.xcodeproj/project.pbxproj index e642a975df..776137b974 100644 --- a/FLEX.xcodeproj/project.pbxproj +++ b/FLEX.xcodeproj/project.pbxproj @@ -200,6 +200,8 @@ C3531BAB23E88FAC00A184AD /* FLEXTabList.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA923E88FAC00A184AD /* FLEXTabList.m */; }; C35DAD822709140700AA95E6 /* FLEXHTTPTransactionDetailController.h in Headers */ = {isa = PBXBuildFile; fileRef = C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */; }; C35DAD832709140700AA95E6 /* FLEXHTTPTransactionDetailController.m in Sources */ = {isa = PBXBuildFile; fileRef = C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */; }; + C35DAD8E2709143000AA95E6 /* FLEXMITMDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */; }; + C35DAD8F2709143000AA95E6 /* FLEXMITMDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */; }; C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; settings = {ATTRIBUTES = (Private, ); }; }; C362AE8223C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m in Sources */ = {isa = PBXBuildFile; fileRef = C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */; }; C3694DBA23EA1096006625D7 /* FLEXTabsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3694DB823EA1096006625D7 /* FLEXTabsViewController.h */; }; @@ -572,6 +574,8 @@ C3531BA923E88FAC00A184AD /* FLEXTabList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTabList.m; sourceTree = ""; }; C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXHTTPTransactionDetailController.h; sourceTree = ""; }; C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXHTTPTransactionDetailController.m; sourceTree = ""; }; + C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMITMDataSource.h; sourceTree = ""; }; + C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMITMDataSource.m; sourceTree = ""; }; C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMapTable+FLEX_Subscripting.h"; sourceTree = ""; }; C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMapTable+FLEX_Subscripting.m"; sourceTree = ""; }; C3694DB823EA1096006625D7 /* FLEXTabsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTabsViewController.h; sourceTree = ""; }; @@ -980,6 +984,8 @@ 3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */, C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */, C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */, + C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */, + C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */, 3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */, 3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */, 2EF6B04C1D494BE50006BDA5 /* FLEXNetworkCurlLogger.h */, @@ -1612,6 +1618,7 @@ C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */, C34D4EB423A2AF2A00C1F903 /* FLEXColorPreviewSection.h in Headers */, C383C3BE23B6B398007A321B /* UITextField+Range.h in Headers */, + C35DAD8E2709143000AA95E6 /* FLEXMITMDataSource.h in Headers */, 224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */, C313853F23F5C1A10046E63C /* FLEXViewControllersViewController.h in Headers */, C386D6ED24199EC600699085 /* FLEX-Runtime.h in Headers */, @@ -1896,6 +1903,7 @@ 3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */, C3474C4123DA496400466532 /* FLEXKeyValueTableViewCell.m in Sources */, 779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */, + C35DAD8F2709143000AA95E6 /* FLEXMITMDataSource.m in Sources */, C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */, C36FBFD5230F3B98008D95D5 /* FLEXMethodBase.m in Sources */, 3A4C95001B5B21410088C3F2 /* FLEXArgumentInputSwitchView.m in Sources */,