Compare commits

..

76 Commits

Author SHA1 Message Date
Tanner Bennett 0c0e936143 Fix bug in new FLEXMirror usage 2022-04-27 12:34:56 -05:00
Tanner Bennett 5ab4f8d2a6 Fix annoying divider color in editor screens 2022-04-27 11:53:57 -05:00
Tanner Bennett ade5f81bc6 Update SPM example deps 2022-04-27 11:53:57 -05:00
Tanner Bennett 1cd3a90809 Enable displaying ivar names for custom struct types
FLEXMetadataExtras.h
2022-04-27 11:53:57 -05:00
Tanner Bennett 29f3c3d3cd Make CommitDetails a struct to demo Reflex 2022-04-26 19:48:49 -05:00
Tanner Bennett a4d49c0ca9 Podspec: gnu++11 2022-04-26 19:48:49 -05:00
Tanner Bennett f4bcfe708c Delete modulemap 2022-04-26 19:46:20 -05:00
Tanner Bennett 1523bf0e4d Use Reflex instead of FLEX in SPM project 2022-04-24 19:06:14 -05:00
Tanner Bennett ee18360f89 Initialize FLEXObjectExplorer.reflexAvailable 2022-04-24 19:06:14 -05:00
Tanner Bennett 19df6d0f0a Use Reflex and FLEXMirror 2022-04-24 19:06:14 -05:00
Tanner Bennett e6e38dfba5 Add FLEXSwiftInternal to check if object from Swift 2022-04-24 19:06:14 -05:00
Tanner Bennett d2a7ba388e Modernize FLEXMirror
Differentiate between class and instance props/methods
2022-04-24 19:05:45 -05:00
Tanner Bennett 33873531d2 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.
2022-04-24 19:05:45 -05:00
matrush fb2a33876e Add FLEXTableRowDataViewController for viewing DB rows 2022-04-24 16:30:43 -07:00
Tanner Bennett a42efe17a1 Use host instead of self here 2022-04-24 16:30:43 -07:00
Tanner Bennett 4bc2d1c7a9 Shorten some line lengths 2022-04-24 14:48:47 -07:00
weiminghuaa c7ebecfcb3 Re-filter system log when new messages arrive 2022-04-24 14:48:47 -07:00
weiminghuaa 208f0a31e4 Pin seach bar in system log 2022-04-24 14:48:47 -07:00
Tanner Bennett 2aeb34a67e Cherry pick #592 2022-04-24 16:23:15 -05:00
Tanner Bennett 39f16bd039 Make properties atomic in hopes of resolving #574 2022-04-24 16:23:00 -05:00
Tanner Bennett eea681d6c5 Consolidate duplicate code 2022-04-24 16:22:08 -05:00
Tanner Bennett a8768da4b9 Fix crash where FIRDocumentSnapshot response is nil 2022-04-24 16:15:34 -05:00
Tanner Bennett 4f6fd29d38 Clean up FLEXMITMDataSource interface 2022-04-23 18:45:24 -05:00
Canius Chu ba9cb43b6a Fix compile errors and warnings (#597)
* Update INSTALL_PATH
* Update run path
* Add FLEXFirebaseTransaction.mm into project which break in commit: d2e0db8773
* Fix header imports
* Rollback LD_RUNPATH_SEARCH_PATHS
2022-04-23 18:12:17 -05:00
Tanner Bennett 3af6da8554 Format _manuallyDeactivateSearchOnDisappear 2022-04-23 18:12:17 -05:00
Chaoshuai Lü 6c21395ac8 Fix self.allTransactions race condition in FLEXMITMDataSource
There is a crash caused by this because of race condition. Here we can simply make a copy of the transaction and it will be used and released properly even within the block.
2022-03-30 20:55:19 -07:00
Chaoshuai Lü 456fda31cd Add fallback column query support when PRAGMA table_info is not working (#573)
* Add fallback column queries support when PRAGMA table_info is not working

* Switch to check columnCount instead of rowsAffected

* Use SELECT * FROM pragma_table_info('%@')

Fallback to SELECT * FROM table where 0=1

Co-authored-by: Tanner Bennett <tannerbennett@me.com>
2022-03-30 20:23:55 -07:00
Chaoshuai Lü 9fc8735925 Check rowIDs instead of rowIDs.count to validate input 2022-03-30 23:16:07 -04:00
Tanner Bennett 9683acbe4f Ignore workspace files 2022-03-28 20:55:31 -07:00
Tanner Bennett 86a1cc9324 Remove DEVELOPMENT_TEAM 2022-03-28 20:55:15 -07:00
Tanner Bennett 88b14b3a92 Escape text for webview on a background thread 2022-03-28 20:54:34 -07:00
Tanner Bennett b9cd2682a7 Clean up web vc file 2022-03-28 20:43:48 -07:00
Nick Holub 64d1534fae Add accessibilityIdentifier and accessibilityLabel to views properties 2022-03-22 15:53:57 -07:00
Chaoshuai Lü 8318902826 Fix -Wobjc-signed-char-bool-implicit-int-conversion issue in FLEXNavigationController
This should be checked explicitly to shut up the warning.
2022-03-08 03:16:34 -08:00
Chaoshuai Lu a7f41fe5fc Mark some firebase related functions as static to avoid warnings 2022-03-03 03:14:38 -06:00
Tanner Bennett 2983649cdb Show descriptions of complex FIRQuery objects 2021-12-25 03:27:58 -06:00
Tanner Bennett d2e0db8773 Move FLEXFirebaseTransaction to own file 2021-12-24 22:03:55 -06:00
Tanner Bennett fd98070166 Fix #499, thank you @zhaogyrain 2021-12-24 19:41:52 -06:00
Hossam Ghareeb 4a9fd00eda Add option to copy row as CSV
This will make it easier to insert a new row
2021-12-24 19:35:53 -06:00
Tanner Bennett 97205fc808 Fix bug in rowid selection
`SELECT rowid` returns rowids corresponding to rows in a different order than `SELECT *`. We will now order the rowids in ascending order to match the order of `SELECT *`.

Previously, the mismatched order would mean deleting a row in the UI might delete a different row.
2021-12-24 19:32:01 -06:00
Tanner Bennett 0cffe72a8f Add more assertions to FLEXTableContentVC 2021-12-24 19:32:01 -06:00
Tanner Bennett d5ab2ee916 Revise copy for FLEXTableContentVC add row screen 2021-12-24 19:32:01 -06:00
Tanner Bennett 90c9a48694 Only allow deleting rows if we have a table name 2021-12-24 19:32:01 -06:00
Tanner Bennett 0222c682a0 Improve FLEXTableContentVC toolbar button logic
Only allow adding rows or deleting rows if we have a table name
2021-12-24 19:32:01 -06:00
Tanner Bennett 1e6f6ee110 Clean up FLEXTableContentVC initializers 2021-12-24 19:32:01 -06:00
Hossam Ghareeb 00d6b348f0 Adding an option to add a row in DB table
Added a new toolbar button to insert a new row. Tapping on the button will show an alert to write the values as a comma separated string which will be added in an INSERT statement to be executed.
2021-12-24 19:32:01 -06:00
Tanner Bennett 5404f37d0f Remember the last selected network observer tab 2021-12-24 19:30:18 -06:00
Tanner Bennett 1e539c7129 Pin network history search bar
This works around an ugly visual glitch that happens when the segmented control is visible
2021-12-24 19:30:18 -06:00
Tanner Bennett ca1b202949 For now, just show the transaction object directly 2021-12-24 19:30:18 -06:00
Tanner Bennett 62220a9a65 Record Firebase document creation 2021-12-24 19:30:18 -06:00
Tanner Bennett 02730c6c86 Allow clearing only filtered requests 2021-12-24 19:30:18 -06:00
Tanner Bennett 68789fbe1d Allow filtering Firebase requests with push/pull 2021-12-24 19:30:18 -06:00
Tanner Bennett b4261f8647 Add description for Firebase transaction "push" type 2021-12-24 19:30:18 -06:00
Tanner Bennett 94bf4eac2a Record "push" Firebase network transaction types 2021-12-23 01:35:17 -06:00
Tanner Bennett bed52392e5 Add new Firebase network transaction types 2021-12-23 01:35:17 -06:00
Tanner Bennett c4891840bd Wrap some method names 2021-12-23 01:35:17 -06:00
Tanner Bennett 9720227ac7 Fix logic in network observer mode property 2021-12-23 01:35:17 -06:00
Tanner Bennett 74622aaf10 Initial work for Firebase 2021-12-23 01:35:17 -06:00
Tanner Bennett 0cb5ad8453 Restore -Wno-deprecated-declarations 2021-12-23 01:35:09 -06:00
Chaoshuai Lu def68eae48 Remove unneeded edgesForExtendedLayout setting
Update FLEXTableContentViewController.m

Remove empty lines/spaces changes
2021-12-05 15:29:30 -08: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
136 changed files with 3002 additions and 430 deletions
+1
View File
@@ -20,3 +20,4 @@ DerivedData
/Example/Pods
Podfile.lock
IDEWorkspaceChecks.plist
*.xcworkspace
+5
View File
@@ -0,0 +1,5 @@
{
"search.exclude": {
"Classes/Headers": true
}
}
@@ -104,7 +104,7 @@
}
- (BOOL)canShowToolbar {
return self.topViewController.toolbarItems.count;
return self.topViewController.toolbarItems.count > 0;
}
- (void)addNavigationBarItemsToViewController:(UINavigationItem *)navigationItem {
@@ -66,9 +66,9 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
_searchBarDebounceInterval = kFLEXDebounceFast;
_showSearchBarInitially = YES;
_style = style;
_manuallyDeactivateSearchOnDisappear = ({
NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11;
});
_manuallyDeactivateSearchOnDisappear = (
NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11
);
// We will be our own search delegate if we implement this method
if ([self respondsToSelector:@selector(updateSearchResults:)]) {
@@ -10,4 +10,7 @@
@interface FLEXArgumentInputStructView : FLEXArgumentInputView
/// Enable displaying ivar names for custom struct types
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding;
@end
@@ -19,6 +19,41 @@
@implementation FLEXArgumentInputStructView
static NSMutableDictionary<NSString *, NSArray<NSString *> *> *structFieldNameRegistrar = nil;
+ (void)initialize {
if (self == [FLEXArgumentInputStructView class]) {
structFieldNameRegistrar = [NSMutableDictionary new];
[self registerDefaultFieldNames];
}
}
+ (void)registerDefaultFieldNames {
NSDictionary *defaults = @{
@(@encode(CGRect)): @[@"CGPoint origin", @"CGSize size"],
@(@encode(CGPoint)): @[@"CGFloat x", @"CGFloat y"],
@(@encode(CGSize)): @[@"CGFloat width", @"CGFloat height"],
@(@encode(CGVector)): @[@"CGFloat dx", @"CGFloat dy"],
@(@encode(UIEdgeInsets)): @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"],
@(@encode(UIOffset)): @[@"CGFloat horizontal", @"CGFloat vertical"],
@(@encode(NSRange)): @[@"NSUInteger location", @"NSUInteger length"],
@(@encode(CATransform3D)): @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
@"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
@"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
@"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"],
@(@encode(CGAffineTransform)): @[@"CGFloat a", @"CGFloat b",
@"CGFloat c", @"CGFloat d",
@"CGFloat tx", @"CGFloat ty"],
};
[structFieldNameRegistrar addEntriesFromDictionary:defaults];
if (@available(iOS 11.0, *)) {
structFieldNameRegistrar[@(@encode(NSDirectionalEdgeInsets))] = @[
@"CGFloat top", @"CGFloat leading", @"CGFloat bottom", @"CGFloat trailing"
];
}
}
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
@@ -181,40 +216,13 @@
return NO;
}
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding {
NSParameterAssert(typeEncoding); NSParameterAssert(names);
structFieldNameRegistrar[typeEncoding] = names;
}
+ (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding {
NSArray<NSString *> *customTitles = nil;
if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
customTitles = @[@"CGPoint origin", @"CGSize size"];
} else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
customTitles = @[@"CGFloat x", @"CGFloat y"];
} else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
customTitles = @[@"CGFloat width", @"CGFloat height"];
} else if (strcmp(typeEncoding, @encode(CGVector)) == 0) {
customTitles = @[@"CGFloat dx", @"CGFloat dy"];
} else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
customTitles = @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"];
} else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
customTitles = @[@"CGFloat horizontal", @"CGFloat vertical"];
} else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
customTitles = @[@"NSUInteger location", @"NSUInteger length"];
} else if (strcmp(typeEncoding, @encode(CATransform3D)) == 0) {
customTitles = @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
@"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
@"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
@"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"];
} else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
customTitles = @[@"CGFloat a", @"CGFloat b",
@"CGFloat c", @"CGFloat d",
@"CGFloat tx", @"CGFloat ty"];
} else {
if (@available(iOS 11.0, *)) {
if (strcmp(typeEncoding, @encode(NSDirectionalEdgeInsets)) == 0) {
customTitles = @[@"CGFloat top", @"CGFloat leading",
@"CGFloat bottom", @"CGFloat trailing"];
}
}
}
return customTitles;
return structFieldNameRegistrar[@(typeEncoding)];
}
@end
@@ -21,4 +21,7 @@
/// Useful when deciding whether to edit or explore a property, ivar, or NSUserDefaults value.
+ (BOOL)canEditFieldWithTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue;
/// Enable displaying ivar names for custom struct types
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding;
@end
@@ -67,4 +67,9 @@
return [self argumentInputViewSubclassForTypeEncoding:typeEncoding currentValue:currentValue] != nil;
}
/// Enable displaying ivar names for custom struct types
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding {
[FLEXArgumentInputStructView registerFieldNames:names forTypeEncoding:typeEncoding];
}
@end
+2 -1
View File
@@ -9,6 +9,7 @@
#import "FLEXFieldEditorView.h"
#import "FLEXArgumentInputView.h"
#import "FLEXUtility.h"
#import "FLEXColor.h"
@interface FLEXFieldEditorView ()
@@ -122,7 +123,7 @@
}
+ (UIColor *)dividerColor {
return UIColor.lightGrayColor;
return FLEXColor.tertiaryBackgroundColor;
}
+ (CGFloat)horizontalPadding {
@@ -11,12 +11,14 @@
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXMetadataExtras.h"
#import "FLEXUtility.h"
#import "FLEXColor.h"
#import "UIBarButtonItem+FLEX.h"
@interface FLEXFieldEditorViewController () <FLEXArgumentInputViewDelegate>
@property (nonatomic, readonly) id<FLEXMetadataAuxiliaryInfo> auxiliaryInfoProvider;
@property (nonatomic) FLEXProperty *property;
@property (nonatomic) FLEXIvar *ivar;
@@ -30,14 +32,14 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit {
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^)(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)(void))onCommit {
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit];
editor.title = [@"Ivar: " stringByAppendingString:ivar.name];
editor.ivar = ivar;
@@ -61,6 +63,8 @@
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace, self.getterButton, self.actionButton
];
[self registerAuxiliaryInfo];
// Configure input view
self.fieldEditorView.fieldDescription = self.fieldDescription;
@@ -122,6 +126,17 @@
#pragma mark - Private
- (void)registerAuxiliaryInfo {
// This is how Reflex will get Swift struct field names into the editor at runtime
NSDictionary<NSString *, NSArray *> *labels = [self.auxiliaryInfoProvider
auxiliaryInfoForKey:FLEXAuxiliarynfoKeyFieldLabels
];
for (NSString *type in labels) {
[FLEXArgumentInputViewFactory registerFieldNames:labels[type] forTypeEncoding:type];
}
}
- (id)currentValue {
if (self.property) {
return [self.property getValue:self.target];
@@ -130,6 +145,10 @@
}
}
- (id<FLEXMetadataAuxiliaryInfo>)auxiliaryInfoProvider {
return self.ivar ?: self.property;
}
- (const FLEXTypeEncoding *)typeEncoding {
if (self.property) {
return self.property.attributes.typeEncoding.UTF8String;
+11 -13
View File
@@ -6,17 +6,15 @@
// Copyright © 2020 FLEX Team. All rights reserved.
//
#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/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 <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"
+17 -15
View File
@@ -6,20 +6,22 @@
// 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 "FLEXMetadataExtras.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"
@@ -13,7 +13,7 @@
@synthesize keyedRows = _keyedRows;
+ (instancetype)message:(NSString *)message {
return [[self alloc] initWithmessage:message columns:nil rows:nil];
return [[self alloc] initWithMessage:message columns:nil rows:nil];
}
+ (instancetype)error:(NSString *)message {
@@ -23,12 +23,12 @@
}
+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
return [[self alloc] initWithmessage:nil columns:columnNames rows:rowData];
return [[self alloc] initWithMessage:nil columns:columnNames rows:rowData];
}
- (id)initWithmessage:(NSString *)message columns:(NSArray *)columns rows:(NSArray<NSArray *> *)rows {
- (instancetype)initWithMessage:(NSString *)message columns:(NSArray<NSString *> *)columns rows:(NSArray<NSArray<NSString *> *> *)rows {
NSParameterAssert(message || (columns && rows));
NSParameterAssert(columns.count == rows.firstObject.count);
NSParameterAssert(rows.count == 0 || columns.count == rows.firstObject.count);
self = [super init];
if (self) {
@@ -12,7 +12,10 @@
#import "FLEXRuntimeConstants.h"
#import <sqlite3.h>
static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
#define kQuery(name, str) static NSString * const QUERY_##name = str
kQuery(TABLENAMES, @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
@interface FLEXSQLiteDatabaseManager ()
@property (nonatomic) sqlite3 *db;
@@ -107,7 +110,19 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
FLEXSQLResult *results = [self executeStatement:sql];
// https://github.com/FLEXTool/FLEX/issues/554
if (!results.keyedRows.count) {
sql = [NSString stringWithFormat:@"SELECT * FROM pragma_table_info('%@')", tableName];
results = [self executeStatement:sql];
// Fallback to empty query
if (!results.keyedRows.count) {
sql = [NSString stringWithFormat:@"SELECT * FROM \"%@\" where 0=1", tableName];
return [self executeStatement:sql].columns ?: @[];
}
}
return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
return column[@"name"];
}] ?: @[];
@@ -119,7 +134,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
}
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName {
NSString *command = [NSString stringWithFormat:@"SELECT rowid FROM \"%@\"", tableName];
NSString *command = [NSString stringWithFormat:QUERY_ROWIDS, tableName];
NSArray<NSArray<NSString *> *> *data = [self executeStatement:command].rows ?: @[];
return [data flex_mapped:^id(NSArray<NSString *> *obj, NSUInteger idx) {
@@ -146,7 +161,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
return self.lastResult;
}
// Grab columns
// Grab columns (columnCount will be 0 for insert/update/delete)
int columnCount = sqlite3_column_count(pstmt);
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
return @(sqlite3_column_name(pstmt, (int)i));
@@ -164,8 +179,9 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
}
if (status == SQLITE_DONE) {
if (rows.count) {
// We selected some rows
// columnCount will be 0 for insert/update/delete
if (rows.count || columnCount > 0) {
// We executed a SELECT query
result = _lastResult = [FLEXSQLResult columns:columns rows:rows];
} else {
// We executed a query like INSERT, UDPATE, or DELETE
@@ -13,13 +13,23 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXTableContentViewController : UIViewController
/// Display a table with the given columns, rows, and name.
/// Display a mutable table with the given columns, rows, and name.
///
/// @param columnNames self explanatory.
/// @param rowData an array of rows, where each row is an array of column data.
/// @param rowIDs an array of stringy row IDs. Required for deleting rows.
/// @param tableName an optional name of the table being viewed, if any. Enables adding rows.
/// @param databaseManager an optional manager to allow modifying the table.
/// Required for deleting rows. Required for adding rows if \c tableName is supplied.
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(nullable NSArray<NSString *> *)rowIds
rowIDs:(NSArray<NSString *> *)rowIDs
tableName:(NSString *)tableName
database:(nullable id<FLEXDatabaseManager>)databaseManager;
database:(id<FLEXDatabaseManager>)databaseManager;
/// Display an immutable table with the given columns and rows.
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData;
@end
@@ -7,6 +7,7 @@
//
#import "FLEXTableContentViewController.h"
#import "FLEXTableRowDataViewController.h"
#import "FLEXMultiColumnTableView.h"
#import "FLEXWebViewController.h"
#import "FLEXUtility.h"
@@ -21,6 +22,8 @@
@property (nonatomic, nullable) NSMutableArray<NSString *> *rowIDs;
@property (nonatomic, readonly, nullable) id<FLEXDatabaseManager> databaseManager;
@property (nonatomic, readonly) BOOL canRefresh;
@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
@end
@@ -28,16 +31,43 @@
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(nullable NSArray<NSString *> *)rowIDs
rowIDs:(NSArray<NSString *> *)rowIDs
tableName:(NSString *)tableName
database:(nullable id<FLEXDatabaseManager>)databaseManager {
FLEXTableContentViewController *controller = [self new];
controller->_columns = columnNames.copy;
controller->_rows = rowData.mutableCopy;
controller->_rowIDs = rowIDs.mutableCopy;
controller->_tableName = tableName.copy;
controller->_databaseManager = databaseManager;
return controller;
database:(id<FLEXDatabaseManager>)databaseManager {
return [[self alloc]
initWithColumns:columnNames
rows:rowData
rowIDs:rowIDs
tableName:tableName
database:databaseManager
];
}
+ (instancetype)columns:(NSArray<NSString *> *)cols
rows:(NSArray<NSArray<NSString *> *> *)rowData {
return [[self alloc] initWithColumns:cols rows:rowData rowIDs:nil tableName:nil database:nil];
}
- (instancetype)initWithColumns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(nullable NSArray<NSString *> *)rowIDs
tableName:(nullable NSString *)tableName
database:(nullable id<FLEXDatabaseManager>)databaseManager {
// Must supply all optional parameters as one, or none
BOOL all = rowIDs && tableName && databaseManager;
BOOL none = !rowIDs && !tableName && !databaseManager;
NSParameterAssert(all || none);
self = [super init];
if (self) {
self->_columns = columnNames.copy;
self->_rows = rowData.mutableCopy;
self->_rowIDs = rowIDs.mutableCopy;
self->_tableName = tableName.copy;
self->_databaseManager = databaseManager;
}
return self;
}
- (void)loadView {
@@ -49,7 +79,6 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.title = self.tableName;
self.edgesForExtendedLayout = UIRectEdgeNone;
[self.multiColumnView reloadData];
[self setupToolbarItems];
}
@@ -67,6 +96,10 @@
return _multiColumnView;
}
- (BOOL)canRefresh {
return self.databaseManager && self.tableName;
}
#pragma mark MultiColumnTableView DataSource
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView {
@@ -122,6 +155,10 @@
return [NSString stringWithFormat:@"%@:\n%@", self.columns[idx], field];
}];
NSArray<NSString *> *values = [self.rows[row] flex_mapped:^id(NSString *value, NSUInteger idx) {
return [NSString stringWithFormat:@"'%@'", value];
}];
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title([@"Row " stringByAppendingString:@(row).stringValue]);
NSString *message = [fields componentsJoinedByString:@"\n\n"];
@@ -129,10 +166,19 @@
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = message;
});
make.button(@"Copy as CSV").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = [values componentsJoinedByString:@", "];
});
make.button(@"Focus on Row").handler(^(NSArray<NSString *> *strings) {
UIViewController *focusedRow = [FLEXTableRowDataViewController
rows:[NSDictionary dictionaryWithObjects:self.rows[row] forKeys:self.columns]
];
[self.navigationController pushViewController:focusedRow animated:YES];
});
// Option to delete row
BOOL hasRowID = self.rows.count && row < self.rows.count;
if (hasRowID) {
if (hasRowID && self.canRefresh) {
make.button(@"Delete").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
NSString *deleteRow = [NSString stringWithFormat:
@"DELETE FROM %@ WHERE rowid = %@",
@@ -142,9 +188,7 @@
[self executeStatementAndShowResult:deleteRow completion:^(BOOL success) {
// Remove deleted row and reload view
if (success) {
[self.rowIDs removeObjectAtIndex:row];
[self.rows removeObjectAtIndex:row];
[self.multiColumnView reloadData];
[self reloadTableDataFromDB];
}
}];
});
@@ -216,14 +260,24 @@
return;
}
UIBarButtonItem *trashButton = [FLEXBarButtonItemSystem(Trash, self, @selector(trashPressed))
flex_withTintColor:UIColor.redColor
UIBarButtonItem *trashButton = FLEXBarButtonItemSystem(Trash, self, @selector(trashPressed));
UIBarButtonItem *addButton = FLEXBarButtonItemSystem(Add, self, @selector(addPressed));
// Only allow adding rows or deleting rows if we have a table name
trashButton.enabled = self.canRefresh;
addButton.enabled = self.canRefresh;
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
addButton,
UIBarButtonItem.flex_flexibleSpace,
[trashButton flex_withTintColor:UIColor.redColor],
];
trashButton.enabled = self.databaseManager && self.rows.count;
self.toolbarItems = @[UIBarButtonItem.flex_flexibleSpace, trashButton];
}
- (void)trashPressed {
NSParameterAssert(self.tableName);
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Delete All Rows");
make.message(@"All rows in this table will be permanently deleted.\nDo you want to proceed?");
@@ -241,9 +295,35 @@
} showFrom:self];
}
- (void)addPressed {
NSParameterAssert(self.tableName);
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Add a New Row");
make.message(@"Comma separate values to use in an INSERT statement.\n\n");
make.message(@"INSERT INTO [table] VALUES (your_input)");
make.textField(@"5, 'John Smith', 14,...");
make.button(@"Insert").handler(^(NSArray<NSString *> *strings) {
NSString *statement = [NSString stringWithFormat:
@"INSERT INTO %@ VALUES (%@)", self.tableName, strings[0]
];
[self executeStatementAndShowResult:statement completion:^(BOOL success) {
if (success) {
[self reloadTableDataFromDB];
}
}];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
}
#pragma mark - Helpers
- (void)executeStatementAndShowResult:(NSString *)statement completion:(void (^_Nullable)(BOOL success))completion {
- (void)executeStatementAndShowResult:(NSString *)statement
completion:(void (^_Nullable)(BOOL success))completion {
NSParameterAssert(self.databaseManager);
FLEXSQLResult *result = [self.databaseManager executeStatement:statement];
[FLEXAlert makeAlert:^(FLEXAlert *make) {
@@ -260,5 +340,20 @@
} showFrom:self];
}
- (void)reloadTableDataFromDB {
if (!self.canRefresh) {
return;
}
NSArray<NSArray *> *rows = [self.databaseManager queryAllDataInTable:self.tableName];
NSArray<NSString *> *rowIDs = nil;
if ([self.databaseManager respondsToSelector:@selector(queryRowIDsInTable:)]) {
rowIDs = [self.databaseManager queryRowIDsInTable:self.tableName];
}
self.rows = rows.mutableCopy;
self.rowIDs = rowIDs.mutableCopy;
[self.multiColumnView reloadData];
}
@end
@@ -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
];
@@ -102,7 +105,7 @@
[FLEXAlert showAlert:@"Message" message:result.message from:self];
} else {
UIViewController *resultsScreen = [FLEXTableContentViewController
columns:result.columns rows:result.rows rowIDs:nil tableName:@"" database:nil
columns:result.columns rows:result.rows
];
[self.navigationController pushViewController:resultsScreen animated:YES];
@@ -0,0 +1,14 @@
//
// FLEXTableRowDataViewController.h
// FLEX
//
// Created by Chaoshuai Lu on 7/8/20.
//
#import "FLEXFilteringTableViewController.h"
@interface FLEXTableRowDataViewController : FLEXFilteringTableViewController
+ (instancetype)rows:(NSDictionary<NSString *, id> *)rowData;
@end
@@ -0,0 +1,54 @@
//
// FLEXTableRowDataViewController.m
// FLEX
//
// Created by Chaoshuai Lu on 7/8/20.
//
#import "FLEXTableRowDataViewController.h"
#import "FLEXMutableListSection.h"
#import "FLEXAlert.h"
@interface FLEXTableRowDataViewController ()
@property (nonatomic) NSDictionary<NSString *, NSString *> *rowsByColumn;
@end
@implementation FLEXTableRowDataViewController
#pragma mark - Initialization
+ (instancetype)rows:(NSDictionary<NSString *, id> *)rowData {
FLEXTableRowDataViewController *controller = [self new];
controller.rowsByColumn = rowData;
return controller;
}
#pragma mark - Overrides
- (NSArray<FLEXTableViewSection *> *)makeSections {
NSDictionary<NSString *, NSString *> *rowsByColumn = self.rowsByColumn;
FLEXMutableListSection<NSString *> *section = [FLEXMutableListSection list:self.rowsByColumn.allKeys
cellConfiguration:^(UITableViewCell *cell, NSString *column, NSInteger row) {
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = column;
cell.detailTextLabel.text = rowsByColumn[column].description;
} filterMatcher:^BOOL(NSString *filterText, NSString *column) {
return [column localizedCaseInsensitiveContainsString:filterText] ||
[rowsByColumn[column] localizedCaseInsensitiveContainsString:filterText];
}
];
section.selectionHandler = ^(UIViewController *host, NSString *column) {
UIPasteboard.generalPasteboard.string = rowsByColumn[column].description;
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Column Copied to Clipboard");
make.message(rowsByColumn[column].description);
make.button(@"Dismiss").cancelStyle();
} showFrom:host];
};
return @[section];
}
@end
@@ -168,15 +168,6 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
}
+ (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
static Class SwiftObjectClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SwiftObjectClass = NSClassFromString(@"SwiftObject");
if (!SwiftObjectClass) {
SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
}
});
NSArray<FLEXObjectRef *> *instances = [FLEXHeapEnumerator
objectsWithReferencesToObject:object retained:retain
];
@@ -222,7 +213,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
}];
}
- (FLEXMutableListSection *)makeSection:(NSArray *)rows title:(NSString *)title { weakify(self)
- (FLEXMutableListSection *)makeSection:(NSArray *)rows title:(NSString *)title {
FLEXMutableListSection *section = [FLEXMutableListSection list:rows
cellConfiguration:^(FLEXTableViewCell *cell, FLEXObjectRef *ref, NSInteger row) {
cell.textLabel.text = ref.reference;
@@ -237,8 +228,8 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
}
];
section.selectionHandler = ^(UIViewController *host, FLEXObjectRef *ref) { strongify(self)
[self.navigationController pushViewController:[
section.selectionHandler = ^(UIViewController *host, FLEXObjectRef *ref) {
[host.navigationController pushViewController:[
FLEXObjectExplorerFactory explorerViewControllerForObject:ref.object
] animated:YES];
};
@@ -38,9 +38,26 @@
self = [self initWithNibName:nil bundle:nil];
if (self) {
self.originalText = 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];
NSString *html = @"<head><style>:root{ color-scheme: light dark; }</style>"
"<meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>";
// Loading message for when input text takes a long time to escape
NSString *loadingMessage = [NSString stringWithFormat:html, @"Loading..."];
[self.webView loadHTMLString:loadingMessage baseURL:nil];
// Escape HTML on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *escapedText = [FLEXUtility stringByEscapingHTMLEntitiesInString:text];
NSString *htmlString = [NSString stringWithFormat:html, escapedText];
// Update webview on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.webView loadHTMLString:htmlString baseURL:nil];
});
});
}
return self;
}
@@ -50,14 +67,8 @@
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
return self;
}
- (void)dealloc {
// WKWebView's delegate is assigned so we need to clear it manually.
if (_webView.navigationDelegate == self) {
_webView.navigationDelegate = nil;
}
return self;
}
- (void)viewDidLoad {
@@ -68,7 +79,9 @@
self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if (self.originalText.length > 0) {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonTapped:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:@"Copy" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonTapped:)
];
}
}
@@ -79,20 +92,23 @@
#pragma mark - WKWebView Delegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))handler {
WKNavigationActionPolicy policy = WKNavigationActionPolicyCancel;
if (navigationAction.navigationType == WKNavigationTypeOther) {
// Allow the initial load
policy = WKNavigationActionPolicyAllow;
} else {
// For clicked links, push another web view controller onto the navigation stack so that hitting the back button works as expected.
// For clicked links, push another web view controller onto the navigation stack
// so that hitting the back button works as expected.
// Don't allow the current web view to handle the navigation.
NSURLRequest *request = navigationAction.request;
FLEXWebViewController *webVC = [[[self class] alloc] initWithURL:[request URL]];
webVC.title = [[request URL] absoluteString];
FLEXWebViewController *webVC = [[[self class] alloc] initWithURL:request.URL];
webVC.title = request.URL.absoluteString;
[self.navigationController pushViewController:webVC animated:YES];
}
decisionHandler(policy);
handler(policy);
}
@@ -101,7 +117,7 @@
+ (BOOL)supportsPathExtension:(NSString *)extension {
BOOL supported = NO;
NSSet<NSString *> *supportedExtensions = [self webViewSupportedPathExtensions];
if ([supportedExtensions containsObject:[extension lowercaseString]]) {
if ([supportedExtensions containsObject:extension.lowercaseString]) {
supported = YES;
}
return supported;
@@ -113,11 +129,14 @@
dispatch_once(&onceToken, ^{
// Note that this is not exhaustive, but all these extensions should work well in the web view.
// See https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/CreatingContentforSafarioniPhone/CreatingContentforSafarioniPhone.html#//apple_ref/doc/uid/TP40006482-SW7
pathExtensions = [NSSet<NSString *> setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
pathExtensions = [NSSet<NSString *> setWithArray:@[
@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"
]];
});
return pathExtensions;
}
@@ -98,7 +98,7 @@ static BOOL my_os_log_shim_enabled(void *addr) {
[super viewDidLoad];
self.showsSearchBar = YES;
self.showSearchBarInitially = NO;
self.pinSearchBar = YES;
weakify(self)
id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) { strongify(self)
@@ -175,9 +175,15 @@ static BOOL my_os_log_shim_enabled(void *addr) {
[self.logMessages mutate:^(NSMutableArray *list) {
[list addObjectsFromArray:newMessages];
}];
// Re-filter messages to filter against new messages
if (self.filterText.length) {
[self updateSearchResults:self.filterText];
}
// "Follow" the log as new messages stream in if we were previously near the bottom.
BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
UITableView *tv = self.tableView;
BOOL wasNearBottom = tv.contentOffset.y >= tv.contentSize.height - tv.frame.size.height - 100.0;
[self reloadData];
if (wasNearBottom) {
[self scrollToLastRow];
@@ -187,8 +193,8 @@ static BOOL my_os_log_shim_enabled(void *addr) {
- (void)scrollToLastRow {
NSInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
if (numberOfRows > 0) {
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:numberOfRows - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
NSIndexPath *last = [NSIndexPath indexPathForRow:numberOfRows - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:last atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
+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/Utility/Runtime/Objc/Reflection/FLEXMetadataExtras.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
@@ -52,6 +52,11 @@ NS_ASSUME_NONNULL_BEGIN
/// Removes all registered global entries.
- (void)clearGlobalEntries;
#pragma mark - Editing
/// Enable displaying ivar names for custom struct types
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding;
#pragma mark - Simulator Shortcuts
/// Simulator keyboard shortcuts are enabled by default.
@@ -15,6 +15,7 @@
#import "FLEXNetworkMITMViewController.h"
#import "FLEXKeyboardHelpViewController.h"
#import "FLEXFileBrowserController.h"
#import "FLEXArgumentInputStructView.h"
#import "FLEXUtility.h"
@interface FLEXManager (ExtensibilityPrivate)
@@ -75,6 +76,13 @@
}
#pragma mark - Editing
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding {
[FLEXArgumentInputStructView registerFieldNames:names forTypeEncoding:typeEncoding];
}
#pragma mark - Simulator Shortcuts
- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description {
+303
View File
@@ -0,0 +1,303 @@
//
// FLEXFirebaseTransaction.m
// FLEX
//
// Created by Tanner Bennett on 12/24/21.
//
#import "FLEXNetworkTransaction.h"
#import "FLEXUtility.h"
#import <dlfcn.h>
#include <string>
typedef std::string (*ReturnsString)(void *);
@implementation FLEXFirebaseSetDataInfo
+ (instancetype)data:(NSDictionary *)data merge:(NSNumber *)merge mergeFields:(NSArray *)mergeFields {
NSParameterAssert(data);
NSParameterAssert(merge || mergeFields);
FLEXFirebaseSetDataInfo *info = [self new];
info->_documentData = data;
info->_merge = merge;
info->_mergeFields = mergeFields;
return info;
}
@end
static NSString *FLEXStringFromFIRRequestType(FLEXFIRRequestType type) {
switch (type) {
case FLEXFIRRequestTypeNotFirebase:
return @"not firebase";
case FLEXFIRRequestTypeFetchQuery:
return @"query fetch";
case FLEXFIRRequestTypeFetchDocument:
return @"document fetch";
case FLEXFIRRequestTypeSetData:
return @"set data";
case FLEXFIRRequestTypeUpdateData:
return @"update data";
case FLEXFIRRequestTypeAddDocument:
return @"create";
case FLEXFIRRequestTypeDeleteDocument:
return @"delete";
}
return nil;
}
static FLEXFIRTransactionDirection FIRDirectionFromRequestType(FLEXFIRRequestType type) {
switch (type) {
case FLEXFIRRequestTypeNotFirebase:
return FLEXFIRTransactionDirectionNone;
case FLEXFIRRequestTypeFetchQuery:
case FLEXFIRRequestTypeFetchDocument:
return FLEXFIRTransactionDirectionPull;
case FLEXFIRRequestTypeSetData:
case FLEXFIRRequestTypeUpdateData:
case FLEXFIRRequestTypeAddDocument:
case FLEXFIRRequestTypeDeleteDocument:
return FLEXFIRTransactionDirectionPush;
}
return FLEXFIRTransactionDirectionNone;
}
@interface FLEXFirebaseTransaction ()
@property (nonatomic) id extraData;
@property (nonatomic, readonly) NSString *queryDescription;
@end
@implementation FLEXFirebaseTransaction
@synthesize queryDescription = _queryDescription;
+ (instancetype)initiator:(id)initiator requestType:(FLEXFIRRequestType)type extraData:(id)data {
FLEXFirebaseTransaction *fire = [FLEXFirebaseTransaction withStartTime:NSDate.date];
fire->_direction = FIRDirectionFromRequestType(type);
fire->_initiator = initiator;
fire->_requestType = type;
fire->_extraData = data;
return fire;
}
+ (instancetype)queryFetch:(FIRQuery *)initiator {
return [self initiator:initiator requestType:FLEXFIRRequestTypeFetchQuery extraData:nil];
}
+ (instancetype)documentFetch:(FIRDocumentReference *)initiator {
return [self initiator:initiator requestType:FLEXFIRRequestTypeFetchDocument extraData:nil];
}
+ (instancetype)setData:(FIRDocumentReference *)initiator data:(NSDictionary *)data
merge:(NSNumber *)merge mergeFields:(NSArray *)mergeFields {
FLEXFirebaseSetDataInfo *info = [FLEXFirebaseSetDataInfo data:data merge:merge mergeFields:mergeFields];
return [self initiator:initiator requestType:FLEXFIRRequestTypeSetData extraData:info];
}
+ (instancetype)updateData:(FIRDocumentReference *)initiator data:(NSDictionary *)data {
return [self initiator:initiator requestType:FLEXFIRRequestTypeUpdateData extraData:data];
}
+ (instancetype)addDocument:(FIRCollectionReference *)initiator document:(FIRDocumentReference *)doc {
return [self initiator:initiator requestType:FLEXFIRRequestTypeAddDocument extraData:doc];
}
+ (instancetype)deleteDocument:(FIRDocumentReference *)initiator {
return [self initiator:initiator requestType:FLEXFIRRequestTypeDeleteDocument extraData:nil];
}
- (NSString *)queryDescription {
if (_queryDescription) {
return _queryDescription;
}
// Grab C++ symbol to describe FIRQuery.query
static ReturnsString firebase_firestore_core_query_tostring = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Is Firebase available?
if (NSClassFromString(@"FIRDocumentReference")) {
firebase_firestore_core_query_tostring = (ReturnsString)dlsym(
RTLD_DEFAULT, "_ZNK8firebase9firestore4core5Query8ToStringEv"
);
}
});
if (!firebase_firestore_core_query_tostring) {
return @"nil";
}
FIRQuery *query = self.initiator_query;
if (!query) return nil;
void *core_query = query.query;
std::string description = firebase_firestore_core_query_tostring(core_query);
// Query strings are like 'Query(canonical_id=...)' so I remove the leading part, and the ()
NSString *prefix = @"Query(canonical_id=";
NSString *desc = @(description.c_str());
desc = [desc stringByReplacingOccurrencesOfString:prefix withString:@""];
desc = [desc stringByReplacingCharactersInRange:NSMakeRange(desc.length-1, 1) withString:@""];
_queryDescription = desc;
return _queryDescription;
}
- (FIRDocumentReference *)initiator_doc {
if ([_initiator isKindOfClass:cFIRDocumentReference]) {
return _initiator;
}
return nil;
}
- (FIRQuery *)initiator_query {
if ([_initiator isKindOfClass:cFIRQuery]) {
return _initiator;
}
return nil;
}
- (FIRCollectionReference *)initiator_collection {
if ([_initiator isKindOfClass:cFIRCollectionReference]) {
return _initiator;
}
return nil;
}
- (FLEXFirebaseSetDataInfo *)setDataInfo {
if (self.requestType == FLEXFIRRequestTypeSetData) {
return self.extraData;
}
return nil;
}
- (NSDictionary *)updateData {
if (self.requestType == FLEXFIRRequestTypeUpdateData) {
return self.extraData;
}
return nil;
}
- (NSString *)path {
switch (self.direction) {
case FLEXFIRTransactionDirectionNone:
return nil;
case FLEXFIRTransactionDirectionPush:
case FLEXFIRTransactionDirectionPull: {
switch (self.requestType) {
case FLEXFIRRequestTypeNotFirebase:
@throw NSInternalInconsistencyException;
case FLEXFIRRequestTypeFetchQuery:
case FLEXFIRRequestTypeAddDocument:
return self.initiator_collection.path ?: self.queryDescription;
case FLEXFIRRequestTypeFetchDocument:
case FLEXFIRRequestTypeSetData:
case FLEXFIRRequestTypeUpdateData:
case FLEXFIRRequestTypeDeleteDocument:
return self.initiator_doc.path;
}
}
}
return nil;
}
- (NSString *)primaryDescription {
if (!_primaryDescription) {
_primaryDescription = self.path.lastPathComponent;
}
return _primaryDescription;
}
- (NSString *)secondaryDescription {
if (!_secondaryDescription) {
_secondaryDescription = self.path.stringByDeletingLastPathComponent;
}
return _secondaryDescription;
}
- (NSString *)tertiaryDescription {
if (!_tertiaryDescription) {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *timestamp = [self timestampStringFromRequestDate:self.startTime];
if (timestamp.length > 0) {
[detailComponents addObject:timestamp];
}
[detailComponents addObject:self.direction == FLEXFIRTransactionDirectionPush ?
@"Push ↑" : @"Pull ↓"
];
if (self.direction == FLEXFIRTransactionDirectionPush) {
[detailComponents addObjectsFromArray:@[FLEXStringFromFIRRequestType(self.requestType)]];
}
if (self.state == FLEXNetworkTransactionStateFinished || self.state == FLEXNetworkTransactionStateFailed) {
if (self.direction == FLEXFIRTransactionDirectionPull) {
NSString *docCount = [NSString stringWithFormat:@"%@ document(s)", @(self.documents.count)];
[detailComponents addObjectsFromArray:@[docCount]];
}
} else {
// Unstarted, Awaiting Response, Receiving Data, etc.
NSString *state = [self.class readableStringFromTransactionState:self.state];
[detailComponents addObject:state];
}
_tertiaryDescription = [detailComponents componentsJoinedByString:@" ・ "];
}
return _tertiaryDescription;
}
- (NSString *)copyString {
return self.path;
}
- (BOOL)matchesQuery:(NSString *)filterString {
if ([self.path localizedCaseInsensitiveContainsString:filterString]) {
return YES;
}
BOOL isPull = self.direction == FLEXFIRTransactionDirectionPull;
BOOL isPush = self.direction == FLEXFIRTransactionDirectionPush;
// Allow filtering for push or pull directly
if (isPull && [filterString localizedCaseInsensitiveCompare:@"pull"] == NSOrderedSame) {
return YES;
}
if (isPush && [filterString localizedCaseInsensitiveCompare:@"push"] == NSOrderedSame) {
return YES;
}
return NO;
}
//- (NSString *)responseString {
// if (!_responseString) {
// _responseString = [NSString stringWithUTF8String:(char *)self.response.bytes];
// }
//
// return _responseString;
//}
//
//- (NSDictionary *)responseObject {
// if (!_responseObject) {
// _responseObject = [NSJSONSerialization JSONObjectWithData:self.response options:0 error:nil];
// }
//
// return _responseObject;
//}
@end
+5 -6
View File
@@ -13,22 +13,21 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)dataSourceWithProvider:(NSArray<TransactionType> *(^)(void))future;
/// Whether or not the data in \c transactions and \c bytesReceived are actually filtered yet or not
@property (nonatomic, readonly) BOOL isFiltered;
/// The content of this array is filtered to match the input of \c filter:completion:
@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
/// The content of this array is filtered to match the input of \c filter:completion:
@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
+13 -11
View File
@@ -24,12 +24,8 @@
return ds;
}
- (NSArray *)transactions {
return _filteredTransactions;
}
- (NSInteger)bytesReceived {
return _filteredBytesReceived;
- (BOOL)isFiltered {
return self.filterString.length > 0;
}
- (void)reloadByteCounts {
@@ -49,8 +45,9 @@
self.filteredTransactions = self.allTransactions;
if (completion) completion(self);
} else {
NSArray<FLEXNetworkTransaction *> *allTransactions = self.allTransactions.copy;
[self onBackgroundQueue:^NSArray *{
return [self.allTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) {
return [allTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) {
return [entry matchesQuery:searchString];
}];
} thenOnMainQueue:^(NSArray *filteredNetworkTransactions) {
@@ -63,15 +60,20 @@
}
- (void)setAllTransactions:(NSArray *)transactions {
_allTransactions = transactions;
_allTransactions = transactions.copy;
[self updateBytesReceived];
}
/// This is really just a semantic setter for \c _transactions
- (void)setFilteredTransactions:(NSArray *)filteredTransactions {
_filteredTransactions = filteredTransactions;
_transactions = filteredTransactions.copy;
[self updateFilteredBytesReceived];
}
- (void)setTransactions:(NSArray *)transactions {
self.filteredTransactions = transactions;
}
- (void)updateBytesReceived {
NSInteger bytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.transactions) {
@@ -83,11 +85,11 @@
- (void)updateFilteredBytesReceived {
NSInteger filteredBytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.filteredTransactions) {
for (FLEXNetworkTransaction *transaction in self.transactions) {
filteredBytesReceived += transaction.receivedDataLength;
}
self.filteredBytesReceived = filteredBytesReceived;
self.bytesReceived = filteredBytesReceived;
}
- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock {
+135 -28
View File
@@ -21,9 +21,14 @@
#import "FLEXWebViewController.h"
#import "UIBarButtonItem+FLEX.h"
#import "FLEXResources.h"
#import "NSUserDefaults+FLEX.h"
typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
FLEXNetworkObserverModeREST = 0,
#define kFirebaseAvailable NSClassFromString(@"FIRDocumentReference")
#define kWebsocketsAvailable @available(iOS 13.0, *)
typedef NS_ENUM(NSInteger, FLEXNetworkObserverMode) {
FLEXNetworkObserverModeFirebase = 0,
FLEXNetworkObserverModeREST,
FLEXNetworkObserverModeWebsockets,
};
@@ -32,11 +37,12 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
@property (nonatomic) BOOL updateInProgress;
@property (nonatomic) BOOL pendingReload;
@property (nonatomic, readonly) FLEXNetworkObserverMode mode;
@property (nonatomic) FLEXNetworkObserverMode mode;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXNetworkTransaction *> *dataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXHTTPTransaction *> *HTTPDataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXWebsocketTransaction *> *websocketDataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXFirebaseTransaction *> *firebaseDataSource;
@end
@@ -52,20 +58,33 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
[super viewDidLoad];
self.showsSearchBar = YES;
self.pinSearchBar = YES;
self.showSearchBarInitially = NO;
NSMutableArray *scopeTitles = [NSMutableArray arrayWithObject:@"REST"];
_HTTPDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * {
return FLEXNetworkRecorder.defaultRecorder.HTTPTransactions;
}];
if (@available(iOS 13.0, *)) {
self.searchController.searchBar.showsScopeBar = YES;
self.searchController.searchBar.scopeButtonTitles = @[@"REST", @"Websockets"];
if (kFirebaseAvailable) {
_firebaseDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * {
return FLEXNetworkRecorder.defaultRecorder.firebaseTransactions;
}];
[scopeTitles insertObject:@"Firebase" atIndex:0]; // First space
}
if (kWebsocketsAvailable) {
[scopeTitles addObject:@"Websockets"]; // Last space
_websocketDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * {
return FLEXNetworkRecorder.defaultRecorder.websocketTransactions;
}];
}
// Scopes will only be shown if we have either firebase or websockets available
self.searchController.searchBar.showsScopeBar = scopeTitles.count > 1;
self.searchController.searchBar.scopeButtonTitles = scopeTitles;
self.mode = NSUserDefaults.standardUserDefaults.flex_lastNetworkObserverMode;
[self addToolbarItems:@[
[UIBarButtonItem
flex_itemWithImage:FLEXResources.gearIcon
@@ -142,12 +161,23 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
- (void)trashButtonTapped:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
make.title(@"Clear All Recorded Requests?");
make.message(@"This cannot be undone.");
BOOL clearAll = !self.dataSource.isFiltered;
if (!clearAll) {
make.title(@"Clear Filtered Requests?");
make.message(@"This will only remove the requests matching your search string on this screen.");
} else {
make.title(@"Clear All Recorded Requests?");
make.message(@"This cannot be undone.");
}
make.button(@"Cancel").cancelStyle();
make.button(@"Clear All").destructiveStyle().handler(^(NSArray *strings) {
[FLEXNetworkRecorder.defaultRecorder clearRecordedActivity];
make.button(@"Clear").destructiveStyle().handler(^(NSArray *strings) {
if (clearAll) {
[FLEXNetworkRecorder.defaultRecorder clearRecordedActivity];
} else {
FLEXNetworkTransactionKind kind = (FLEXNetworkTransactionKind)self.mode;
[FLEXNetworkRecorder.defaultRecorder clearRecordedActivity:kind matching:self.searchText];
}
});
} showFrom:self source:sender];
}
@@ -160,7 +190,72 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
#pragma mark Transactions
- (FLEXNetworkObserverMode)mode {
return self.searchController.searchBar.selectedScopeButtonIndex;
FLEXNetworkObserverMode mode = self.searchController.searchBar.selectedScopeButtonIndex;
switch (mode) {
case FLEXNetworkObserverModeFirebase:
if (kFirebaseAvailable) {
return FLEXNetworkObserverModeFirebase;
}
return FLEXNetworkObserverModeREST;
case FLEXNetworkObserverModeREST:
if (kFirebaseAvailable) {
return FLEXNetworkObserverModeREST;
}
return FLEXNetworkObserverModeWebsockets;
case FLEXNetworkObserverModeWebsockets:
return FLEXNetworkObserverModeWebsockets;
}
}
- (void)setMode:(FLEXNetworkObserverMode)mode {
// The segmentd control will have different appearances based on which APIs
// are available. For example, when only Websockets is available:
//
// 0 1
//
// REST Websockets
//
//
// And when both Firebase and Websockets are available:
//
// 0 1 2
//
// Firebase REST Websockets
//
//
// As a result, we need to adjust the input mode variable accordingly
// before we actually set it. When we try to set it to Firebase but
// Firebase is not available, we don't do anything, because when Firebase
// is unavailable, FLEXNetworkObserverModeFirebase represents the same index
// as REST would without Firebase. For each of the others, we subtract 1
// from them for every relevant API that is unavailable. So for Websockets,
// if it is unavailable, we subtract 1 and it becomes FLEXNetworkObserverModeREST.
// And if Firebase is also unavailable, we subtract 1 again.
switch (mode) {
case FLEXNetworkObserverModeFirebase:
// Will default to REST if Firebase is unavailable
break;
case FLEXNetworkObserverModeREST:
// Firebase will become REST when Firebase is unavailable
if (!kFirebaseAvailable) {
mode--;
}
break;
case FLEXNetworkObserverModeWebsockets:
// Default to REST if Websockets are unavailable
if (!kWebsocketsAvailable) {
mode--;
}
// Firebase will become REST when Firebase is unavailable
if (!kFirebaseAvailable) {
mode--;
}
}
self.searchController.searchBar.selectedScopeButtonIndex = mode;
}
- (FLEXMITMDataSource<FLEXNetworkTransaction *> *)dataSource {
@@ -169,9 +264,8 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
return self.HTTPDataSource;
case FLEXNetworkObserverModeWebsockets:
return self.websocketDataSource;
default:
@throw NSInternalInconsistencyException;
case FLEXNetworkObserverModeFirebase:
return self.firebaseDataSource;
}
}
@@ -184,6 +278,7 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
[self.HTTPDataSource reloadData:completion];
[self.websocketDataSource reloadData:completion];
[self.firebaseDataSource reloadData:completion];
}
@@ -199,20 +294,21 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
}
- (NSString *)headerText {
long long bytesReceived = 0;
NSInteger totalRequests = 0;
if (self.searchController.isActive) {
bytesReceived = self.dataSource.filteredBytesReceived;
totalRequests = self.dataSource.transactions.count;
} else {
bytesReceived = self.dataSource.bytesReceived;
totalRequests = self.dataSource.transactions.count;
}
long long bytesReceived = self.dataSource.bytesReceived;
NSInteger totalRequests = self.dataSource.transactions.count;
NSString *byteCountText = [NSByteCountFormatter
stringFromByteCount:bytesReceived countStyle:NSByteCountFormatterCountStyleBinary
];
NSString *requestsText = totalRequests == 1 ? @"Request" : @"Requests";
// Exclude byte count from Firebase
if (self.mode == FLEXNetworkObserverModeFirebase) {
return [NSString stringWithFormat:@"%@ %@",
@(totalRequests), requestsText
];
}
return [NSString stringWithFormat:@"%@ %@ (%@ received)",
@(totalRequests), requestsText, byteCountText
];
@@ -332,6 +428,7 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
- (void)handleTransactionUpdatedNotification:(NSNotification *)notification {
[self.HTTPDataSource reloadByteCounts];
[self.websocketDataSource reloadByteCounts];
// Don't need to reload Firebase here
FLEXNetworkTransaction *transaction = notification.userInfo[kFLEXNetworkRecorderUserInfoTransactionKey];
@@ -421,9 +518,13 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
}
break;
}
default:
@throw NSInternalInconsistencyException;
case FLEXNetworkObserverModeFirebase: {
FLEXFirebaseTransaction *transaction = [self firebaseTransactionAtIndexPath:indexPath];
// id obj = transaction.documents.count == 1 ? transaction.documents.firstObject : transaction.documents;
UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:transaction];
[self.navigationController pushViewController:explorer animated:YES];
}
}
}
@@ -500,6 +601,9 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
return self.websocketDataSource.transactions[indexPath.row];
}
- (FLEXFirebaseTransaction *)firebaseTransactionAtIndexPath:(NSIndexPath *)indexPath {
return self.firebaseDataSource.transactions[indexPath.row];
}
#pragma mark - Search Bar
@@ -512,11 +616,14 @@ typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
[self.HTTPDataSource filter:searchString completion:callback];
[self.websocketDataSource filter:searchString completion:callback];
[self.firebaseDataSource filter:searchString completion:callback];
}
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)newScope {
[self updateFirstSectionHeader];
[self.tableView reloadData];
NSUserDefaults.standardUserDefaults.flex_lastNetworkObserverMode = self.mode;
}
- (void)willDismissSearchController:(UISearchController *)searchController {
+38 -1
View File
@@ -14,7 +14,14 @@ extern NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification;
extern NSString *const kFLEXNetworkRecorderUserInfoTransactionKey;
extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
@class FLEXNetworkTransaction, FLEXHTTPTransaction, FLEXWebsocketTransaction;
@class FLEXNetworkTransaction, FLEXHTTPTransaction, FLEXWebsocketTransaction, FLEXFirebaseTransaction;
@class FIRQuery, FIRDocumentReference, FIRCollectionReference, FIRDocumentSnapshot, FIRQuerySnapshot;
typedef NS_ENUM(NSUInteger, FLEXNetworkTransactionKind) {
FLEXNetworkTransactionKindFirebase = 0,
FLEXNetworkTransactionKindREST,
FLEXNetworkTransactionKindWebsockets,
};
@interface FLEXNetworkRecorder : NSObject
@@ -43,6 +50,8 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
@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));
/// Array of FLEXFirebaseTransaction objects ordered by start time with the newest first.
@property (nonatomic, readonly) NSArray<FLEXFirebaseTransaction *> *firebaseTransactions;
/// The full response data IFF it hasn't been purged due to memory pressure.
- (NSData *)cachedResponseBodyForTransaction:(FLEXHTTPTransaction *)transaction;
@@ -50,6 +59,9 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
/// Dumps all network transactions and cached response bodies.
- (void)clearRecordedActivity;
/// Clear only transactions matching the given query.
- (void)clearRecordedActivity:(FLEXNetworkTransactionKind)kind matching:(NSString *)query;
#pragma mark Recording network activity
@@ -82,4 +94,29 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
- (void)recordWebsocketMessageReceived:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task API_AVAILABLE(ios(13.0));
- (void)recordFIRQueryWillFetch:(FIRQuery *)query withTransactionID:(NSString *)transactionID;
- (void)recordFIRDocumentWillFetch:(FIRDocumentReference *)document withTransactionID:(NSString *)transactionID;
- (void)recordFIRQueryDidFetch:(FIRQuerySnapshot *)response error:(NSError *)error
transactionID:(NSString *)transactionID;
- (void)recordFIRDocumentDidFetch:(FIRDocumentSnapshot *)response error:(NSError *)error
transactionID:(NSString *)transactionID;
- (void)recordFIRWillSetData:(FIRDocumentReference *)doc
data:(NSDictionary *)documentData
merge:(NSNumber *)yesorno
mergeFields:(NSArray *)fields
transactionID:(NSString *)transactionID;
- (void)recordFIRWillUpdateData:(FIRDocumentReference *)doc fields:(NSDictionary *)fields
transactionID:(NSString *)transactionID;
- (void)recordFIRWillDeleteDocument:(FIRDocumentReference *)doc transactionID:(NSString *)transactionID;
- (void)recordFIRWillAddDocument:(FIRCollectionReference *)initiator
document:(FIRDocumentReference *)doc
transactionID:(NSString *)transactionID;
- (void)recordFIRDidSetData:(NSError *)error transactionID:(NSString *)transactionID;
- (void)recordFIRDidUpdateData:(NSError *)error transactionID:(NSString *)transactionID;
- (void)recordFIRDidDeleteDocument:(NSError *)error transactionID:(NSString *)transactionID;
- (void)recordFIRDidAddDocument:(NSError *)error transactionID:(NSString *)transactionID;
@end
+188 -19
View File
@@ -24,9 +24,10 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
@interface FLEXNetworkRecorder ()
@property (nonatomic) OSCache *restCache;
@property (nonatomic) NSMutableArray<FLEXHTTPTransaction *> *orderedHTTPTransactions;
@property (nonatomic) NSMutableArray<FLEXWebsocketTransaction *> *orderedWSTransactions;
@property (nonatomic) NSMutableDictionary<NSString *, FLEXHTTPTransaction *> *requestIDsToHTTPTransactions;
@property (atomic) NSMutableArray<FLEXHTTPTransaction *> *orderedHTTPTransactions;
@property (atomic) NSMutableArray<FLEXWebsocketTransaction *> *orderedWSTransactions;
@property (atomic) NSMutableArray<FLEXFirebaseTransaction *> *orderedFirebaseTransactions;
@property (atomic) NSMutableDictionary<NSString *, __kindof FLEXNetworkTransaction *> *requestIDsToTransactions;
@property (nonatomic) dispatch_queue_t queue;
@end
@@ -47,7 +48,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
self.orderedWSTransactions = [NSMutableArray new];
self.orderedHTTPTransactions = [NSMutableArray new];
self.requestIDsToHTTPTransactions = [NSMutableDictionary new];
self.orderedFirebaseTransactions = [NSMutableArray new];
self.requestIDsToTransactions = [NSMutableDictionary new];
self.hostDenylist = NSUserDefaults.standardUserDefaults.flex_networkHostDenylist.mutableCopy;
// Serial queue used because we use mutable objects that are not thread safe
@@ -89,6 +91,10 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
return self.orderedWSTransactions.copy;
}
- (NSArray<FLEXFirebaseTransaction *> *)firebaseTransactions {
return self.orderedFirebaseTransactions.copy;
}
- (NSData *)cachedResponseBodyForTransaction:(FLEXHTTPTransaction *)transaction {
return [self.restCache objectForKey:transaction.requestID];
}
@@ -98,7 +104,45 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
[self.restCache removeAllObjects];
[self.orderedWSTransactions removeAllObjects];
[self.orderedHTTPTransactions removeAllObjects];
[self.requestIDsToHTTPTransactions removeAllObjects];
[self.orderedFirebaseTransactions removeAllObjects];
[self.requestIDsToTransactions removeAllObjects];
[self notify:kFLEXNetworkRecorderTransactionsClearedNotification transaction:nil];
});
}
- (void)clearRecordedActivity:(FLEXNetworkTransactionKind)kind matching:(NSString *)query {
dispatch_async(self.queue, ^{
switch (kind) {
case FLEXNetworkTransactionKindFirebase: {
[self.orderedFirebaseTransactions flex_filter:^BOOL(FLEXFirebaseTransaction *obj, NSUInteger idx) {
return ![obj matchesQuery:query];
}];
break;
}
case FLEXNetworkTransactionKindREST: {
NSArray<FLEXHTTPTransaction *> *toRemove;
toRemove = [self.orderedHTTPTransactions flex_filtered:^BOOL(FLEXHTTPTransaction *obj, NSUInteger idx) {
return [obj matchesQuery:query];
}];
// Remove from cache
for (FLEXHTTPTransaction *t in toRemove) {
[self.restCache removeObjectForKey:t.requestID];
}
// Remove from list
[self.orderedHTTPTransactions removeObjectsInArray:toRemove];
break;
}
case FLEXNetworkTransactionKindWebsockets: {
[self.orderedWSTransactions flex_filter:^BOOL(FLEXWebsocketTransaction *obj, NSUInteger idx) {
return ![obj matchesQuery:query];
}];
break;
}
}
[self notify:kFLEXNetworkRecorderTransactionsClearedNotification transaction:nil];
});
@@ -144,10 +188,10 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
[self recordLoadingFinishedWithRequestID:requestID responseBody:nil];
}
// A redirect is always a new request
dispatch_async(self.queue, ^{
[self.orderedHTTPTransactions insertObject:transaction atIndex:0];
[self.requestIDsToHTTPTransactions setObject:transaction forKey:requestID];
transaction.transactionState = FLEXNetworkTransactionStateAwaitingResponse;
self.requestIDsToTransactions[requestID] = transaction;
[self postNewTransactionNotificationWithTransaction:transaction];
});
@@ -158,13 +202,13 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
NSDate *responseDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToTransactions[requestID];
if (!transaction) {
return;
}
transaction.response = response;
transaction.transactionState = FLEXNetworkTransactionStateReceivingData;
transaction.state = FLEXNetworkTransactionStateReceivingData;
transaction.latency = -[transaction.startTime timeIntervalSinceDate:responseDate];
[self postUpdateNotificationForTransaction:transaction];
@@ -173,7 +217,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength {
dispatch_async(self.queue, ^{
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToTransactions[requestID];
if (!transaction) {
return;
}
@@ -187,12 +231,12 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
NSDate *finishedDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToTransactions[requestID];
if (!transaction) {
return;
}
transaction.transactionState = FLEXNetworkTransactionStateFinished;
transaction.state = FLEXNetworkTransactionStateFinished;
transaction.duration = -[transaction.startTime timeIntervalSinceDate:finishedDate];
BOOL shouldCache = responseBody.length > 0;
@@ -246,12 +290,12 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error {
dispatch_async(self.queue, ^{
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToTransactions[requestID];
if (!transaction) {
return;
}
transaction.transactionState = FLEXNetworkTransactionStateFailed;
transaction.state = FLEXNetworkTransactionStateFailed;
transaction.duration = -[transaction.startTime timeIntervalSinceNow];
transaction.error = error;
@@ -261,7 +305,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID {
dispatch_async(self.queue, ^{
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToTransactions[requestID];
if (!transaction) {
return;
}
@@ -279,7 +323,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
withMessage:message task:task direction:FLEXWebsocketOutgoing
];
[self.orderedWSTransactions addObject:send];
[self.orderedWSTransactions insertObject:send atIndex:0];
[self postNewTransactionNotificationWithTransaction:send];
});
}
@@ -290,7 +334,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
return t.message == message;
}];
send.error = error;
send.transactionState = error ? FLEXNetworkTransactionStateFailed : FLEXNetworkTransactionStateFinished;
send.state = error ? FLEXNetworkTransactionStateFailed : FLEXNetworkTransactionStateFinished;
[self postUpdateNotificationForTransaction:send];
});
@@ -302,12 +346,137 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
withMessage:message task:task direction:FLEXWebsocketIncoming
];
[self.orderedWSTransactions addObject:receive];
[self.orderedWSTransactions insertObject:receive atIndex:0];
[self postNewTransactionNotificationWithTransaction:receive];
});
}
#pragma mark Notification Posting
#pragma mark - Firebase, Reading
- (void)recordFIRQueryWillFetch:(FIRQuery *)query withTransactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = [FLEXFirebaseTransaction queryFetch:query];
self.requestIDsToTransactions[transactionID] = transaction;
[self postNewTransactionNotificationWithTransaction:transaction];
});
}
- (void)recordFIRDocumentWillFetch:(FIRDocumentReference *)document withTransactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = [FLEXFirebaseTransaction documentFetch:document];
self.requestIDsToTransactions[transactionID] = transaction;
[self postNewTransactionNotificationWithTransaction:transaction];
});
}
- (void)recordFIRQueryDidFetch:(FIRQuerySnapshot *)response error:(NSError *)error transactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = self.requestIDsToTransactions[transactionID];
if (!transaction) {
return;
}
transaction.error = error;
transaction.documents = response.documents;
transaction.state = FLEXNetworkTransactionStateFinished;
[self.orderedFirebaseTransactions insertObject:transaction atIndex:0];
[self postUpdateNotificationForTransaction:transaction];
});
}
- (void)recordFIRDocumentDidFetch:(FIRDocumentSnapshot *)response error:(NSError *)error transactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = self.requestIDsToTransactions[transactionID];
if (!transaction) {
return;
}
transaction.error = error;
transaction.documents = response ? @[response] : @[];
transaction.state = FLEXNetworkTransactionStateFinished;
[self.orderedFirebaseTransactions insertObject:transaction atIndex:0];
[self postUpdateNotificationForTransaction:transaction];
});
}
#pragma mark Firebase, Writing
- (void)recordFIRWillSetData:(FIRDocumentReference *)doc
data:(NSDictionary *)documentData
merge:(NSNumber *)yesorno
mergeFields:(NSArray *)fields
transactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = [FLEXFirebaseTransaction
setData:doc data:documentData merge:yesorno mergeFields:fields
];
self.requestIDsToTransactions[transactionID] = transaction;
[self postNewTransactionNotificationWithTransaction:transaction];
});
}
- (void)recordFIRWillUpdateData:(FIRDocumentReference *)doc fields:(NSDictionary *)fields
transactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = [FLEXFirebaseTransaction updateData:doc data:fields];
self.requestIDsToTransactions[transactionID] = transaction;
[self postNewTransactionNotificationWithTransaction:transaction];
});
}
- (void)recordFIRWillDeleteDocument:(FIRDocumentReference *)doc transactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = [FLEXFirebaseTransaction deleteDocument:doc];
self.requestIDsToTransactions[transactionID] = transaction;
[self postNewTransactionNotificationWithTransaction:transaction];
});
}
- (void)recordFIRWillAddDocument:(FIRCollectionReference *)initiator document:(FIRDocumentReference *)doc
transactionID:(NSString *)transactionID {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = [FLEXFirebaseTransaction
addDocument:initiator document:doc
];
self.requestIDsToTransactions[transactionID] = transaction;
[self postNewTransactionNotificationWithTransaction:transaction];
});
}
- (void)recordFIRDidSetData:(NSError *)error transactionID:(NSString *)transactionID {
[self firebaseTransaction:transactionID didUpdate:error];
}
- (void)recordFIRDidUpdateData:(NSError *)error transactionID:(NSString *)transactionID {
[self firebaseTransaction:transactionID didUpdate:error];
}
- (void)recordFIRDidDeleteDocument:(NSError *)error transactionID:(NSString *)transactionID {
[self firebaseTransaction:transactionID didUpdate:error];
}
- (void)recordFIRDidAddDocument:(NSError *)error transactionID:(NSString *)transactionID {
[self firebaseTransaction:transactionID didUpdate:error];
}
- (void)firebaseTransaction:(NSString *)transactionID didUpdate:(NSError *)error {
dispatch_async(self.queue, ^{
FLEXFirebaseTransaction *transaction = self.requestIDsToTransactions[transactionID];
if (!transaction) {
return;
}
transaction.error = error;
transaction.state = FLEXNetworkTransactionStateFinished;
[self.orderedFirebaseTransactions insertObject:transaction atIndex:0];
[self postUpdateNotificationForTransaction:transaction];
});
}
#pragma mark - Notification Posting
- (void)postNewTransactionNotificationWithTransaction:(FLEXNetworkTransaction *)transaction {
[self notify:kFLEXNetworkRecorderNewTransactionNotification transaction:transaction];
+80 -9
View File
@@ -6,12 +6,13 @@
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "UIKit/UIKit.h"
#import <UIKit/UIKit.h>
#import "Firestore.h"
typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
FLEXNetworkTransactionStateUnstarted,
FLEXNetworkTransactionStateAwaitingResponse,
FLEXNetworkTransactionStateUnstarted = -1,
/// This is the default; it's usually nonsense for a request to be marked as "unstarted"
FLEXNetworkTransactionStateAwaitingResponse = 0,
FLEXNetworkTransactionStateReceivingData,
FLEXNetworkTransactionStateFinished,
FLEXNetworkTransactionStateFailed
@@ -24,7 +25,13 @@ typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
/// The shared base class for all types of network transactions.
/// Subclasses should implement the descriptions and details properties, and assign a thumbnail.
@interface FLEXNetworkTransaction : NSObject
@interface FLEXNetworkTransaction : NSObject {
@protected
NSString *_primaryDescription;
NSString *_secondaryDescription;
NSString *_tertiaryDescription;
}
+ (instancetype)withStartTime:(NSDate *)startTime;
@@ -35,7 +42,7 @@ typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
@property (nonatomic, readonly) BOOL displayAsError;
@property (nonatomic, readonly) NSDate *startTime;
@property (nonatomic) FLEXNetworkTransactionState transactionState;
@property (nonatomic) FLEXNetworkTransactionState state;
@property (nonatomic) int64_t receivedDataLength;
/// A small thumbnail to preview the type of/the response
@property (nonatomic) UIImage *thumbnail;
@@ -48,15 +55,15 @@ typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
/// Minor details to display at the bottom of the cell, such as a timestamp, HTTP method, or status.
@property (nonatomic, readonly) NSString *tertiaryDescription;
/// Subclasses should implement for when the transaction is complete
@property (nonatomic, readonly) NSArray<NSString *> *details;
/// The string to copy when the user selects the "copy" action
@property (nonatomic, readonly) NSString *copyString;
/// Whether or not this request should show up when the user searches for a given string
- (BOOL)matchesQuery:(NSString *)filterString;
/// For internal use
- (NSString *)timestampStringFromRequestDate:(NSDate *)date;
@end
/// The shared base class for all NSURL-API-related transactions.
@@ -66,6 +73,8 @@ typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime;
@property (nonatomic, readonly) NSURLRequest *request;
/// Subclasses should implement for when the transaction is complete
@property (nonatomic, readonly) NSArray<NSString *> *details;
@end
@@ -105,3 +114,65 @@ typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
@property (nonatomic, readonly) int64_t dataLength API_AVAILABLE(ios(13.0));
@end
typedef NS_ENUM(NSUInteger, FLEXFIRTransactionDirection) {
FLEXFIRTransactionDirectionNone,
FLEXFIRTransactionDirectionPush,
FLEXFIRTransactionDirectionPull,
};
typedef NS_ENUM(NSUInteger, FLEXFIRRequestType) {
FLEXFIRRequestTypeNotFirebase,
FLEXFIRRequestTypeFetchQuery,
FLEXFIRRequestTypeFetchDocument,
FLEXFIRRequestTypeSetData,
FLEXFIRRequestTypeUpdateData,
FLEXFIRRequestTypeAddDocument,
FLEXFIRRequestTypeDeleteDocument,
};
@interface FLEXFirebaseSetDataInfo : NSObject
/// The data that was set
@property (nonatomic, readonly) NSDictionary *documentData;
/// \c nil if \c mergeFields is populated
@property (nonatomic, readonly) NSNumber *merge;
/// \c nil if \c merge is populated
@property (nonatomic, readonly) NSArray *mergeFields;
@end
@interface FLEXFirebaseTransaction : FLEXNetworkTransaction
+ (instancetype)queryFetch:(FIRQuery *)initiator;
+ (instancetype)documentFetch:(FIRDocumentReference *)initiator;
+ (instancetype)setData:(FIRDocumentReference *)initiator
data:(NSDictionary *)data
merge:(NSNumber *)merge
mergeFields:(NSArray *)mergeFields;
+ (instancetype)updateData:(FIRDocumentReference *)initiator data:(NSDictionary *)data;
+ (instancetype)addDocument:(FIRCollectionReference *)initiator document:(FIRDocumentReference *)doc;
+ (instancetype)deleteDocument:(FIRDocumentReference *)initiator;
@property (nonatomic, readonly) FLEXFIRTransactionDirection direction;
@property (nonatomic, readonly) FLEXFIRRequestType requestType;
@property (nonatomic, readonly) id initiator;
@property (nonatomic, readonly) FIRQuery *initiator_query;
@property (nonatomic, readonly) FIRDocumentReference *initiator_doc;
@property (nonatomic, readonly) FIRCollectionReference *initiator_collection;
/// Only used for fetch types
@property (nonatomic, copy) NSArray<FIRDocumentSnapshot *> *documents;
/// Only used for the "set data" type
@property (nonatomic, readonly) FLEXFirebaseSetDataInfo *setDataInfo;
/// Only used for the "update data" type
@property (nonatomic, readonly) NSDictionary *updateData;
/// Only used for the "add document" type
@property (nonatomic, readonly) FIRDocumentReference *addedDocument;
@property (nonatomic, readonly) NSString *path;
//@property (nonatomic, readonly) NSString *responseString;
//@property (nonatomic, readonly) NSDictionary *responseObject;
@end
+20 -30
View File
@@ -10,16 +10,6 @@
#import "FLEXResources.h"
#import "FLEXUtility.h"
@interface FLEXNetworkTransaction () {
@protected
NSString *_primaryDescription;
NSString *_secondaryDescription;
NSString *_tertiaryDescription;
}
@end
@implementation FLEXNetworkTransaction
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state {
@@ -54,6 +44,23 @@
return transaction;
}
- (NSString *)timestampStringFromRequestDate:(NSDate *)date {
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = @"HH:mm:ss";
});
return [dateFormatter stringFromDate:date];
}
- (void)setState:(FLEXNetworkTransactionState)transactionState {
_state = transactionState;
// Reset bottom description
_tertiaryDescription = nil;
}
- (BOOL)displayAsError {
return _error != nil;
}
@@ -81,17 +88,6 @@
return transaction;
}
- (NSString *)timestampStringFromRequestDate:(NSDate *)date {
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = @"HH:mm:ss";
});
return [dateFormatter stringFromDate:date];
}
- (NSString *)primaryDescription {
if (!_primaryDescription) {
NSString *name = self.request.URL.lastPathComponent;
@@ -142,11 +138,11 @@
[detailComponents addObject:httpMethod];
}
if (self.transactionState == FLEXNetworkTransactionStateFinished || self.transactionState == FLEXNetworkTransactionStateFailed) {
if (self.state == FLEXNetworkTransactionStateFinished || self.state == FLEXNetworkTransactionStateFailed) {
[detailComponents addObjectsFromArray:self.details];
} else {
// Unstarted, Awaiting Response, Receiving Data, etc.
NSString *state = [self.class readableStringFromTransactionState:self.transactionState];
NSString *state = [self.class readableStringFromTransactionState:self.state];
[detailComponents addObject:state];
}
@@ -156,12 +152,6 @@
return _tertiaryDescription;
}
- (void)setTransactionState:(FLEXNetworkTransactionState)transactionState {
super.transactionState = transactionState;
// Reset bottom description
_tertiaryDescription = nil;
}
- (NSString *)copyString {
return self.request.URL.absoluteString;
}
@@ -261,7 +251,7 @@
// Populate receivedDataLength
if (direction == FLEXWebsocketIncoming) {
wst.receivedDataLength = wst.dataLength;
wst.transactionState = FLEXNetworkTransactionStateFinished;
wst.state = FLEXNetworkTransactionStateFinished;
}
// Populate thumbnail image

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