Compare commits

...

57 Commits

Author SHA1 Message Date
Tanner Bennett f916174070 Bump version and update URLs 2021-10-06 19:11:01 -05:00
Tanner Bennett 60e23e126e Fix crash for unsupported property type encodings 2021-10-06 19:11:01 -05:00
Tanner Bennett afeff1b562 Add FLEXMITMDataSource, refactor MITMVC 2021-10-06 18:18:54 -05:00
Tanner Bennett 5acb33005b Refactor HTTP transaction detail controller 2021-10-06 18:18:54 -05:00
Tanner Bennett 3446eff353 Hook and record websocket methods 2021-10-06 18:18:54 -05:00
Tanner Bennett ad1f1f579e Move network transaction model logic into model 2021-10-06 18:18:54 -05:00
Tanner Bennett e03b5f7e5d Send example websocket traffic 2021-10-06 18:18:54 -05:00
Tanner Bennett d010c82dd0 Add OSCache for network caching 2021-10-06 18:18:54 -05:00
Tanner Bennett 652d03c39a Add -[NSArray flex_firstWhere:] 2021-10-06 18:18:54 -05:00
Tanner Bennett 1d39669a52 Add button to dlopen a library in Runtime Browser 2021-10-06 18:18:54 -05:00
Iulian Onofrei d558ca6852 Fix code style 2021-09-22 11:49:00 -07:00
Duraid Abdul ad32ca0f05 Update project.pbxproj 2021-08-31 12:04:22 -07:00
Tanner Bennett 67097982ea Bump version 2021-08-19 00:58:13 -05:00
matrush 1342d3029c Add availability check for iOS 11+ only SFSafariViewController properities 2021-08-09 09:37:23 -07:00
Hossam Ghareeb 62dcef4644 Add ability to delete SQLite rows 2021-07-25 18:15:26 -05:00
Tanner Bennett 7ee296143e Add button to clear SQLite table (@hossamghareeb) 2021-07-25 17:31:29 -05:00
Tanner Bennett c6bac54597 Fix #544; utilize smartQuotesType = .no 2021-07-25 17:03:05 -05:00
Tanner Bennett 5f7bce64ed Create FUNDING.yml 2021-07-25 16:52:59 -05:00
Tanner Bennett d2dde55bb1 Lay groundwork for multiple shortcut sections 2021-05-25 17:47:29 -05:00
Tanner Bennett f1a0a5c5e5 Add shortcuts for collections 2021-05-25 17:46:50 -05:00
Tanner Bennett 0db073459e iOS 13 is now the minimum required SDK
Remove FLEX_AT_LEAST_IOS13_SDK and usages. It was causing several issues being in a header that needed to be private but also included in public headers.
2021-05-25 15:30:46 -05:00
Tanner Bennett 7c7ed9286f Fix code typos 2021-05-25 15:10:45 -05:00
Tanner Bennett aac88dd6c8 Fix presentTool:
presentTool
2021-05-07 14:01:57 -05:00
Tanner Bennett d7376b75cd Add flex_observers shortcut to NSNotificationCenter 2021-05-07 13:58:04 -05:00
Tanner Bennett 5b39b3ed03 Add UIPasteboard shortcuts 2021-05-07 13:58:04 -05:00
Tanner Bennett bbaa85bdbf Add WKWebView/SFVC shortcuts 2021-05-07 12:03:06 -05:00
Tanner Bennett 34e27bc5d9 Stable sort and sort integers numerically 2021-05-01 01:04:12 -05:00
Tanner Bennett 714307273e Show full text for DB browser column headers 2021-05-01 01:04:12 -05:00
TekYin 5242d3c5a1 Fix crash when sharing single file on iPad 2021-04-30 23:28:32 -05:00
ray cf2e94a1d2 Fix crash when viewing keychain item 2021-04-30 23:17:01 -05:00
canius 800acb4cad Fix private header warnings and install path (#514)
Co-authored-by: Canius Chu <canius.chu@farfetch.com>
2021-04-30 22:37:39 -05:00
Saafo 368ce64121 Fix iPad/multi-scene FLEX bar layout bugs (#534)
Prior to this, FLEX could appear in the wrong scene or with the wrong width if the scene is not the full width of the screen, which is almost always the case in multi scene environments.

Thanks @Saafo!
2021-04-25 14:54:23 -05:00
Bang Lee 05f03090a9 Display access group for keychain items (#532) 2021-04-12 01:44:29 -05:00
Tanner Bennett a8803781e8 Add missing files to xcodeproj; oops, thanks @BB9z 2021-04-08 16:41:54 -05:00
Tanner Bennett 170f74b297 Don't codesign FLEX.framework 2021-04-08 16:39:39 -05:00
Tanner Bennett 0d0f2a3073 Add FLEXAlert nullabilities for Swift 2021-04-08 16:39:39 -05:00
Michael Gray d6bddf5199 Quote table names in SQL commands (#529)
Sqlite is often smart enough to not need quotes for identifiers, unless you want to use a keyword as a name. Then you need to quote it

https://www.sqlite.org/lang_keywords.html
2021-04-05 15:02:05 -05:00
Tanner Bennett 258ec8697b Bump version 2021-03-30 14:20:02 -05:00
Tanner Bennett 8f9a6e88ec Fix toolbar appearing when empty 2021-03-30 14:20:02 -05:00
Tanner Bennett c37270e6ac Add shortcuts for NSString and NSData 2021-03-30 14:20:02 -05:00
Tanner Bennett ce10d45c29 Fix "hide likely private methods" behavior
Hide relevant private properties too, also class properties and methods
2021-03-30 14:20:02 -05:00
Tanner Bennett b2716c4b2e Catch potential exceptions thrown from descriptions 2021-03-30 14:20:02 -05:00
Tanner Bennett ab135ba94e Remove uninclusive terms
Blacklist → exclude[d] / denylist
Whitelist → allow[ed] / unexcluded
2021-03-30 14:20:02 -05:00
Tanner Bennett bcc04f4113 Make FLEXMacros.h private 2021-03-30 14:20:02 -05:00
Tanner Bennett 470b3fa3b3 Add tintColor as real property 2021-03-30 14:20:02 -05:00
Tanner Bennett e84dfeae5c Fix object explorer class pan gesture… again… 2021-03-30 14:20:02 -05:00
Tanner Bennett 44e86e59b7 Fix Xcode 12 compile errors 2021-03-30 14:20:02 -05:00
Tanner Bennett ac6d9cfa3f Misc refactoring 2021-03-30 14:20:02 -05:00
Tanner Bennett 1a59711760 Fix a crash, add some missing nullabilities 2021-03-30 14:20:01 -05:00
Tanner Bennett b60ce7a057 Add UIAlertController shortcuts
Fix alert controller
2021-03-30 14:20:01 -05:00
Tanner Bennett b4ac210bef Use likely ivar to determine matching property 2021-03-29 13:55:52 -05:00
Tanner Bennett 9360c58975 Add likely ivar data to FLEXProperty 2021-03-29 13:55:52 -05:00
Tanner Bennett 43d9a460ce Make the keyboard seem to appear faster
On screens where the keyboard is set to appear right away, make it appear faster—or at least, make it look like it appears faster…

Also add a property to make the search bar appear initially instead of duplicating becomeFirstResponder code across several classes
2021-03-29 13:55:51 -05:00
ray 15fee5a8a5 Fix keychain viewer crash (#521) 2021-03-23 10:36:29 -05:00
Alexey Salangin fad038392b Don't show toolbar when tapping on a nav bar button (#517) 2021-03-11 13:45:23 -06:00
Tanner Bennett 558d65a0b0 Update license 2021-02-02 14:18:09 -06:00
Tanner Bennett 077fca36c0 Bump deployment targets (oops?) 2021-01-27 23:24:39 -06:00
101 changed files with 2418 additions and 704 deletions
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [NSExceptional]
@@ -9,6 +9,7 @@
#import "FLEXFilteringTableViewController.h"
#import "FLEXTableViewSection.h"
#import "NSArray+FLEX.h"
#import "FLEXMacros.h"
@interface FLEXFilteringTableViewController ()
@@ -187,8 +188,6 @@
[self.filterDelegate.sections[indexPath.section] didPressInfoButtonAction:indexPath.row](self);
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section];
NSString *title = [section menuTitleForRow:indexPath.row];
@@ -207,6 +206,4 @@
return nil;
}
#endif
@end
@@ -20,6 +20,7 @@
@interface FLEXNavigationController ()
@property (nonatomic, readonly) BOOL toolbarWasHidden;
@property (nonatomic) BOOL waitingToAddTab;
@property (nonatomic, readonly) BOOL canShowToolbar;
@property (nonatomic) BOOL didSetupPendingDismissButtons;
@property (nonatomic) UISwipeGestureRecognizer *navigationBarSwipeGesture;
@end
@@ -99,6 +100,10 @@
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)canShowToolbar {
return self.topViewController.toolbarItems.count;
}
- (void)addNavigationBarItemsToViewController:(UINavigationItem *)navigationItem {
if (!self.presentingViewController) {
return;
@@ -148,8 +153,15 @@
}
- (void)handleNavigationBarTap:(UIGestureRecognizer *)sender {
// Don't reveal the toolbar if we were just tapping a button
CGPoint location = [sender locationInView:self.navigationBar];
UIView *hitView = [self.navigationBar hitTest:location withEvent:nil];
if ([hitView isKindOfClass:[UIControl class]]) {
return;
}
if (sender.state == UIGestureRecognizerStateRecognized) {
if (self.toolbarHidden) {
if (self.toolbarHidden && self.canShowToolbar) {
[self setToolbarHidden:NO animated:YES];
}
}
@@ -165,7 +177,7 @@
- (void)_gestureRecognizedInteractiveHide:(UIPanGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateRecognized) {
BOOL show = self.topViewController.toolbarItems.count;
BOOL show = self.canShowToolbar;
CGFloat yTranslation = [sender translationInView:self.view].y;
CGFloat yVelocity = [sender velocityInView:self.view].y;
if (yVelocity > 2000) {
@@ -67,6 +67,10 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
/// Setting this to YES will make the search bar appear whenever the view appears.
/// Otherwise, iOS will only show the search bar when you scroll up.
@property (nonatomic) BOOL showSearchBarInitially;
/// Defaults to NO.
///
/// Setting this to YES will make the search bar activate whenever the view appears.
@property (nonatomic) BOOL activatesSearchBarAutomatically;
/// nil unless showsSearchBar is set to YES.
///
@@ -50,15 +50,12 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
#pragma mark - Initialization
- (id)init {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
self = [self initWithStyle:UITableViewStyleInsetGrouped];
} else {
self = [self initWithStyle:UITableViewStyleGrouped];
}
#else
self = [self initWithStyle:UITableViewStyleGrouped];
#endif
return self;
}
@@ -106,11 +103,9 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.automaticallyShowsSearchBarCancelButton = YES;
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
self.searchController.automaticallyShowsScopeBar = NO;
}
#endif
[self addSearchController:self.searchController];
} else {
@@ -170,21 +165,17 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
}
- (BOOL)automaticallyShowsSearchBarCancelButton {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
return self.searchController.automaticallyShowsCancelButton;
}
#endif
return _automaticallyShowsSearchBarCancelButton;
}
- (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)value {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
self.searchController.automaticallyShowsCancelButton = value;
}
#endif
_automaticallyShowsSearchBarCancelButton = value;
}
@@ -238,7 +229,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
// Toolbar
self.navigationController.toolbarHidden = NO;
self.navigationController.toolbarHidden = self.toolbarItems.count > 0;
self.navigationController.hidesBarsOnSwipe = YES;
// On iOS 13, the root view controller shows it's search bar no matter what.
@@ -256,12 +247,17 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// When going back, make the search bar reappear instead of hiding
if (@available(iOS 11.0, *)) {
// When going back, make the search bar reappear instead of hiding
if ((self.pinSearchBar || self.showSearchBarInitially) && !self.didInitiallyRevealSearchBar) {
self.navigationItem.hidesSearchBarWhenScrolling = NO;
}
}
// Make the keyboard seem to appear faster
if (self.activatesSearchBarAutomatically) {
[self makeKeyboardAppearNow];
}
[self setupToolbarItems];
}
@@ -283,6 +279,17 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
}];
}
}
if (self.activatesSearchBarAutomatically) {
// Keyboard has appeared, now we call this as we soon present our search bar
[self removeDummyTextField];
// Activate the search bar
dispatch_async(dispatch_get_main_queue(), ^{
// This doesn't work unless it's wrapped in this dispatch_async call
[self.searchController.searchBar becomeFirstResponder];
});
}
// We only want to reveal the search bar when the view controller first appears.
self.didInitiallyRevealSearchBar = YES;
@@ -522,6 +529,30 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
#pragma mark - Search Bar
#pragma mark Faster keyboard
static UITextField *kDummyTextField = nil;
/// Make the keyboard appear instantly. We use this to make the
/// keyboard appear faster when the search bar is set to appear initially.
/// You must call \c -removeDummyTextField before your search bar is to appear.
- (void)makeKeyboardAppearNow {
if (!kDummyTextField) {
kDummyTextField = [UITextField new];
kDummyTextField.autocorrectionType = UITextAutocorrectionTypeNo;
}
kDummyTextField.inputAccessoryView = self.searchController.searchBar.inputAccessoryView;
[UIApplication.sharedApplication.keyWindow addSubview:kDummyTextField];
[kDummyTextField becomeFirstResponder];
}
- (void)removeDummyTextField {
if (kDummyTextField.superview) {
[kDummyTextField removeFromSuperview];
}
}
#pragma mark UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
-3
View File
@@ -7,7 +7,6 @@
//
#import <UIKit/UIKit.h>
#import "FLEXMacros.h"
#import "NSArray+FLEX.h"
@class FLEXTableView;
@@ -101,7 +100,6 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable void(^)(__kindof UIViewController *host))didPressInfoButtonAction:(NSInteger)row;
#pragma mark - Context Menus
#if FLEX_AT_LEAST_IOS13_SDK
/// By default, this is the title of the row.
/// @return The title of the context menu, if any.
@@ -121,7 +119,6 @@ NS_ASSUME_NONNULL_BEGIN
/// should be a description of what will be copied, and the values should be
/// the strings to copy. Return an empty string as a value to show a disabled action.
- (nullable NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
#endif
#pragma mark - Cell Configuration
-4
View File
@@ -64,8 +64,6 @@
return kFLEXDefaultCell;
}
#if FLEX_AT_LEAST_IOS13_SDK
- (NSString *)menuTitleForRow:(NSInteger)row {
NSString *title = [self titleForRow:row];
NSString *subtitle = [self menuSubtitleForRow:row];
@@ -127,8 +125,6 @@
return @[];
}
#endif
- (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row {
return nil;
}
-8
View File
@@ -30,29 +30,21 @@ FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell = @"kFLEXCodeFontCell";
@implementation FLEXTableView
+ (instancetype)flexDefaultTableView {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
} else {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}
#else
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
#endif
}
#pragma mark - Initialization
+ (id)groupedTableView {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
} else {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}
#else
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
#endif
}
+ (id)plainTableView {
@@ -33,6 +33,7 @@
self.inputTextView.delegate = self;
self.inputTextView.inputAccessoryView = [self createToolBar];
if (@available(iOS 11, *)) {
self.inputTextView.smartQuotesType = UITextSmartQuotesTypeNo;
[self.inputTextView.layer setValue:@YES forKey:@"continuousCorners"];
} else {
self.inputTextView.layer.borderWidth = 1.f;
@@ -21,13 +21,22 @@
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
/// @brief Used to present (or dismiss) a modal view controller ("tool"), typically triggered by pressing a button in the toolbar.
/// @brief Used to present (or dismiss) a modal view controller ("tool"),
/// typically triggered by pressing a button in the toolbar.
///
/// If a tool is already presented, this method simply dismisses it and calls the completion block.
/// If no tool is presented, @code future() @endcode is presented and the completion block is called.
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future
completion:(void (^)(void))completion;
/// @brief Used to present (or dismiss) a modal view controller ("tool"),
/// typically triggered by pressing a button in the toolbar.
///
/// If a tool is already presented, this method dismisses it and presents the given tool.
/// The completion block is called once the tool has been presented.
- (void)presentTool:(UINavigationController *(^)(void))future
completion:(void (^)(void))completion;
// Keyboard shortcut helpers
- (void)toggleSelectTool;
@@ -925,12 +925,26 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future
completion:(void (^)(void))completion {
if (self.presentedViewController) {
// We do NOT want to present the future; this is
// a convenience method for toggling the SAME TOOL
[self dismissViewControllerAnimated:YES completion:completion];
} else if (future) {
[self presentViewController:future() animated:YES completion:completion];
}
}
- (void)presentTool:(UINavigationController *(^)(void))future
completion:(void (^)(void))completion {
if (self.presentedViewController) {
// If a tool is already presented, dismiss it first
[self dismissViewControllerAnimated:YES completion:^{
[self presentViewController:future() animated:YES completion:completion];
}];
} else if (future) {
[self presentViewController:future() animated:YES completion:completion];
}
}
- (FLEXWindow *)window {
return (id)self.view.window;
}
-1
View File
@@ -20,6 +20,5 @@
#import <FLEX/FLEX-Categories.h>
#import <FLEX/FLEX-ObjectExploring.h>
#import <FLEX/FLEXMacros.h>
#import <FLEX/FLEXAlert.h>
#import <FLEX/FLEXResources.h>
@@ -8,12 +8,21 @@
#import <UIKit/UIKit.h>
@class FLEXDBQueryRowCell;
extern NSString * const kFLEXDBQueryRowCellReuse;
@protocol FLEXDBQueryRowCellLayoutSource <NSObject>
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell minXForColumn:(NSUInteger)column;
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell widthForColumn:(NSUInteger)column;
@end
@interface FLEXDBQueryRowCell : UITableViewCell
/// An array of NSString, NSNumber, or NSData objects
@property (nonatomic) NSArray *data;
@property (nonatomic, weak) id<FLEXDBQueryRowCellLayoutSource> layoutSource;
@end
@@ -63,11 +63,12 @@ NSString * const kFLEXDBQueryRowCellReuse = @"kFLEXDBQueryRowCellReuse";
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat width = self.contentView.frame.size.width / self.labels.count;
CGFloat height = self.contentView.frame.size.height;
[self.labels flex_forEach:^(UILabel *label, NSUInteger i) {
label.frame = CGRectMake(width * i + 5, 0, (width - 10), height);
CGFloat width = [self.layoutSource dbQueryRowCell:self widthForColumn:i];
CGFloat minX = [self.layoutSource dbQueryRowCell:self minXForColumn:i];
label.frame = CGRectMake(minX + 5, 0, (width - 10), height);
}];
}
@@ -29,6 +29,7 @@
@optional
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName;
- (FLEXSQLResult *)executeStatement:(NSString *)SQLStatement;
@end
@@ -29,7 +29,7 @@
- (NSString *)rowTitle:(NSInteger)row;
- (NSArray<NSString *> *)contentForRow:(NSInteger)row;
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView widthForContentCellInColumn:(NSInteger)column;
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView minWidthForContentCellInColumn:(NSInteger)column;
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView heightForContentCellInRow:(NSInteger)row;
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
@@ -9,10 +9,12 @@
#import "FLEXMultiColumnTableView.h"
#import "FLEXDBQueryRowCell.h"
#import "FLEXTableLeftCell.h"
#import "NSArray+FLEX.h"
#import "FLEXColor.h"
@interface FLEXMultiColumnTableView () <
UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate
UITableViewDataSource, UITableViewDelegate,
UIScrollViewDelegate, FLEXDBQueryRowCellLayoutSource
>
@property (nonatomic) UIScrollView *contentScrollView;
@@ -21,12 +23,12 @@
@property (nonatomic) UITableView *contentTableView;
@property (nonatomic) UIView *leftHeader;
@property (nonatomic) NSArray<UIView *> *headerViews;
/// \c NSNotFound if no column selected
@property (nonatomic) NSInteger sortColumn;
@property (nonatomic) FLEXTableColumnHeaderSortType sortType;
@property (nonatomic) NSArray *rowData;
@property (nonatomic, readonly) NSInteger numberOfColumns;
@property (nonatomic, readonly) NSInteger numberOfRows;
@property (nonatomic, readonly) CGFloat topHeaderHeight;
@@ -71,9 +73,9 @@ static const CGFloat kColumnMargin = 1;
}
CGFloat contentWidth = 0.0;
NSInteger rowsCount = self.numberOfColumns;
for (int i = 0; i < rowsCount; i++) {
contentWidth += [self contentWidthForColumn:i];
NSInteger columnsCount = self.numberOfColumns;
for (int i = 0; i < columnsCount; i++) {
contentWidth += CGRectGetWidth(self.headerViews[i].bounds);
}
CGFloat contentHeight = height - topheaderHeight - topInsets;
@@ -147,26 +149,30 @@ static const CGFloat kColumnMargin = 1;
#pragma mark - Data
- (void)reloadData {
[self loadHeaderData];
[self loadLeftViewData];
[self loadContentData];
[self loadHeaderData];
}
- (void)loadHeaderData {
// Remove existing headers, if any
for (UIView *subview in self.headerScrollView.subviews) {
for (UIView *subview in self.headerViews) {
[subview removeFromSuperview];
}
CGFloat xOffset = 0.0;
for (NSInteger column = 0; column < self.numberOfColumns; column++) {
CGFloat width = [self contentWidthForColumn:column] + self.columnMargin;
FLEXTableColumnHeader *header = [[FLEXTableColumnHeader alloc]
initWithFrame:CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1)
];
__block CGFloat xOffset = 0;
self.headerViews = [NSArray flex_forEachUpTo:self.numberOfColumns map:^id(NSUInteger column) {
FLEXTableColumnHeader *header = [FLEXTableColumnHeader new];
header.titleLabel.text = [self columnTitle:column];
CGSize fittingSize = CGSizeMake(CGFLOAT_MAX, self.topHeaderHeight - 1);
CGFloat width = self.columnMargin + MAX(
[self minContentWidthForColumn:column],
[header sizeThatFits:fittingSize].width
);
header.frame = CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1);
if (column == self.sortColumn) {
header.sortType = self.sortType;
}
@@ -178,21 +184,22 @@ static const CGFloat kColumnMargin = 1;
[header addGestureRecognizer:gesture];
header.userInteractionEnabled = YES;
[self.headerScrollView addSubview:header];
xOffset += width;
}
[self.headerScrollView addSubview:header];
return header;
}];
}
- (void)contentHeaderTap:(UIGestureRecognizer *)gesture {
NSInteger newSortColumn = [self.headerScrollView.subviews indexOfObject:gesture.view];
NSInteger newSortColumn = [self.headerViews indexOfObject:gesture.view];
FLEXTableColumnHeaderSortType newType = FLEXNextTableColumnHeaderSortType(self.sortType);
// Reset old header
FLEXTableColumnHeader *oldHeader = (id)self.headerScrollView.subviews[self.sortColumn];
FLEXTableColumnHeader *oldHeader = (id)self.headerViews[self.sortColumn];
oldHeader.sortType = FLEXTableColumnHeaderSortTypeNone;
// Update new header
FLEXTableColumnHeader *newHeader = (id)self.headerScrollView.subviews[newSortColumn];
FLEXTableColumnHeader *newHeader = (id)self.headerViews[newSortColumn];
newHeader.sortType = newType;
// Update self
@@ -227,13 +234,13 @@ static const CGFloat kColumnMargin = 1;
}
// Right side table view for data
else {
self.rowData = [self.dataSource contentForRow:indexPath.row];
FLEXDBQueryRowCell *cell = [tableView
dequeueReusableCellWithIdentifier:kFLEXDBQueryRowCellReuse forIndexPath:indexPath
];
cell.contentView.backgroundColor = backgroundColor;
cell.data = [self.dataSource contentForRow:indexPath.row];
cell.layoutSource = self;
NSAssert(cell.data.count == self.numberOfColumns, @"Count of data provided was incorrect");
return cell;
}
@@ -280,6 +287,17 @@ static const CGFloat kColumnMargin = 1;
}
#pragma mark FLEXDBQueryRowCellLayoutSource
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell minXForColumn:(NSUInteger)column {
return CGRectGetMinX(self.headerViews[column].frame);
}
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell widthForColumn:(NSUInteger)column {
return CGRectGetWidth(self.headerViews[column].bounds);
}
#pragma mark DataSource Accessor
- (NSInteger)numberOfRows {
@@ -298,8 +316,8 @@ static const CGFloat kColumnMargin = 1;
return [self.dataSource rowTitle:row];
}
- (CGFloat)contentWidthForColumn:(NSInteger)column {
return [self.dataSource multiColumnTableView:self widthForContentCellInColumn:column];
- (CGFloat)minContentWidthForColumn:(NSInteger)column {
return [self.dataSource multiColumnTableView:self minWidthForContentCellInColumn:column];
}
- (CGFloat)contentHeightForRow:(NSInteger)row {
@@ -30,7 +30,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
- (instancetype)initWithPath:(NSString *)path {
self = [super init];
if (self) {
self.path = path;;
self.path = path;
}
return self;
@@ -114,9 +114,17 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
}
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
return [self executeStatement:[@"SELECT * FROM "
stringByAppendingString:tableName
]].rows ?: @[];
NSString *command = [NSString stringWithFormat:@"SELECT * FROM \"%@\"", tableName];
return [self executeStatement:command].rows ?: @[];
}
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName {
NSString *command = [NSString stringWithFormat:@"SELECT rowid FROM \"%@\"", tableName];
NSArray<NSArray<NSString *> *> *data = [self executeStatement:command].rows ?: @[];
return [data flex_mapped:^id(NSArray<NSString *> *obj, NSUInteger idx) {
return obj.firstObject;
}];
}
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
@@ -257,7 +265,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
- (FLEXSQLResult *)errorResult:(NSString *)description {
const char *error = sqlite3_errmsg(_db);
NSString *message = error ? @(error) : [NSString
stringWithFormat:@"(%@: empty error", description
stringWithFormat:@"(%@: empty error)", description
];
return [FLEXSQLResult error:message];
@@ -11,6 +11,9 @@
#import "UIFont+FLEX.h"
#import "FLEXUtility.h"
static const CGFloat kMargin = 5;
static const CGFloat kArrowWidth = 20;
@interface FLEXTableColumnHeader ()
@property (nonatomic, readonly) UILabel *arrowLabel;
@property (nonatomic, readonly) UIView *lineView;
@@ -60,9 +63,16 @@
CGSize size = self.frame.size;
self.titleLabel.frame = CGRectMake(5, 0, size.width - 25, size.height);
self.arrowLabel.frame = CGRectMake(size.width - 20, 0, 20, size.height);
self.titleLabel.frame = CGRectMake(kMargin, 0, size.width - kArrowWidth - kMargin, size.height);
self.arrowLabel.frame = CGRectMake(size.width - kArrowWidth, 0, kArrowWidth, size.height);
self.lineView.frame = CGRectMake(size.width - 1, 2, FLEXPointsToPixels(1), size.height - 4);
}
- (CGSize)sizeThatFits:(CGSize)size {
CGFloat margins = kArrowWidth - 2 * kMargin;
size = CGSizeMake(size.width - margins, size.height);
CGFloat width = [_titleLabel sizeThatFits:size].width + margins;
return CGSizeMake(width, size.height);
}
@end
@@ -7,10 +7,20 @@
//
#import <UIKit/UIKit.h>
#import "FLEXDatabaseManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXTableContentViewController : UIViewController
/// Display a table with the given columns, rows, and name.
/// @param databaseManager an optional manager to allow modifying the table.
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData;
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(nullable NSArray<NSString *> *)rowIds
tableName:(NSString *)tableName
database:(nullable id<FLEXDatabaseManager>)databaseManager;
@end
NS_ASSUME_NONNULL_END
@@ -10,12 +10,16 @@
#import "FLEXMultiColumnTableView.h"
#import "FLEXWebViewController.h"
#import "FLEXUtility.h"
#import "UIBarButtonItem+FLEX.h"
@interface FLEXTableContentViewController () <
FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate
>
@property (nonatomic, readonly) NSArray<NSString *> *columns;
@property (nonatomic, copy) NSArray<NSArray *> *rows;
@property (nonatomic) NSMutableArray<NSArray *> *rows;
@property (nonatomic, readonly) NSString *tableName;
@property (nonatomic, nullable) NSMutableArray<NSString *> *rowIDs;
@property (nonatomic, readonly, nullable) id<FLEXDatabaseManager> databaseManager;
@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
@end
@@ -23,10 +27,16 @@
@implementation FLEXTableContentViewController
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData {
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(nullable NSArray<NSString *> *)rowIDs
tableName:(NSString *)tableName
database:(nullable id<FLEXDatabaseManager>)databaseManager {
FLEXTableContentViewController *controller = [self new];
controller->_columns = columnNames;
controller->_rows = rowData;
controller->_columns = columnNames.copy;
controller->_rows = rowData.mutableCopy;
controller->_rowIDs = rowIDs.mutableCopy;
controller->_tableName = tableName.copy;
controller->_databaseManager = databaseManager;
return controller;
}
@@ -38,9 +48,10 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.title = self.tableName;
self.edgesForExtendedLayout = UIRectEdgeNone;
[self.multiColumnView reloadData];
[self setupToolbarItems];
}
- (FLEXMultiColumnTableView *)multiColumnView {
@@ -84,8 +95,8 @@
}
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
widthForContentCellInColumn:(NSInteger)column {
return 120;
minWidthForContentCellInColumn:(NSInteger)column {
return 100;
}
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView {
@@ -118,6 +129,27 @@
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = message;
});
// Option to delete row
BOOL hasRowID = self.rows.count && row < self.rows.count;
if (hasRowID) {
make.button(@"Delete").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
NSString *deleteRow = [NSString stringWithFormat:
@"DELETE FROM %@ WHERE rowid = %@",
self.tableName, self.rowIDs[row]
];
[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];
}
}];
});
}
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
}
@@ -127,7 +159,8 @@
sortType:(FLEXTableColumnHeaderSortType)sortType {
NSArray<NSArray *> *sortContentData = [self.rows
sortedArrayUsingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
sortedArrayWithOptions:NSSortStable
usingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
id a = obj1[column], b = obj2[column];
if (a == NSNull.null) {
return NSOrderedAscending;
@@ -135,6 +168,11 @@
if (b == NSNull.null) {
return NSOrderedDescending;
}
if ([a respondsToSelector:@selector(compare:options:)] &&
[b respondsToSelector:@selector(compare:options:)]) {
return [a compare:b options:NSNumericSearch];
}
if ([a respondsToSelector:@selector(compare:)] && [b respondsToSelector:@selector(compare:)]) {
return [a compare:b];
@@ -148,12 +186,11 @@
sortContentData = sortContentData.reverseObjectEnumerator.allObjects.copy;
}
self.rows = sortContentData;
self.rows = sortContentData.mutableCopy;
[self.multiColumnView reloadData];
}
#pragma mark -
#pragma mark About Transition
#pragma mark - About Transition
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
@@ -171,4 +208,57 @@
} completion:nil];
}
#pragma mark - Toolbar
- (void)setupToolbarItems {
// We do not support modifying realm databases
if (![self.databaseManager respondsToSelector:@selector(executeStatement:)]) {
return;
}
UIBarButtonItem *trashButton = [FLEXBarButtonItemSystem(Trash, self, @selector(trashPressed))
flex_withTintColor:UIColor.redColor
];
trashButton.enabled = self.databaseManager && self.rows.count;
self.toolbarItems = @[UIBarButtonItem.flex_flexibleSpace, trashButton];
}
- (void)trashPressed {
[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?");
make.button(@"Yes, I'm sure").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
NSString *deleteAll = [NSString stringWithFormat:@"DELETE FROM %@", self.tableName];
[self executeStatementAndShowResult:deleteAll completion:^(BOOL success) {
// Only dismiss on success
if (success) {
[self.navigationController popViewControllerAnimated:YES];
}
}];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
}
#pragma mark - Helpers
- (void)executeStatementAndShowResult:(NSString *)statement completion:(void (^_Nullable)(BOOL success))completion {
FLEXSQLResult *result = [self.databaseManager executeStatement:statement];
[FLEXAlert makeAlert:^(FLEXAlert *make) {
if (result.isError) {
make.title(@"Error");
}
make.message(result.message ?: @"<no output>");
make.button(@"Dismiss").cancelStyle().handler(^(NSArray<NSString *> *_) {
if (completion) {
completion(!result.isError);
}
});
} showFrom:self];
}
@end
@@ -14,6 +14,7 @@
#import "FLEXMutableListSection.h"
#import "NSArray+FLEX.h"
#import "FLEXAlert.h"
#import "FLEXMacros.h"
@interface FLEXTableListViewController ()
@property (nonatomic, readonly) id<FLEXDatabaseManager> dbm;
@@ -70,9 +71,10 @@
self.tables.selectionHandler = ^(FLEXTableListViewController *host, NSString *tableName) {
NSArray *rows = [host.dbm queryAllDataInTable:tableName];
NSArray *columns = [host.dbm queryAllColumnsOfTable:tableName];
UIViewController *resultsScreen = [FLEXTableContentViewController columns:columns rows:rows];
resultsScreen.title = tableName;
NSArray *rowIDs = [host.dbm queryRowIDsInTable:tableName];
UIViewController *resultsScreen = [FLEXTableContentViewController
columns:columns rows:rows rowIDs:rowIDs tableName:tableName database:host.dbm
];
[host.navigationController pushViewController:resultsScreen animated:YES];
};
@@ -100,7 +102,7 @@
[FLEXAlert showAlert:@"Message" message:result.message from:self];
} else {
UIViewController *resultsScreen = [FLEXTableContentViewController
columns:result.columns rows:result.rows
columns:result.columns rows:result.rows rowIDs:nil tableName:@"" database:nil
];
[self.navigationController pushViewController:resultsScreen animated:YES];
@@ -35,6 +35,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
self.showsSearchBar = YES;
self.showSearchBarInitially = YES;
self.activatesSearchBarAutomatically = YES;
self.searchBarDebounceInterval = kFLEXDebounceInstant;
self.showsCarousel = YES;
self.carousel.items = @[@"A→Z", @"Count", @"Size"];
@@ -45,15 +46,6 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
[self reloadTableData];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
dispatch_async(dispatch_get_main_queue(), ^{
// This doesn't work unless it's wrapped in this dispatch_async call
[self.searchController.searchBar becomeFirstResponder];
});
}
- (NSArray<NSString *> *)allClassNames {
return self.instanceCountsForClassNames.allKeys;
}
@@ -38,7 +38,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
@property (nonatomic, copy) NSArray<FLEXMutableListSection *> *sections;
@property (nonatomic, copy) NSArray<FLEXMutableListSection *> *allSections;
@property (nonatomic, readonly) NSArray<FLEXObjectRef *> *references;
@property (nonatomic, readonly, nullable) NSArray<FLEXObjectRef *> *references;
@property (nonatomic, readonly) NSArray<NSPredicate *> *predicates;
@property (nonatomic, readonly) NSArray<NSString *> *sectionTitles;
@@ -122,7 +122,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
#pragma mark - Initialization
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references {
- (id)initWithReferences:(nullable NSArray<FLEXObjectRef *> *)references {
return [self initWithReferences:references predicates:nil sectionTitles:nil];
}
@@ -25,7 +25,7 @@
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
if (@available(iOS 10.0, *)) {
configuration.dataDetectorTypes = UIDataDetectorTypeLink;
configuration.dataDetectorTypes = WKDataDetectorTypeLink;
}
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
@@ -356,8 +356,6 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
// Since our actions are outside of that protocol, we need to manually handle the action forwarding from the cells.
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
@@ -395,8 +393,6 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
];
}
#endif
- (void)openFileController:(NSString *)fullPath {
UIDocumentInteractionController *controller = [UIDocumentInteractionController new];
controller.URL = [NSURL fileURLWithPath:fullPath];
@@ -474,6 +470,9 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
} else {
// Share sheet for files
UIActivityViewController *shareSheet = [[UIActivityViewController alloc] initWithActivityItems:@[filePath] applicationActivities:nil];
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
shareSheet.popoverPresentationController.sourceView = sender;
}
[self presentViewController:shareSheet animated:true completion:nil];
}
}
@@ -34,6 +34,9 @@ extern NSString *const kFLEXKeychainClassKey;
/// Item description.
extern NSString *const kFLEXKeychainDescriptionKey;
/// Item group.
extern NSString *const kFLEXKeychainGroupKey;
/// Item label.
extern NSString *const kFLEXKeychainLabelKey;
@@ -15,6 +15,7 @@ NSString * const kFLEXKeychainAccountKey = @"acct";
NSString * const kFLEXKeychainCreatedAtKey = @"cdat";
NSString * const kFLEXKeychainClassKey = @"labl";
NSString * const kFLEXKeychainDescriptionKey = @"desc";
NSString * const kFLEXKeychainGroupKey = @"agrp";
NSString * const kFLEXKeychainLabelKey = @"labl";
NSString * const kFLEXKeychainLastModifiedKey = @"mdat";
NSString * const kFLEXKeychainWhereKey = @"svce";
@@ -46,7 +46,7 @@
id service = item[kFLEXKeychainWhereKey];
if ([service isKindOfClass:[NSString class]]) {
cell.textLabel.text = service;
cell.detailTextLabel.text = item[kFLEXKeychainAccountKey];
cell.detailTextLabel.text = [item[kFLEXKeychainAccountKey] description];
} else {
cell.textLabel.text = [NSString stringWithFormat:
@"[%@]\n\n%@",
@@ -99,8 +99,9 @@
NSDictionary *item = self.section.filteredList[idx];
FLEXKeychainQuery *query = [FLEXKeychainQuery new];
query.service = item[kFLEXKeychainWhereKey];
query.account = item[kFLEXKeychainAccountKey];
query.service = [item[kFLEXKeychainWhereKey] description];
query.account = [item[kFLEXKeychainAccountKey] description];
query.accessGroup = [item[kFLEXKeychainGroupKey] description];
[query fetch:nil];
return query;
@@ -232,6 +233,7 @@
make.message(@"Service: ").message(query.service);
make.message(@"\nAccount: ").message(query.account);
make.message(@"\nPassword: ").message(query.password);
make.message(@"\nGroup: ").message(query.accessGroup);
make.button(@"Copy Service").handler(^(NSArray<NSString *> *strings) {
[UIPasteboard.generalPasteboard flex_copy:query.service];
@@ -51,7 +51,7 @@
[self sizeToFit];
if (@available(iOS 13, *)) {
self.appearance = UIKeyboardTypeDefault;
self.appearance = UIKeyboardAppearanceDefault;
} else {
self.appearance = UIKeyboardAppearanceLight;
}
@@ -84,7 +84,6 @@
switch (_appearance) {
default:
case UIKeyboardAppearanceDefault:
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
titleColor = UIColor.labelColor;
@@ -97,7 +96,6 @@
}
break;
}
#endif
case UIKeyboardAppearanceLight:
titleColor = UIColor.blackColor;
backgroundColor = lightColor;
@@ -61,6 +61,7 @@
searchBar.keyboardType = UIKeyboardTypeWebSearch;
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
if (@available(iOS 11, *)) {
searchBar.smartQuotesType = UITextSmartQuotesTypeNo;
searchBar.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo;
}
@@ -40,7 +40,7 @@
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if (@available(iOS 13, *)) {
self.appearance = UIKeyboardTypeDefault;
self.appearance = UIKeyboardAppearanceDefault;
} else {
self.appearance = UIKeyboardAppearanceLight;
}
@@ -106,7 +106,6 @@
switch (_appearance) {
case UIKeyboardAppearanceDefault:
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
borderColor = UIColor.systemBackgroundColor;
@@ -119,7 +118,6 @@
}
break;
}
#endif
case UIKeyboardAppearanceLight: {
borderColor = UIColor.clearColor;
backgroundColor = lightColor;
@@ -10,10 +10,12 @@
#import "FLEXKeyPathSearchController.h"
#import "FLEXRuntimeBrowserToolbar.h"
#import "UIGestureRecognizer+Blocks.h"
#import "UIBarButtonItem+FLEX.h"
#import "FLEXTableView.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXAlert.h"
#import "FLEXRuntimeClient.h"
#import <dlfcn.h>
@interface FLEXObjcRuntimeViewController () <FLEXKeyPathSearchControllerDelegate>
@@ -43,9 +45,12 @@
]
];
[self addToolbarItems:@[FLEXBarButtonItem(@"dlopen()", self, @selector(dlopenPressed:))]];
// Search bar stuff, must be first because this creates self.searchController
self.showsSearchBar = YES;
self.showSearchBarInitially = YES;
self.activatesSearchBarAutomatically = YES;
// Using pinSearchBar on this screen causes a weird visual
// thing on the next view controller that gets pushed.
//
@@ -72,13 +77,61 @@
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
dispatch_async(dispatch_get_main_queue(), ^{
// This doesn't work unless it's wrapped in this dispatch_async call
[self.searchController.searchBar becomeFirstResponder];
});
#pragma mark dlopen
/// Prompt user for dlopen shortcuts to choose from
- (void)dlopenPressed:(id)sender {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Dynamically Open Library");
make.message(@"Invoke dlopen() with the given path. Choose an option below.");
make.button(@"System Framework").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:@"/System/Library/Frameworks/%@.framework/%@"];
});
make.button(@"System Private Framework").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:@"/System/Library/PrivateFrameworks/%@.framework/%@"];
});
make.button(@"Arbitrary Binary").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:nil];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
}
/// Prompt user for input and dlopen
- (void)dlopenWithFormat:(NSString *)format {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Dynamically Open Library");
if (format) {
make.message(@"Pass in a framework name, such as CarKit or FrontBoard.");
} else {
make.message(@"Pass in an absolute path to a binary.");
}
make.textField(format ? @"ARKit" : @"/System/Library/Frameworks/ARKit.framework/ARKit");
make.button(@"Cancel").cancelStyle();
make.button(@"Open").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
NSString *path = strings[0];
if (path.length < 2) {
[self dlopenInvalidPath];
} else if (format) {
path = [NSString stringWithFormat:format, path, path];
}
dlopen(path.UTF8String, RTLD_NOW);
});
} showFrom:self];
}
- (void)dlopenInvalidPath {
[FLEXAlert makeAlert:^(FLEXAlert * _Nonnull make) {
make.title(@"Path or Name Too Short");
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
}
@@ -266,8 +266,6 @@ static BOOL my_os_log_shim_enabled(void *addr) {
}
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
@@ -286,6 +284,4 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
];
}
#endif
@end
+2 -2
View File
@@ -21,10 +21,10 @@ NS_ASSUME_NONNULL_BEGIN
/// The response cache uses an NSCache, so it may purge prior to hitting the limit when the app is under memory pressure.
@property (nonatomic) NSUInteger networkResponseCacheByteLimit;
/// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
/// Requests whose host ends with one of the excluded entries in this array will be not be recorded (eg. google.com).
/// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
/// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
@property (nonatomic) NSMutableArray<NSString *> *networkRequestHostBlacklist;
@property (nonatomic) NSMutableArray<NSString *> *networkRequestHostDenylist;
/// Sets custom viewer for specific content type.
/// @param contentType Mime type like application/json
+4 -4
View File
@@ -48,12 +48,12 @@
FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit = networkResponseCacheByteLimit;
}
- (NSMutableArray<NSString *> *)networkRequestHostBlacklist {
return FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
- (NSMutableArray<NSString *> *)networkRequestHostDenylist {
return FLEXNetworkRecorder.defaultRecorder.hostDenylist;
}
- (void)setNetworkRequestHostBlacklist:(NSMutableArray<NSString *> *)networkRequestHostBlacklist {
FLEXNetworkRecorder.defaultRecorder.hostBlacklist = networkRequestHostBlacklist;
- (void)setNetworkRequestHostDenylist:(NSMutableArray<NSString *> *)networkRequestHostDenylist {
FLEXNetworkRecorder.defaultRecorder.hostDenylist = networkRequestHostDenylist;
}
- (void)setCustomViewerForContentType:(NSString *)contentType
-4
View File
@@ -8,10 +8,6 @@
#import "FLEXExplorerToolbar.h"
#if !FLEX_AT_LEAST_IOS13_SDK
@class UIWindowScene;
#endif
NS_ASSUME_NONNULL_BEGIN
@interface FLEXManager : NSObject
+9 -8
View File
@@ -49,7 +49,7 @@
NSAssert(NSThread.isMainThread, @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
if (!_explorerWindow) {
_explorerWindow = [[FLEXWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
_explorerWindow = [[FLEXWindow alloc] initWithFrame:FLEXUtility.appKeyWindow.bounds];
_explorerWindow.eventDelegate = self;
_explorerWindow.rootViewController = self.explorerViewController;
}
@@ -69,14 +69,12 @@
- (void)showExplorer {
UIWindow *flex = self.explorerWindow;
flex.hidden = NO;
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
// Only look for a new scene if we don't have one
if (!flex.windowScene) {
flex.windowScene = FLEXUtility.activeScene;
flex.windowScene = FLEXUtility.appKeyWindow.windowScene;
}
}
#endif
}
- (void)hideExplorer {
@@ -85,7 +83,11 @@
- (void)toggleExplorer {
if (self.explorerWindow.isHidden) {
[self showExplorer];
if (@available(iOS 13.0, *)) {
[self showExplorerFromScene:FLEXUtility.appKeyWindow.windowScene];
} else {
[self showExplorer];
}
} else {
[self hideExplorer];
}
@@ -100,15 +102,14 @@
}
- (void)presentTool:(UINavigationController * _Nonnull (^)(void))future completion:(void (^)(void))completion {
[self.explorerViewController toggleToolWithViewControllerProvider:future completion:completion];
[self showExplorer];
[self.explorerViewController presentTool:future completion:completion];
}
- (void)showExplorerFromScene:(UIWindowScene *)scene {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
self.explorerWindow.windowScene = scene;
}
#endif
self.explorerWindow.hidden = NO;
}
@@ -0,0 +1,17 @@
//
// FLEXHTTPTransactionDetailController.h
// Flipboard
//
// Created by Ryan Olson on 2/10/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FLEXHTTPTransaction;
@interface FLEXHTTPTransactionDetailController : UITableViewController
+ (instancetype)withTransaction:(FLEXHTTPTransaction *)transaction;
@end
@@ -7,7 +7,7 @@
//
#import "FLEXColor.h"
#import "FLEXNetworkTransactionDetailController.h"
#import "FLEXHTTPTransactionDetailController.h"
#import "FLEXNetworkCurlLogger.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkTransaction.h"
@@ -22,64 +22,63 @@
typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@interface FLEXNetworkDetailRow : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *detailText;
@property (nonatomic, copy) FLEXNetworkDetailRowSelectionFuture selectionFuture;
@end
@implementation FLEXNetworkDetailRow
@end
@interface FLEXNetworkDetailSection : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
@end
@implementation FLEXNetworkDetailSection
@end
@interface FLEXNetworkTransactionDetailController ()
@interface FLEXHTTPTransactionDetailController ()
@property (nonatomic, readonly) FLEXHTTPTransaction *transaction;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
@end
@implementation FLEXNetworkTransactionDetailController
@implementation FLEXHTTPTransactionDetailController
+ (instancetype)withTransaction:(FLEXHTTPTransaction *)transaction {
FLEXHTTPTransactionDetailController *controller = [self new];
controller.transaction = transaction;
return controller;
}
- (instancetype)initWithStyle:(UITableViewStyle)style {
// Force grouped style
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(handleTransactionUpdatedNotification:)
name:kFLEXNetworkRecorderTransactionUpdatedNotification
object:nil
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
flex_itemWithTitle:@"Copy curl"
target:self
action:@selector(copyButtonPressed:)
]
];
}
return self;
return [super initWithStyle:UITableViewStyleGrouped];
}
- (void)viewDidLoad {
[super viewDidLoad];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(handleTransactionUpdatedNotification:)
name:kFLEXNetworkRecorderTransactionUpdatedNotification
object:nil
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
flex_itemWithTitle:@"Copy curl"
target:self
action:@selector(copyButtonPressed:)
]
];
[self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineCell];
}
- (void)setTransaction:(FLEXNetworkTransaction *)transaction {
- (void)setTransaction:(FLEXHTTPTransaction *)transaction {
if (![_transaction isEqual:transaction]) {
_transaction = transaction;
self.title = [transaction.request.URL lastPathComponent];
@@ -189,6 +188,12 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
];
}
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [NSArray flex_forEachUpTo:self.sections.count map:^id(NSUInteger i) {
return @"";
}];
}
- (FLEXNetworkDetailRow *)rowModelAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkDetailSection *sectionModel = self.sections[indexPath.section];
return sectionModel.rows[indexPath.row];
@@ -211,8 +216,6 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
}
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
@@ -236,8 +239,6 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
];
}
#endif
#pragma mark - View Configuration
+ (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row {
@@ -257,7 +258,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
#pragma mark - Table Data Generation
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXHTTPTransaction *)transaction {
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
FLEXNetworkDetailRow *requestURLRow = [FLEXNetworkDetailRow new];
@@ -411,7 +412,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return generalSection;
}
+ (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *requestHeadersSection = [FLEXNetworkDetailSection new];
requestHeadersSection.title = @"Request Headers";
requestHeadersSection.rows = [self networkDetailRowsFromDictionary:transaction.request.allHTTPHeaderFields];
@@ -419,7 +420,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return requestHeadersSection;
}
+ (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *postBodySection = [FLEXNetworkDetailSection new];
postBodySection.title = @"Request Body Parameters";
if (transaction.cachedRequestBody.length > 0) {
@@ -433,7 +434,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return postBodySection;
}
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
NSArray<NSURLQueryItem *> *queries = [FLEXUtility itemsFromQueryString:transaction.request.URL.query];
FLEXNetworkDetailSection *querySection = [FLEXNetworkDetailSection new];
querySection.title = @"Query Parameters";
@@ -442,7 +443,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return querySection;
}
+ (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *responseHeadersSection = [FLEXNetworkDetailSection new];
responseHeadersSection.title = @"Response Headers";
if ([transaction.response isKindOfClass:[NSHTTPURLResponse class]]) {
@@ -520,7 +521,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return detailViewController;
}
+ (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction {
+ (NSData *)postBodyDataForTransaction:(FLEXHTTPTransaction *)transaction {
NSData *bodyData = transaction.cachedRequestBody;
if (bodyData.length > 0) {
NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
+34
View File
@@ -0,0 +1,34 @@
//
// FLEXMITMDataSource.h
// FLEX
//
// Created by Tanner Bennett on 8/22/21.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FLEXMITMDataSource<__covariant TransactionType> : NSObject
+ (instancetype)dataSourceWithProvider:(NSArray<TransactionType> *(^)())future;
@property (nonatomic, readonly) NSArray<TransactionType> *transactions;
@property (nonatomic, readonly) NSArray<TransactionType> *allTransactions;
/// Equal to \c allTransactions if not filtered
@property (nonatomic, readonly) NSArray<TransactionType> *filteredTransactions;
/// Use this instead of either of the other two as it updates based on whether we have a filter or not
@property (nonatomic) NSInteger bytesReceived;
@property (nonatomic) NSInteger totalBytesReceived;
/// Equal to \c totalBytesReceived if not filtered
@property (nonatomic) NSInteger filteredBytesReceived;
- (void)reloadByteCounts;
- (void)reloadData:(void (^_Nullable)(FLEXMITMDataSource *dataSource))completion;
- (void)filter:(NSString *)searchString completion:(void(^_Nullable)(FLEXMITMDataSource *dataSource))completion;
@end
NS_ASSUME_NONNULL_END
+102
View File
@@ -0,0 +1,102 @@
//
// FLEXMITMDataSource.m
// FLEX
//
// Created by Tanner Bennett on 8/22/21.
//
#import "FLEXMITMDataSource.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXUtility.h"
@interface FLEXMITMDataSource ()
@property (nonatomic, readonly) NSArray *(^dataProvider)();
@property (nonatomic) NSString *filterString;
@end
@implementation FLEXMITMDataSource
+ (instancetype)dataSourceWithProvider:(NSArray<id> *(^)())future {
FLEXMITMDataSource *ds = [self new];
ds->_dataProvider = future;
[ds reloadData:nil];
return ds;
}
- (NSArray *)transactions {
return _filteredTransactions;
}
- (NSInteger)bytesReceived {
return _filteredBytesReceived;
}
- (void)reloadByteCounts {
[self updateBytesReceived];
[self updateFilteredBytesReceived];
}
- (void)reloadData:(void (^)(FLEXMITMDataSource *dataSource))completion {
self.allTransactions = self.dataProvider();
[self filter:self.filterString completion:completion];
}
- (void)filter:(NSString *)searchString completion:(void (^)(FLEXMITMDataSource *dataSource))completion {
self.filterString = searchString;
if (!searchString.length) {
self.filteredTransactions = self.allTransactions;
if (completion) completion(self);
} else {
[self onBackgroundQueue:^NSArray *{
return [self.allTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) {
return [entry.request.URL.absoluteString localizedCaseInsensitiveContainsString:searchString];
}];
} thenOnMainQueue:^(NSArray *filteredNetworkTransactions) {
if ([self.filterString isEqual:searchString]) {
self.filteredTransactions = filteredNetworkTransactions;
if (completion) completion(self);
}
}];
}
}
- (void)setAllTransactions:(NSArray *)transactions {
_allTransactions = transactions;
[self updateBytesReceived];
}
- (void)setFilteredTransactions:(NSArray *)filteredTransactions {
_filteredTransactions = filteredTransactions;
[self updateFilteredBytesReceived];
}
- (void)updateBytesReceived {
NSInteger bytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.transactions) {
bytesReceived += transaction.receivedDataLength;
}
self.bytesReceived = bytesReceived;
}
- (void)updateFilteredBytesReceived {
NSInteger filteredBytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.filteredTransactions) {
filteredBytesReceived += transaction.receivedDataLength;
}
self.filteredBytesReceived = filteredBytesReceived;
}
- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *items = backgroundBlock();
dispatch_async(dispatch_get_main_queue(), ^{
mainBlock(items);
});
});
}
@end
@@ -9,6 +9,7 @@
#import "FLEXTableViewController.h"
#import "FLEXGlobalsEntry.h"
/// The main screen for the network observer, which displays a list of network transactions.
@interface FLEXNetworkMITMViewController : FLEXTableViewController <FLEXGlobalsEntry>
@end
+171 -131
View File
@@ -8,29 +8,34 @@
#import "FLEXColor.h"
#import "FLEXUtility.h"
#import "FLEXMITMDataSource.h"
#import "FLEXNetworkMITMViewController.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkTransactionCell.h"
#import "FLEXNetworkTransactionDetailController.h"
#import "FLEXHTTPTransactionDetailController.h"
#import "FLEXNetworkSettingsController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXGlobalsViewController.h"
#import "FLEXWebViewController.h"
#import "UIBarButtonItem+FLEX.h"
#import "FLEXResources.h"
typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
FLEXNetworkObserverModeREST = 0,
FLEXNetworkObserverModeWebsockets,
};
@interface FLEXNetworkMITMViewController ()
/// Backing model
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *networkTransactions;
@property (nonatomic) long long bytesReceived;
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions;
@property (nonatomic) long long filteredBytesReceived;
@property (nonatomic) BOOL rowInsertInProgress;
@property (nonatomic) BOOL isPresentingSearch;
@property (nonatomic) BOOL updateInProgress;
@property (nonatomic) BOOL pendingReload;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXNetworkTransaction *> *dataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXHTTPTransaction *> *HTTPDataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXWebsocketTransaction *> *websocketDataSource;
@end
@implementation FLEXNetworkMITMViewController
@@ -47,6 +52,18 @@
self.showsSearchBar = YES;
self.showSearchBarInitially = NO;
_HTTPDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * {
return FLEXNetworkRecorder.defaultRecorder.HTTPTransactions;
}];
if (@available(iOS 13.0, *)) {
self.searchController.searchBar.showsScopeBar = YES;
self.searchController.searchBar.scopeButtonTitles = @[@"REST", @"Websockets"];
_websocketDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * {
return FLEXNetworkRecorder.defaultRecorder.websocketTransactions;
}];
}
[self addToolbarItems:@[
[UIBarButtonItem
flex_itemWithImage:FLEXResources.gearIcon
@@ -61,14 +78,14 @@
]];
[self.tableView
registerClass:[FLEXNetworkTransactionCell class]
forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier
registerClass:FLEXNetworkTransactionCell.class
forCellReuseIdentifier:FLEXNetworkTransactionCell.reuseID
];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.rowHeight = FLEXNetworkTransactionCell.preferredCellHeight;
[self registerForNotifications];
[self updateTransactions];
[self updateTransactions:nil];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -137,44 +154,32 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark Transactions
- (void)updateTransactions {
self.networkTransactions = [FLEXNetworkRecorder.defaultRecorder networkTransactions];
}
- (void)setNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions {
if (![_networkTransactions isEqual:networkTransactions]) {
_networkTransactions = networkTransactions;
[self updateBytesReceived];
[self updateFilteredBytesReceived];
- (FLEXMITMDataSource<FLEXNetworkTransaction *> *)dataSource {
switch (self.searchController.searchBar.selectedScopeButtonIndex) {
case FLEXNetworkObserverModeREST:
return self.HTTPDataSource;
case FLEXNetworkObserverModeWebsockets:
return self.websocketDataSource;
default:
@throw NSInternalInconsistencyException;
}
}
- (void)updateBytesReceived {
long long bytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.networkTransactions) {
bytesReceived += transaction.receivedDataLength;
}
self.bytesReceived = bytesReceived;
[self updateFirstSectionHeader];
- (void)updateTransactions:(void(^)())callback {
id completion = ^(FLEXMITMDataSource *dataSource) {
// Update byte count
[self updateFirstSectionHeader];
if (callback && dataSource == self.dataSource) callback();
};
[self.HTTPDataSource reloadData:completion];
[self.websocketDataSource reloadData:completion];
}
- (void)setFilteredNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions {
if (![_filteredNetworkTransactions isEqual:networkTransactions]) {
_filteredNetworkTransactions = networkTransactions;
[self updateFilteredBytesReceived];
}
}
- (void)updateFilteredBytesReceived {
long long filteredBytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.filteredNetworkTransactions) {
filteredBytesReceived += transaction.receivedDataLength;
}
self.filteredBytesReceived = filteredBytesReceived;
[self updateFirstSectionHeader];
}
#pragma mark Header
@@ -191,11 +196,11 @@
long long bytesReceived = 0;
NSInteger totalRequests = 0;
if (self.searchController.isActive) {
bytesReceived = self.filteredBytesReceived;
totalRequests = self.filteredNetworkTransactions.count;
bytesReceived = self.dataSource.filteredBytesReceived;
totalRequests = self.dataSource.transactions.count;
} else {
bytesReceived = self.bytesReceived;
totalRequests = self.networkTransactions.count;
bytesReceived = self.dataSource.bytesReceived;
totalRequests = self.dataSource.transactions.count;
}
NSString *byteCountText = [NSByteCountFormatter
@@ -253,7 +258,7 @@
- (void)tryUpdateTransactions {
// Don't do any view updating if we aren't in the view hierarchy
if (!self.viewIfLoaded.window) {
[self updateTransactions];
[self updateTransactions:nil];
self.pendingReload = YES;
return;
}
@@ -261,57 +266,71 @@
// Let the previous row insert animation finish before starting a new one to avoid stomping.
// We'll try calling the method again when the insertion completes,
// and we properly no-op if there haven't been changes.
if (self.rowInsertInProgress) {
if (self.updateInProgress) {
return;
}
if (self.searchController.isActive) {
[self updateTransactions];
[self updateSearchResults:self.searchText];
return;
}
self.updateInProgress = YES;
NSInteger existingRowCount = self.networkTransactions.count;
[self updateTransactions];
NSInteger newRowCount = self.networkTransactions.count;
NSInteger addedRowCount = newRowCount - existingRowCount;
if (addedRowCount != 0 && !self.isPresentingSearch) {
// Insert animation if we're at the top.
if (self.tableView.contentOffset.y <= 0.0 && addedRowCount > 0) {
[CATransaction begin];
self.rowInsertInProgress = YES;
[CATransaction setCompletionBlock:^{
self.rowInsertInProgress = NO;
[self tryUpdateTransactions];
}];
NSMutableArray<NSIndexPath *> *indexPathsToReload = [NSMutableArray new];
for (NSInteger row = 0; row < addedRowCount; row++) {
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]];
}
[self.tableView insertRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[CATransaction commit];
} else {
// Maintain the user's position if they've scrolled down.
CGSize existingContentSize = self.tableView.contentSize;
[self.tableView reloadData];
CGFloat contentHeightChange = self.tableView.contentSize.height - existingContentSize.height;
self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + contentHeightChange);
// Get state before update
NSString *currentFilter = self.searchText;
FLEXNetworkObserverMode currentMode = self.searchController.searchBar.selectedScopeButtonIndex;
NSInteger existingRowCount = self.dataSource.transactions.count;
[self updateTransactions:^{
// Compare to state after update
NSString *newFilter = self.searchText;
FLEXNetworkObserverMode newMode = self.searchController.searchBar.selectedScopeButtonIndex;
NSInteger newRowCount = self.dataSource.transactions.count;
NSInteger rowCountDiff = newRowCount - existingRowCount;
// Abort if the observation mode changed, or if the search field text changed
if (newMode != currentMode || ![currentFilter isEqualToString:newFilter]) {
self.updateInProgress = NO;
return;
}
}
if (rowCountDiff) {
// Insert animation if we're at the top.
if (self.tableView.contentOffset.y <= 0.0 && rowCountDiff > 0) {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
self.updateInProgress = NO;
// This isn't an infinite loop, it won't run a third time
// if there were no new transactions the second time
[self tryUpdateTransactions];
}];
NSMutableArray<NSIndexPath *> *indexPathsToReload = [NSMutableArray new];
for (NSInteger row = 0; row < rowCountDiff; row++) {
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]];
}
[self.tableView insertRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[CATransaction commit];
} else {
// Maintain the user's position if they've scrolled down.
CGSize existingContentSize = self.tableView.contentSize;
[self.tableView reloadData];
CGFloat contentHeightChange = self.tableView.contentSize.height - existingContentSize.height;
self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + contentHeightChange);
self.updateInProgress = NO;
}
} else {
self.updateInProgress = NO;
}
}];
}
- (void)handleTransactionUpdatedNotification:(NSNotification *)notification {
[self updateBytesReceived];
[self updateFilteredBytesReceived];
[self.HTTPDataSource reloadByteCounts];
[self.websocketDataSource reloadByteCounts];
FLEXNetworkTransaction *transaction = notification.userInfo[kFLEXNetworkRecorderUserInfoTransactionKey];
// Update both the main table view and search table view if needed.
for (FLEXNetworkTransactionCell *cell in [self.tableView visibleCells]) {
for (FLEXNetworkTransactionCell *cell in self.tableView.visibleCells) {
if ([cell.transaction isEqual:transaction]) {
// Using -[UITableView reloadRowsAtIndexPaths:withRowAnimation:] is overkill here and kicks off a lot of
// work that can make the table view somewhat unresponsive when lots of updates are streaming in.
@@ -320,12 +339,14 @@
break;
}
}
[self updateFirstSectionHeader];
}
- (void)handleTransactionsClearedNotification:(NSNotification *)notification {
[self updateTransactions];
[self.tableView reloadData];
[self updateTransactions:^{
[self.tableView reloadData];
}];
}
- (void)handleNetworkObserverEnabledStateChangedNotification:(NSNotification *)notification {
@@ -337,7 +358,7 @@
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.searchController.isActive ? self.filteredNetworkTransactions.count : self.networkTransactions.count;
return self.dataSource.transactions.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
@@ -352,7 +373,11 @@
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkTransactionCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath];
FLEXNetworkTransactionCell *cell = [tableView
dequeueReusableCellWithIdentifier:FLEXNetworkTransactionCell.reuseID
forIndexPath:indexPath
];
cell.transaction = [self transactionAtIndexPath:indexPath];
// Since we insert from the top, assign background colors bottom up to keep them consistent for each transaction.
@@ -367,9 +392,33 @@
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkTransactionDetailController *detailViewController = [FLEXNetworkTransactionDetailController new];
detailViewController.transaction = [self transactionAtIndexPath:indexPath];
[self.navigationController pushViewController:detailViewController animated:YES];
switch (self.searchController.searchBar.selectedScopeButtonIndex) {
case FLEXNetworkObserverModeREST: {
FLEXHTTPTransaction *transaction = [self HTTPTransactionAtIndexPath:indexPath];
UIViewController *details = [FLEXHTTPTransactionDetailController withTransaction:transaction];
[self.navigationController pushViewController:details animated:YES];
break;
}
case FLEXNetworkObserverModeWebsockets: {
if (@available(iOS 13.0, *)) { // This check will never fail
FLEXWebsocketTransaction *transaction = [self websocketTransactionAtIndexPath:indexPath];
UIViewController *details = nil;
if (transaction.message.type == NSURLSessionWebSocketMessageTypeData) {
details = [FLEXObjectExplorerFactory explorerViewControllerForObject:transaction.message.data];
} else {
details = [[FLEXWebViewController alloc] initWithText:transaction.message.string];
}
[self.navigationController pushViewController:details animated:YES];
}
break;
}
default:
@throw NSInternalInconsistencyException;
}
}
@@ -390,8 +439,6 @@
}
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
return [UIContextMenuConfiguration
@@ -399,70 +446,63 @@
previewProvider:nil
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
UIAction *copy = [UIAction
actionWithTitle:@"Copy"
actionWithTitle:@"Copy URL"
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
}
];
UIAction *blacklist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Blacklist '%@'", request.URL.host]
UIAction *denylist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSMutableArray *blacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
[blacklist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearBlacklistedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
[denylist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self tryUpdateTransactions];
}
];
return [UIMenu
menuWithTitle:@"" image:nil identifier:nil
options:UIMenuOptionsDisplayInline
children:@[copy, blacklist]
children:@[copy, denylist]
];
}
];
}
#endif
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath {
return self.searchController.isActive ? self.filteredNetworkTransactions[indexPath.row] : self.networkTransactions[indexPath.row];
return self.dataSource.transactions[indexPath.row];
}
- (FLEXHTTPTransaction *)HTTPTransactionAtIndexPath:(NSIndexPath *)indexPath {
return self.HTTPDataSource.transactions[indexPath.row];
}
- (FLEXWebsocketTransaction *)websocketTransactionAtIndexPath:(NSIndexPath *)indexPath {
return self.websocketDataSource.transactions[indexPath.row];
}
#pragma mark - Search Bar
- (void)updateSearchResults:(NSString *)searchString {
if (!searchString.length) {
self.filteredNetworkTransactions = self.networkTransactions;
[self.tableView reloadData];
} else {
[self onBackgroundQueue:^NSArray *{
return [self.networkTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) {
return [entry.request.URL.absoluteString localizedCaseInsensitiveContainsString:searchString];
}];
} thenOnMainQueue:^(NSArray *filteredNetworkTransactions) {
if ([self.searchText isEqual:searchString]) {
self.filteredNetworkTransactions = filteredNetworkTransactions;
[self.tableView reloadData];
}
}];
}
id callback = ^(FLEXMITMDataSource *dataSource) {
if (self.dataSource == dataSource) {
[self.tableView reloadData];
}
};
[self.HTTPDataSource filter:searchString completion:callback];
[self.websocketDataSource filter:searchString completion:callback];
}
#pragma mark UISearchControllerDelegate
- (void)willPresentSearchController:(UISearchController *)searchController {
self.isPresentingSearch = YES;
}
- (void)didPresentSearchController:(UISearchController *)searchController {
self.isPresentingSearch = NO;
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
[self updateFirstSectionHeader];
[self.tableView reloadData];
}
- (void)willDismissSearchController:(UISearchController *)searchController {
+21 -11
View File
@@ -14,7 +14,7 @@ extern NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification;
extern NSString *const kFLEXNetworkRecorderUserInfoTransactionKey;
extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
@class FLEXNetworkTransaction;
@class FLEXNetworkTransaction, FLEXHTTPTransaction, FLEXWebsocketTransaction;
@interface FLEXNetworkRecorder : NSObject
@@ -28,28 +28,30 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
/// with an "image", "video", or "audio" prefix.
@property (nonatomic) BOOL shouldCacheMediaResponses;
@property (nonatomic) NSMutableArray<NSString *> *hostBlacklist;
@property (nonatomic) NSMutableArray<NSString *> *hostDenylist;
/// Call this after adding to or setting the \c hostBlacklist to remove blacklisted transactions
- (void)clearBlacklistedTransactions;
/// Call this after adding to or setting the \c hostDenylist to remove excluded transactions
- (void)clearExcludedTransactions;
/// Call this to save the blacklist to the disk to be loaded next time
- (void)synchronizeBlacklist;
/// Call this to save the denylist to the disk to be loaded next time
- (void)synchronizeDenylist;
// Accessing recorded network activity
#pragma mark Accessing recorded network activity
/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first.
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions;
/// Array of FLEXHTTPTransaction objects ordered by start time with the newest first.
@property (nonatomic, readonly) NSArray<FLEXHTTPTransaction *> *HTTPTransactions;
/// Array of FLEXWebsocketTransaction objects ordered by start time with the newest first.
@property (nonatomic, readonly) NSArray<FLEXWebsocketTransaction *> *websocketTransactions API_AVAILABLE(ios(13.0));
/// The full response data IFF it hasn't been purged due to memory pressure.
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction;
- (NSData *)cachedResponseBodyForTransaction:(FLEXHTTPTransaction *)transaction;
/// Dumps all network transactions and cached response bodies.
- (void)clearRecordedActivity;
// Recording network activity
#pragma mark Recording network activity
/// Call when app is about to send HTTP request.
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID
@@ -72,4 +74,12 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
/// This string can be set to anything useful about the API used to make the request.
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID;
- (void)recordWebsocketMessageSend:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task API_AVAILABLE(ios(13.0));
- (void)recordWebsocketMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error API_AVAILABLE(ios(13.0));
- (void)recordWebsocketMessageReceived:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task API_AVAILABLE(ios(13.0));
@end
+90 -56
View File
@@ -12,6 +12,7 @@
#import "FLEXUtility.h"
#import "FLEXResources.h"
#import "NSUserDefaults+FLEX.h"
#import "OSCache.h"
NSString *const kFLEXNetworkRecorderNewTransactionNotification = @"kFLEXNetworkRecorderNewTransactionNotification";
NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification = @"kFLEXNetworkRecorderTransactionUpdatedNotification";
@@ -22,9 +23,10 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
@interface FLEXNetworkRecorder ()
@property (nonatomic) NSCache *responseCache;
@property (nonatomic) NSMutableArray<FLEXNetworkTransaction *> *orderedTransactions;
@property (nonatomic) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *requestIDsToTransactions;
@property (nonatomic) OSCache *restCache;
@property (nonatomic) NSMutableArray<FLEXHTTPTransaction *> *orderedHTTPTransactions;
@property (nonatomic) NSMutableArray<FLEXWebsocketTransaction *> *orderedWSTransactions;
@property (nonatomic) NSMutableDictionary<NSString *, FLEXHTTPTransaction *> *requestIDsToHTTPTransactions;
@property (nonatomic) dispatch_queue_t queue;
@end
@@ -34,18 +36,19 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (instancetype)init {
self = [super init];
if (self) {
self.responseCache = [NSCache new];
self.restCache = [OSCache new];
NSUInteger responseCacheLimit = [[NSUserDefaults.standardUserDefaults
objectForKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey] unsignedIntegerValue
];
// Default to 25 MB max. The cache will purge earlier if there is memory pressure.
self.responseCache.totalCostLimit = responseCacheLimit ?: 25 * 1024 * 1024;
[self.responseCache setTotalCostLimit:responseCacheLimit];
self.restCache.totalCostLimit = responseCacheLimit ?: 25 * 1024 * 1024;
[self.restCache setTotalCostLimit:responseCacheLimit];
self.orderedTransactions = [NSMutableArray new];
self.requestIDsToTransactions = [NSMutableDictionary new];
self.hostBlacklist = NSUserDefaults.standardUserDefaults.flex_networkHostBlacklist.mutableCopy;
self.orderedWSTransactions = [NSMutableArray new];
self.orderedHTTPTransactions = [NSMutableArray new];
self.requestIDsToHTTPTransactions = [NSMutableDictionary new];
self.hostDenylist = NSUserDefaults.standardUserDefaults.flex_networkHostDenylist.mutableCopy;
// Serial queue used because we use mutable objects that are not thread safe
self.queue = dispatch_queue_create("com.flex.FLEXNetworkRecorder", DISPATCH_QUEUE_SERIAL);
@@ -67,46 +70,46 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
#pragma mark - Public Data Access
- (NSUInteger)responseCacheByteLimit {
return self.responseCache.totalCostLimit;
return self.restCache.totalCostLimit;
}
- (void)setResponseCacheByteLimit:(NSUInteger)responseCacheByteLimit {
self.responseCache.totalCostLimit = responseCacheByteLimit;
self.restCache.totalCostLimit = responseCacheByteLimit;
[NSUserDefaults.standardUserDefaults
setObject:@(responseCacheByteLimit)
forKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey
];
}
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions {
__block NSArray<FLEXNetworkTransaction *> *transactions = nil;
dispatch_sync(self.queue, ^{
transactions = self.orderedTransactions.copy;
});
return transactions;
- (NSArray<FLEXHTTPTransaction *> *)HTTPTransactions {
return self.orderedHTTPTransactions.copy;
}
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction {
return [self.responseCache objectForKey:transaction.requestID];
- (NSArray<FLEXWebsocketTransaction *> *)websocketTransactions {
return self.orderedWSTransactions.copy;
}
- (NSData *)cachedResponseBodyForTransaction:(FLEXHTTPTransaction *)transaction {
return [self.restCache objectForKey:transaction.requestID];
}
- (void)clearRecordedActivity {
dispatch_async(self.queue, ^{
[self.responseCache removeAllObjects];
[self.orderedTransactions removeAllObjects];
[self.requestIDsToTransactions removeAllObjects];
[self.restCache removeAllObjects];
[self.orderedHTTPTransactions removeAllObjects];
[self.requestIDsToHTTPTransactions removeAllObjects];
[self notify:kFLEXNetworkRecorderTransactionsClearedNotification transaction:nil];
});
}
- (void)clearBlacklistedTransactions {
- (void)clearExcludedTransactions {
dispatch_sync(self.queue, ^{
self.orderedTransactions = ({
[self.orderedTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *ta, NSUInteger idx) {
self.orderedHTTPTransactions = ({
[self.orderedHTTPTransactions flex_filtered:^BOOL(FLEXHTTPTransaction *ta, NSUInteger idx) {
NSString *host = ta.request.URL.host;
for (NSString *blacklisted in self.hostBlacklist) {
if ([host hasSuffix:blacklisted]) {
for (NSString *excluded in self.hostDenylist) {
if ([host hasSuffix:excluded]) {
return NO;
}
}
@@ -117,8 +120,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
});
}
- (void)synchronizeBlacklist {
NSUserDefaults.standardUserDefaults.flex_networkHostBlacklist = self.hostBlacklist;
- (void)synchronizeDenylist {
NSUserDefaults.standardUserDefaults.flex_networkHostDenylist = self.hostDenylist;
}
#pragma mark - Network Events
@@ -126,28 +129,23 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID
request:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse {
for (NSString *host in self.hostBlacklist) {
for (NSString *host in self.hostDenylist) {
if ([request.URL.host hasSuffix:host]) {
return;
}
}
// Before async block to stay accurate
NSDate *startDate = [NSDate date];
FLEXHTTPTransaction *transaction = [FLEXHTTPTransaction request:request identifier:requestID];
// Before async block to keep times accurate
if (redirectResponse) {
[self recordResponseReceivedWithRequestID:requestID response:redirectResponse];
[self recordLoadingFinishedWithRequestID:requestID responseBody:nil];
}
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = [FLEXNetworkTransaction new];
transaction.requestID = requestID;
transaction.request = request;
transaction.startTime = startDate;
[self.orderedTransactions insertObject:transaction atIndex:0];
[self.requestIDsToTransactions setObject:transaction forKey:requestID];
[self.orderedHTTPTransactions insertObject:transaction atIndex:0];
[self.requestIDsToHTTPTransactions setObject:transaction forKey:requestID];
transaction.transactionState = FLEXNetworkTransactionStateAwaitingResponse;
[self postNewTransactionNotificationWithTransaction:transaction];
@@ -159,7 +157,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
NSDate *responseDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -174,7 +172,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength {
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -188,7 +186,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
NSDate *finishedDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -205,7 +203,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
}
if (shouldCache) {
[self.responseCache setObject:responseBody forKey:requestID cost:responseBody.length];
[self.restCache setObject:responseBody forKey:requestID cost:responseBody.length];
}
NSString *mimeType = transaction.response.MIMEType;
@@ -213,32 +211,32 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
// Thumbnail image previews on a separate background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSInteger maxPixelDimension = UIScreen.mainScreen.scale * 32.0;
transaction.responseThumbnail = [FLEXUtility
transaction.thumbnail = [FLEXUtility
thumbnailedImageWithMaxPixelDimension:maxPixelDimension
fromImageData:responseBody
];
[self postUpdateNotificationForTransaction:transaction];
});
} else if ([mimeType isEqual:@"application/json"]) {
transaction.responseThumbnail = FLEXResources.jsonIcon;
transaction.thumbnail = FLEXResources.jsonIcon;
} else if ([mimeType isEqual:@"text/plain"]){
transaction.responseThumbnail = FLEXResources.textPlainIcon;
transaction.thumbnail = FLEXResources.textPlainIcon;
} else if ([mimeType isEqual:@"text/html"]) {
transaction.responseThumbnail = FLEXResources.htmlIcon;
transaction.thumbnail = FLEXResources.htmlIcon;
} else if ([mimeType isEqual:@"application/x-plist"]) {
transaction.responseThumbnail = FLEXResources.plistIcon;
transaction.thumbnail = FLEXResources.plistIcon;
} else if ([mimeType isEqual:@"application/octet-stream"] || [mimeType isEqual:@"application/binary"]) {
transaction.responseThumbnail = FLEXResources.binaryIcon;
transaction.thumbnail = FLEXResources.binaryIcon;
} else if ([mimeType containsString:@"javascript"]) {
transaction.responseThumbnail = FLEXResources.jsIcon;
transaction.thumbnail = FLEXResources.jsIcon;
} else if ([mimeType containsString:@"xml"]) {
transaction.responseThumbnail = FLEXResources.xmlIcon;
transaction.thumbnail = FLEXResources.xmlIcon;
} else if ([mimeType hasPrefix:@"audio"]) {
transaction.responseThumbnail = FLEXResources.audioIcon;
transaction.thumbnail = FLEXResources.audioIcon;
} else if ([mimeType hasPrefix:@"video"]) {
transaction.responseThumbnail = FLEXResources.videoIcon;
transaction.thumbnail = FLEXResources.videoIcon;
} else if ([mimeType hasPrefix:@"text"]) {
transaction.responseThumbnail = FLEXResources.textIcon;
transaction.thumbnail = FLEXResources.textIcon;
}
[self postUpdateNotificationForTransaction:transaction];
@@ -247,7 +245,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error {
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -262,7 +260,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID {
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -272,6 +270,42 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
});
}
#pragma mark - Websocket Events
- (void)recordWebsocketMessageSend:(NSURLSessionWebSocketMessage *)message task:(NSURLSessionWebSocketTask *)task {
dispatch_async(self.queue, ^{
FLEXWebsocketTransaction *send = [FLEXWebsocketTransaction
withMessage:message task:task direction:FLEXWebsocketOutgoing
];
[self.orderedWSTransactions addObject:send];
[self postNewTransactionNotificationWithTransaction:send];
});
}
- (void)recordWebsocketMessageSendCompletion:(NSURLSessionWebSocketMessage *)message error:(NSError *)error {
dispatch_async(self.queue, ^{
FLEXWebsocketTransaction *send = [self.orderedWSTransactions flex_firstWhere:^BOOL(FLEXWebsocketTransaction *t) {
return t.message == message;
}];
send.error = error;
send.transactionState = error ? FLEXNetworkTransactionStateFailed : FLEXNetworkTransactionStateFinished;
[self postUpdateNotificationForTransaction:send];
});
}
- (void)recordWebsocketMessageReceived:(NSURLSessionWebSocketMessage *)message task:(NSURLSessionWebSocketTask *)task {
dispatch_async(self.queue, ^{
FLEXWebsocketTransaction *receive = [FLEXWebsocketTransaction
withMessage:message task:task direction:FLEXWebsocketIncoming
];
[self.orderedWSTransactions addObject:receive];
[self postNewTransactionNotificationWithTransaction:receive];
});
}
#pragma mark Notification Posting
- (void)postNewTransactionNotificationWithTransaction:(FLEXNetworkTransaction *)transaction {
+17 -17
View File
@@ -23,7 +23,7 @@
@property (nonatomic, readonly) UISlider *cacheLimitSlider;
@property (nonatomic) UILabel *cacheLimitLabel;
@property (nonatomic) NSMutableArray<NSString *> *hostBlacklist;
@property (nonatomic) NSMutableArray<NSString *> *hostDenylist;
@end
@implementation FLEXNetworkSettingsController
@@ -32,7 +32,7 @@
[super viewDidLoad];
[self disableToolbar];
self.hostBlacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist.mutableCopy;
self.hostDenylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist.mutableCopy;
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
@@ -107,13 +107,13 @@
#pragma mark - Table View Data Source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.hostBlacklist.count ? 2 : 1;
return self.hostDenylist.count ? 2 : 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0: return 5;
case 1: return self.hostBlacklist.count;
case 1: return self.hostDenylist.count;
default: return 0;
}
}
@@ -121,7 +121,7 @@
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
switch (section) {
case 0: return @"General";
case 1: return @"Host Blacklist";
case 1: return @"Host Denylist";
default: return nil;
}
}
@@ -162,7 +162,7 @@
cell.accessoryView = self.jsonViewerSwitch;
break;
case 3:
cell.textLabel.text = @"Reset Host Blacklist";
cell.textLabel.text = @"Reset Host Denylist";
cell.textLabel.textColor = tableView.tintColor;
break;
case 4:
@@ -195,9 +195,9 @@
break;
}
// Blacklist entries
// Denylist entries
case 1: {
cell.textLabel.text = self.hostBlacklist[indexPath.row];
cell.textLabel.text = self.hostDenylist[indexPath.row];
break;
}
@@ -212,7 +212,7 @@
#pragma mark - Table View Delegate
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)ip {
// Can only select the "Reset Host Blacklist" row
// Can only select the "Reset Host Denylist" row
return ip.section == 0 && ip.row == 2;
}
@@ -220,12 +220,12 @@
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Reset Host Blacklist");
make.title(@"Reset Host Denylist");
make.message(@"You cannot undo this action. Are you sure?");
make.button(@"Reset").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
self.hostBlacklist = nil;
[FLEXNetworkRecorder.defaultRecorder.hostBlacklist removeAllObjects];
[FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
self.hostDenylist = nil;
[FLEXNetworkRecorder.defaultRecorder.hostDenylist removeAllObjects];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self.tableView deleteSections:
[NSIndexSet indexSetWithIndex:1]
withRowAnimation:UITableViewRowAnimationAutomatic];
@@ -242,10 +242,10 @@
forRowAtIndexPath:(NSIndexPath *)indexPath {
NSParameterAssert(style == UITableViewCellEditingStyleDelete);
NSString *host = self.hostBlacklist[indexPath.row];
[self.hostBlacklist removeObjectAtIndex:indexPath.row];
[FLEXNetworkRecorder.defaultRecorder.hostBlacklist removeObject:host];
[FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
NSString *host = self.hostDenylist[indexPath.row];
[self.hostDenylist removeObjectAtIndex:indexPath.row];
[FLEXNetworkRecorder.defaultRecorder.hostDenylist removeObject:host];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
+55 -11
View File
@@ -17,28 +17,72 @@ typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
FLEXNetworkTransactionStateFailed
};
typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
FLEXWebsocketIncoming = 1,
FLEXWebsocketOutgoing,
};
@interface FLEXNetworkTransaction : NSObject
@property (nonatomic, copy) NSString *requestID;
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime;
@property (nonatomic) NSURLRequest *request;
@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSDate *startTime;
@property (nonatomic) NSError *error;
/// Subclasses can override to provide error state based on response data as well
@property (nonatomic, readonly) BOOL displayAsError;
@property (nonatomic) FLEXNetworkTransactionState transactionState;
@property (nonatomic) int64_t receivedDataLength;
/// A small thumbnail to preview the type of/the response
@property (nonatomic) UIImage *thumbnail;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
/// Generated by this class using the URL provided by subclasses
@property (nonatomic, readonly) NSString *primaryDescription;
@property (nonatomic, readonly) NSString *secondaryDescription;
@property (nonatomic, readonly) NSString *tertiaryDescription;
/// Subclasses should implement for when the transaction is complete
@property (nonatomic, readonly) NSArray<NSString *> *details;
@end
@interface FLEXHTTPTransaction : FLEXNetworkTransaction
+ (instancetype)request:(NSURLRequest *)request identifier:(NSString *)requestID;
@property (nonatomic, readonly) NSString *requestID;
@property (nonatomic) NSURLResponse *response;
@property (nonatomic, copy) NSString *requestMechanism;
@property (nonatomic) FLEXNetworkTransactionState transactionState;
@property (nonatomic) NSError *error;
@property (nonatomic) NSDate *startTime;
@property (nonatomic) NSTimeInterval latency;
@property (nonatomic) NSTimeInterval duration;
@property (nonatomic) int64_t receivedDataLength;
/// Only applicable for image downloads. A small thumbnail to preview the full response.
@property (nonatomic) UIImage *responseThumbnail;
/// Populated lazily. Handles both normal HTTPBody data and HTTPBodyStreams.
@property (nonatomic, readonly) NSData *cachedRequestBody;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
@end
@interface FLEXWebsocketTransaction : FLEXNetworkTransaction
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction API_AVAILABLE(ios(13.0));
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction
startTime:(NSDate *)started API_AVAILABLE(ios(13.0));
//@property (nonatomic, readonly) NSURLSessionWebSocketTask *task;
@property (nonatomic, readonly) NSURLSessionWebSocketMessage *message API_AVAILABLE(ios(13.0));
@property (nonatomic, readonly) FLEXWebsocketMessageDirection direction API_AVAILABLE(ios(13.0));
@property (nonatomic, readonly) int64_t dataLength API_AVAILABLE(ios(13.0));
@end
+218 -28
View File
@@ -7,23 +7,155 @@
//
#import "FLEXNetworkTransaction.h"
#import "FLEXResources.h"
#import "FLEXUtility.h"
@interface FLEXNetworkTransaction ()
@interface FLEXHTTPTransaction ()
@property (nonatomic, readwrite) NSData *cachedRequestBody;
@end
@implementation FLEXNetworkTransaction
@synthesize primaryDescription = _primaryDescription;
@synthesize secondaryDescription = _secondaryDescription;
@synthesize tertiaryDescription = _tertiaryDescription;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state {
NSString *readableString = nil;
switch (state) {
case FLEXNetworkTransactionStateUnstarted:
readableString = @"Unstarted";
break;
case FLEXNetworkTransactionStateAwaitingResponse:
readableString = @"Awaiting Response";
break;
case FLEXNetworkTransactionStateReceivingData:
readableString = @"Receiving Data";
break;
case FLEXNetworkTransactionStateFinished:
readableString = @"Finished";
break;
case FLEXNetworkTransactionStateFailed:
readableString = @"Failed";
break;
}
return readableString;
}
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime {
FLEXNetworkTransaction *transaction = [self new];
transaction->_request = request;
transaction->_startTime = startTime;
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;
if (!name.length) {
name = @"/";
}
if (_request.URL.query) {
name = [name stringByAppendingFormat:@"?%@", self.request.URL.query];
}
_primaryDescription = name;
}
return _primaryDescription;
}
- (NSString *)secondaryDescription {
if (!_secondaryDescription) {
NSMutableArray<NSString *> *mutablePathComponents = self.request.URL.pathComponents.mutableCopy;
if (mutablePathComponents.count > 0) {
[mutablePathComponents removeLastObject];
}
NSString *path = self.request.URL.host;
for (NSString *pathComponent in mutablePathComponents) {
path = [path stringByAppendingPathComponent:pathComponent];
}
_secondaryDescription = path;
}
return _secondaryDescription;
}
- (NSString *)tertiaryDescription {
if (!_tertiaryDescription) {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *timestamp = [self timestampStringFromRequestDate:self.startTime];
if (timestamp.length > 0) {
[detailComponents addObject:timestamp];
}
// Omit method for GET (assumed as default)
NSString *httpMethod = self.request.HTTPMethod;
if (httpMethod.length > 0) {
[detailComponents addObject:httpMethod];
}
if (self.transactionState == FLEXNetworkTransactionStateFinished || self.transactionState == FLEXNetworkTransactionStateFailed) {
[detailComponents addObjectsFromArray:self.details];
} else {
// Unstarted, Awaiting Response, Receiving Data, etc.
NSString *state = [self.class readableStringFromTransactionState:self.transactionState];
[detailComponents addObject:state];
}
_tertiaryDescription = [detailComponents componentsJoinedByString:@""];
}
return _tertiaryDescription;
}
- (void)setTransactionState:(FLEXNetworkTransactionState)transactionState {
_transactionState = transactionState;
// Reset bottom description
_tertiaryDescription = nil;
}
- (BOOL)displayAsError {
return _error != nil;
}
@end
@implementation FLEXHTTPTransaction
+ (instancetype)request:(NSURLRequest *)request identifier:(NSString *)requestID {
FLEXHTTPTransaction *httpt = [self withRequest:request startTime:NSDate.date];
httpt->_requestID = requestID;
return httpt;
}
- (NSString *)description {
NSString *description = [super description];
description = [description stringByAppendingFormat:@" id = %@;", self.requestID];
description = [description stringByAppendingFormat:@" url = %@;", self.request.URL];
description = [description stringByAppendingFormat:@" duration = %f;", self.duration];
description = [description stringByAppendingFormat:@" receivedDataLength = %lld", self.receivedDataLength];
return description;
}
@@ -49,30 +181,88 @@
return _cachedRequestBody;
}
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state {
NSString *readableString = nil;
switch (state) {
case FLEXNetworkTransactionStateUnstarted:
readableString = @"Unstarted";
break;
case FLEXNetworkTransactionStateAwaitingResponse:
readableString = @"Awaiting Response";
break;
case FLEXNetworkTransactionStateReceivingData:
readableString = @"Receiving Data";
break;
case FLEXNetworkTransactionStateFinished:
readableString = @"Finished";
break;
case FLEXNetworkTransactionStateFailed:
readableString = @"Failed";
break;
- (NSArray *)detailString {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *statusCodeString = [FLEXUtility statusCodeStringFromURLResponse:self.response];
if (statusCodeString.length > 0) {
[detailComponents addObject:statusCodeString];
}
return readableString;
if (self.receivedDataLength > 0) {
NSString *responseSize = [NSByteCountFormatter
stringFromByteCount:self.receivedDataLength
countStyle:NSByteCountFormatterCountStyleBinary
];
[detailComponents addObject:responseSize];
}
NSString *totalDuration = [FLEXUtility stringFromRequestDuration:self.duration];
NSString *latency = [FLEXUtility stringFromRequestDuration:self.latency];
NSString *duration = [NSString stringWithFormat:@"%@ (%@)", totalDuration, latency];
[detailComponents addObject:duration];
return detailComponents;
}
- (BOOL)displayAsError {
return [FLEXUtility isErrorStatusCodeFromURLResponse:self.response] || super.displayAsError;
}
@end
@implementation FLEXWebsocketTransaction
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction
startTime:(NSDate *)started {
FLEXWebsocketTransaction *wst = [self withRequest:task.originalRequest startTime:started];
wst->_message = message;
wst->_direction = direction;
// Populate receivedDataLength
if (direction == FLEXWebsocketIncoming) {
wst.receivedDataLength = wst.dataLength;
}
// Populate thumbnail image
if (message.type == NSURLSessionWebSocketMessageTypeData) {
wst.thumbnail = FLEXResources.binaryIcon;
} else {
wst.thumbnail = FLEXResources.textIcon;
}
return wst;
}
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction {
return [self withMessage:message task:task direction:direction startTime:NSDate.date];
}
- (NSArray<NSString *> *)details API_AVAILABLE(ios(13.0)) {
return @[
self.direction == FLEXWebsocketOutgoing ? @"SENT →" : @"→ RECEIVED",
[NSByteCountFormatter
stringFromByteCount:self.dataLength
countStyle:NSByteCountFormatterCountStyleBinary
]
];
}
- (int64_t)dataLength {
if (self.message) {
if (self.message.type == NSURLSessionWebSocketMessageTypeString) {
return self.message.string.length;
}
return self.message.data.length;
}
return 0;
}
@end
+2 -3
View File
@@ -8,14 +8,13 @@
#import <UIKit/UIKit.h>
extern NSString * const kFLEXNetworkTransactionCellIdentifier;
@class FLEXNetworkTransaction;
@interface FLEXNetworkTransactionCell : UITableViewCell
@property (nonatomic) FLEXNetworkTransaction *transaction;
+ (CGFloat)preferredCellHeight;
@property (nonatomic, readonly, class) NSString *reuseID;
@property (nonatomic, readonly, class) CGFloat preferredCellHeight;
@end
+10 -68
View File
@@ -12,7 +12,7 @@
#import "FLEXUtility.h"
#import "FLEXResources.h"
NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactionCellIdentifier";
NSString * const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactionCellIdentifier";
@interface FLEXNetworkTransactionCell ()
@@ -69,7 +69,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
CGFloat thumbnailOriginY = round((self.contentView.bounds.size.height - kImageDimension) / 2.0);
self.thumbnailImageView.frame = CGRectMake(kLeftPadding, thumbnailOriginY, kImageDimension, kImageDimension);
self.thumbnailImageView.image = self.transaction.responseThumbnail;
self.thumbnailImageView.image = self.transaction.thumbnail;
CGFloat textOriginX = CGRectGetMaxX(self.thumbnailImageView.frame) + kLeftPadding;
CGFloat availableTextWidth = self.contentView.bounds.size.width - textOriginX;
@@ -77,7 +77,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
self.nameLabel.text = [self nameLabelText];
CGSize nameLabelPreferredSize = [self.nameLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
self.nameLabel.frame = CGRectMake(textOriginX, kVerticalPadding, availableTextWidth, nameLabelPreferredSize.height);
self.nameLabel.textColor = (self.transaction.error || [FLEXUtility isErrorStatusCodeFromURLResponse:self.transaction.response]) ? UIColor.redColor : FLEXColor.primaryTextColor;
self.nameLabel.textColor = self.transaction.displayAsError ? UIColor.redColor : FLEXColor.primaryTextColor;
self.pathLabel.text = [self pathLabelText];
CGSize pathLabelPreferredSize = [self.pathLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
@@ -93,81 +93,23 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
}
- (NSString *)nameLabelText {
NSURL *url = self.transaction.request.URL;
NSString *name = [url lastPathComponent];
if (name.length == 0) {
name = @"/";
}
NSString *query = [url query];
if (query) {
name = [name stringByAppendingFormat:@"?%@", query];
}
return name;
return self.transaction.primaryDescription;
}
- (NSString *)pathLabelText {
NSURL *url = self.transaction.request.URL;
NSMutableArray<NSString *> *mutablePathComponents = url.pathComponents.mutableCopy;
if (mutablePathComponents.count > 0) {
[mutablePathComponents removeLastObject];
}
NSString *path = [url host];
for (NSString *pathComponent in mutablePathComponents) {
path = [path stringByAppendingPathComponent:pathComponent];
}
return path;
return self.transaction.secondaryDescription;
}
- (NSString *)transactionDetailsLabelText {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *timestamp = [[self class] timestampStringFromRequestDate:self.transaction.startTime];
if (timestamp.length > 0) {
[detailComponents addObject:timestamp];
}
// Omit method for GET (assumed as default)
NSString *httpMethod = self.transaction.request.HTTPMethod;
if (httpMethod.length > 0) {
[detailComponents addObject:httpMethod];
}
if (self.transaction.transactionState == FLEXNetworkTransactionStateFinished || self.transaction.transactionState == FLEXNetworkTransactionStateFailed) {
NSString *statusCodeString = [FLEXUtility statusCodeStringFromURLResponse:self.transaction.response];
if (statusCodeString.length > 0) {
[detailComponents addObject:statusCodeString];
}
if (self.transaction.receivedDataLength > 0) {
NSString *responseSize = [NSByteCountFormatter stringFromByteCount:self.transaction.receivedDataLength countStyle:NSByteCountFormatterCountStyleBinary];
[detailComponents addObject:responseSize];
}
NSString *totalDuration = [FLEXUtility stringFromRequestDuration:self.transaction.duration];
NSString *latency = [FLEXUtility stringFromRequestDuration:self.transaction.latency];
NSString *duration = [NSString stringWithFormat:@"%@ (%@)", totalDuration, latency];
[detailComponents addObject:duration];
} else {
// Unstarted, Awaiting Response, Receiving Data, etc.
NSString *state = [FLEXNetworkTransaction readableStringFromTransactionState:self.transaction.transactionState];
[detailComponents addObject:state];
}
return [detailComponents componentsJoinedByString:@""];
}
+ (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];
return self.transaction.tertiaryDescription;
}
+ (CGFloat)preferredCellHeight {
return 65.0;
}
+ (NSString *)reuseID {
return kFLEXNetworkTransactionCellIdentifier;
}
@end
@@ -1,17 +0,0 @@
//
// FLEXNetworkTransactionDetailController.h
// Flipboard
//
// Created by Ryan Olson on 2/10/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FLEXNetworkTransaction;
@interface FLEXNetworkTransactionDetailController : UITableViewController
@property (nonatomic) FLEXNetworkTransaction *transaction;
@end
+20
View File
@@ -0,0 +1,20 @@
OSCache
version 1.2.1, Decembet 18th, 2015
Copyright (C) 2014 Charcoal Design
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
+57
View File
@@ -0,0 +1,57 @@
//
// OSCache.h
//
// Version 1.2.1
//
// Created by Nick Lockwood on 01/01/2014.
// Copyright (C) 2014 Charcoal Design
//
// Distributed under the permissive zlib License
// Get the latest version from here:
//
// https://github.com/nicklockwood/OSCache
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface OSCache <KeyType, ObjectType> : NSCache <NSFastEnumeration>
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) NSUInteger totalCost;
- (id)objectForKeyedSubscript:(KeyType <NSCopying>)key;
- (void)setObject:(ObjectType)obj forKeyedSubscript:(KeyType <NSCopying>)key;
- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(KeyType key, ObjectType obj, BOOL *stop))block;
@end
@protocol OSCacheDelegate <NSCacheDelegate>
@optional
- (BOOL)cache:(OSCache *)cache shouldEvictObject:(id)entry;
- (void)cache:(OSCache *)cache willEvictObject:(id)entry;
@end
NS_ASSUME_NONNULL_END
+409
View File
@@ -0,0 +1,409 @@
//
// OSCache.m
//
// Version 1.2.1
//
// Created by Nick Lockwood on 01/01/2014.
// Copyright (C) 2014 Charcoal Design
//
// Distributed under the permissive zlib License
// Get the latest version from here:
//
// https://github.com/nicklockwood/OSCache
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#import "OSCache.h"
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
#import <Availability.h>
#if !__has_feature(objc_arc)
#error This class requires automatic reference counting
#endif
#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
#pragma GCC diagnostic ignored "-Wdirect-ivar-access"
#pragma GCC diagnostic ignored "-Wgnu"
@interface OSCacheEntry : NSObject
@property (nonatomic, strong) NSObject *object;
@property (nonatomic, assign) NSUInteger cost;
@property (nonatomic, assign) NSInteger sequenceNumber;
@end
@implementation OSCacheEntry
@end
@interface OSCache_Private : NSObject
@property (nonatomic, unsafe_unretained) id<OSCacheDelegate> delegate;
@property (nonatomic, assign) NSUInteger countLimit;
@property (nonatomic, assign) NSUInteger totalCostLimit;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSMutableDictionary *cache;
@property (nonatomic, assign) NSUInteger totalCost;
@property (nonatomic, assign) NSInteger sequenceNumber;
@end
@implementation OSCache_Private
{
BOOL _delegateRespondsToWillEvictObject;
BOOL _delegateRespondsToShouldEvictObject;
BOOL _currentlyCleaning;
NSMutableArray *_entryPool;
NSLock *_lock;
}
- (instancetype)init
{
if ((self = [super init]))
{
//create storage
_cache = [[NSMutableDictionary alloc] init];
_entryPool = [[NSMutableArray alloc] init];
_lock = [[NSLock alloc] init];
_totalCost = 0;
#if TARGET_OS_IPHONE
//clean up in the event of a memory warning
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanUpAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setDelegate:(id<OSCacheDelegate>)delegate
{
_delegate = delegate;
_delegateRespondsToShouldEvictObject = [delegate respondsToSelector:@selector(cache:shouldEvictObject:)];
_delegateRespondsToWillEvictObject = [delegate respondsToSelector:@selector(cache:willEvictObject:)];
}
- (void)setCountLimit:(NSUInteger)countLimit
{
[_lock lock];
_countLimit = countLimit;
[_lock unlock];
[self cleanUp:NO];
}
- (void)setTotalCostLimit:(NSUInteger)totalCostLimit
{
[_lock lock];
_totalCostLimit = totalCostLimit;
[_lock unlock];
[self cleanUp:NO];
}
- (NSUInteger)count
{
return [_cache count];
}
- (void)cleanUp:(BOOL)keepEntries
{
[_lock lock];
NSUInteger maxCount = _countLimit ?: INT_MAX;
NSUInteger maxCost = _totalCostLimit ?: INT_MAX;
NSUInteger totalCount = _cache.count;
NSMutableArray *keys = [_cache.allKeys mutableCopy];
while (totalCount > maxCount || _totalCost > maxCost)
{
NSInteger lowestSequenceNumber = INT_MAX;
OSCacheEntry *lowestEntry = nil;
id lowestKey = nil;
//remove oldest items until within limit
for (id key in keys)
{
OSCacheEntry *entry = _cache[key];
if (entry.sequenceNumber < lowestSequenceNumber)
{
lowestSequenceNumber = entry.sequenceNumber;
lowestEntry = entry;
lowestKey = key;
}
}
if (lowestKey)
{
[keys removeObject:lowestKey];
if (!_delegateRespondsToShouldEvictObject ||
[_delegate cache:(OSCache *)self shouldEvictObject:lowestEntry.object])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
[self.delegate cache:(OSCache *)self willEvictObject:lowestEntry.object];
_currentlyCleaning = NO;
}
[_cache removeObjectForKey:lowestKey];
_totalCost -= lowestEntry.cost;
totalCount --;
if (keepEntries)
{
[_entryPool addObject:lowestEntry];
lowestEntry.object = nil;
}
}
}
}
[_lock unlock];
}
- (void)cleanUpAllObjects
{
[_lock lock];
if (_delegateRespondsToShouldEvictObject || _delegateRespondsToWillEvictObject)
{
NSArray *keys = [_cache allKeys];
if (_delegateRespondsToShouldEvictObject)
{
//sort, oldest first (in case we want to use that information in our eviction test)
keys = [keys sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) {
OSCacheEntry *entry1 = self->_cache[key1];
OSCacheEntry *entry2 = self->_cache[key2];
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
}
//remove all items individually
for (id key in keys)
{
OSCacheEntry *entry = _cache[key];
if (!_delegateRespondsToShouldEvictObject || [_delegate cache:(OSCache *)self shouldEvictObject:entry.object])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
[_delegate cache:(OSCache *)self willEvictObject:entry.object];
_currentlyCleaning = NO;
}
[_cache removeObjectForKey:key];
_totalCost -= entry.cost;
}
}
}
else
{
_totalCost = 0;
[_cache removeAllObjects];
_sequenceNumber = 0;
}
[_lock unlock];
}
- (void)resequence
{
//sort, oldest first
NSArray *entries = [[_cache allValues] sortedArrayUsingComparator:^NSComparisonResult(OSCacheEntry *entry1, OSCacheEntry *entry2) {
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
//renumber items
NSInteger index = 0;
for (OSCacheEntry *entry in entries)
{
entry.sequenceNumber = index++;
}
}
- (id)objectForKey:(id)key
{
[_lock lock];
OSCacheEntry *entry = _cache[key];
entry.sequenceNumber = _sequenceNumber++;
if (_sequenceNumber < 0)
{
[self resequence];
}
id object = entry.object;
[_lock unlock];
return object;
}
- (id)objectForKeyedSubscript:(id<NSCopying>)key
{
return [self objectForKey:key];
}
- (void)setObject:(id)obj forKey:(id)key
{
[self setObject:obj forKey:key cost:0];
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
[self setObject:obj forKey:key cost:0];
}
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g
{
if (!obj)
{
[self removeObjectForKey:key];
return;
}
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost -= [_cache[key] cost];
_totalCost += g;
OSCacheEntry *entry = _cache[key];
if (!entry) {
entry = [[OSCacheEntry alloc] init];
_cache[key] = entry;
}
entry.object = obj;
entry.cost = g;
entry.sequenceNumber = _sequenceNumber++;
if (_sequenceNumber < 0)
{
[self resequence];
}
[_lock unlock];
[self cleanUp:YES];
}
- (void)removeObjectForKey:(id)key
{
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
OSCacheEntry *entry = _cache[key];
if (entry) {
_totalCost -= entry.cost;
entry.object = nil;
[_entryPool addObject:entry];
[_cache removeObjectForKey:key];
}
[_lock unlock];
}
- (void)removeAllObjects
{
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost = 0;
_sequenceNumber = 0;
for (OSCacheEntry *entry in _cache.allValues)
{
entry.object = nil;
[_entryPool addObject:entry];
}
[_cache removeAllObjects];
[_lock unlock];
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id __unsafe_unretained [])buffer
count:(NSUInteger)len
{
[_lock lock];
NSUInteger count = [_cache countByEnumeratingWithState:state objects:buffer count:len];
[_lock unlock];
return count;
}
- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block
{
if (block)
{
[_lock lock];
[_cache enumerateKeysAndObjectsUsingBlock:^(id key, OSCacheEntry *entry, BOOL *stop) {
block(key, entry.object, stop);
}];
[_lock unlock];
}
}
//handle unimplemented methods
- (BOOL)isKindOfClass:(Class)aClass
{
//pretend that we're an NSCache if anyone asks
if (aClass == [OSCache class] || aClass == [NSCache class])
{
return YES;
}
return [super isKindOfClass:aClass];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
//protect against calls to unimplemented NSCache methods
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature)
{
signature = [NSCache instanceMethodSignatureForSelector:selector];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
[invocation invokeWithTarget:nil];
#pragma clang diagnostic pop
}
@end
@implementation OSCache
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
return (OSCache *)[OSCache_Private allocWithZone:zone];
}
- (id)objectForKeyedSubscript:(__unused id<NSCopying>)key { return nil; }
- (void)setObject:(__unused id)obj forKeyedSubscript:(__unused id<NSCopying>)key {}
- (void)enumerateKeysAndObjectsUsingBlock:(__unused void (^)(id, id, BOOL *))block { }
- (NSUInteger)countByEnumeratingWithState:(__unused NSFastEnumerationState *)state
objects:(__unused __unsafe_unretained id [])buffer
count:(__unused NSUInteger)len { return 0; }
@end
@@ -68,6 +68,15 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
- (void)URLSessionTaskWillResume:(NSURLSessionTask *)task;
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
sendMessagage:(NSURLSessionWebSocketMessage *)message API_AVAILABLE(ios(13.0));
- (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error API_AVAILABLE(ios(13.0));
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
receiveMessagage:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error API_AVAILABLE(ios(13.0));
@end
@interface FLEXNetworkObserver ()
@@ -89,7 +98,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
if (enabled) {
// Inject if needed. This injection is protected with a dispatch_once, so we're ok calling it multiple times.
// By doing the injection lazily, we keep the impact of the tool lower when this feature isn't enabled.
[self injectIntoAllNSURLConnectionDelegateClasses];
[self injectIntoAllNSURLThings];
}
if (previouslyEnabled != enabled) {
@@ -105,7 +114,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
// We don't want to do the swizzling from +load because not all the classes may be loaded at this point.
dispatch_async(dispatch_get_main_queue(), ^{
if ([self isEnabled]) {
[self injectIntoAllNSURLConnectionDelegateClasses];
[self injectIntoAllNSURLThings];
}
});
}
@@ -154,7 +163,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
#pragma mark - Delegate Injection
+ (void)injectIntoAllNSURLConnectionDelegateClasses {
+ (void)injectIntoAllNSURLThings {
// Only allow swizzling once.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -224,6 +233,15 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
[self injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods];
[self injectIntoNSURLSessionAsyncUploadTaskMethods];
if (@available(iOS 13.0, *)) {
Class websocketTask = NSClassFromString(@"__NSURLSessionWebSocketTask");
[self injectWebsocketSendMessage:websocketTask];
[self injectWebsocketReceiveMessage:websocketTask];
websocketTask = [NSURLSessionWebSocketTask class];
[self injectWebsocketSendMessage:websocketTask];
[self injectWebsocketReceiveMessage:websocketTask];
}
});
}
@@ -1266,7 +1284,76 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
implementationBlock:implementationBlock
undefinedBlock:undefinedBlock
];
}
+ (void)injectWebsocketSendMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
SEL selector = @selector(sendMessage:completionHandler:);
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
typedef void (^SendMessageBlock)(
NSURLSessionWebSocketTask *slf,
NSURLSessionWebSocketMessage *message,
void (^completion)(NSError *error)
);
SendMessageBlock implementationBlock = ^(
NSURLSessionWebSocketTask *slf,
NSURLSessionWebSocketMessage *message,
void (^completion)(NSError *error)
) {
[FLEXNetworkObserver.sharedObserver
websocketTask:slf sendMessagage:message
];
completion = ^(NSError *error) {
[FLEXNetworkObserver.sharedObserver
websocketTaskMessageSendCompletion:message
error:error
];
};
((void(*)(id, SEL, id, id))objc_msgSend)(
slf, swizzledSelector, message, completion
);
};
[FLEXUtility replaceImplementationOfKnownSelector:selector
onClass:cls
withBlock:implementationBlock
swizzledSelector:swizzledSelector
];
}
+ (void)injectWebsocketReceiveMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
SEL selector = @selector(receiveMessageWithCompletionHandler:);
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
typedef void (^SendMessageBlock)(
NSURLSessionWebSocketTask *slf,
void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
);
SendMessageBlock implementationBlock = ^(
NSURLSessionWebSocketTask *slf,
void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
) {
id completionHook = ^(NSURLSessionWebSocketMessage *message, NSError *error) {
[FLEXNetworkObserver.sharedObserver
websocketTask:slf receiveMessagage:message error:error
];
completion(message, error);
};
((void(*)(id, SEL, id))objc_msgSend)(
slf, swizzledSelector, completionHook
);
};
[FLEXUtility replaceImplementationOfKnownSelector:selector
onClass:cls
withBlock:implementationBlock
swizzledSelector:swizzledSelector
];
}
static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
@@ -1589,4 +1676,35 @@ didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data
}];
}
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
sendMessagage:(NSURLSessionWebSocketMessage *)message {
[self performBlock:^{
// NSString *requestID = [[self class] requestIDForConnectionOrTask:task];
[FLEXNetworkRecorder.defaultRecorder recordWebsocketMessageSend:message task:task];
}];
}
- (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error {
[self performBlock:^{
[FLEXNetworkRecorder.defaultRecorder
recordWebsocketMessageSendCompletion:message
error:error
];
}];
}
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
receiveMessagage:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error {
[self performBlock:^{
if (!error && message) {
[FLEXNetworkRecorder.defaultRecorder
recordWebsocketMessageReceived:message
task:task
];
}
}];
}
@end
+24 -7
View File
@@ -139,7 +139,10 @@
BOOL hidePrivateMethods = defaults.flex_explorerHidesPrivateMethods;
BOOL showMethodOverrides = defaults.flex_explorerShowsMethodOverrides;
NSMutableArray<NSArray<FLEXProperty *> *> *allProperties = [NSMutableArray new];
NSMutableArray<NSArray<FLEXProperty *> *> *allClassProps = [NSMutableArray new];
NSMutableArray<NSArray<FLEXMethod *> *> *allMethods = [NSMutableArray new];
NSMutableArray<NSArray<FLEXMethod *> *> *allClassMethods = [NSMutableArray new];
// Loop over each class and each superclass, collect
// the fresh and unique metadata in each category
@@ -150,13 +153,13 @@
Class cls = self.classHierarchyClasses[i];
superclass = (i < rootIdx) ? self.classHierarchyClasses[i+1] : nil;
[_allProperties addObject:[self
[allProperties addObject:[self
metadataUniquedByName:[cls flex_allInstanceProperties]
superclass:superclass
kind:FLEXMetadataKindProperties
skip:showMethodOverrides
]];
[_allClassProperties addObject:[self
[allClassProps addObject:[self
metadataUniquedByName:[cls flex_allClassProperties]
superclass:superclass
kind:FLEXMetadataKindClassProperties
@@ -174,7 +177,7 @@
kind:FLEXMetadataKindMethods
skip:showMethodOverrides
]];
[_allClassMethods addObject:[self
[allClassMethods addObject:[self
metadataUniquedByName:[cls flex_allClassMethods]
superclass:superclass
kind:FLEXMetadataKindClassMethods
@@ -201,7 +204,7 @@
_classHierarchy = [FLEXStaticMetadata classHierarchy:self.classHierarchyClasses];
NSArray<NSArray<FLEXProperty *> *> *properties = _allProperties;
NSArray<NSArray<FLEXProperty *> *> *properties = allProperties;
// Potentially filter property-backing ivars
if (hideBackingIvars) {
@@ -211,7 +214,7 @@
NSSet *ivarNames = [NSSet setWithArray:({
[properties[idx] flex_mapped:^id(FLEXProperty *p, NSUInteger idx) {
// Nil if no ivar, and array is flatted
return p.attributes.backingIvar;
return p.likelyIvarName;
}];
})];
@@ -250,15 +253,29 @@
}
if (hidePrivateMethods) {
allMethods = [allMethods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
id methodMapBlock = ^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
// Remove methods which contain an underscore
return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
return ![method.selectorString containsString:@"_"];
}];
}];
};
id propertyMapBlock = ^id(NSArray<FLEXProperty *> *list, NSUInteger idx) {
// Remove methods which contain an underscore
return [list flex_filtered:^BOOL(FLEXProperty *prop, NSUInteger idx) {
return ![prop.name containsString:@"_"];
}];
};
allMethods = [allMethods flex_mapped:methodMapBlock];
allClassMethods = [allClassMethods flex_mapped:methodMapBlock];
allProperties = [allProperties flex_mapped:propertyMapBlock];
allClassProps = [allClassProps flex_mapped:propertyMapBlock];
}
_allProperties = allProperties;
_allClassProperties = allClassProps;
_allMethods = allMethods;
_allClassMethods = allClassMethods;
// Set up UIKit helper data
// Really, we only need to call this on properties and ivars
@@ -17,6 +17,8 @@
#import "FLEXColorPreviewSection.h"
#import "FLEXDefaultsContentSection.h"
#import "FLEXBundleShortcuts.h"
#import "FLEXNSStringShortcuts.h"
#import "FLEXNSDataShortcuts.h"
#import "FLEXBlockShortcuts.h"
#import "FLEXUtility.h"
@@ -50,6 +52,8 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
ClassKey(CALayer) : [FLEXLayerShortcuts class],
ClassKey(UIColor) : [FLEXColorPreviewSection class],
ClassKey(NSBundle) : [FLEXBundleShortcuts class],
ClassKey(NSString) : [FLEXNSStringShortcuts class],
ClassKey(NSData) : [FLEXNSDataShortcuts class],
ClassKeyByName(NSBlock) : [FLEXBlockShortcuts class],
}];
#undef ClassKey
@@ -72,19 +76,33 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
// shortcut section for NSObject.
//
// TODO: rename it to FLEXNSObjectShortcuts or something?
Class sectionClass = nil;
FLEXShortcutsSection *shortcutsSection = [FLEXShortcutsSection forObject:object];
NSArray *sections = @[shortcutsSection];
Class customSectionClass = nil;
Class cls = object_getClass(object);
do {
sectionClass = classesToRegisteredSections[(id<NSCopying>)cls];
} while (!sectionClass && (cls = [cls superclass]));
customSectionClass = classesToRegisteredSections[(id<NSCopying>)cls];
} while (!customSectionClass && (cls = [cls superclass]));
if (!sectionClass) {
sectionClass = [FLEXShortcutsSection class];
if (customSectionClass) {
id customSection = [customSectionClass forObject:object];
BOOL isFLEXShortcutSection = [customSection respondsToSelector:@selector(isNewSection)];
// If the section "replaces" the default shortcuts section,
// only return that section. Otherwise, return both this
// section and the default shortcuts section.
if (isFLEXShortcutSection && ![customSection isNewSection]) {
sections = @[customSection];
} else {
// Custom section will go before shortcuts
sections = @[customSection, shortcutsSection];
}
}
return [FLEXObjectExplorerViewController
exploringObject:object
customSection:[sectionClass forObject:object]
customSections:sections
];
}
@@ -29,6 +29,9 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)exploringObject:(id)objectOrClass;
/// No custom section unless you provide one.
+ (instancetype)exploringObject:(id)objectOrClass customSection:(nullable FLEXTableViewSection *)customSection;
/// No custom sections unless you provide some.
+ (instancetype)exploringObject:(id)objectOrClass
customSections:(nullable NSArray<FLEXTableViewSection *> *)customSections;
/// The object being explored, which may be an instance of a class or a class itself.
@property (nonatomic, readonly) id object;
@@ -30,7 +30,7 @@
#pragma mark - Private properties
@interface FLEXObjectExplorerViewController () <UIGestureRecognizerDelegate>
@property (nonatomic, readonly) FLEXSingleRowSection *descriptionSection;
@property (nonatomic, readonly) FLEXTableViewSection *customSection;
@property (nonatomic, readonly) NSArray<FLEXTableViewSection *> *customSections;
@property (nonatomic) NSIndexSet *customSectionVisibleIndexes;
@property (nonatomic, readonly) NSArray<NSString *> *observedNotifications;
@@ -46,23 +46,27 @@
}
+ (instancetype)exploringObject:(id)target customSection:(FLEXTableViewSection *)section {
return [self exploringObject:target customSections:@[section]];
}
+ (instancetype)exploringObject:(id)target customSections:(NSArray *)customSections {
return [[self alloc]
initWithObject:target
explorer:[FLEXObjectExplorer forObject:target]
customSection:section
customSections:customSections
];
}
- (id)initWithObject:(id)target
explorer:(__kindof FLEXObjectExplorer *)explorer
customSection:(FLEXTableViewSection *)customSection {
customSections:(NSArray<FLEXTableViewSection *> *)customSections {
NSParameterAssert(target);
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
_object = target;
_explorer = explorer;
_customSection = customSection;
_customSections = customSections;
}
return self;
@@ -195,8 +199,10 @@
referencesSection
]];
if (self.customSection) {
[sections insertObject:self.customSection atIndex:0];
if (self.customSections) {
[sections insertObjects:self.customSections atIndexes:[NSIndexSet
indexSetWithIndexesInRange:NSMakeRange(0, self.customSections.count)
]];
}
if (self.descriptionSection) {
[sections insertObject:self.descriptionSection atIndex:0];
@@ -269,6 +275,10 @@
if (g2 == self.navigationController.interactivePopGestureRecognizer) {
return NO;
}
if (g2 == self.tableView.panGestureRecognizer) {
return NO;
}
}
return YES;
@@ -13,7 +13,7 @@
@interface FLEXDefaultsContentSection ()
@property (nonatomic) NSUserDefaults *defaults;
@property (nonatomic) NSArray *keys;
@property (nonatomic, readonly) NSDictionary *whitelistedDefaults;
@property (nonatomic, readonly) NSDictionary *unexcludedDefaults;
@end
@implementation FLEXDefaultsContentSection
@@ -33,7 +33,7 @@
FLEXDefaultsContentSection *section = [self forReusableFuture:^id(FLEXDefaultsContentSection *section) {
section.defaults = userDefaults;
section.onlyShowKeysForAppPrefs = YES;
return section.whitelistedDefaults;
return section.unexcludedDefaults;
}];
return section;
}
@@ -87,16 +87,16 @@
_keys = [keys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
}
- (NSDictionary *)whitelistedDefaults {
// Case: no whitelisting
- (NSDictionary *)unexcludedDefaults {
// Case: no excluding
if (!self.onlyShowKeysForAppPrefs) {
return self.defaults.dictionaryRepresentation;
}
// Always regenerate key whitelist when this method is called
// Always regenerate key allowlist when this method is called
_keys = nil;
// Generate new dictionary from whitelisted keys
// Generate new dictionary from unexcluded keys
NSArray *values = [self.defaults.dictionaryRepresentation
objectsForKeys:self.keys notFoundMarker:NSNull.null
];
@@ -189,8 +189,6 @@
cell.accessoryType = [self accessoryTypeForRow:row];
}
#if FLEX_AT_LEAST_IOS13_SDK
- (NSString *)menuSubtitleForRow:(NSInteger)row {
return [self.metadata[row] contextualSubtitleWithTarget:self.explorer.object];
}
@@ -232,6 +230,4 @@
return [self.metadata[row] copiableMetadataWithTarget:self.explorer.object];
}
#endif
@end
@@ -7,6 +7,7 @@
//
#import "FLEXMutableListSection.h"
#import "FLEXMacros.h"
@interface FLEXMutableListSection ()
@property (nonatomic, readonly) FLEXMutableListCellForElement configureCell;
@@ -9,6 +9,7 @@
#import "FLEXBundleShortcuts.h"
#import "FLEXShortcut.h"
#import "FLEXAlert.h"
#import "FLEXMacros.h"
#import "FLEXRuntimeExporter.h"
#import "FLEXTableListViewController.h"
#import "FLEXFileBrowserController.h"
@@ -10,6 +10,7 @@
#import "FLEXImagePreviewViewController.h"
#import "FLEXShortcut.h"
#import "FLEXAlert.h"
#import "FLEXMacros.h"
@interface UIAlertController (FLEXImageShortcuts)
- (void)flex_image:(UIImage *)image disSaveWithError:(NSError *)error :(void *)context;
@@ -0,0 +1,13 @@
//
// FLEXNSDataShortcuts.h
// FLEX
//
// Created by Tanner on 3/29/21.
//
#import "FLEXShortcutsSection.h"
/// Adds a "UTF-8 String" shortcut
@interface FLEXNSDataShortcuts : FLEXShortcutsSection
@end
@@ -0,0 +1,48 @@
//
// FLEXNSDataShortcuts.m
// FLEX
//
// Created by Tanner on 3/29/21.
//
#import "FLEXNSDataShortcuts.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXShortcut.h"
@implementation FLEXNSDataShortcuts
+ (instancetype)forObject:(NSData *)data {
NSString *string = [self stringForData:data];
return [self forObject:data additionalRows:@[
[FLEXActionShortcut title:@"UTF-8 String" subtitle:^(NSData *object) {
return string.length ? string : (string ?
@"Data is not a UTF8 String" : @"Empty string"
);
} viewer:^UIViewController *(id object) {
return [FLEXObjectExplorerFactory explorerViewControllerForObject:string];
} accessoryType:^UITableViewCellAccessoryType(NSData *object) {
if (string.length) {
return UITableViewCellAccessoryDisclosureIndicator;
}
return UITableViewCellAccessoryNone;
}]
]];
}
+ (NSString *)stringForData:(NSData *)data {
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
@end
@interface NSData (Overrides) @end
@implementation NSData (Overrides)
// This normally crashes
- (NSUInteger)length {
return 0;
}
@end
@@ -0,0 +1,13 @@
//
// FLEXNSStringShortcuts.h
// FLEX
//
// Created by Tanner on 3/29/21.
//
#import "FLEXShortcutsSection.h"
/// Adds a "UTF-8 Data" shortcut
@interface FLEXNSStringShortcuts : FLEXShortcutsSection
@end
@@ -0,0 +1,29 @@
//
// FLEXNSStringShortcuts.m
// FLEX
//
// Created by Tanner on 3/29/21.
//
#import "FLEXNSStringShortcuts.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXShortcut.h"
@implementation FLEXNSStringShortcuts
+ (instancetype)forObject:(NSString *)string {
NSUInteger length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytesNoCopy:(void *)string.UTF8String length:length freeWhenDone:NO];
return [self forObject:string additionalRows:@[
[FLEXActionShortcut title:@"UTF-8 Data" subtitle:^NSString *(id _) {
return data.description;
} viewer:^UIViewController *(id _) {
return [FLEXObjectExplorerFactory explorerViewControllerForObject:data];
} accessoryType:^UITableViewCellAccessoryType(id _) {
return UITableViewCellAccessoryDisclosureIndicator;
}]
]];
}
@end
@@ -25,3 +25,7 @@
@interface FLEXShortcutsFactory (Blocks) @end
@interface FLEXShortcutsFactory (Foundation) @end
@interface FLEXShortcutsFactory (WebKit_Safari) @end
@interface FLEXShortcutsFactory (Pasteboard) @end
@@ -8,8 +8,12 @@
#import "FLEXShortcutsFactory+Defaults.h"
#import "FLEXShortcut.h"
#import "FLEXMacros.h"
#import "FLEXRuntimeUtility.h"
#import "NSArray+FLEX.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXObjcInternal.h"
#import "Cocoa+FLEXShortcuts.h"
#pragma mark - UIApplication
@@ -59,6 +63,7 @@
FLEXRuntimeUtilityTryAddObjectProperty(6, constraints, UIView_, NSArray, PropertyKey(ReadOnly));
FLEXRuntimeUtilityTryAddObjectProperty(2, subviews, UIView_, NSArray, PropertyKey(ReadOnly));
FLEXRuntimeUtilityTryAddObjectProperty(2, superview, UIView_, UIView, PropertyKey(ReadOnly));
FLEXRuntimeUtilityTryAddObjectProperty(7, tintColor, UIView_, UIView);
// UIButton, private
FLEXRuntimeUtilityTryAddObjectProperty(2, font, UIButton.class, UIFont, PropertyKey(ReadOnly));
@@ -141,7 +146,26 @@
@"viewIfLoaded", @"title", @"navigationItem", @"toolbarItems", @"tabBarItem",
@"childViewControllers", @"navigationController", @"tabBarController", @"splitViewController",
@"parentViewController", @"presentedViewController", @"presentingViewController",
]).methods(@[@"view"]).forClass(UIViewController.class);
])
.methods(@[@"view"])
.forClass(UIViewController.class);
// UIAlertController
NSMutableArray *alertControllerProps = @[
@"title", @"message", @"actions", @"textFields",
@"preferredAction", @"presentingViewController", @"viewIfLoaded",
].mutableCopy;
if (@available(iOS 14.0, *)) {
[alertControllerProps insertObject:@"image" atIndex:4];
}
self.append
.properties(alertControllerProps)
.methods(@[@"addAction:"])
.forClass(UIAlertController.class);
self.append.properties(@[
@"title", @"style", @"enabled", @"flex_styleName",
@"image", @"keyCommandInput", @"_isPreferred", @"_alertController",
]).forClass(UIAlertAction.class);
}
@end
@@ -256,12 +280,164 @@
FLEXRuntimeUtilityTryAddObjectProperty(2, abbreviationDictionary, NSTimeZone.flex_metaclass, NSDictionary);
self.append.classMethods(@[
@"timeZoneWithName:", @"timeZoneWithAbbreviation:", @"timeZoneForSecondsFromGMT:", @"", @"", @"",
@"timeZoneWithName:", @"timeZoneWithAbbreviation:", @"timeZoneForSecondsFromGMT:",
]).forClass(NSTimeZone.flex_metaclass);
self.append.classProperties(@[
@"defaultTimeZone", @"systemTimeZone", @"localTimeZone"
@"defaultTimeZone", @"systemTimeZone", @"localTimeZone",
]).forClass(NSTimeZone.class);
// UTF8String is not a real property under the hood
FLEXRuntimeUtilityTryAddNonatomicProperty(2, UTF8String, NSString.class, const char *, PropertyKey(ReadOnly));
self.append.properties(@[@"length"]).methods(@[@"characterAtIndex:"]).forClass(NSString.class);
self.append.methods(@[
@"writeToFile:atomically:", @"subdataWithRange:", @"isEqualToData:",
]).properties(@[
@"length", @"bytes",
]).forClass(NSData.class);
self.append.classMethods(@[
@"dataWithJSONObject:options:error:",
@"JSONObjectWithData:options:error:",
@"isValidJSONObject:",
]).forClass(NSJSONSerialization.class);
// NSArray
self.append.classMethods(@[
@"arrayWithObject:", @"arrayWithContentsOfFile:"
]).forClass(NSArray.flex_metaclass);
self.append.methods(@[
@"valueForKeyPath:", @"subarrayWithRange:",
@"arrayByAddingObject:", @"arrayByAddingObjectsFromArray:",
@"filteredArrayUsingPredicate:", @"subarrayWithRange:",
@"containsObject:", @"objectAtIndex:", @"indexOfObject:",
@"makeObjectsPerformSelector:", @"makeObjectsPerformSelector:withObject:",
@"sortedArrayUsingSelector:", @"reverseObjectEnumerator",
@"isEqualToArray:", @"mutableCopy",
]).forClass(NSArray.class);
// NSDictionary
self.append.methods(@[
@"objectForKey:", @"valueForKeyPath:",
@"isEqualToDictionary:", @"mutableCopy",
]).forClass(NSDictionary.class);
// NSSet
self.append.classMethods(@[
@"setWithObject:", @"setWithArray:"
]).forClass(NSSet.flex_metaclass);
self.append.methods(@[
@"allObjects", @"valueForKeyPath:", @"containsObject:",
@"setByAddingObject:", @"setByAddingObjectsFromArray:",
@"filteredSetUsingPredicate:", @"isSubsetOfSet:",
@"makeObjectsPerformSelector:", @"makeObjectsPerformSelector:withObject:",
@"reverseObjectEnumerator", @"isEqualToSet:", @"mutableCopy",
]).forClass(NSSet.class);
// NSMutableArray
self.prepend.methods(@[
@"addObject:", @"insertObject:atIndex:", @"addObjectsFromArray:",
@"removeObject:", @"removeObjectAtIndex:",
@"removeObjectsInArray:", @"removeAllObjects",
@"removeLastObject", @"filterUsingPredicate:",
@"sortUsingSelector:", @"copy",
]).forClass(NSMutableArray.class);
// NSMutableDictionary
self.prepend.methods(@[
@"setObject:forKey:", @"removeObjectForKey:",
@"removeAllObjects", @"removeObjectsForKeys:", @"copy",
]).forClass(NSMutableDictionary.class);
// NSMutableSet
self.prepend.methods(@[
@"addObject:", @"removeObject:", @"filterUsingPredicate:",
@"removeAllObjects", @"addObjectsFromArray:",
@"unionSet:", @"minusSet:", @"intersectSet:", @"copy"
]).forClass(NSMutableSet.class);
self.append.methods(@[@"nextObject", @"allObjects"]).forClass(NSEnumerator.class);
self.append.properties(@[@"flex_observers"]).forClass(NSNotificationCenter.class);
}
@end
#pragma mark - WebKit / Safari
@implementation FLEXShortcutsFactory (WebKit_Safari)
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
Class WKWebView = NSClassFromString(@"WKWebView");
Class SafariVC = NSClassFromString(@"SFSafariViewController");
if (WKWebView) {
self.append.properties(@[
@"configuration", @"scrollView", @"title", @"URL",
@"customUserAgent", @"navigationDelegate"
]).methods(@[@"reload", @"stopLoading"]).forClass(WKWebView);
}
if (SafariVC) {
self.append.properties(@[
@"delegate"
]).forClass(SafariVC);
if (@available(iOS 10.0, *)) {
self.append.properties(@[
@"preferredBarTintColor", @"preferredControlTintColor"
]).forClass(SafariVC);
}
if (@available(iOS 11.0, *)) {
self.append.properties(@[
@"configuration", @"dismissButtonStyle"
]).forClass(SafariVC);
}
}
}
@end
#pragma mark - Pasteboard
@implementation FLEXShortcutsFactory (Pasteboard)
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
self.append.properties(@[
@"name", @"numberOfItems", @"items",
@"string", @"image", @"color", @"URL",
]).forClass(UIPasteboard.class);
}
@end
@interface NSNotificationCenter (Observers)
@property (readonly) NSArray<NSString *> *flex_observers;
@end
@implementation NSNotificationCenter (Observers)
- (id)flex_observers {
NSString *debug = self.debugDescription;
NSArray<NSString *> *observers = [debug componentsSeparatedByString:@"\n"];
NSArray<NSArray<NSString *> *> *splitObservers = [observers flex_mapped:^id(NSString *entry, NSUInteger idx) {
return [entry componentsSeparatedByString:@","];
}];
NSArray *names = [splitObservers flex_mapped:^id(NSArray<NSString *> *entry, NSUInteger idx) {
return entry[0];
}];
NSArray *objects = [splitObservers flex_mapped:^id(NSArray<NSString *> *entry, NSUInteger idx) {
if (entry.count < 2) return NSNull.null;
NSScanner *scanner = [NSScanner scannerWithString:entry[1]];
unsigned long long objectPointerValue;
if ([scanner scanHexLongLong:&objectPointerValue]) {
void *objectPointer = (void *)objectPointerValue;
if (FLEXPointerIsValidObjcObject(objectPointer))
return (__bridge id)(void *)objectPointer;
}
return NSNull.null;
}];
return [NSArray flex_forEachUpTo:names.count map:^id(NSUInteger i) {
return @[names[i], objects[i]];
}];
}
@end
@@ -10,6 +10,8 @@
#import "FLEXObjectInfoSection.h"
@class FLEXProperty, FLEXIvar, FLEXMethod;
NS_ASSUME_NONNULL_BEGIN
/// An abstract base class for custom object "shortcuts" where every
/// row can possibly have some action. The section title is "Shortcuts".
///
@@ -25,11 +27,11 @@
@interface FLEXShortcutsSection : FLEXTableViewSection <FLEXObjectInfoSection>
/// Uses \c kFLEXDefaultCell
+ (instancetype)forObject:(id)objectOrClass rowTitles:(NSArray<NSString *> *)titles;
+ (instancetype)forObject:(id)objectOrClass rowTitles:(nullable NSArray<NSString *> *)titles;
/// Uses \c kFLEXDetailCell for non-empty subtitles, otherwise uses \c kFLEXDefaultCell
+ (instancetype)forObject:(id)objectOrClass
rowTitles:(NSArray<NSString *> *)titles
rowSubtitles:(NSArray<NSString *> *)subtitles;
rowTitles:(nullable NSArray<NSString *> *)titles
rowSubtitles:(nullable NSArray<NSString *> *)subtitles;
/// Uses \c kFLEXDefaultCell for rows that are given a title, otherwise
/// this uses \c kFLEXDetailCell for any other allowed object.
@@ -44,16 +46,15 @@
/// - a \c FLEXIvar
/// - a \c FLEXMethodBase (includes \c FLEXMethod of course)
/// Passing one of the latter 3 will provide a shortcut to that property/ivar/method.
/// @return \c nil if no rows are provided
+ (instancetype)forObject:(id)objectOrClass rows:(NSArray *)rows;
+ (instancetype)forObject:(id)objectOrClass rows:(nullable NSArray *)rows;
/// Same as \c forObject:rows: but the given rows are prepended
/// to the shortcuts already registered for the object's class.
/// \c forObject:rows: does not use the registered shortcuts at all.
+ (instancetype)forObject:(id)objectOrClass additionalRows:(NSArray *)rows;
+ (instancetype)forObject:(id)objectOrClass additionalRows:(nullable NSArray *)rows;
/// Calls into \c forObject:rows: using the registered shortcuts for the object's class.
/// @return \c nil if the object has no shortcuts registered at all
/// @return An empty section if the object has no shortcuts registered at all.
+ (instancetype)forObject:(id)objectOrClass;
/// Subclasses \e may override this to hide the disclosure indicator
@@ -72,10 +73,16 @@
/// Defaults to NO. Has no effect on static subtitles that are passed explicitly.
@property (nonatomic) BOOL cacheSubtitles;
/// Whether this shortcut section overrides the default section or not.
/// Subclasses should not override this method. To provide a second
/// section alongside the default shortcuts section, use \c forObject:rows:
/// @return \c NO if initialized with \c forObject: or \c forObject:additionalRows:
@property (nonatomic, readonly) BOOL isNewSection;
@end
@class FLEXShortcutsFactory;
typedef FLEXShortcutsFactory *(^FLEXShortcutsFactoryNames)(NSArray *names);
typedef FLEXShortcutsFactory *_Nonnull(^FLEXShortcutsFactoryNames)(NSArray *names);
typedef void (^FLEXShortcutsFactoryTarget)(Class targetClass);
/// The block properties below are to be used like SnapKit or Masonry.
@@ -123,3 +130,5 @@ typedef void (^FLEXShortcutsFactoryTarget)(Class targetClass);
@property (nonatomic, readonly) FLEXShortcutsFactoryTarget forClass;
@end
NS_ASSUME_NONNULL_END
@@ -33,6 +33,7 @@
@end
@implementation FLEXShortcutsSection
@synthesize isNewSection = _isNewSection;
#pragma mark Initialization
@@ -47,13 +48,13 @@
}
+ (instancetype)forObject:(id)objectOrClass rows:(NSArray *)rows {
return [[self alloc] initWithObject:objectOrClass rows:rows];
return [[self alloc] initWithObject:objectOrClass rows:rows isNewSection:YES];
}
+ (instancetype)forObject:(id)objectOrClass additionalRows:(NSArray *)toPrepend {
NSArray *rows = [FLEXShortcutsFactory shortcutsForObjectOrClass:objectOrClass];
NSArray *allRows = [toPrepend arrayByAddingObjectsFromArray:rows] ?: rows;
return [self forObject:objectOrClass rows:allRows];
return [[self alloc] initWithObject:objectOrClass rows:allRows isNewSection:NO];
}
+ (instancetype)forObject:(id)objectOrClass {
@@ -72,16 +73,18 @@
_object = object;
_allTitles = titles.copy;
_allSubtitles = subtitles.copy;
_isNewSection = YES;
_numberOfLines = 1;
}
return self;
}
- (id)initWithObject:object rows:(NSArray *)rows {
- (id)initWithObject:object rows:(NSArray *)rows isNewSection:(BOOL)newSection {
self = [super init];
if (self) {
_object = object;
_isNewSection = newSection;
_allShortcuts = [rows flex_mapped:^id(id obj, NSUInteger idx) {
return [FLEXShortcut shortcutFor:obj];
@@ -300,6 +303,7 @@ typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> Re
}
- (NSArray<id<FLEXRuntimeMetadata>> *)shortcutsForObjectOrClass:(id)objectOrClass {
NSParameterAssert(objectOrClass);
NSMutableArray<id<FLEXRuntimeMetadata>> *shortcuts = [NSMutableArray new];
BOOL isClass = object_isClass(objectOrClass);
@@ -50,8 +50,6 @@
/// Return nil to use the default reuse identifier
- (NSString *)reuseIdentifierWithTarget:(id)object;
#if FLEX_AT_LEAST_IOS13_SDK
/// An array of actions to place in the first section of the context menu.
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender API_AVAILABLE(ios(13.0));
/// An array where every 2 elements are a key-value pair. The key is a description
@@ -60,8 +58,6 @@
/// Properties and ivars return the address of an object, if they hold one.
- (NSString *)contextualSubtitleWithTarget:(id)object;
#endif
@end
// Even if a property is readonly, it still may be editable
@@ -129,8 +129,6 @@ FLEXObjectExplorerDefaultsImpl
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
#if FLEX_AT_LEAST_IOS13_SDK
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
BOOL returnsObject = self.attributes.typeEncoding.flex_typeIsObjectOrClass;
BOOL targetNotNil = [self appropriateTargetForPropertyType:object] != nil;
@@ -207,8 +205,6 @@ FLEXObjectExplorerDefaultsImpl
return nil;
}
#endif
@end
@@ -285,8 +281,6 @@ FLEXObjectExplorerDefaultsImpl
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
#if FLEX_AT_LEAST_IOS13_SDK
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
Class ivarClass = self.typeEncoding.flex_typeClass;
@@ -337,8 +331,6 @@ FLEXObjectExplorerDefaultsImpl
return nil;
}
#endif
@end
@@ -383,8 +375,6 @@ FLEXObjectExplorerDefaultsImpl
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
#if FLEX_AT_LEAST_IOS13_SDK
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
return nil;
}
@@ -401,8 +391,6 @@ FLEXObjectExplorerDefaultsImpl
return nil;
}
#endif
@end
@implementation FLEXMethod (UIKitHelpers)
@@ -480,8 +468,6 @@ FLEXObjectExplorerDefaultsImpl
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
#if FLEX_AT_LEAST_IOS13_SDK
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
return nil;
}
@@ -499,8 +485,6 @@ FLEXObjectExplorerDefaultsImpl
return nil;
}
#endif
@end
@@ -592,8 +576,6 @@ FLEXObjectExplorerDefaultsImpl
return UITableViewCellAccessoryNone;
}
#if FLEX_AT_LEAST_IOS13_SDK
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
return nil;
}
@@ -606,8 +588,6 @@ FLEXObjectExplorerDefaultsImpl
return nil;
}
#endif
@end
@@ -29,6 +29,8 @@
- (instancetype)flex_sortedUsingSelector:(SEL)selector;
- (T)flex_firstWhere:(BOOL(^)(T obj))meetingCriteria;
@end
@interface NSMutableArray<T> (Functional)
+10
View File
@@ -113,6 +113,16 @@
}
}
- (id)flex_firstWhere:(BOOL (^)(id))meetsCriteria {
for (id e in self) {
if (meetsCriteria(e)) {
return e;
}
}
return nil;
}
@end
@@ -21,21 +21,21 @@ NS_ASSUME_NONNULL_BEGIN
/// @return The type encoding string, or \c nil if \e returnType is \c NULL.
NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...);
NSArray<Class> *FLEXGetAllSubclasses(_Nullable Class cls, BOOL includeSelf);
NSArray<Class> *FLEXGetClassHierarchy(_Nullable Class cls, BOOL includeSelf);
NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(_Nullable Class cls);
NSArray<Class> * _Nullable FLEXGetAllSubclasses(_Nullable Class cls, BOOL includeSelf);
NSArray<Class> * _Nullable FLEXGetClassHierarchy(_Nullable Class cls, BOOL includeSelf);
NSArray<FLEXProtocol *> * _Nullable FLEXGetConformedProtocols(_Nullable Class cls);
NSArray<FLEXIvar *> *FLEXGetAllIvars(_Nullable Class cls);
NSArray<FLEXIvar *> * _Nullable FLEXGetAllIvars(_Nullable Class cls);
/// @param cls a class object to get instance properties,
/// or a metaclass object to get class properties
NSArray<FLEXProperty *> *FLEXGetAllProperties(_Nullable Class cls);
NSArray<FLEXProperty *> * _Nullable FLEXGetAllProperties(_Nullable Class cls);
/// @param cls a class object to get instance methods,
/// or a metaclass object to get class methods
/// @param instance used to mark methods as instance methods or not.
/// Not used to determine whether to get instance or class methods.
NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance);
NSArray<FLEXMethod *> * _Nullable FLEXGetAllMethods(_Nullable Class cls, BOOL instance);
/// @param cls a class object to get all instance and class methods.
NSArray<FLEXMethod *> *FLEXGetAllInstanceAndClassMethods(_Nullable Class cls);
NSArray<FLEXMethod *> * _Nullable FLEXGetAllInstanceAndClassMethods(_Nullable Class cls);
@@ -20,7 +20,7 @@
NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...) {
if (returnType == NULL) return nil;
if (!returnType) return nil;
NSMutableString *encoding = [NSMutableString new];
[encoding appendFormat:@"%s%s%s", returnType, @encode(id), @encode(SEL)];
@@ -37,9 +37,7 @@ NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...)
}
NSArray<Class> *FLEXGetAllSubclasses(Class cls, BOOL includeSelf) {
if (!cls) {
return nil;
}
if (!cls) return nil;
Class *buffer = NULL;
@@ -71,9 +69,7 @@ NSArray<Class> *FLEXGetAllSubclasses(Class cls, BOOL includeSelf) {
}
NSArray<Class> *FLEXGetClassHierarchy(Class cls, BOOL includeSelf) {
if (!cls) {
return nil;
}
if (!cls) return nil;
NSMutableArray *classes = [NSMutableArray new];
if (includeSelf) {
@@ -88,9 +84,7 @@ NSArray<Class> *FLEXGetClassHierarchy(Class cls, BOOL includeSelf) {
}
NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
if (!cls) {
return nil;
}
if (!cls) return nil;
unsigned int count = 0;
Protocol *__unsafe_unretained *list = class_copyProtocolList(cls, &count);
@@ -247,7 +241,7 @@ NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
}
+ (NSArray<FLEXMethod *> *)flex_allClassMethods {
return FLEXGetAllMethods(self.flex_metaclass, NO);
return FLEXGetAllMethods(self.flex_metaclass, NO) ?: @[];
}
+ (FLEXMethod *)flex_methodNamed:(NSString *)name {
@@ -395,7 +389,7 @@ NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
}
+ (NSArray<FLEXProperty *> *)flex_allClassProperties {
return FLEXGetAllProperties(self.flex_metaclass);
return FLEXGetAllProperties(self.flex_metaclass) ?: @[];
}
+ (FLEXProperty *)flex_propertyNamed:(NSString *)name {
@@ -16,7 +16,7 @@ extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
extern NSString * const kFLEXDefaultsHidePrivateMethodsKey;
extern NSString * const kFLEXDefaultsShowMethodOverridesKey;
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
extern NSString * const kFLEXDefaultsNetworkHostBlacklistKey;
extern NSString * const kFLEXDefaultsNetworkHostDenylistKey;
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
@@ -28,7 +28,7 @@ extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
@property (nonatomic) double flex_toolbarTopMargin;
// Not actually stored in defaults, but written to a file
@property (nonatomic) NSArray<NSString *> *flex_networkHostBlacklist;
@property (nonatomic) NSArray<NSString *> *flex_networkHostDenylist;
/// Whether or not to register the object explorer as a JSON viewer on launch
@property (nonatomic) BOOL flex_registerDictionaryJSONViewerOnLaunch;
@@ -15,7 +15,7 @@ NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide
NSString * const kFLEXDefaultsHidePrivateMethodsKey = @"com.flipboard.FLEX.hide_private_or_namespaced_methods";
NSString * const kFLEXDefaultsShowMethodOverridesKey = @"com.flipboard.FLEX.show_method_overrides";
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.network_host_blacklist";
NSString * const kFLEXDefaultsNetworkHostDenylistKey = @"com.flipboard.FLEX.network_host_denylist";
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.view_json_as_object";
@@ -62,16 +62,16 @@ NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.vie
[self setDouble:margin forKey:kFLEXDefaultsToolbarTopMarginKey];
}
- (NSArray<NSString *> *)flex_networkHostBlacklist {
- (NSArray<NSString *> *)flex_networkHostDenylist {
return [NSArray arrayWithContentsOfFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
]] ?: @[];
}
- (void)setFlex_networkHostBlacklist:(NSArray<NSString *> *)blacklist {
NSParameterAssert(blacklist);
[blacklist writeToFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
- (void)setFlex_networkHostDenylist:(NSArray<NSString *> *)denylist {
NSParameterAssert(denylist);
[denylist writeToFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
] atomically:YES];
}
@@ -0,0 +1,13 @@
//
// Cocoa+FLEXShortcuts.h
// Pods
//
// Created by Tanner on 2/24/21.
//
//
#import <UIKit/UIKit.h>
@interface UIAlertAction (FLEXShortcuts)
@property (nonatomic, readonly) NSString *flex_styleName;
@end
@@ -0,0 +1,25 @@
//
// Cocoa+FLEXShortcuts.m
// Pods
//
// Created by Tanner on 2/24/21.
//
//
#import "Cocoa+FLEXShortcuts.h"
@implementation UIAlertAction (FLEXShortcuts)
- (NSString *)flex_styleName {
switch (self.style) {
case UIAlertActionStyleDefault:
return @"Default style";
case UIAlertActionStyleCancel:
return @"Cancel style";
case UIAlertActionStyleDestructive:
return @"Destructive style";
default:
return [NSString stringWithFormat:@"Unknown (%@)", @(self.style)];
}
}
@end
@@ -8,6 +8,8 @@
#import <UIKit/UIKit.h>
#define FLEXBarButtonItem(title, tgt, sel) \
[UIBarButtonItem flex_itemWithTitle:title target:tgt action:sel]
#define FLEXBarButtonItemSystem(item, tgt, sel) \
[UIBarButtonItem flex_systemItem:UIBarButtonSystemItem##item target:tgt action:sel]
+13 -9
View File
@@ -8,23 +8,25 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class FLEXAlert, FLEXAlertAction;
typedef void (^FLEXAlertReveal)(void);
typedef void (^FLEXAlertBuilder)(FLEXAlert *make);
typedef FLEXAlert *(^FLEXAlertStringProperty)(NSString *);
typedef FLEXAlert *(^FLEXAlertStringArg)(NSString *);
typedef FLEXAlert *(^FLEXAlertTextField)(void(^configurationHandler)(UITextField *textField));
typedef FLEXAlertAction *(^FLEXAlertAddAction)(NSString *title);
typedef FLEXAlertAction *(^FLEXAlertActionStringProperty)(NSString *);
typedef FLEXAlertAction *(^FLEXAlertActionProperty)(void);
typedef FLEXAlertAction *(^FLEXAlertActionBOOLProperty)(BOOL);
typedef FLEXAlertAction *(^FLEXAlertActionHandler)(void(^handler)(NSArray<NSString *> *strings));
typedef FLEXAlert * _Nonnull (^FLEXAlertStringProperty)(NSString * _Nullable);
typedef FLEXAlert * _Nonnull (^FLEXAlertStringArg)(NSString * _Nullable);
typedef FLEXAlert * _Nonnull (^FLEXAlertTextField)(void(^configurationHandler)(UITextField *textField));
typedef FLEXAlertAction * _Nonnull (^FLEXAlertAddAction)(NSString *title);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionStringProperty)(NSString * _Nullable);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionProperty)(void);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionBOOLProperty)(BOOL);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionHandler)(void(^handler)(NSArray<NSString *> *strings));
@interface FLEXAlert : NSObject
/// Shows a simple alert with one button which says "Dismiss"
+ (void)showAlert:(NSString *)title message:(NSString *)message from:(UIViewController *)viewController;
+ (void)showAlert:(NSString * _Nullable)title message:(NSString * _Nullable)message from:(UIViewController *)viewController;
/// Construct and display an alert
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController;
@@ -79,3 +81,5 @@ typedef FLEXAlertAction *(^FLEXAlertActionHandler)(void(^handler)(NSArray<NSStri
@property (nonatomic, readonly) UIAlertAction *action;
@end
NS_ASSUME_NONNULL_END
-4
View File
@@ -9,7 +9,6 @@
#import "FLEXColor.h"
#import "FLEXUtility.h"
#if FLEX_AT_LEAST_IOS13_SDK
#define FLEXDynamicColor(dynamic, static) ({ \
UIColor *c; \
if (@available(iOS 13.0, *)) { \
@@ -19,9 +18,6 @@
} \
c; \
});
#else
#define FLEXDynamicColor(dynamic, static) [UIColor static]
#endif
@implementation FLEXColor
-6
View File
@@ -75,12 +75,6 @@ NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
r.size.height = height; return r;
}
#ifdef __IPHONE_13_0
#define FLEX_AT_LEAST_IOS13_SDK (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
#else
#define FLEX_AT_LEAST_IOS13_SDK NO
#endif
#define FLEXPluralString(count, plural, singular) [NSString \
stringWithFormat:@"%@ %@", @(count), (count == 1 ? singular : plural) \
]
-4
View File
@@ -17,10 +17,6 @@
#import "UIFont+FLEX.h"
#import "FLEXMacros.h"
#if !FLEX_AT_LEAST_IOS13_SDK
@class UIWindowScene;
#endif
@interface FLEXUtility : NSObject
/// The key window of the app, if it is not a \c FLEXWindow.
-5
View File
@@ -63,7 +63,6 @@ BOOL FLEXConstructorsShouldRun() {
return nil;
}
#if FLEX_AT_LEAST_IOS13_SDK
+ (UIWindowScene *)activeScene {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
// Look for an active UIWindowScene
@@ -75,7 +74,6 @@ BOOL FLEXConstructorsShouldRun() {
return nil;
}
#endif
+ (UIViewController *)topViewControllerInWindow:(UIWindow *)window {
UIViewController *topViewController = window.rootViewController;
@@ -180,8 +178,6 @@ BOOL FLEXConstructorsShouldRun() {
dispatch_once(&onceToken, ^{
UIImage *indentationPatternImage = FLEXResources.hierarchyIndentPattern;
patternColor = [UIColor colorWithPatternImage:indentationPatternImage];
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
// Create a dark mode version
UIGraphicsBeginImageContextWithOptions(
@@ -201,7 +197,6 @@ BOOL FLEXConstructorsShouldRun() {
: [UIColor colorWithPatternImage:darkModePatternImage]);
}];
}
#endif
});
return patternColor;
@@ -34,6 +34,15 @@
#define FLEXRuntimeUtilityTryAddObjectProperty(iOS_atLeast, name, cls, type, ...) \
FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, FLEXEncodeClass(type), PropertyKey(NonAtomic), __VA_ARGS__);
extern NSString * const FLEXRuntimeUtilityErrorDomain;
typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
// Start at a random value instead of 0 to avoid confusion with an absent code
FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0xbabe,
FLEXRuntimeUtilityErrorCodeInvocationFailed,
FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch
};
@interface FLEXRuntimeUtility : NSObject
// General Helpers
@@ -75,6 +84,12 @@
onObject:(id)object
withArguments:(NSArray *)arguments
error:(NSError * __autoreleasing *)error;
+ (id)performSelector:(SEL)selector
onObject:(id)object
withArguments:(NSArray *)arguments
allowForwarding:(BOOL)mightForwardMsgSend
error:(NSError * __autoreleasing *)error;
+ (NSString *)editableJSONStringForObject:(id)object;
+ (id)objectValueFromEditableJSONString:(NSString *)string;
+ (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString;
+36 -22
View File
@@ -10,13 +10,9 @@
#import "FLEXRuntimeUtility.h"
#import "FLEXObjcInternal.h"
#import "FLEXTypeEncodingParser.h"
#import "FLEXMethod.h"
static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
FLEXRuntimeUtilityErrorCodeInvocationFailed = 1,
FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch = 2
};
NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
@implementation FLEXRuntimeUtility
@@ -109,7 +105,11 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
+ (NSString *)safeDescriptionForObject:(id)object {
// Don't assume that we have an NSObject subclass; not all objects respond to -description
if ([self safeObject:object respondsToSelector:@selector(description)]) {
return [object description];
@try {
return [object description];
} @catch (NSException *exception) {
return nil;
}
}
return nil;
@@ -120,7 +120,9 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
NSString *description = nil;
if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) {
description = [object debugDescription];
@try {
description = [object debugDescription];
} @catch (NSException *exception) { }
} else {
description = [self safeDescriptionForObject:object];
}
@@ -294,18 +296,31 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
onObject:(id)object
withArguments:(NSArray *)arguments
error:(NSError * __autoreleasing *)error {
return [self performSelector:selector
onObject:object
withArguments:arguments
allowForwarding:NO
error:error
];
}
+ (id)performSelector:(SEL)selector
onObject:(id)object
withArguments:(NSArray *)arguments
allowForwarding:(BOOL)mightForwardMsgSend
error:(NSError * __autoreleasing *)error {
static dispatch_once_t onceToken;
static SEL stdStringExclusion = nil;
dispatch_once(&onceToken, ^{
stdStringExclusion = NSSelectorFromString(@"stdString");
});
// Bail if the object won't respond to this selector.
if (![self safeObject:object respondsToSelector:selector]) {
// Bail if the object won't respond to this selector
if (mightForwardMsgSend || ![self safeObject:object respondsToSelector:selector]) {
if (error) {
NSString *msg = [NSString
stringWithFormat:@"%@ does not respond to the selector %@",
object, NSStringFromSelector(selector)
stringWithFormat:@"This object does not respond to the selector %@",
NSStringFromSelector(selector)
];
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
*error = [NSError
@@ -318,15 +333,14 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
return nil;
}
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:({
Method method;
if (object_isClass(object)) {
method = class_getClassMethod(object, selector);
} else {
method = class_getInstanceMethod(object_getClass(object), selector);
}
method_getTypeEncoding(method);
})];
// It is important to use object_getClass and not -class here, as
// object_getClass will return a different result for class objects
Class cls = object_getClass(object);
NSMethodSignature *methodSignature = [FLEXMethod selector:selector class:cls].signature;
if (!methodSignature) {
// Unsupported type encoding
return nil;
}
// Probably an unsupported type encoding, like bitfields.
// In the future, we could calculate the return length
@@ -348,7 +362,7 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
[invocation retainArguments];
// Always self and _cmd
NSUInteger numberOfArguments = [methodSignature numberOfArguments];
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
id argumentObject = arguments.count > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil;
@@ -51,6 +51,10 @@
@property (nonatomic, readonly) NSString *likelyGetterString;
/// Not valid unless initialized with the owning class.
@property (nonatomic, readonly) BOOL likelyGetterExists;
/// Always \c nil for class properties.
@property (nonatomic, readonly) NSString *likelyIvarName;
/// Not valid unless initialized with the owning class.
@property (nonatomic, readonly) BOOL likelyIvarExists;
/// Whether there are certainly multiple definitions of this property,
/// such as in categories in other binary images or something.
@@ -124,6 +124,10 @@
_likelySetterString = NSStringFromSelector(_likelySetter);
_isClassProperty = _cls ? class_isMetaClass(_cls) : NO;
_likelyIvarName = _isClassProperty ? nil : (
self.attributes.backingIvar ?: [@"_" stringByAppendingString:_name]
);
}
#pragma mark Overrides
@@ -187,6 +191,14 @@
return _imageName;
}
- (BOOL)likelyIvarExists {
if (_likelyIvarName && _cls) {
return class_getInstanceVariable(_cls, _likelyIvarName.UTF8String) != nil;
}
return NO;
}
- (NSString *)fullDescription {
NSMutableArray<NSString *> *attributesStrings = [NSMutableArray new];
FLEXPropertyAttributes *attributes = self.attributes;
+1 -1
View File
@@ -10,7 +10,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface MiscNetworkRequests : NSObject <NSURLConnectionDataDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
@interface MiscNetworkRequests : NSObject <NSURLConnectionDataDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionWebSocketDelegate>
+ (void)sendExampleRequests;
+27 -8
View File
@@ -11,14 +11,21 @@
@implementation MiscNetworkRequests
+ (void)sendExampleRequests {
[[self new] sendExampleNetworkRequests];
MiscNetworkRequests *misc = [self new];
NSURLSessionConfiguration *config = NSURLSessionConfiguration.defaultSessionConfiguration;
config.timeoutIntervalForRequest = 10.0;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:misc delegateQueue:nil];
[misc sendExampleNetworkRequests:session];
[misc sendExampleWebsocketTraffic:session];
}
- (NSMutableURLRequest *)request:(NSString *)url {
return [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
}
- (void)sendExampleNetworkRequests {
- (void)sendExampleNetworkRequests:(NSURLSession *)sessionWithDelegate {
NSString *kFlipboardIcon = @"https://cdn.flipboard.com/serviceIcons/v2/social-icon-flipboard-96.png";
NSString *kRandomAnimal = @"https://lorempixel.com/248/250/animals/";
NSString *kSnowLeopard = @"https://lorempixel.com/248/250/animals/4/";
@@ -35,13 +42,10 @@
// With delegate //
NSURLSessionConfiguration *config = NSURLSessionConfiguration.defaultSessionConfiguration;
config.timeoutIntervalForRequest = 10.0;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
// NSURLSessionDataTask
[pendingTasks addObject:[session dataTaskWithURL:[NSURL URLWithString:kFlipboardIcon]]];
[pendingTasks addObject:[sessionWithDelegate dataTaskWithURL:[NSURL URLWithString:kFlipboardIcon]]];
// NSURLSessionDownloadTask
[pendingTasks addObject:[session downloadTaskWithURL:[NSURL URLWithString:kRandomAnimal]]];
[pendingTasks addObject:[sessionWithDelegate downloadTaskWithURL:[NSURL URLWithString:kRandomAnimal]]];
// Without delegate //
@@ -71,7 +75,7 @@
NSMutableURLRequest *upload = [self request:kImgurUpload];
upload.HTTPMethod = @"POST";
[upload setValue:@"Client-ID 0e8a1cb2eb594ef" forHTTPHeaderField:@"Authorization"];
[pendingTasks addObject:[session
[pendingTasks addObject:[sessionWithDelegate
uploadTaskWithRequest:upload
fromFile:[NSBundle.mainBundle URLForResource:@"image" withExtension:@"jpg"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
@@ -148,6 +152,21 @@
#pragma clang diagnostic pop
}
- (void)sendExampleWebsocketTraffic:(NSURLSession *)sessionWithDelegate {
NSString *APIKey = @"oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm";
NSString *wsurl = [NSString stringWithFormat:@"wss://demo.piesocket.com/v3/channel_1?api_key=%@&notify_self", APIKey];
NSURLSessionWebSocketTask *task = [sessionWithDelegate webSocketTaskWithURL:[NSURL URLWithString:wsurl]];
[task resume];
}
- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)task didOpenWithProtocol:(NSString *)protocol {
[task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) {
if (!error) {
NSLog(@"Received WS message: %@", message.string);
}
}];
}
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
NSLog(@"URLSession didBecomeInvalidWithError: %@", error.localizedDescription);
}
@@ -30,7 +30,7 @@
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
+5 -5
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "FLEX"
spec.version = "4.4.0"
spec.version = "4.6.0"
spec.summary = "A set of in-app debugging and exploration tools for iOS"
spec.description = <<-DESC
- Inspect and modify views in the hierarchy.
@@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
- Dynamically view and modify `NSUserDefaults` values.
DESC
spec.homepage = "https://github.com/Flipboard/FLEX"
spec.homepage = "https://github.com/FLEXTool/FLEX"
spec.screenshots = [ "http://engineering.flipboard.com/assets/flex/basic-view-exploration.gif",
"http://engineering.flipboard.com/assets/flex/advanced-view-editing.gif",
"http://engineering.flipboard.com/assets/flex/heap-browser.gif",
@@ -30,7 +30,7 @@ Pod::Spec.new do |spec|
spec.author = { "Tanner Bennett" => "tannerbennett@me.com" }
spec.social_media_url = "https://twitter.com/NSExceptional"
spec.platform = :ios, "9.0"
spec.source = { :git => "https://github.com/Flipboard/FLEX.git", :tag => "#{spec.version}" }
spec.source = { :git => "https://github.com/FLEXTool/FLEX.git", :tag => "#{spec.version}" }
spec.source_files = "Classes/**/*.{h,c,m,mm}"
spec.frameworks = [ "Foundation", "UIKit", "CoreGraphics", "ImageIO", "QuartzCore", "WebKit", "Security", "SceneKit" ]
spec.libraries = [ "z", "sqlite3" ]
@@ -40,6 +40,6 @@ Pod::Spec.new do |spec|
"Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h",
"Classes/Core/**/*.h", "Classes/Utility/Runtime/Objc/**/*.h",
"Classes/ObjectExplorers/**/*.h", "Classes/Editing/**/*.h",
"Classes/Utility/FLEXMacros.h", "Classes/Utility/Categories/*.h",
"Classes/Utility/FLEXAlert.h", "Classes/Utility/FLEXResources.h" ]
"Classes/Utility/Categories/*.h", "Classes/Utility/FLEXAlert.h",
"Classes/Utility/FLEXResources.h" ]
end
+74 -22
View File
@@ -95,8 +95,6 @@
3A4C953B1B5B21410088C3F2 /* FLEXNetworkSettingsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */; };
3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */; };
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */; };
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */; };
3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94C21B5B21410088C3F2 /* FLEXNetworkObserver.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -144,6 +142,12 @@
C301994A2409B38A00759E8E /* CALayer+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C30199482409B38A00759E8E /* CALayer+FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
C301994B2409B38A00759E8E /* CALayer+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C30199492409B38A00759E8E /* CALayer+FLEX.m */; };
C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
C30D2960261FAE9E00D89649 /* FLEXNSStringShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C30D295C261FAE9E00D89649 /* FLEXNSStringShortcuts.h */; };
C30D2961261FAE9E00D89649 /* FLEXNSDataShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C30D295D261FAE9E00D89649 /* FLEXNSDataShortcuts.m */; };
C30D2962261FAE9E00D89649 /* FLEXNSDataShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C30D295E261FAE9E00D89649 /* FLEXNSDataShortcuts.h */; };
C30D2963261FAE9E00D89649 /* FLEXNSStringShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C30D295F261FAE9E00D89649 /* FLEXNSStringShortcuts.m */; };
C30D2968261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C30D2966261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h */; };
C30D2969261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C30D2967261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m */; };
C312A13023ECB5D300E38049 /* FLEXBookmarkManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C312A12E23ECB5D300E38049 /* FLEXBookmarkManager.h */; };
C312A13123ECB5D300E38049 /* FLEXBookmarkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C312A12F23ECB5D300E38049 /* FLEXBookmarkManager.m */; };
C312A13423ECBE5800E38049 /* FLEXBookmarksViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C312A13223ECBE5800E38049 /* FLEXBookmarksViewController.h */; };
@@ -194,7 +198,11 @@
C3531BA623E88A2100A184AD /* FLEXNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA423E88A2100A184AD /* FLEXNavigationController.m */; };
C3531BAA23E88FAC00A184AD /* FLEXTabList.h in Headers */ = {isa = PBXBuildFile; fileRef = C3531BA823E88FAC00A184AD /* FLEXTabList.h */; };
C3531BAB23E88FAC00A184AD /* FLEXTabList.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA923E88FAC00A184AD /* FLEXTabList.m */; };
C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; settings = {ATTRIBUTES = (Public, ); }; };
C35DAD822709140700AA95E6 /* FLEXHTTPTransactionDetailController.h in Headers */ = {isa = PBXBuildFile; fileRef = C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */; };
C35DAD832709140700AA95E6 /* FLEXHTTPTransactionDetailController.m in Sources */ = {isa = PBXBuildFile; fileRef = C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */; };
C35DAD8E2709143000AA95E6 /* FLEXMITMDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */; };
C35DAD8F2709143000AA95E6 /* FLEXMITMDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */; };
C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; settings = {ATTRIBUTES = (Private, ); }; };
C362AE8223C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m in Sources */ = {isa = PBXBuildFile; fileRef = C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */; };
C3694DBA23EA1096006625D7 /* FLEXTabsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3694DB823EA1096006625D7 /* FLEXTabsViewController.h */; };
C3694DBB23EA1096006625D7 /* FLEXTabsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3694DB923EA1096006625D7 /* FLEXTabsViewController.m */; };
@@ -248,7 +256,7 @@
C3878DBE23A74A8F0038FDBE /* FLEXNetworkRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94B81B5B21410088C3F2 /* FLEXNetworkRecorder.m */; };
C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */; };
C387C87B22DFCD6A00750E58 /* FLEXCarouselCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C87922DFCD6A00750E58 /* FLEXCarouselCell.m */; };
C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */; settings = {ATTRIBUTES = (Public, ); }; };
C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */; settings = {ATTRIBUTES = (Private, ); }; };
C387C88422E0D24A00750E58 /* UIView+FLEX_Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */; };
C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
@@ -282,7 +290,7 @@
C398626E23AD71C1007E6793 /* FLEXRuntimeClient.m in Sources */ = {isa = PBXBuildFile; fileRef = C398626A23AD71C1007E6793 /* FLEXRuntimeClient.m */; };
C398627223AD7951007E6793 /* UIGestureRecognizer+Blocks.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627023AD7951007E6793 /* UIGestureRecognizer+Blocks.h */; settings = {ATTRIBUTES = (Public, ); }; };
C398627323AD7951007E6793 /* UIGestureRecognizer+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = C398627123AD7951007E6793 /* UIGestureRecognizer+Blocks.m */; };
C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627423AD79B6007E6793 /* NSString+FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627423AD79B6007E6793 /* NSString+FLEX.h */; settings = {ATTRIBUTES = (Private, ); }; };
C398627723AD79B7007E6793 /* NSString+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C398627523AD79B7007E6793 /* NSString+FLEX.m */; };
C398682523AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m in Sources */ = {isa = PBXBuildFile; fileRef = C398682323AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m */; };
C398682623AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h in Headers */ = {isa = PBXBuildFile; fileRef = C398682423AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -311,6 +319,8 @@
C3BFD071233C23ED0015FB82 /* NSArray+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */; };
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
C3DBFD0C26CE2FAF00E0466A /* OSCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DBFD0A26CE2FAF00E0466A /* OSCache.m */; };
C3DBFD0D26CE2FAF00E0466A /* OSCache.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DBFD0B26CE2FAF00E0466A /* OSCache.h */; };
C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
C3DFCD942416BC6500BB7084 /* FLEXFilteringTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DFCD922416BC6500BB7084 /* FLEXFilteringTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3DFCD952416BC6500BB7084 /* FLEXFilteringTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DFCD932416BC6500BB7084 /* FLEXFilteringTableViewController.m */; };
@@ -343,9 +353,9 @@
C3F646F323A045DB00D4A011 /* FLEXClassShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646F123A045DB00D4A011 /* FLEXClassShortcuts.m */; };
C3F646F623A04A7500D4A011 /* FLEXShortcut.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F646F423A04A7500D4A011 /* FLEXShortcut.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3F646F723A04A7500D4A011 /* FLEXShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646F523A04A7500D4A011 /* FLEXShortcut.m */; };
C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */; settings = {ATTRIBUTES = (Private, ); }; };
C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */; };
C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; settings = {ATTRIBUTES = (Private, ); }; };
C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */; };
C3F977872311B38F0032776D /* NSObject+FLEX_Reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F977812311B38F0032776D /* NSObject+FLEX_Reflection.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3F977882311B38F0032776D /* NSObject+FLEX_Reflection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977822311B38F0032776D /* NSObject+FLEX_Reflection.m */; };
@@ -456,8 +466,6 @@
3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkSettingsController.m; sourceTree = "<group>"; };
3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransaction.h; sourceTree = "<group>"; };
3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransaction.m; sourceTree = "<group>"; };
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionDetailController.h; sourceTree = "<group>"; };
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionDetailController.m; sourceTree = "<group>"; };
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionCell.h; sourceTree = "<group>"; };
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionCell.m; sourceTree = "<group>"; };
3A4C94C21B5B21410088C3F2 /* FLEXNetworkObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkObserver.h; sourceTree = "<group>"; };
@@ -506,6 +514,12 @@
C30199482409B38A00759E8E /* CALayer+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CALayer+FLEX.h"; sourceTree = "<group>"; };
C30199492409B38A00759E8E /* CALayer+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CALayer+FLEX.m"; sourceTree = "<group>"; };
C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
C30D295C261FAE9E00D89649 /* FLEXNSStringShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNSStringShortcuts.h; sourceTree = "<group>"; };
C30D295D261FAE9E00D89649 /* FLEXNSDataShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNSDataShortcuts.m; sourceTree = "<group>"; };
C30D295E261FAE9E00D89649 /* FLEXNSDataShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNSDataShortcuts.h; sourceTree = "<group>"; };
C30D295F261FAE9E00D89649 /* FLEXNSStringShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNSStringShortcuts.m; sourceTree = "<group>"; };
C30D2966261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Cocoa+FLEXShortcuts.h"; sourceTree = "<group>"; };
C30D2967261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Cocoa+FLEXShortcuts.m"; sourceTree = "<group>"; };
C312A12E23ECB5D300E38049 /* FLEXBookmarkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBookmarkManager.h; sourceTree = "<group>"; };
C312A12F23ECB5D300E38049 /* FLEXBookmarkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBookmarkManager.m; sourceTree = "<group>"; };
C312A13223ECBE5800E38049 /* FLEXBookmarksViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBookmarksViewController.h; sourceTree = "<group>"; };
@@ -558,6 +572,10 @@
C3531BA423E88A2100A184AD /* FLEXNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNavigationController.m; sourceTree = "<group>"; };
C3531BA823E88FAC00A184AD /* FLEXTabList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTabList.h; sourceTree = "<group>"; };
C3531BA923E88FAC00A184AD /* FLEXTabList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTabList.m; sourceTree = "<group>"; };
C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXHTTPTransactionDetailController.h; sourceTree = "<group>"; };
C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXHTTPTransactionDetailController.m; sourceTree = "<group>"; };
C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMITMDataSource.h; sourceTree = "<group>"; };
C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMITMDataSource.m; sourceTree = "<group>"; };
C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMapTable+FLEX_Subscripting.h"; sourceTree = "<group>"; };
C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMapTable+FLEX_Subscripting.m"; sourceTree = "<group>"; };
C3694DB823EA1096006625D7 /* FLEXTabsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTabsViewController.h; sourceTree = "<group>"; };
@@ -672,6 +690,8 @@
C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+FLEX.m"; sourceTree = "<group>"; };
C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectRef.h; sourceTree = "<group>"; };
C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectRef.m; sourceTree = "<group>"; };
C3DBFD0A26CE2FAF00E0466A /* OSCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSCache.m; sourceTree = "<group>"; };
C3DBFD0B26CE2FAF00E0466A /* OSCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSCache.h; sourceTree = "<group>"; };
C3DFCD922416BC6500BB7084 /* FLEXFilteringTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXFilteringTableViewController.h; sourceTree = "<group>"; };
C3DFCD932416BC6500BB7084 /* FLEXFilteringTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXFilteringTableViewController.m; sourceTree = "<group>"; };
C3DFCD962416E7DD00BB7084 /* FLEXMutableListSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMutableListSection.h; sourceTree = "<group>"; };
@@ -962,12 +982,15 @@
3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */,
3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */,
3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */,
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */,
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */,
C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */,
C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */,
C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */,
C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */,
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */,
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */,
2EF6B04C1D494BE50006BDA5 /* FLEXNetworkCurlLogger.h */,
2EF6B04B1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m */,
C3DBFD0926CE2FAF00E0466A /* OSCache */,
3A4C94C11B5B21410088C3F2 /* PonyDebugger */,
);
path = Network;
@@ -1094,6 +1117,8 @@
C31E4E53259D4A4100712288 /* Private */ = {
isa = PBXGroup;
children = (
C30D2966261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h */,
C30D2967261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m */,
C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */,
C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */,
C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */,
@@ -1392,10 +1417,23 @@
C32F3A17247C6B3E0063542D /* FLEXUIAppShortcuts.m */,
C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */,
C31D93E323E38CBE005517BF /* FLEXBlockShortcuts.m */,
C30D295E261FAE9E00D89649 /* FLEXNSDataShortcuts.h */,
C30D295D261FAE9E00D89649 /* FLEXNSDataShortcuts.m */,
C30D295C261FAE9E00D89649 /* FLEXNSStringShortcuts.h */,
C30D295F261FAE9E00D89649 /* FLEXNSStringShortcuts.m */,
);
path = Shortcuts;
sourceTree = "<group>";
};
C3DBFD0926CE2FAF00E0466A /* OSCache */ = {
isa = PBXGroup;
children = (
C3DBFD0A26CE2FAF00E0466A /* OSCache.m */,
C3DBFD0B26CE2FAF00E0466A /* OSCache.h */,
);
path = OSCache;
sourceTree = "<group>";
};
C3E5D9FF2317007F00E655DB /* Sections */ = {
isa = PBXGroup;
children = (
@@ -1463,7 +1501,6 @@
94A515141C4CA1C00063292F /* FLEXManager.h in Headers */,
C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
C3A9422C23C3DA39006871A3 /* FHSView.h in Headers */,
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h in Headers */,
3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */,
C398625523AD6C67007E6793 /* FLEXObjcRuntimeViewController.h in Headers */,
C3E5DA02231700EE00E655DB /* FLEXObjectInfoSection.h in Headers */,
@@ -1500,6 +1537,7 @@
C312A13023ECB5D300E38049 /* FLEXBookmarkManager.h in Headers */,
3A4C95341B5B21410088C3F2 /* FLEXSystemLogViewController.h in Headers */,
C34C9BDD23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h in Headers */,
C35DAD822709140700AA95E6 /* FLEXHTTPTransactionDetailController.h in Headers */,
C398624F23AD6C67007E6793 /* FLEXRuntimeKeyPathTokenizer.h in Headers */,
C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */,
3A4C95091B5B21410088C3F2 /* FLEXFieldEditorView.h in Headers */,
@@ -1519,14 +1557,17 @@
C32F3A18247C6B3E0063542D /* FLEXUIAppShortcuts.h in Headers */,
779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */,
C3DFCDB82418336D00BB7084 /* NSUserDefaults+FLEX.h in Headers */,
C30D2968261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h in Headers */,
3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */,
C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */,
C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */,
3A4C94FF1B5B21410088C3F2 /* FLEXArgumentInputSwitchView.h in Headers */,
C398625423AD6C67007E6793 /* FLEXRuntimeBrowserToolbar.h in Headers */,
3A4C94E71B5B21410088C3F2 /* FLEXHierarchyTableViewCell.h in Headers */,
C3DBFD0D26CE2FAF00E0466A /* OSCache.h in Headers */,
224D49AA1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.h in Headers */,
C386D6A9241995A800699085 /* FLEXTypeEncodingParser.h in Headers */,
C30D2962261FAE9E00D89649 /* FLEXNSDataShortcuts.h in Headers */,
3A4C95031B5B21410088C3F2 /* FLEXArgumentInputView.h in Headers */,
C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */,
94A5151D1C4CA1F10063292F /* FLEXExplorerViewController.h in Headers */,
@@ -1577,6 +1618,7 @@
C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */,
C34D4EB423A2AF2A00C1F903 /* FLEXColorPreviewSection.h in Headers */,
C383C3BE23B6B398007A321B /* UITextField+Range.h in Headers */,
C35DAD8E2709143000AA95E6 /* FLEXMITMDataSource.h in Headers */,
224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
C313853F23F5C1A10046E63C /* FLEXViewControllersViewController.h in Headers */,
C386D6ED24199EC600699085 /* FLEX-Runtime.h in Headers */,
@@ -1593,6 +1635,7 @@
C3F977872311B38F0032776D /* NSObject+FLEX_Reflection.h in Headers */,
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */,
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionCell.h in Headers */,
C30D2960261FAE9E00D89649 /* FLEXNSStringShortcuts.h in Headers */,
C398626523AD70F5007E6793 /* FLEXKBToolbarButton.h in Headers */,
3A4C95241B5B21410088C3F2 /* FLEXFileBrowserController.h in Headers */,
C31D93E423E38CBE005517BF /* FLEXBlockShortcuts.h in Headers */,
@@ -1662,7 +1705,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = FLEX;
LastUpgradeCheck = 1200;
LastUpgradeCheck = 1240;
ORGANIZATIONNAME = Flipboard;
TargetAttributes = {
1C27A8B51F0E5A0300F0D02D = {
@@ -1732,7 +1775,9 @@
C3DFCDB92418336D00BB7084 /* NSUserDefaults+FLEX.m in Sources */,
C3878DBE23A74A8F0038FDBE /* FLEXNetworkRecorder.m in Sources */,
C3878DBC23A749F70038FDBE /* FLEXFieldEditorViewController.m in Sources */,
C35DAD832709140700AA95E6 /* FLEXHTTPTransactionDetailController.m in Sources */,
942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */,
C30D2961261FAE9E00D89649 /* FLEXNSDataShortcuts.m in Sources */,
C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */,
C3A9424A23C78878006871A3 /* FLEXHierarchyViewController.m in Sources */,
C31C4A6A23342A2200C35F12 /* FLEXMetadataSection.m in Sources */,
@@ -1823,6 +1868,7 @@
3A4C94FC1B5B21410088C3F2 /* FLEXArgumentInputStringView.m in Sources */,
3A4C94F81B5B21410088C3F2 /* FLEXArgumentInputNotSupportedView.m in Sources */,
3A4C95351B5B21410088C3F2 /* FLEXSystemLogViewController.m in Sources */,
C3DBFD0C26CE2FAF00E0466A /* OSCache.m in Sources */,
C383C3C623B6BB81007A321B /* FLEXCodeFontCell.m in Sources */,
3A4C95271B5B21410088C3F2 /* FLEXGlobalsViewController.m in Sources */,
C313854823F5F7D50046E63C /* flex_fishhook.c in Sources */,
@@ -1835,6 +1881,7 @@
C32A195F231732E800EB02AC /* FLEXCollectionContentSection.m in Sources */,
C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */,
779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
C30D2963261FAE9E00D89649 /* FLEXNSStringShortcuts.m in Sources */,
C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
C31D93E523E38CBE005517BF /* FLEXBlockShortcuts.m in Sources */,
C312A13123ECB5D300E38049 /* FLEXBookmarkManager.m in Sources */,
@@ -1856,14 +1903,15 @@
3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
C3474C4123DA496400466532 /* FLEXKeyValueTableViewCell.m in Sources */,
779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
C35DAD8F2709143000AA95E6 /* FLEXMITMDataSource.m in Sources */,
C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */,
C36FBFD5230F3B98008D95D5 /* FLEXMethodBase.m in Sources */,
3A4C95001B5B21410088C3F2 /* FLEXArgumentInputSwitchView.m in Sources */,
C386D6F02419A33F00699085 /* FLEXRuntimeConstants.m in Sources */,
C3F31D442267D883003C991A /* FLEXTableView.m in Sources */,
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m in Sources */,
224D49AB1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.m in Sources */,
C312A13C23ECE79000E38049 /* FLEXWindowManagerController.m in Sources */,
C30D2969261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m in Sources */,
C36FBFDA230F3B98008D95D5 /* FLEXPropertyAttributes.m in Sources */,
779B1ED91C0C4D7C001F5E49 /* FLEXTableLeftCell.m in Sources */,
3A4C94E61B5B21410088C3F2 /* FLEXUtility.m in Sources */,
@@ -1987,7 +2035,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "";
@@ -2042,7 +2090,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "";
SDKROOT = iphoneos;
@@ -2061,7 +2109,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
@@ -2069,13 +2117,13 @@
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Classes/Info.plist;
INSTALL_PATH = "@rpath";
INSTALL_PATH = "@rpath";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
@@ -2083,8 +2131,10 @@
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
WARNING_CFLAGS = "-Wno-unsupported-availability-guard";
};
name = Debug;
@@ -2096,7 +2146,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
@@ -2104,13 +2154,13 @@
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Classes/Info.plist;
INSTALL_PATH = "@rpath";
INSTALL_PATH = "@rpath";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
@@ -2118,7 +2168,9 @@
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
WARNING_CFLAGS = "-Wno-unsupported-availability-guard";
};
name = Release;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

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