Compare commits

...

58 Commits

Author SHA1 Message Date
Tanner Bennett 9db1b544df Use Reflex instead of FLEX in SPM project 2021-11-18 18:45:01 -06:00
Tanner Bennett b58629b8ea Initialize FLEXObjectExplorer.reflexAvailable 2021-11-18 18:45:01 -06:00
Tanner Bennett adeb22af15 Use Reflex and FLEXMirror 2021-11-18 18:45:01 -06:00
Tanner Bennett 6ab183e109 Add FLEXSwiftInternal to check if object from Swift 2021-11-18 18:45:01 -06:00
Tanner Bennett 034f401cb4 Modernize FLEXMirror
Differentiate between class and instance props/methods
2021-11-18 18:45:01 -06:00
Tanner Bennett da1958def5 Add (unused) libflex modulemap file
For my own use case, I dynamically link FLEX, so I need to build Reflex using libflex.tbd as well as this modulemap file after copying it into the Headers folder.
2021-11-18 18:45:01 -06:00
Tanner Bennett ffa658c49b Make a bunch of stuff private that should be private 2021-11-18 18:42:04 -06:00
Tanner Bennett 6066de480f FLEXMacros should be public 2021-11-18 18:20:59 -06:00
Tanner Bennett 0fd7dfa002 SPM: clear Headers folder when running script 2021-11-18 17:50:44 -06:00
Tanner Bennett 60403e614d Ignore Classes/Headers in VS Code search 2021-11-18 17:49:03 -06:00
skytoup fa7db997bd Fix realm database viewer crash 2021-11-16 17:34:11 -08:00
Tanner Bennett d15e72c681 Separate example projects for SPM and Cocoapods 2021-11-14 01:01:24 -06:00
Rafael Fernández 4f1ff7784d Add SPM usage in README, close #482 2021-11-14 00:56:24 -06:00
Tanner Bennett 8129a034e3 Clean up Package.swift 2021-11-14 00:56:24 -06:00
Tanner Bennett 838db6954b Add cxx standard setting 2021-11-14 00:56:24 -06:00
Tanner Bennett 5b3d3af99c Update package.swift 2021-11-14 00:56:24 -06:00
Tanner Bennett 2f9f266493 Update import format 2021-11-14 00:56:24 -06:00
Tanner Bennett 9d94979d08 Exclude LICENSE.md 2021-11-14 00:56:24 -06:00
Tanner Bennett 19b83f4404 headerSearchPath 2021-11-14 00:56:24 -06:00
Tanner Bennett 2b13378d98 Remove unsafe flags 😑 2021-11-14 00:56:24 -06:00
Tanner Bennett 4ae9d41104 Add header references 2021-11-14 00:56:24 -06:00
Tanner Bennett 1490170eb4 Add Package.swift, add script to generate headers
Update script
2021-11-14 00:56:24 -06:00
Tanner Bennett cd695ed106 Bump version 2021-11-14 00:53:41 -06:00
Tanner Bennett 69c1719159 Remove redundant/unused methods in arginputfactory 2021-11-14 00:39:44 -06:00
Tanner Bennett 4019518bf5 Fix #564
entry->log_message.format appears to be garbage on iOS 15, and it doesn't look like it is ever really used in practice anyway, as far as I can tell. Thanks @matrush for pointing this out!
2021-11-14 00:39:44 -06:00
Matt Robinson 3fd8e7c77d Fix CLANG_WARN_STRICT_PROTOTYPES/-Wstrict-prototypes issues
This allows `FLEX` to be linked statically to a binary that has `-Wstrict-prototypes` enabled (for example in CocoaPods without `use_frameworks!`.

Changes:
- Apply `void` to all the empty function/block declarations that don't take arguments.
- Apply `SEL`/`UIImage *` to the others that actually take arguments.
- Remove `CLANG_WARN_STRICT_PROTOTYPES = NO` since the default is enabled.
2021-11-14 00:39:44 -06:00
Tanner Bennett 99c3bcb8c5 Remove init exceptions from flex meta classes 2021-11-14 00:39:44 -06:00
Tanner Bennett b587e96e70 Make FLEXMirror protocol 2021-11-14 00:39:43 -06:00
Tanner Bennett ef8f0a303e Add missing nullability to metadata types 2021-11-14 00:36:52 -06:00
Tanner Bennett cfb1e4caab Add NS_SWIFT_NAME to some enums; does not work
Left it commented out. The enums just disappear for some reason?
2021-11-04 19:11:18 -05:00
daniel 2411c331cd Added a dynamic background color to the WebViewControllers response (Depending on being in Light/Dark mode. See this gist for an example: https://gist.githubusercontent.com/dlevi309/e4d48556836b26125e95cbd82d32a9de/raw/d01112d6e0734db11fa74e2fdb0a1330ddcf82a2/dynamicPage.html) 2021-10-27 13:45:33 -05:00
Tanner Bennett fd4b38f46d Fix bug in tab-close logic 2021-10-26 14:43:20 -05:00
Chaoshuai Lu 269e31894c Remove NSParameterAssert check in closeTab method 2021-10-25 18:24:50 -07:00
Tanner Bennett 2f2da50aed Fix potential crash in FLEXPointerIsReadable
vm_read allocates heap memory, this was intended to be an allocation-free check. Some apps may run out of memory in this code path.
2021-10-20 15:21:26 -05:00
Tanner Bennett d87779212c Add data property to FIRDocumentSnapshot at runtime 2021-10-17 17:33:23 -05:00
Tanner Bennett 5db6a12c6e project.pbxcproj formatting? 2021-10-17 17:33:02 -05:00
Tanner Bennett 6d0f776102 Add a FLEXHeapEnumerator test
Ensure we can spoof an object that is found by FLEXHeapEnumerator and also later prove it isn't a real object
2021-10-17 17:32:09 -05:00
Tanner Bennett 6c83ddc2c7 Refactor FLEXHeapEnumerator and FLEXObjectRef
Move logic that was in FLEXLiveObjectsController into FLEXHeapEnumerator. Also adjust FLEXObjectRef initializers to reflect the type of reference you want to hold to the object. FLEXObjectRef now supports unsafe_unretained and retained references.
2021-10-17 17:31:12 -05:00
Tanner Bennett b510d24e13 Improve FLEXPointerIsValidObjcObject
Compare allocation size to expected instance size
2021-10-17 17:28:59 -05:00
Tanner Bennett 6cdf2e61dc Fix websocket activity not being cleared 2021-10-14 00:28:49 -05:00
Tanner Bennett 9b0ed83ff5 Remove old commented out code 2021-10-13 23:41:13 -05:00
Tanner Bennett dbe1b93f48 Add network observer flag to our NSUserDefaults category 2021-10-13 18:45:52 -05:00
Tanner Bennett 06444f1576 Clean up some comments 2021-10-11 20:21:37 -05:00
Tanner Bennett 9bbf1d0d48 Refactor FLEXNetworkTransaction
Add FLEXURLTransaction to differentiate between NSURL-API-related transactions and other transactions, such as lower level protocols or Firebase ;)
2021-10-11 20:11:04 -05:00
Tanner Bennett 0562f15cd0 Fix incoming WS messages not showing correct details 2021-10-11 20:09:58 -05:00
Tanner Bennett eb63c91481 Send example WS message 2021-10-11 20:09:40 -05:00
Tanner Bennett f916174070 Bump version and update URLs 2021-10-06 19:11:01 -05:00
Tanner Bennett 60e23e126e Fix crash for unsupported property type encodings 2021-10-06 19:11:01 -05:00
Tanner Bennett afeff1b562 Add FLEXMITMDataSource, refactor MITMVC 2021-10-06 18:18:54 -05:00
Tanner Bennett 5acb33005b Refactor HTTP transaction detail controller 2021-10-06 18:18:54 -05:00
Tanner Bennett 3446eff353 Hook and record websocket methods 2021-10-06 18:18:54 -05:00
Tanner Bennett ad1f1f579e Move network transaction model logic into model 2021-10-06 18:18:54 -05:00
Tanner Bennett e03b5f7e5d Send example websocket traffic 2021-10-06 18:18:54 -05:00
Tanner Bennett d010c82dd0 Add OSCache for network caching 2021-10-06 18:18:54 -05:00
Tanner Bennett 652d03c39a Add -[NSArray flex_firstWhere:] 2021-10-06 18:18:54 -05:00
Tanner Bennett 1d39669a52 Add button to dlopen a library in Runtime Browser 2021-10-06 18:18:54 -05:00
Iulian Onofrei d558ca6852 Fix code style 2021-09-22 11:49:00 -07:00
Duraid Abdul ad32ca0f05 Update project.pbxproj 2021-08-31 12:04:22 -07:00
159 changed files with 3213 additions and 763 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"search.exclude": {
"Classes/Headers": true
}
}
@@ -74,7 +74,7 @@
#pragma mark - Search
- (void)updateSearchResults:(NSString *)newText {
NSArray *(^filter)() = ^NSArray *{
NSArray *(^filter)(void) = ^NSArray *{
self.filterText = newText;
// Sections will adjust data based on this property
@@ -96,7 +96,10 @@
- (void)dismissAnimated {
// Tabs are only closed if the done button is pressed; this
// allows you to leave a tab open by dragging down to dismiss
[FLEXTabList.sharedList closeTab:self];
if ([self.presentingViewController isKindOfClass:[FLEXExplorerViewController class]]) {
[FLEXTabList.sharedList closeTab:self];
}
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
@@ -559,7 +559,7 @@ static UITextField *kDummyTextField = nil;
[self.debounceTimer invalidate];
NSString *text = searchController.searchBar.text;
void (^updateSearchResults)() = ^{
void (^updateSearchResults)(void) = ^{
if (self.searchResultsUpdater) {
[self.searchResultsUpdater updateSearchResults:text];
} else {
@@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXDefaultEditorViewController : FLEXVariableEditorViewController
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit;
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit;
+ (BOOL)canEditDefaultWithValue:(nullable id)currentValue;
@@ -21,7 +21,7 @@
@implementation FLEXDefaultEditorViewController
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit {
FLEXDefaultEditorViewController *editor = [self target:defaults data:key commitHandler:onCommit];
editor.title = @"Edit Default";
return editor;
@@ -15,9 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXFieldEditorViewController : FLEXVariableEditorViewController
/// @return nil if the property is readonly or if the type is unsupported
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit;
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit;
/// @return nil if the ivar type is unsupported
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit;
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit;
/// Subclasses can change the button title via the \c title property
@property (nonatomic, readonly) UIBarButtonItem *getterButton;
@@ -30,19 +30,14 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit {
id value = [property getValue:target];
if (![self canEditProperty:property onObject:target currentValue:value]) {
return nil;
}
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:property commitHandler:onCommit];
editor.title = [@"Property: " stringByAppendingString:property.name];
editor.property = property;
return editor;
}
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit];
editor.title = [@"Ivar: " stringByAppendingString:ivar.name];
editor.ivar = ivar;
@@ -151,14 +146,4 @@
}
}
+ (BOOL)canEditProperty:(FLEXProperty *)property onObject:(id)object currentValue:(id)value {
const FLEXTypeEncoding *typeEncoding = property.attributes.typeEncoding.UTF8String;
BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
return canEditType && [object respondsToSelector:property.likelySetter];
}
+ (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value {
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
}
@end
@@ -21,17 +21,17 @@ NS_ASSUME_NONNULL_BEGIN
@protected
id _target;
_Nullable id _data;
void (^_Nullable _commitHandler)();
void (^_Nullable _commitHandler)(void);
}
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
@property (nonatomic, readonly) id target;
@@ -25,11 +25,11 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
return [[self alloc] initWithTarget:target data:data commitHandler:onCommit];
}
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
self = [super init];
if (self) {
_target = target;
@@ -460,17 +460,12 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
initWithTarget:self action:@selector(handleToolbarDetailsTapGesture:)
];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:self.detailsTapGR];
// Swipe gestures for selecting deeper / higher views at a point
UIPanGestureRecognizer *leftSwipe = [[UIPanGestureRecognizer alloc]
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
];
// UIPanGestureRecognizer *rightSwipe = [[UIPanGestureRecognizer alloc]
// initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
// ];
// leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
// rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:leftSwipe];
// [toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:panGesture];
// Long press gesture to present tabs manager
[toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
@@ -72,7 +72,7 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)showRevertOrDismissAlert:(void(^)())revertBlock {
- (void)showRevertOrDismissAlert:(void(^)(void))revertBlock {
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
[self reloadData];
[self.tableView reloadData];
+3 -3
View File
@@ -80,10 +80,10 @@
- (void)closeTab:(UINavigationController *)tab {
NSParameterAssert(tab);
NSParameterAssert([self.openTabs containsObject:tab]);
NSInteger idx = [self.openTabs indexOfObject:tab];
[self closeTabAtIndex:idx];
if (idx != NSNotFound) {
[self closeTabAtIndex:idx];
}
}
- (void)closeTabAtIndex:(NSInteger)idx {
+11 -11
View File
@@ -8,15 +8,15 @@
#import <FLEX/UIBarButtonItem+FLEX.h>
#import <FLEX/CALayer+FLEX.h>
#import <FLEX/UIFont+FLEX.h>
#import <FLEX/UIGestureRecognizer+Blocks.h>
#import <FLEX/UIPasteboard+FLEX.h>
#import <FLEX/UIMenu+FLEX.h>
#import <FLEX/UITextField+Range.h>
#import <UIBarButtonItem+FLEX.h>
#import <CALayer+FLEX.h>
#import <UIFont+FLEX.h>
#import <UIGestureRecognizer+Blocks.h>
#import <UIPasteboard+FLEX.h>
#import <UIMenu+FLEX.h>
#import <UITextField+Range.h>
#import <FLEX/NSObject+FLEX_Reflection.h>
#import <FLEX/NSArray+FLEX.h>
#import <FLEX/NSUserDefaults+FLEX.h>
#import <FLEX/NSTimer+FLEX.h>
#import <NSObject+FLEX_Reflection.h>
#import <NSArray+FLEX.h>
#import <NSUserDefaults+FLEX.h>
#import <NSTimer+FLEX.h>
+11 -12
View File
@@ -6,18 +6,17 @@
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXFilteringTableViewController.h>
#import <FLEX/FLEXNavigationController.h>
#import <FLEX/FLEXTableViewController.h>
#import <FLEX/FLEXTableView.h>
#import <FLEXFilteringTableViewController.h>
#import <FLEXNavigationController.h>
#import <FLEXTableViewController.h>
#import <FLEXTableView.h>
#import <FLEX/FLEXSingleRowSection.h>
#import <FLEX/FLEXTableViewSection.h>
#import <FLEXSingleRowSection.h>
#import <FLEXTableViewSection.h>
#import <FLEX/FLEXCodeFontCell.h>
#import <FLEX/FLEXSubtitleTableViewCell.h>
#import <FLEX/FLEXTableViewCell.h>
#import <FLEX/FLEXMultilineTableViewCell.h>
#import <FLEX/FLEXKeyValueTableViewCell.h>
#import <FLEXCodeFontCell.h>
#import <FLEXSubtitleTableViewCell.h>
#import <FLEXTableViewCell.h>
#import <FLEXMultilineTableViewCell.h>
#import <FLEXKeyValueTableViewCell.h>
#import <FLEX/FLEXScopeCarousel.h>
+11 -19
View File
@@ -6,25 +6,17 @@
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXObjectExplorerFactory.h>
#import <FLEX/FLEXObjectExplorerViewController.h>
#import <FLEXObjectExplorerFactory.h>
#import <FLEXObjectExplorerViewController.h>
#import <FLEX/FLEXObjectExplorer.h>
#import <FLEXObjectExplorer.h>
#import <FLEX/FLEXShortcut.h>
#import <FLEX/FLEXShortcutsFactory+Defaults.h>
#import <FLEX/FLEXShortcutsSection.h>
#import <FLEX/FLEXBlockShortcuts.h>
#import <FLEX/FLEXBundleShortcuts.h>
#import <FLEX/FLEXClassShortcuts.h>
#import <FLEX/FLEXImageShortcuts.h>
#import <FLEX/FLEXLayerShortcuts.h>
#import <FLEX/FLEXViewControllerShortcuts.h>
#import <FLEX/FLEXViewShortcuts.h>
#import <FLEXShortcut.h>
#import <FLEXShortcutsSection.h>
#import <FLEX/FLEXCollectionContentSection.h>
#import <FLEX/FLEXColorPreviewSection.h>
#import <FLEX/FLEXDefaultsContentSection.h>
#import <FLEX/FLEXMetadataSection.h>
#import <FLEX/FLEXMutableListSection.h>
#import <FLEX/FLEXObjectInfoSection.h>
#import <FLEXCollectionContentSection.h>
#import <FLEXColorPreviewSection.h>
#import <FLEXDefaultsContentSection.h>
#import <FLEXMetadataSection.h>
#import <FLEXMutableListSection.h>
#import <FLEXObjectInfoSection.h>
+16 -15
View File
@@ -6,20 +6,21 @@
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXObjcInternal.h>
#import <FLEX/FLEXRuntimeSafety.h>
#import <FLEX/FLEXBlockDescription.h>
#import <FLEX/FLEXTypeEncodingParser.h>
#import <FLEXObjcInternal.h>
#import <FLEXSwiftInternal.h>
#import <FLEXRuntimeSafety.h>
#import <FLEXBlockDescription.h>
#import <FLEXTypeEncodingParser.h>
#import <FLEX/FLEXMirror.h>
#import <FLEX/FLEXProtocol.h>
#import <FLEX/FLEXProperty.h>
#import <FLEX/FLEXIvar.h>
#import <FLEX/FLEXMethodBase.h>
#import <FLEX/FLEXMethod.h>
#import <FLEX/FLEXPropertyAttributes.h>
#import <FLEX/FLEXRuntime+Compare.h>
#import <FLEX/FLEXRuntime+UIKitHelpers.h>
#import <FLEXMirror.h>
#import <FLEXProtocol.h>
#import <FLEXProperty.h>
#import <FLEXIvar.h>
#import <FLEXMethodBase.h>
#import <FLEXMethod.h>
#import <FLEXPropertyAttributes.h>
#import <FLEXRuntime+Compare.h>
#import <FLEXRuntime+UIKitHelpers.h>
#import <FLEX/FLEXProtocolBuilder.h>
#import <FLEX/FLEXClassBuilder.h>
#import <FLEXProtocolBuilder.h>
#import <FLEXClassBuilder.h>
+13 -12
View File
@@ -7,18 +7,19 @@
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <FLEX/FLEXManager.h>
#import <FLEX/FLEXManager+Extensibility.h>
#import <FLEX/FLEXManager+Networking.h>
#import <FLEXManager.h>
#import <FLEXManager+Extensibility.h>
#import <FLEXManager+Networking.h>
#import <FLEX/FLEXExplorerToolbar.h>
#import <FLEX/FLEXExplorerToolbarItem.h>
#import <FLEX/FLEXGlobalsEntry.h>
#import <FLEXExplorerToolbar.h>
#import <FLEXExplorerToolbarItem.h>
#import <FLEXGlobalsEntry.h>
#import <FLEX/FLEX-Core.h>
#import <FLEX/FLEX-Runtime.h>
#import <FLEX/FLEX-Categories.h>
#import <FLEX/FLEX-ObjectExploring.h>
#import <FLEX-Core.h>
#import <FLEX-Runtime.h>
#import <FLEX-Categories.h>
#import <FLEX-ObjectExploring.h>
#import <FLEX/FLEXAlert.h>
#import <FLEX/FLEXResources.h>
#import <FLEXMacros.h>
#import <FLEXAlert.h>
#import <FLEXResources.h>
@@ -71,7 +71,10 @@
self.tables.selectionHandler = ^(FLEXTableListViewController *host, NSString *tableName) {
NSArray *rows = [host.dbm queryAllDataInTable:tableName];
NSArray *columns = [host.dbm queryAllColumnsOfTable:tableName];
NSArray *rowIDs = [host.dbm queryRowIDsInTable:tableName];
NSArray *rowIDs = nil;
if ([host.dbm respondsToSelector:@selector(queryRowIDsInTable:)]) {
rowIDs = [host.dbm queryRowIDsInTable:tableName];
}
UIViewController *resultsScreen = [FLEXTableContentViewController
columns:columns rows:rows rowIDs:rowIDs tableName:tableName database:host.dbm
];
@@ -226,7 +226,10 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *className = self.filteredClassNames[indexPath.row];
UIViewController *instances = [FLEXObjectListViewController instancesOfClassWithName:className];
UIViewController *instances = [FLEXObjectListViewController
instancesOfClassWithName:className
retained:YES
];
[self.navigationController pushViewController:instances animated:YES];
}
@@ -12,8 +12,8 @@
/// This will either return a list of the instances, or take you straight
/// to the explorer itself if there is only one instance.
+ (UIViewController *)instancesOfClassWithName:(NSString *)className;
+ (UIViewController *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain;
+ (instancetype)subclassesOfClassWithName:(NSString *)className;
+ (instancetype)objectsWithReferencesToObject:(id)object;
+ (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain;
@end
@@ -141,83 +141,36 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
return self;
}
+ (UIViewController *)instancesOfClassWithName:(NSString *)className {
const char *classNameCString = className.UTF8String;
NSMutableArray *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
// Note: objects of certain classes crash when retain is called.
// It is up to the user to avoid tapping into instance lists for these classes.
// Ex. OS_dispatch_queue_specific_queue
// In the future, we could provide some kind of warning for classes that are known to be problematic.
if (malloc_size((__bridge const void *)(object)) > 0) {
[instances addObject:object];
}
}
}];
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
+ (UIViewController *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain {
NSArray<FLEXObjectRef *> *references = [FLEXHeapEnumerator
instancesOfClassWithName:className retained:retain
];
if (references.count == 1) {
return [FLEXObjectExplorerFactory
explorerViewControllerForObject:references.firstObject.object
explorerViewControllerForObject:references.firstObject.object
];
}
FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
controller.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)instances.count];
controller.title = [NSString stringWithFormat:@"%@ (%@)", className, @(references.count)];
return controller;
}
+ (instancetype)subclassesOfClassWithName:(NSString *)className {
NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
NSArray<FLEXObjectRef *> *references = [FLEXHeapEnumerator subclassesOfClassWithName:className];
FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%lu)",
className, (unsigned long)classes.count
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%@)",
className, @(references.count)
];
return controller;
}
+ (instancetype)objectsWithReferencesToObject:(id)object {
static Class SwiftObjectClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SwiftObjectClass = NSClassFromString(@"SwiftObject");
if (!SwiftObjectClass) {
SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
}
});
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
// Once we find a match, record it and move on to the next object. There's no reason to find multiple matches within the same object.
Class tryClass = actualClass;
while (tryClass) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
Ivar ivar = ivars[ivarIndex];
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
if (typeEncoding.flex_typeIsObjectOrClass) {
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
[instances addObject:[FLEXObjectRef referencing:tryObject ivar:ivarName]];
return;
}
}
}
free(ivars);
tryClass = class_getSuperclass(tryClass);
}
}];
+ (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
NSArray<FLEXObjectRef *> *instances = [FLEXHeapEnumerator
objectsWithReferencesToObject:object retained:retain
];
FLEXObjectListViewController *viewController = [[self alloc]
initWithReferences:instances
+18 -4
View File
@@ -10,10 +10,19 @@
@interface FLEXObjectRef : NSObject
+ (instancetype)referencing:(id)object;
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName;
/// Reference an object without affecting its lifespan or or emitting reference-counting operations.
+ (instancetype)unretained:(__unsafe_unretained id)object;
+ (instancetype)unretained:(__unsafe_unretained id)object ivar:(NSString *)ivarName;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects;
/// Reference an object and control its lifespan.
+ (instancetype)retained:(id)object;
+ (instancetype)retained:(id)object ivar:(NSString *)ivarName;
/// Reference an object and conditionally choose to retain it or not.
+ (instancetype)referencing:(__unsafe_unretained id)object retained:(BOOL)retain;
+ (instancetype)referencing:(__unsafe_unretained id)object ivar:(NSString *)ivarName retained:(BOOL)retain;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects retained:(BOOL)retain;
/// Classes do not have a summary, and the reference is just the class name.
+ (NSArray<FLEXObjectRef *> *)referencingClasses:(NSArray<Class> *)classes;
@@ -22,6 +31,11 @@
/// For instances, this is the result of -[FLEXRuntimeUtility summaryForObject:]
/// For classes, there is no summary.
@property (nonatomic, readonly) NSString *summary;
@property (nonatomic, readonly) id object;
@property (nonatomic, readonly, unsafe_unretained) id object;
/// Retains the referenced object if it is not already retained
- (void)retainObject;
/// Releases the referenced object if it is already retained
- (void)releaseObject;
@end
+47 -11
View File
@@ -10,42 +10,68 @@
#import "FLEXRuntimeUtility.h"
#import "NSArray+FLEX.h"
@interface FLEXObjectRef ()
@interface FLEXObjectRef () {
/// Used to retain the object if desired
id _retainer;
}
@property (nonatomic, readonly) BOOL wantsSummary;
@end
@implementation FLEXObjectRef
@synthesize summary = _summary;
+ (instancetype)referencing:(id)object {
return [self referencing:object showSummary:YES];
+ (instancetype)unretained:(__unsafe_unretained id)object {
return [self referencing:object showSummary:YES retained:NO];
}
+ (instancetype)referencing:(id)object showSummary:(BOOL)showSummary {
return [[self alloc] initWithObject:object ivarName:nil showSummary:showSummary];
+ (instancetype)unretained:(__unsafe_unretained id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES retained:NO];
}
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES];
+ (instancetype)retained:(id)object {
return [self referencing:object showSummary:YES retained:YES];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects {
+ (instancetype)retained:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES retained:YES];
}
+ (instancetype)referencing:(__unsafe_unretained id)object retained:(BOOL)retain {
return retain ? [self retained:object] : [self unretained:object];
}
+ (instancetype)referencing:(__unsafe_unretained id)object ivar:(NSString *)ivarName retained:(BOOL)retain {
return retain ? [self retained:object ivar:ivarName] : [self unretained:object ivar:ivarName];
}
+ (instancetype)referencing:(__unsafe_unretained id)object showSummary:(BOOL)showSummary retained:(BOOL)retain {
return [[self alloc] initWithObject:object ivarName:nil showSummary:showSummary retained:retain];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects retained:(BOOL)retain {
return [objects flex_mapped:^id(id obj, NSUInteger idx) {
return [self referencing:obj showSummary:YES];
return [self referencing:obj showSummary:YES retained:retain];
}];
}
+ (NSArray<FLEXObjectRef *> *)referencingClasses:(NSArray<Class> *)classes {
return [classes flex_mapped:^id(id obj, NSUInteger idx) {
return [self referencing:obj showSummary:NO];
return [self referencing:obj showSummary:NO retained:NO];
}];
}
- (id)initWithObject:(id)object ivarName:(NSString *)ivar showSummary:(BOOL)showSummary {
- (id)initWithObject:(__unsafe_unretained id)object
ivarName:(NSString *)ivar
showSummary:(BOOL)showSummary
retained:(BOOL)retain {
self = [super init];
if (self) {
_object = object;
_wantsSummary = showSummary;
if (retain) {
_retainer = object;
}
NSString *class = [FLEXRuntimeUtility safeClassNameForObject:object];
if (ivar) {
@@ -73,4 +99,14 @@
}
}
- (void)retainObject {
if (!_retainer) {
_retainer = _object;
}
}
- (void)releaseObject {
_retainer = nil;
}
@end
@@ -38,7 +38,7 @@
self = [self initWithNibName:nil bundle:nil];
if (self) {
self.originalText = text;
NSString *htmlString = [NSString stringWithFormat:@"<head><meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
NSString *htmlString = [NSString stringWithFormat:@"<head><style>:root{ color-scheme: light dark; }</style><meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
[self.webView loadHTMLString:htmlString baseURL:nil];
}
return self;
@@ -195,7 +195,7 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
RTLD_LAZY
);
void (*WebKitInitialize)() = dlsym(handle, "WebKitInitialize");
void (*WebKitInitialize)(void) = dlsym(handle, "WebKitInitialize");
if (WebKitInitialize) {
NSAssert(NSThread.isMainThread,
@"WebKitInitialize can only be called on the main thread"
@@ -10,10 +10,12 @@
#import "FLEXKeyPathSearchController.h"
#import "FLEXRuntimeBrowserToolbar.h"
#import "UIGestureRecognizer+Blocks.h"
#import "UIBarButtonItem+FLEX.h"
#import "FLEXTableView.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXAlert.h"
#import "FLEXRuntimeClient.h"
#import <dlfcn.h>
@interface FLEXObjcRuntimeViewController () <FLEXKeyPathSearchControllerDelegate>
@@ -43,6 +45,8 @@
]
];
[self addToolbarItems:@[FLEXBarButtonItem(@"dlopen()", self, @selector(dlopenPressed:))]];
// Search bar stuff, must be first because this creates self.searchController
self.showsSearchBar = YES;
self.showSearchBarInitially = YES;
@@ -74,6 +78,63 @@
}
#pragma mark dlopen
/// Prompt user for dlopen shortcuts to choose from
- (void)dlopenPressed:(id)sender {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Dynamically Open Library");
make.message(@"Invoke dlopen() with the given path. Choose an option below.");
make.button(@"System Framework").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:@"/System/Library/Frameworks/%@.framework/%@"];
});
make.button(@"System Private Framework").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:@"/System/Library/PrivateFrameworks/%@.framework/%@"];
});
make.button(@"Arbitrary Binary").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:nil];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
}
/// Prompt user for input and dlopen
- (void)dlopenWithFormat:(NSString *)format {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Dynamically Open Library");
if (format) {
make.message(@"Pass in a framework name, such as CarKit or FrontBoard.");
} else {
make.message(@"Pass in an absolute path to a binary.");
}
make.textField(format ? @"ARKit" : @"/System/Library/Frameworks/ARKit.framework/ARKit");
make.button(@"Cancel").cancelStyle();
make.button(@"Open").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
NSString *path = strings[0];
if (path.length < 2) {
[self dlopenInvalidPath];
} else if (format) {
path = [NSString stringWithFormat:format, path, path];
}
dlopen(path.UTF8String, RTLD_NOW);
});
} showFrom:self];
}
- (void)dlopenInvalidPath {
[FLEXAlert makeAlert:^(FLEXAlert * _Nonnull make) {
make.title(@"Path or Name Too Short");
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
}
#pragma mark Delegate stuff
- (void)didSelectImagePath:(NSString *)path shortName:(NSString *)shortName {
@@ -168,11 +168,10 @@ static uint8_t (*OSLogGetType)(void *);
NSDate *date = [NSDate dateWithTimeIntervalSince1970:log_message->tv_gmt.tv_sec];
// Get log message text
const char *messageText = OSLogCopyFormattedMessage(log_message);
// https://github.com/limneos/oslog/issues/1
if (entry->log_message.format && !(strcmp(entry->log_message.format, messageText))) {
messageText = (char *)entry->log_message.format;
}
// https://github.com/FLEXTool/FLEX/issues/564
const char *messageText = OSLogCopyFormattedMessage(log_message) ?: "";
// move messageText from stack to heap
NSString *msg = [NSString stringWithUTF8String:messageText];
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/CALayer+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/FLEX-Categories.h
+1
View File
@@ -0,0 +1 @@
../../Classes/FLEX-Core.h
+1
View File
@@ -0,0 +1 @@
../../Classes/FLEX-ObjectExploring.h
+1
View File
@@ -0,0 +1 @@
../../Classes/FLEX-Runtime.h
+1
View File
@@ -0,0 +1 @@
../../Classes/FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/FLEXAlert.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXBlockDescription.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXClassBuilder.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Views/Cells/FLEXCodeFontCell.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/FLEXCollectionContentSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/FLEXColorPreviewSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/FLEXDefaultsContentSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Toolbar/FLEXExplorerToolbar.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Toolbar/FLEXExplorerToolbarItem.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Controllers/FLEXFilteringTableViewController.h
+1
View File
@@ -0,0 +1 @@
../../Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXIvar.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Views/Cells/FLEXKeyValueTableViewCell.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/FLEXMacros.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Manager/FLEXManager+Extensibility.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Manager/FLEXManager+Networking.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Manager/FLEXManager.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/FLEXMetadataSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXMethod.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXMethodBase.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXMirror.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Views/Cells/FLEXMultilineTableViewCell.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/FLEXMutableListSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Controllers/FLEXNavigationController.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/FLEXObjcInternal.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/FLEXObjectExplorer.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/FLEXObjectExplorerFactory.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/FLEXObjectExplorerViewController.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/FLEXObjectInfoSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXProperty.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXPropertyAttributes.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXProtocol.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/Reflection/FLEXProtocolBuilder.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/FLEXResources.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/FLEXRuntime+Compare.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/FLEXRuntimeConstants.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/FLEXRuntimeSafety.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.h
+1
View File
@@ -0,0 +1 @@
../../Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/FLEXSingleRowSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Views/Cells/FLEXSubtitleTableViewCell.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/FLEXSwiftInternal.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Views/FLEXTableView.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Views/Cells/FLEXTableViewCell.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/Controllers/FLEXTableViewController.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Core/FLEXTableViewSection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Runtime/Objc/FLEXTypeEncodingParser.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/NSArray+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/NSObject+FLEX_Reflection.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/NSTimer+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/NSUserDefaults+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/UIBarButtonItem+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/UIFont+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/UIGestureRecognizer+Blocks.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/UIMenu+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/UIPasteboard+FLEX.h
+1
View File
@@ -0,0 +1 @@
../../Classes/Utility/Categories/UITextField+Range.h
+8
View File
@@ -0,0 +1,8 @@
module FLEX {
umbrella header "FLEX.h"
link "flex"
export *
module * { export * }
}
@@ -0,0 +1,17 @@
//
// FLEXHTTPTransactionDetailController.h
// Flipboard
//
// Created by Ryan Olson on 2/10/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FLEXHTTPTransaction;
@interface FLEXHTTPTransactionDetailController : UITableViewController
+ (instancetype)withTransaction:(FLEXHTTPTransaction *)transaction;
@end
@@ -7,7 +7,7 @@
//
#import "FLEXColor.h"
#import "FLEXNetworkTransactionDetailController.h"
#import "FLEXHTTPTransactionDetailController.h"
#import "FLEXNetworkCurlLogger.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkTransaction.h"
@@ -22,64 +22,63 @@
typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@interface FLEXNetworkDetailRow : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *detailText;
@property (nonatomic, copy) FLEXNetworkDetailRowSelectionFuture selectionFuture;
@end
@implementation FLEXNetworkDetailRow
@end
@interface FLEXNetworkDetailSection : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
@end
@implementation FLEXNetworkDetailSection
@end
@interface FLEXNetworkTransactionDetailController ()
@interface FLEXHTTPTransactionDetailController ()
@property (nonatomic, readonly) FLEXHTTPTransaction *transaction;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
@end
@implementation FLEXNetworkTransactionDetailController
@implementation FLEXHTTPTransactionDetailController
+ (instancetype)withTransaction:(FLEXHTTPTransaction *)transaction {
FLEXHTTPTransactionDetailController *controller = [self new];
controller.transaction = transaction;
return controller;
}
- (instancetype)initWithStyle:(UITableViewStyle)style {
// Force grouped style
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(handleTransactionUpdatedNotification:)
name:kFLEXNetworkRecorderTransactionUpdatedNotification
object:nil
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
flex_itemWithTitle:@"Copy curl"
target:self
action:@selector(copyButtonPressed:)
]
];
}
return self;
return [super initWithStyle:UITableViewStyleGrouped];
}
- (void)viewDidLoad {
[super viewDidLoad];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(handleTransactionUpdatedNotification:)
name:kFLEXNetworkRecorderTransactionUpdatedNotification
object:nil
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
flex_itemWithTitle:@"Copy curl"
target:self
action:@selector(copyButtonPressed:)
]
];
[self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineCell];
}
- (void)setTransaction:(FLEXNetworkTransaction *)transaction {
- (void)setTransaction:(FLEXHTTPTransaction *)transaction {
if (![_transaction isEqual:transaction]) {
_transaction = transaction;
self.title = [transaction.request.URL lastPathComponent];
@@ -189,6 +188,12 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
];
}
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [NSArray flex_forEachUpTo:self.sections.count map:^id(NSUInteger i) {
return @"";
}];
}
- (FLEXNetworkDetailRow *)rowModelAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkDetailSection *sectionModel = self.sections[indexPath.section];
return sectionModel.rows[indexPath.row];
@@ -253,7 +258,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
#pragma mark - Table Data Generation
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXHTTPTransaction *)transaction {
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
FLEXNetworkDetailRow *requestURLRow = [FLEXNetworkDetailRow new];
@@ -407,7 +412,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return generalSection;
}
+ (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *requestHeadersSection = [FLEXNetworkDetailSection new];
requestHeadersSection.title = @"Request Headers";
requestHeadersSection.rows = [self networkDetailRowsFromDictionary:transaction.request.allHTTPHeaderFields];
@@ -415,7 +420,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return requestHeadersSection;
}
+ (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *postBodySection = [FLEXNetworkDetailSection new];
postBodySection.title = @"Request Body Parameters";
if (transaction.cachedRequestBody.length > 0) {
@@ -429,7 +434,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return postBodySection;
}
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
NSArray<NSURLQueryItem *> *queries = [FLEXUtility itemsFromQueryString:transaction.request.URL.query];
FLEXNetworkDetailSection *querySection = [FLEXNetworkDetailSection new];
querySection.title = @"Query Parameters";
@@ -438,7 +443,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return querySection;
}
+ (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *responseHeadersSection = [FLEXNetworkDetailSection new];
responseHeadersSection.title = @"Response Headers";
if ([transaction.response isKindOfClass:[NSHTTPURLResponse class]]) {
@@ -516,7 +521,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return detailViewController;
}
+ (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction {
+ (NSData *)postBodyDataForTransaction:(FLEXHTTPTransaction *)transaction {
NSData *bodyData = transaction.cachedRequestBody;
if (bodyData.length > 0) {
NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
+34
View File
@@ -0,0 +1,34 @@
//
// FLEXMITMDataSource.h
// FLEX
//
// Created by Tanner Bennett on 8/22/21.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FLEXMITMDataSource<__covariant TransactionType> : NSObject
+ (instancetype)dataSourceWithProvider:(NSArray<TransactionType> *(^)(void))future;
@property (nonatomic, readonly) NSArray<TransactionType> *transactions;
@property (nonatomic, readonly) NSArray<TransactionType> *allTransactions;
/// Equal to \c allTransactions if not filtered
@property (nonatomic, readonly) NSArray<TransactionType> *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
+102
View File
@@ -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)(void);
@property (nonatomic) NSString *filterString;
@end
@implementation FLEXMITMDataSource
+ (instancetype)dataSourceWithProvider:(NSArray<id> *(^)(void))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 matchesQuery: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
@@ -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 <FLEXGlobalsEntry>
@end
+193 -135
View File
@@ -8,29 +8,36 @@
#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"
typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
FLEXNetworkObserverModeREST = 0,
FLEXNetworkObserverModeWebsockets,
};
@interface FLEXNetworkMITMViewController ()
/// Backing model
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *networkTransactions;
@property (nonatomic) long long bytesReceived;
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions;
@property (nonatomic) long long filteredBytesReceived;
@property (nonatomic) BOOL rowInsertInProgress;
@property (nonatomic) BOOL isPresentingSearch;
@property (nonatomic) BOOL updateInProgress;
@property (nonatomic) BOOL pendingReload;
@property (nonatomic, readonly) FLEXNetworkObserverMode mode;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXNetworkTransaction *> *dataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXHTTPTransaction *> *HTTPDataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXWebsocketTransaction *> *websocketDataSource;
@end
@implementation FLEXNetworkMITMViewController
@@ -47,6 +54,18 @@
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 +80,14 @@
]];
[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 +156,36 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark Transactions
- (void)updateTransactions {
self.networkTransactions = [FLEXNetworkRecorder.defaultRecorder networkTransactions];
- (FLEXNetworkObserverMode)mode {
return self.searchController.searchBar.selectedScopeButtonIndex;
}
- (void)setNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions {
if (![_networkTransactions isEqual:networkTransactions]) {
_networkTransactions = networkTransactions;
[self updateBytesReceived];
[self updateFilteredBytesReceived];
- (FLEXMITMDataSource<FLEXNetworkTransaction *> *)dataSource {
switch (self.mode) {
case FLEXNetworkObserverModeREST:
return self.HTTPDataSource;
case FLEXNetworkObserverModeWebsockets:
return self.websocketDataSource;
default:
@throw NSInternalInconsistencyException;
}
}
- (void)updateBytesReceived {
long long bytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.networkTransactions) {
bytesReceived += transaction.receivedDataLength;
}
self.bytesReceived = bytesReceived;
[self updateFirstSectionHeader];
- (void)updateTransactions:(void(^)(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)setFilteredNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions {
if (![_filteredNetworkTransactions isEqual:networkTransactions]) {
_filteredNetworkTransactions = networkTransactions;
[self updateFilteredBytesReceived];
}
}
- (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 +202,11 @@
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 +264,7 @@
- (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 +272,71 @@
// 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;
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<NSIndexPath *> *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);
// Get state before update
NSString *currentFilter = self.searchText;
FLEXNetworkObserverMode currentMode = self.mode;
NSInteger existingRowCount = self.dataSource.transactions.count;
[self updateTransactions:^{
// Compare to state after update
NSString *newFilter = self.searchText;
FLEXNetworkObserverMode newMode = self.mode;
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<NSIndexPath *> *indexPathsToReload = [NSMutableArray new];
for (NSInteger row = 0; row < rowCountDiff; 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;
}
} else {
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 +345,14 @@
break;
}
}
[self updateFirstSectionHeader];
}
- (void)handleTransactionsClearedNotification:(NSNotification *)notification {
[self updateTransactions];
[self.tableView reloadData];
[self updateTransactions:^{
[self.tableView reloadData];
}];
}
- (void)handleNetworkObserverEnabledStateChangedNotification:(NSNotification *)notification {
@@ -337,7 +364,7 @@
#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 +379,11 @@
}
- (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 +398,33 @@
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkTransactionDetailController *detailViewController = [FLEXNetworkTransactionDetailController new];
detailViewController.transaction = [self transactionAtIndexPath:indexPath];
[self.navigationController pushViewController:detailViewController animated:YES];
switch (self.mode) {
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;
}
}
@@ -385,80 +440,83 @@
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(copy:)) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
UIPasteboard.generalPasteboard.string = [self transactionAtIndexPath:indexPath].copyString;
}
}
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
FLEXNetworkTransaction *transaction = [self transactionAtIndexPath:indexPath];
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
UIAction *copy = [UIAction
actionWithTitle:@"Copy"
actionWithTitle:@"Copy URL"
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
}
];
UIAction *denylist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
[denylist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self tryUpdateTransactions];
UIPasteboard.generalPasteboard.string = transaction.copyString;
}
];
NSArray *children = @[copy];
if (self.mode == FLEXNetworkObserverModeREST) {
NSURLRequest *request = [self HTTPTransactionAtIndexPath:indexPath].request;
UIAction *denylist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
[denylist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self tryUpdateTransactions];
}
];
children = [children arrayByAddingObject:denylist];
}
return [UIMenu
menuWithTitle:@"" image:nil identifier:nil
options:UIMenuOptionsDisplayInline
children:@[copy, denylist]
children:children
];
}
];
}
- (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];
}
- (FLEXWebsocketTransaction *)websocketTransactionAtIndexPath:(NSIndexPath *)indexPath {
return self.websocketDataSource.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];
}
}];
}
id callback = ^(FLEXMITMDataSource *dataSource) {
if (self.dataSource == dataSource) {
[self.tableView reloadData];
}
};
[self.HTTPDataSource filter:searchString completion:callback];
[self.websocketDataSource filter:searchString completion:callback];
}
#pragma mark UISearchControllerDelegate
- (void)willPresentSearchController:(UISearchController *)searchController {
self.isPresentingSearch = YES;
}
- (void)didPresentSearchController:(UISearchController *)searchController {
self.isPresentingSearch = NO;
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
[self updateFirstSectionHeader];
[self.tableView reloadData];
}
- (void)willDismissSearchController:(UISearchController *)searchController {
+16 -6
View File
@@ -14,7 +14,7 @@ extern NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification;
extern NSString *const kFLEXNetworkRecorderUserInfoTransactionKey;
extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
@class FLEXNetworkTransaction;
@class FLEXNetworkTransaction, FLEXHTTPTransaction, FLEXWebsocketTransaction;
@interface FLEXNetworkRecorder : NSObject
@@ -37,19 +37,21 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
- (void)synchronizeDenylist;
// Accessing recorded network activity
#pragma mark Accessing recorded network activity
/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first.
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions;
/// Array of FLEXHTTPTransaction objects ordered by start time with the newest first.
@property (nonatomic, readonly) NSArray<FLEXHTTPTransaction *> *HTTPTransactions;
/// Array of FLEXWebsocketTransaction objects ordered by start time with the newest first.
@property (nonatomic, readonly) NSArray<FLEXWebsocketTransaction *> *websocketTransactions API_AVAILABLE(ios(13.0));
/// The full response data IFF it hasn't been purged due to memory pressure.
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction;
- (NSData *)cachedResponseBodyForTransaction:(FLEXHTTPTransaction *)transaction;
/// Dumps all network transactions and cached response bodies.
- (void)clearRecordedActivity;
// Recording network activity
#pragma mark Recording network activity
/// Call when app is about to send HTTP request.
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID
@@ -72,4 +74,12 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
/// This string can be set to anything useful about the API used to make the request.
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID;
- (void)recordWebsocketMessageSend:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task API_AVAILABLE(ios(13.0));
- (void)recordWebsocketMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error API_AVAILABLE(ios(13.0));
- (void)recordWebsocketMessageReceived:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task API_AVAILABLE(ios(13.0));
@end

Some files were not shown because too many files have changed in this diff Show More