Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 258ec8697b | |||
| 8f9a6e88ec | |||
| c37270e6ac | |||
| ce10d45c29 | |||
| b2716c4b2e | |||
| ab135ba94e | |||
| bcc04f4113 | |||
| 470b3fa3b3 | |||
| e84dfeae5c | |||
| 44e86e59b7 | |||
| ac6d9cfa3f | |||
| 1a59711760 | |||
| b60ce7a057 | |||
| b4ac210bef | |||
| 9360c58975 | |||
| 43d9a460ce | |||
| 15fee5a8a5 | |||
| fad038392b | |||
| 558d65a0b0 | |||
| 077fca36c0 |
@@ -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.
|
||||
///
|
||||
|
||||
@@ -238,7 +238,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 +256,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 +288,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 +538,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 {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXMacros.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
@class FLEXTableView;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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%@",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
[self sizeToFit];
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
self.appearance = UIKeyboardTypeDefault;
|
||||
self.appearance = UIKeyboardAppearanceDefault;
|
||||
} else {
|
||||
self.appearance = UIKeyboardAppearanceLight;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
self.appearance = UIKeyboardTypeDefault;
|
||||
self.appearance = UIKeyboardAppearanceDefault;
|
||||
} else {
|
||||
self.appearance = UIKeyboardAppearanceLight;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
// 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,15 +73,6 @@
|
||||
[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 Delegate stuff
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -406,22 +406,22 @@
|
||||
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]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
@@ -28,13 +28,13 @@ 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
|
||||
|
||||
@@ -45,7 +45,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
self.orderedTransactions = [NSMutableArray new];
|
||||
self.requestIDsToTransactions = [NSMutableDictionary new];
|
||||
self.hostBlacklist = NSUserDefaults.standardUserDefaults.flex_networkHostBlacklist.mutableCopy;
|
||||
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);
|
||||
@@ -100,13 +100,13 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
});
|
||||
}
|
||||
|
||||
- (void)clearBlacklistedTransactions {
|
||||
- (void)clearExcludedTransactions {
|
||||
dispatch_sync(self.queue, ^{
|
||||
self.orderedTransactions = ({
|
||||
[self.orderedTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *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 +117,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,7 +126,7 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -269,6 +269,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
|
||||
];
|
||||
|
||||
@@ -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
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
#import "FLEXShortcutsFactory+Defaults.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXMacros.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "Cocoa+FLEXShortcuts.h"
|
||||
|
||||
#pragma mark - UIApplication
|
||||
|
||||
@@ -59,6 +61,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 +144,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 +278,18 @@
|
||||
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"
|
||||
]).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.properties(@[@"length", @"bytes"]).forClass(NSData.class);
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -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;
|
||||
|
||||
@@ -11,12 +11,7 @@
|
||||
#import "FLEXObjcInternal.h"
|
||||
#import "FLEXTypeEncodingParser.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 +104,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 +119,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 +295,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "FLEX"
|
||||
spec.version = "4.4.0"
|
||||
spec.version = "4.4.1"
|
||||
spec.summary = "A set of in-app debugging and exploration tools for iOS"
|
||||
spec.description = <<-DESC
|
||||
- Inspect and modify views in the hierarchy.
|
||||
@@ -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
|
||||
|
||||
@@ -1987,7 +1987,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 +2042,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;
|
||||
@@ -2083,8 +2083,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;
|
||||
@@ -2118,7 +2120,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;
|
||||
|
||||
@@ -15,6 +15,9 @@ are permitted provided that the following conditions are met:
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
* You must NOT include this project in an application to be submitted
|
||||
to the App Store™, as this project uses too many private APIs.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
|
||||
Reference in New Issue
Block a user