Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b908d66b94 | |||
| e6c324d761 | |||
| 784e030e44 | |||
| 046eb49f7b | |||
| 6ec1814f2e | |||
| f179545972 | |||
| d0d1632ca1 | |||
| 79e22cf828 | |||
| 563cb514a1 | |||
| 84131d8533 | |||
| 55cb7ebf8f | |||
| b9e2c1ebd9 | |||
| 7377399673 | |||
| 21199b2a56 | |||
| e72c6aa349 | |||
| 13c583d32b | |||
| 129c291469 | |||
| 67609b28a4 | |||
| 4dc206eaa6 | |||
| 28e91507db | |||
| 5f74fb0d43 | |||
| 09f5859feb | |||
| cee416889a | |||
| e2a334384a | |||
| a840e909a1 | |||
| 2f952c380f | |||
| 5d919eb329 | |||
| 1d5d825135 | |||
| 2a8cdbdb84 | |||
| 5e2081b8f9 | |||
| fbaeda1956 | |||
| c761865b9b | |||
| 700c50af5d | |||
| b38cca06b1 | |||
| 6429573918 | |||
| f77f5ccdc9 | |||
| 7aeddcdb2c | |||
| a25ef87a51 | |||
| fbeb1beca0 | |||
| 059bde9711 | |||
| 2ca563f570 | |||
| 88c7ca9373 | |||
| 83486641aa | |||
| 6bd0c87881 | |||
| 1a64da70c9 | |||
| 87ea2bb147 | |||
| d9e9be53d8 | |||
| 142f037497 | |||
| 6cdb626d78 | |||
| 6e81029b8b | |||
| 1c7048e710 |
@@ -25,7 +25,7 @@
|
||||
@property (nonatomic, copy) NSArray<FLEXTableViewSection *> *allSections;
|
||||
|
||||
/// This computed property should filter \c allSections for assignment to \c sections
|
||||
@property (nonatomic, readonly) NSArray<FLEXTableViewSection *> *nonemptySections;
|
||||
@property (nonatomic, readonly, copy) NSArray<FLEXTableViewSection *> *nonemptySections;
|
||||
|
||||
/// This should be able to re-initialize \c allSections
|
||||
- (NSArray<FLEXTableViewSection *> *)makeSections;
|
||||
@@ -80,7 +80,7 @@
|
||||
/// if using \c self as the \c filterDelegate, as is the default.
|
||||
///
|
||||
/// For example, the object explorer hides the description section when searching.
|
||||
@property (nonatomic, readonly) NSArray<FLEXTableViewSection *> *nonemptySections;
|
||||
@property (nonatomic, readonly, copy) NSArray<FLEXTableViewSection *> *nonemptySections;
|
||||
|
||||
/// If using \c self as the \c filterDelegate, as is the default,
|
||||
/// subclasses should override to provide the sections for the table view.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import "FLEXFilteringTableViewController.h"
|
||||
#import "FLEXTableViewSection.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
|
||||
@interface FLEXFilteringTableViewController ()
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
|
||||
/// or it will not be respsected. Use this instead.
|
||||
/// Defaults to NO.
|
||||
@property (nonatomic) BOOL pinSearchBar;
|
||||
/// By default, we will show the search bar's cancel button when
|
||||
/// By default, we will show the search bar's cancel button when
|
||||
/// search becomes active and hide it when search is dismissed.
|
||||
///
|
||||
/// Do not set the showsCancelButton property on the searchController's
|
||||
@@ -102,11 +102,11 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
|
||||
/// Otherwise, this is the selected index of the carousel, or NSNotFound if using neither.
|
||||
@property (nonatomic) NSInteger selectedScope;
|
||||
/// self.searchController.searchBar.text
|
||||
@property (nonatomic, readonly) NSString *searchText;
|
||||
@property (nonatomic, readonly, copy) NSString *searchText;
|
||||
|
||||
/// A totally optional delegate to forward search results updater calls to.
|
||||
/// If a delegate is set, updateSearchResults: is not called on this view controller.
|
||||
@property (nonatomic, weak ) id<FLEXSearchResultsUpdating> searchResultsUpdater;
|
||||
/// If a delegate is set, updateSearchResults: is not called on this view controller.
|
||||
@property (nonatomic, weak) id<FLEXSearchResultsUpdating> searchResultsUpdater;
|
||||
|
||||
/// self.view.window as a \c FLEXWindow
|
||||
@property (nonatomic, readonly) FLEXWindow *window;
|
||||
@@ -140,7 +140,7 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
|
||||
@property (nonatomic) BOOL showsShareToolbarItem;
|
||||
/// Called when the share button is pressed.
|
||||
/// Default implementation does nothign. Subclasses may override.
|
||||
- (void)shareButtonPressed;
|
||||
- (void)shareButtonPressed:(UIBarButtonItem *)sender;
|
||||
|
||||
/// Subclasses may call this to opt-out of all toolbar related behavior.
|
||||
/// This is necessary if you want to disable the gesture which reveals the toolbar.
|
||||
|
||||
@@ -222,7 +222,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
self.tableView.dataSource = self;
|
||||
self.tableView.delegate = self;
|
||||
|
||||
_shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed));
|
||||
_shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed:));
|
||||
_bookmarksToolbarItem = [UIBarButtonItem
|
||||
itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks)
|
||||
];
|
||||
@@ -386,7 +386,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
[self setupToolbarItems];
|
||||
}
|
||||
|
||||
- (void)shareButtonPressed {
|
||||
- (void)shareButtonPressed:(UIBarButtonItem *)sender {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXMacros.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
@class FLEXTableView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// A title to be displayed for the custom section.
|
||||
/// Subclasses may override or use the \c _title ivar.
|
||||
@property (nonatomic, readonly, nullable) NSString *title;
|
||||
@property (nonatomic, readonly, nullable, copy) NSString *title;
|
||||
/// The number of rows in this section. Subclasses must override.
|
||||
/// This should not change until \c filterText is changed or \c reloadData is called.
|
||||
@property (nonatomic, readonly) NSInteger numberOfRows;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
|
||||
@interface FLEXArgumentInputStructView ()
|
||||
@interface FLEXArgumentInputStructView () <FLEXArgumentInputViewDelegate>
|
||||
|
||||
@property (nonatomic) NSArray<FLEXArgumentInputView *> *argumentInputViews;
|
||||
|
||||
@@ -30,8 +30,11 @@
|
||||
NSUInteger fieldIndex,
|
||||
NSUInteger fieldOffset) {
|
||||
|
||||
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
|
||||
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory
|
||||
argumentInputViewForTypeEncoding:fieldTypeEncoding
|
||||
];
|
||||
inputView.targetSize = FLEXArgumentInputViewSizeSmall;
|
||||
inputView.delegate = self;
|
||||
|
||||
if (fieldIndex < customTitles.count) {
|
||||
inputView.title = customTitles[fieldIndex];
|
||||
@@ -125,15 +128,45 @@
|
||||
return boxedStruct;
|
||||
}
|
||||
|
||||
- (BOOL)inputViewIsFirstResponder {
|
||||
BOOL isFirstResponder = NO;
|
||||
- (FLEXArgumentInputView *)firstResponderInputView {
|
||||
for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
|
||||
if ([inputView inputViewIsFirstResponder]) {
|
||||
isFirstResponder = YES;
|
||||
break;
|
||||
return inputView;
|
||||
}
|
||||
}
|
||||
return isFirstResponder;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder {
|
||||
FLEXArgumentInputView *responder = [self firstResponderInputView];
|
||||
if (responder) {
|
||||
return [responder resignFirstResponder];
|
||||
} else {
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - FLEXArgumentInputViewDelegate
|
||||
|
||||
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
|
||||
// Nothing to see here
|
||||
}
|
||||
|
||||
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)inputView {
|
||||
if (self.argumentInputViews.lastObject == inputView) {
|
||||
// If this is our last or only input view,
|
||||
// notify the delegate or dismiss the keyboard
|
||||
if (self.delegate) {
|
||||
[self.delegate argumentInputViewWantsNextAsFirstResponder:self];
|
||||
} else {
|
||||
[inputView resignFirstResponder];
|
||||
}
|
||||
} else {
|
||||
NSInteger idx = [self.argumentInputViews indexOfObject:inputView];
|
||||
[self.argumentInputViews[idx+1] becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
|
||||
#import "FLEXArgumentInputView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXArgumentInputTextView : FLEXArgumentInputView <UITextViewDelegate>
|
||||
|
||||
// For subclass eyes only
|
||||
|
||||
@property (nonatomic, readonly) UITextView *inputTextView;
|
||||
@property (nonatomic) NSString *inputPlaceholderText;
|
||||
@property (nonatomic, nullable) NSString *inputPlaceholderText;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
self.inputTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
||||
self.inputTextView.autocorrectionType = UITextAutocorrectionTypeNo;
|
||||
self.inputTextView.delegate = self;
|
||||
self.inputTextView.inputAccessoryView = [self createToolBar];
|
||||
self.inputAccessoryView = [self createToolBar];
|
||||
if (@available(iOS 11, *)) {
|
||||
[self.inputTextView.layer setValue:@YES forKey:@"continuousCorners"];
|
||||
} else {
|
||||
@@ -51,6 +51,14 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIToolbar *)inputAccessoryView {
|
||||
return (id)self.inputTextView.inputAccessoryView;
|
||||
}
|
||||
|
||||
- (void)setInputAccessoryView:(UIToolbar *)inputAccessoryView {
|
||||
self.inputTextView.inputAccessoryView = inputAccessoryView;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (UIToolbar *)createToolBar {
|
||||
@@ -62,7 +70,7 @@
|
||||
];
|
||||
UIBarButtonItem *pasteItem = [[UIBarButtonItem alloc]
|
||||
initWithTitle:@"Paste" style:UIBarButtonItemStyleDone
|
||||
target:self.inputTextView action:@selector(paste:)
|
||||
target:self action:@selector(paste:)
|
||||
];
|
||||
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc]
|
||||
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
|
||||
@@ -91,6 +99,16 @@
|
||||
return self.placeholderLabel.text;
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender {
|
||||
[self.inputTextView paste:sender];
|
||||
|
||||
if (self.delegate) {
|
||||
[self.delegate argumentInputViewWantsNextAsFirstResponder:self];
|
||||
} else {
|
||||
[self.inputTextView resignFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Superclass Overrides
|
||||
|
||||
@@ -98,6 +116,18 @@
|
||||
return self.inputTextView.isFirstResponder;
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder {
|
||||
return [self.inputTextView becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder {
|
||||
if (self.inputViewIsFirstResponder) {
|
||||
return self.inputTextView.resignFirstResponder;
|
||||
} else {
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Layout and Sizing
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
|
||||
|
||||
@protocol FLEXArgumentInputViewDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXArgumentInputView : UIView
|
||||
|
||||
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding;
|
||||
@@ -31,23 +33,25 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
|
||||
/// Primitive types and structs should/will be boxed in NSValue containers.
|
||||
/// Concrete subclasses should override both the setter and getter for this property.
|
||||
/// Subclasses can call super.inputValue to access a backing store for the value.
|
||||
@property (nonatomic) id inputValue;
|
||||
@property (nonatomic, nullable) id inputValue;
|
||||
|
||||
/// Setting this value to large will make some argument input views increase the size of their input field(s).
|
||||
/// Useful to increase the use of space if there is only one input view on screen (i.e. for property and ivar editing).
|
||||
@property (nonatomic) FLEXArgumentInputViewSize targetSize;
|
||||
|
||||
/// Users of the input view can get delegate callbacks for incremental changes in user input.
|
||||
@property (nonatomic, weak) id <FLEXArgumentInputViewDelegate> delegate;
|
||||
@property (nonatomic, weak, nullable) id <FLEXArgumentInputViewDelegate> delegate;
|
||||
|
||||
// Subclasses can override
|
||||
|
||||
@property (nonatomic, nullable) UIToolbar *inputAccessoryView;
|
||||
|
||||
/// If the input view has one or more text views, returns YES when one of them is focused.
|
||||
@property (nonatomic, readonly) BOOL inputViewIsFirstResponder;
|
||||
|
||||
/// For subclasses to indicate that they can handle editing a field the give type and value.
|
||||
/// Used by FLEXArgumentInputViewFactory to create appropriate input views.
|
||||
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value;
|
||||
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(nullable id)value;
|
||||
|
||||
// For subclass eyes only
|
||||
|
||||
@@ -59,6 +63,11 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
|
||||
|
||||
@protocol FLEXArgumentInputViewDelegate <NSObject>
|
||||
|
||||
//- (void)
|
||||
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView;
|
||||
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)argumentInputView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
];
|
||||
inputView.backgroundColor = self.view.backgroundColor;
|
||||
inputView.inputValue = currentValue;
|
||||
inputView.delegate = self;
|
||||
self.fieldEditorView.argumentInputViews = @[inputView];
|
||||
}
|
||||
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
[self exploreObjectOrPopViewController:self.currentValue];
|
||||
}
|
||||
|
||||
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView {
|
||||
if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
|
||||
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
|
||||
if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
|
||||
[self actionButtonPressed:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
|
||||
inputView.backgroundColor = self.view.backgroundColor;
|
||||
inputView.title = methodComponent;
|
||||
inputView.delegate = self;
|
||||
[argumentInputViews addObject:inputView];
|
||||
argumentIndex++;
|
||||
}
|
||||
|
||||
@@ -6,19 +6,20 @@
|
||||
// Copyright (c) 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXArgumentInputView.h"
|
||||
|
||||
@class FLEXFieldEditorView;
|
||||
@class FLEXArgumentInputView;
|
||||
|
||||
/// Provides a screen for editing or configuring one or more variables.
|
||||
@interface FLEXVariableEditorViewController : UIViewController
|
||||
@interface FLEXVariableEditorViewController : UIViewController <FLEXArgumentInputViewDelegate>
|
||||
|
||||
+ (instancetype)target:(id)target;
|
||||
- (id)initWithTarget:(id)target;
|
||||
|
||||
// Convenience accessor since many subclasses only use one input view
|
||||
@property (nonatomic, readonly) FLEXArgumentInputView *firstInputView;
|
||||
// Also a convenience accessor
|
||||
@property (nonatomic, readonly) NSArray<FLEXArgumentInputView *> *inputViews;
|
||||
|
||||
// For subclass use only.
|
||||
@property (nonatomic, readonly) id target;
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
#pragma mark - UIViewController methods
|
||||
|
||||
- (void)keyboardDidShow:(NSNotification *)notification {
|
||||
CGRect keyboardRectInWindow = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
CGSize keyboardSize = [self.view convertRect:keyboardRectInWindow fromView:nil].size;
|
||||
CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
CGSize keyboardSize = [self.view convertRect:keyboardRect fromView:nil].size;
|
||||
UIEdgeInsets scrollInsets = self.scrollView.contentInset;
|
||||
scrollInsets.bottom = keyboardSize.height;
|
||||
self.scrollView.contentInset = scrollInsets;
|
||||
@@ -114,7 +114,11 @@
|
||||
#pragma mark - Public
|
||||
|
||||
- (FLEXArgumentInputView *)firstInputView {
|
||||
return [self.fieldEditorView argumentInputViews].firstObject;
|
||||
return self.fieldEditorView.argumentInputViews.firstObject;
|
||||
}
|
||||
|
||||
- (NSArray<FLEXArgumentInputView *> *)inputViews {
|
||||
return self.fieldEditorView.argumentInputViews;
|
||||
}
|
||||
|
||||
- (void)actionButtonPressed:(id)sender {
|
||||
@@ -134,4 +138,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - FLEXArgumentInputViewDelegate
|
||||
|
||||
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
|
||||
// Subclasses might want to do something here but we don't
|
||||
}
|
||||
|
||||
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)inputView {
|
||||
if (self.inputViews.lastObject == inputView) {
|
||||
// If this is our last or only input view, dismiss the keyboard
|
||||
[inputView resignFirstResponder];
|
||||
} else {
|
||||
NSInteger idx = [self.inputViews indexOfObject:inputView];
|
||||
[self.inputViews[idx+1] becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
- (void)setupEditingBarItems {
|
||||
self.navigationItem.rightBarButtonItem = nil;
|
||||
self.toolbarItems = @[
|
||||
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed)],
|
||||
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
// We use a non-system done item because we change its title dynamically
|
||||
[UIBarButtonItem doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
|
||||
@@ -149,7 +149,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)closeAllButtonPressed {
|
||||
- (void)closeAllButtonPressed:(UIBarButtonItem *)sender {
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
NSInteger count = self.bookmarks.count;
|
||||
NSString *title = FLEXPluralFormatString(count, @"Remove %@ bookmarks", @"Remove %@ bookmark");
|
||||
@@ -158,7 +158,7 @@
|
||||
[self toggleEditing];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
- (void)closeAll {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
self.title = @"View Controllers at Tap";
|
||||
self.showsSearchBar = YES;
|
||||
[self disableToolbar];
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
self.toolbarItems = @[
|
||||
UIBarButtonItem.flex_fixedSpace,
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
FLEXBarButtonItemSystem(Add, self, @selector(addTabButtonPressed)),
|
||||
FLEXBarButtonItemSystem(Add, self, @selector(addTabButtonPressed:)),
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
FLEXBarButtonItemSystem(Edit, self, @selector(toggleEditing)),
|
||||
];
|
||||
@@ -121,7 +121,7 @@
|
||||
- (void)setupEditingBarItems {
|
||||
self.navigationItem.rightBarButtonItem = nil;
|
||||
self.toolbarItems = @[
|
||||
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed)],
|
||||
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
[UIBarButtonItem disabledSystemItem:UIBarButtonSystemItemAdd],
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
@@ -193,7 +193,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addTabButtonPressed {
|
||||
- (void)addTabButtonPressed:(UIBarButtonItem *)sender {
|
||||
if (FLEXBookmarkManager.bookmarks.count) {
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
make.title(@"New Tab");
|
||||
@@ -208,7 +208,7 @@
|
||||
] animated:YES completion:nil];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
} else {
|
||||
// No bookmarks, just open the main menu
|
||||
[self addTabAndDismiss:[FLEXNavigationController
|
||||
@@ -224,7 +224,7 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)closeAllButtonPressed {
|
||||
- (void)closeAllButtonPressed:(UIBarButtonItem *)sender {
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
NSInteger count = self.openTabs.count;
|
||||
NSString *title = FLEXPluralFormatString(count, @"Close %@ tabs", @"Close %@ tab");
|
||||
@@ -233,7 +233,7 @@
|
||||
[self toggleEditing];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
- (void)closeAll {
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
#import <FLEX/UIMenu+FLEX.h>
|
||||
#import <FLEX/UITextField+Range.h>
|
||||
|
||||
#import <FLEX/NSObject+Reflection.h>
|
||||
#import <FLEX/NSArray+Functional.h>
|
||||
#import <FLEX/NSObject+FLEX_Reflection.h>
|
||||
#import <FLEX/NSArray+FLEX.h>
|
||||
#import <FLEX/NSDictionary+ObjcRuntime.h>
|
||||
#import <FLEX/NSString+ObjcRuntime.h>
|
||||
#import <FLEX/NSString+FLEX.h>
|
||||
#import <FLEX/NSUserDefaults+FLEX.h>
|
||||
#import <FLEX/NSMapTable+FLEX_Subscripting.h>
|
||||
#import <FLEX/NSTimer+Blocks.h>
|
||||
#import <FLEX/NSTimer+FLEX.h>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import "FLEXDBQueryRowCell.h"
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "UIFont+FLEX.h"
|
||||
#import "FLEXColor.h"
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FLEXSQLResult.h"
|
||||
|
||||
/// Conformers should automatically open and close the database
|
||||
@protocol FLEXDatabaseManager <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/// @return \c nil if the database couldn't be opened
|
||||
+ (instancetype)managerForDatabase:(NSString *)path;
|
||||
|
||||
- (BOOL)open;
|
||||
|
||||
/// @return a list of all table names
|
||||
- (NSArray<NSString *> *)queryAllTables;
|
||||
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXRealmDatabaseManager.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXSQLResult.h"
|
||||
|
||||
#if __has_include(<Realm/Realm.h>)
|
||||
@@ -20,7 +20,7 @@
|
||||
@interface FLEXRealmDatabaseManager ()
|
||||
|
||||
@property (nonatomic, copy) NSString *path;
|
||||
@property (nonatomic) RLMRealm * realm;
|
||||
@property (nonatomic) RLMRealm *realm;
|
||||
|
||||
@end
|
||||
|
||||
@@ -43,6 +43,10 @@ static Class RLMRealmClass = nil;
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_path = path;
|
||||
|
||||
if (![self open]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -64,9 +68,11 @@ static Class RLMRealmClass = nil;
|
||||
|
||||
- (NSArray<NSString *> *)queryAllTables {
|
||||
// Map each schema to its name
|
||||
return [self.realm.schema.objectSchema flex_mapped:^id(RLMObjectSchema *schema, NSUInteger idx) {
|
||||
NSArray<NSString *> *tableNames = [self.realm.schema.objectSchema flex_mapped:^id(RLMObjectSchema *schema, NSUInteger idx) {
|
||||
return schema.className ?: nil;
|
||||
}];
|
||||
|
||||
return [tableNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
|
||||
|
||||
@@ -14,6 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Describes the result of a non-select query, or an error of any kind of query
|
||||
+ (instancetype)message:(NSString *)message;
|
||||
/// Describes the result of a known failed execution
|
||||
+ (instancetype)error:(NSString *)message;
|
||||
|
||||
/// @param rowData A list of rows, where each element in the row
|
||||
/// corresponds to the column given in /c columnNames
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
|
||||
@@ -21,6 +24,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSString *message;
|
||||
|
||||
/// A value of YES means this is surely an error,
|
||||
/// but it still might be an error even with a value of NO
|
||||
@property (nonatomic, readonly) BOOL isError;
|
||||
|
||||
/// A list of column names
|
||||
@property (nonatomic, readonly, nullable) NSArray<NSString *> *columns;
|
||||
/// A list of rows, where each element in the row corresponds
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXSQLResult.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
|
||||
@implementation FLEXSQLResult
|
||||
@synthesize keyedRows = _keyedRows;
|
||||
@@ -16,6 +16,12 @@
|
||||
return [[self alloc] initWithmessage:message columns:nil rows:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)error:(NSString *)message {
|
||||
FLEXSQLResult *result = [self message:message];
|
||||
result->_isError = YES;
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
|
||||
return [[self alloc] initWithmessage:nil columns:columnNames rows:rowData];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,20 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FLEXDatabaseManager.h"
|
||||
#import "FLEXSQLResult.h"
|
||||
|
||||
@interface FLEXSQLiteDatabaseManager : NSObject <FLEXDatabaseManager>
|
||||
|
||||
/// Contains the result of the last operation, which may be an error
|
||||
@property (nonatomic, readonly) FLEXSQLResult *lastResult;
|
||||
/// Calls into \c sqlite3_last_insert_rowid()
|
||||
@property (nonatomic, readonly) NSInteger lastRowID;
|
||||
|
||||
/// Given a statement like 'SELECT * from @table where @col = @val' and arguments
|
||||
/// like { @"table": @"Album", @"col": @"year", @"val" @1 } this method will
|
||||
/// invoke the statement and properly bind the given arguments to the statement.
|
||||
///
|
||||
/// You may pass NSStrings, NSData, NSNumbers, or NSNulls as values.
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)statement arguments:(NSDictionary<NSString *, id> *)args;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#import "FLEXSQLiteDatabaseManager.h"
|
||||
#import "FLEXManager.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "FLEXSQLResult.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXRuntimeConstants.h"
|
||||
#import <sqlite3.h>
|
||||
|
||||
static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
|
||||
@@ -36,6 +36,10 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self close];
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
if (self.db) {
|
||||
return YES;
|
||||
@@ -52,8 +56,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
#endif
|
||||
|
||||
if (err != SQLITE_OK) {
|
||||
NSLog(@"error opening!: %d", err);
|
||||
return NO;
|
||||
return [self storeErrorForLastTask:@"Open"];
|
||||
}
|
||||
|
||||
return YES;
|
||||
@@ -81,7 +84,9 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
}
|
||||
}
|
||||
} else if (SQLITE_OK != rc) {
|
||||
NSLog(@"error closing!: %d", rc);
|
||||
[self storeErrorForLastTask:@"Close"];
|
||||
self.db = nil;
|
||||
return NO;
|
||||
}
|
||||
} while (retry);
|
||||
|
||||
@@ -89,36 +94,50 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSInteger)lastRowID {
|
||||
return (NSInteger)sqlite3_last_insert_rowid(self.db);
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllTables {
|
||||
return [[self executeStatement:QUERY_TABLENAMES].rows flex_mapped:^id(NSArray *table, NSUInteger idx) {
|
||||
return table.firstObject;
|
||||
}];
|
||||
}] ?: @[];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
|
||||
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
|
||||
FLEXSQLResult *results = [self executeStatement:sql];
|
||||
|
||||
|
||||
return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
|
||||
return column[@"name"];
|
||||
}];
|
||||
}] ?: @[];
|
||||
}
|
||||
|
||||
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
|
||||
return [self executeStatement:[@"SELECT * FROM "
|
||||
stringByAppendingString:tableName
|
||||
]].rows;
|
||||
]].rows ?: @[];
|
||||
}
|
||||
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
|
||||
return [self executeStatement:sql arguments:nil];
|
||||
}
|
||||
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)sql arguments:(NSDictionary *)args {
|
||||
[self open];
|
||||
|
||||
FLEXSQLResult *result = nil;
|
||||
|
||||
sqlite3_stmt *pstmt;
|
||||
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0) == SQLITE_OK) {
|
||||
int status;
|
||||
if ((status = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0)) == SQLITE_OK) {
|
||||
NSMutableArray<NSArray *> *rows = [NSMutableArray new];
|
||||
|
||||
// Bind parameters, if any
|
||||
if (![self bindParameters:args toStatement:pstmt]) {
|
||||
return self.lastResult;
|
||||
}
|
||||
|
||||
// Grab columns
|
||||
int columnCount = sqlite3_column_count(pstmt);
|
||||
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
|
||||
@@ -126,7 +145,6 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
}];
|
||||
|
||||
// Execute statement
|
||||
int status;
|
||||
while ((status = sqlite3_step(pstmt)) == SQLITE_ROW) {
|
||||
// Grab rows if this is a selection query
|
||||
int dataCount = sqlite3_data_count(pstmt);
|
||||
@@ -140,30 +158,111 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
if (status == SQLITE_DONE) {
|
||||
if (rows.count) {
|
||||
// We selected some rows
|
||||
result = [FLEXSQLResult columns:columns rows:rows];
|
||||
result = _lastResult = [FLEXSQLResult columns:columns rows:rows];
|
||||
} else {
|
||||
// We executed a query like INSERT, UDPATE, or DELETE
|
||||
int rowsAffected = sqlite3_changes(_db);
|
||||
NSString *message = [NSString stringWithFormat:@"%d row(s) affected", rowsAffected];
|
||||
result = [FLEXSQLResult message:message];
|
||||
result = _lastResult = [FLEXSQLResult message:message];
|
||||
}
|
||||
} else {
|
||||
// An error occured executing the query
|
||||
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Execution: empty error)")];
|
||||
result = _lastResult = [self errorResult:@"Execution"];
|
||||
}
|
||||
} else {
|
||||
// An error occurred creating the prepared statement
|
||||
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Prepared statement: empty error)")];
|
||||
result = _lastResult = [self errorResult:@"Prepared statement"];
|
||||
}
|
||||
|
||||
sqlite3_finalize(pstmt);
|
||||
[self close];
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
/// @return YES on success, NO if an error was encountered and stored in \c lastResult
|
||||
- (BOOL)bindParameters:(NSDictionary *)args toStatement:(sqlite3_stmt *)pstmt {
|
||||
for (NSString *param in args.allKeys) {
|
||||
int status = SQLITE_OK, idx = sqlite3_bind_parameter_index(pstmt, param.UTF8String);
|
||||
id value = args[param];
|
||||
|
||||
if (idx == 0) {
|
||||
// No parameter matching that arg
|
||||
@throw NSInternalInconsistencyException;
|
||||
}
|
||||
|
||||
// Null
|
||||
if ([value isKindOfClass:[NSNull class]]) {
|
||||
status = sqlite3_bind_null(pstmt, idx);
|
||||
}
|
||||
// String params
|
||||
else if ([value isKindOfClass:[NSString class]]) {
|
||||
const char *str = [value UTF8String];
|
||||
status = sqlite3_bind_text(pstmt, idx, str, (int)strlen(str), SQLITE_TRANSIENT);
|
||||
}
|
||||
// Data params
|
||||
else if ([value isKindOfClass:[NSData class]]) {
|
||||
const void *blob = [value bytes];
|
||||
status = sqlite3_bind_blob64(pstmt, idx, blob, [value length], SQLITE_TRANSIENT);
|
||||
}
|
||||
// Primitive params
|
||||
else if ([value isKindOfClass:[NSNumber class]]) {
|
||||
FLEXTypeEncoding type = [value objCType][0];
|
||||
switch (type) {
|
||||
case FLEXTypeEncodingCBool:
|
||||
case FLEXTypeEncodingChar:
|
||||
case FLEXTypeEncodingUnsignedChar:
|
||||
case FLEXTypeEncodingShort:
|
||||
case FLEXTypeEncodingUnsignedShort:
|
||||
case FLEXTypeEncodingInt:
|
||||
case FLEXTypeEncodingUnsignedInt:
|
||||
case FLEXTypeEncodingLong:
|
||||
case FLEXTypeEncodingUnsignedLong:
|
||||
case FLEXTypeEncodingLongLong:
|
||||
case FLEXTypeEncodingUnsignedLongLong:
|
||||
status = sqlite3_bind_int64(pstmt, idx, (sqlite3_int64)[value longValue]);
|
||||
break;
|
||||
|
||||
case FLEXTypeEncodingFloat:
|
||||
case FLEXTypeEncodingDouble:
|
||||
status = sqlite3_bind_double(pstmt, idx, [value doubleValue]);
|
||||
break;
|
||||
|
||||
default:
|
||||
@throw NSInternalInconsistencyException;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Unsupported type
|
||||
else {
|
||||
@throw NSInternalInconsistencyException;
|
||||
}
|
||||
|
||||
if (status != SQLITE_OK) {
|
||||
return [self storeErrorForLastTask:
|
||||
[NSString stringWithFormat:@"Binding param named '%@'", param]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)storeErrorForLastTask:(NSString *)action {
|
||||
_lastResult = [self errorResult:action];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (FLEXSQLResult *)errorResult:(NSString *)description {
|
||||
const char *error = sqlite3_errmsg(_db);
|
||||
NSString *message = error ? @(error) : [NSString
|
||||
stringWithFormat:@"(%@: empty error", description
|
||||
];
|
||||
|
||||
return [FLEXSQLResult error:message];
|
||||
}
|
||||
|
||||
- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
|
||||
int columnType = sqlite3_column_type(stmt, columnIdx);
|
||||
|
||||
|
||||
@@ -113,7 +113,11 @@
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title([@"Row " stringByAppendingString:@(row).stringValue]);
|
||||
make.message([fields componentsJoinedByString:@"\n\n"]);
|
||||
NSString *message = [fields componentsJoinedByString:@"\n\n"];
|
||||
make.message(message);
|
||||
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
|
||||
UIPasteboard.generalPasteboard.string = message;
|
||||
});
|
||||
make.button(@"Dismiss").cancelStyle();
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#import "FLEXRealmDatabaseManager.h"
|
||||
#import "FLEXTableContentViewController.h"
|
||||
#import "FLEXMutableListSection.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXAlert.h"
|
||||
|
||||
@interface FLEXTableListViewController ()
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXCookiesTableViewController.h
|
||||
// FLEXCookiesViewController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Rich Robinson on 19/10/2015.
|
||||
@@ -9,6 +9,6 @@
|
||||
#import "FLEXGlobalsEntry.h"
|
||||
#import "FLEXFilteringTableViewController.h"
|
||||
|
||||
@interface FLEXCookiesTableViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
|
||||
@interface FLEXCookiesViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
|
||||
|
||||
@end
|
||||
+4
-4
@@ -1,22 +1,22 @@
|
||||
//
|
||||
// FLEXCookiesTableViewController.m
|
||||
// FLEXCookiesViewController.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Rich Robinson on 19/10/2015.
|
||||
// Copyright © 2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXCookiesTableViewController.h"
|
||||
#import "FLEXCookiesViewController.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXMutableListSection.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
@interface FLEXCookiesTableViewController ()
|
||||
@interface FLEXCookiesViewController ()
|
||||
@property (nonatomic, readonly) FLEXMutableListSection<NSHTTPCookie *> *cookies;
|
||||
@property (nonatomic) NSString *headerTitle;
|
||||
@end
|
||||
|
||||
@implementation FLEXCookiesTableViewController
|
||||
@implementation FLEXCookiesViewController
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXLiveObjectsTableViewController.h
|
||||
// FLEXLiveObjectsController.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 5/28/14.
|
||||
@@ -9,6 +9,6 @@
|
||||
#import "FLEXTableViewController.h"
|
||||
#import "FLEXGlobalsEntry.h"
|
||||
|
||||
@interface FLEXLiveObjectsTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
|
||||
@interface FLEXLiveObjectsController : FLEXTableViewController <FLEXGlobalsEntry>
|
||||
|
||||
@end
|
||||
+8
-7
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// FLEXLiveObjectsTableViewController.m
|
||||
// FLEXLiveObjectsController.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 5/28/14.
|
||||
// Copyright (c) 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXLiveObjectsTableViewController.h"
|
||||
#import "FLEXLiveObjectsController.h"
|
||||
#import "FLEXHeapEnumerator.h"
|
||||
#import "FLEXObjectListViewController.h"
|
||||
#import "FLEXUtility.h"
|
||||
@@ -18,7 +18,7 @@ static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
|
||||
static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
|
||||
@interface FLEXLiveObjectsTableViewController ()
|
||||
@interface FLEXLiveObjectsController ()
|
||||
|
||||
@property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceCountsForClassNames;
|
||||
@property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
|
||||
@@ -28,12 +28,13 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXLiveObjectsTableViewController
|
||||
@implementation FLEXLiveObjectsController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
self.showSearchBarInitially = YES;
|
||||
self.searchBarDebounceInterval = kFLEXDebounceInstant;
|
||||
self.showsCarousel = YES;
|
||||
self.carousel.items = @[@"A→Z", @"Count", @"Size"];
|
||||
@@ -139,7 +140,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
}
|
||||
|
||||
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
|
||||
FLEXLiveObjectsTableViewController *liveObjectsViewController = [self new];
|
||||
FLEXLiveObjectsController *liveObjectsViewController = [self new];
|
||||
liveObjectsViewController.title = [self globalsEntryTitle:row];
|
||||
|
||||
return liveObjectsViewController;
|
||||
@@ -224,8 +225,8 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSString *className = self.filteredClassNames[indexPath.row];
|
||||
FLEXObjectListViewController *instancesViewController = [FLEXObjectListViewController instancesOfClassWithName:className];
|
||||
[self.navigationController pushViewController:instancesViewController animated:YES];
|
||||
UIViewController *instances = [FLEXObjectListViewController instancesOfClassWithName:className];
|
||||
[self.navigationController pushViewController:instances animated:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
@interface FLEXObjectListViewController : FLEXFilteringTableViewController
|
||||
|
||||
+ (instancetype)instancesOfClassWithName:(NSString *)className;
|
||||
/// This will either return a list of the instances, or take you straight
|
||||
/// to the explorer itself if there is only one instance.
|
||||
+ (UIViewController *)instancesOfClassWithName:(NSString *)className;
|
||||
+ (instancetype)subclassesOfClassWithName:(NSString *)className;
|
||||
+ (instancetype)objectsWithReferencesToObject:(id)object;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#import "FLEXHeapEnumerator.h"
|
||||
#import "FLEXObjectRef.h"
|
||||
#import "NSString+FLEX.h"
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "FLEXTableViewCell.h"
|
||||
#import <malloc/malloc.h>
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)instancesOfClassWithName:(NSString *)className {
|
||||
+ (UIViewController *)instancesOfClassWithName:(NSString *)className {
|
||||
const char *classNameCString = className.UTF8String;
|
||||
NSMutableArray *instances = [NSMutableArray new];
|
||||
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
|
||||
@@ -125,8 +125,14 @@
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
|
||||
if (references.count == 1) {
|
||||
return [FLEXObjectExplorerFactory
|
||||
explorerViewControllerForObject:references.firstObject.object
|
||||
];
|
||||
}
|
||||
|
||||
FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
|
||||
controller.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)instances.count];
|
||||
return controller;
|
||||
@@ -139,7 +145,7 @@
|
||||
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%lu)",
|
||||
className, (unsigned long)classes.count
|
||||
];
|
||||
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -152,7 +158,7 @@
|
||||
SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
|
||||
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
|
||||
// Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
|
||||
@@ -161,15 +167,15 @@
|
||||
while (tryClass) {
|
||||
unsigned int ivarCount = 0;
|
||||
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
|
||||
|
||||
|
||||
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
|
||||
Ivar ivar = ivars[ivarIndex];
|
||||
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
|
||||
|
||||
|
||||
if (typeEncoding.flex_typeIsObjectOrClass) {
|
||||
ptrdiff_t offset = ivar_getOffset(ivar);
|
||||
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
|
||||
|
||||
|
||||
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
|
||||
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
|
||||
[instances addObject:[FLEXObjectRef referencing:tryObject ivar:ivarName]];
|
||||
@@ -177,7 +183,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tryClass = class_getSuperclass(tryClass);
|
||||
}
|
||||
}];
|
||||
@@ -200,7 +206,7 @@
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
}
|
||||
|
||||
@@ -218,7 +224,7 @@
|
||||
- (NSArray *)buildSections:(NSArray<NSString *> *)titles predicates:(NSArray<NSPredicate *> *)predicates {
|
||||
NSParameterAssert(titles.count == predicates.count);
|
||||
NSParameterAssert(titles); NSParameterAssert(predicates);
|
||||
|
||||
|
||||
return [NSArray flex_forEachUpTo:titles.count map:^id(NSUInteger i) {
|
||||
NSArray *rows = [self.references filteredArrayUsingPredicate:predicates[i]];
|
||||
return [self makeSection:rows title:titles[i]];
|
||||
@@ -235,18 +241,22 @@
|
||||
if (ref.summary && [ref.summary localizedCaseInsensitiveContainsString:filterText]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
return [ref.reference localizedCaseInsensitiveContainsString:filterText];
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
section.selectionHandler = ^(__kindof UIViewController *host, FLEXObjectRef *ref) {
|
||||
[self.navigationController pushViewController:[
|
||||
FLEXObjectExplorerFactory explorerViewControllerForObject:ref.object
|
||||
] animated:YES];
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
[strongSelf.navigationController pushViewController:[
|
||||
FLEXObjectExplorerFactory explorerViewControllerForObject:ref.object
|
||||
] animated:YES];
|
||||
}
|
||||
};
|
||||
|
||||
section.customTitle = title;
|
||||
section.customTitle = title;
|
||||
return section;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import "FLEXObjectRef.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
|
||||
@interface FLEXObjectRef ()
|
||||
@property (nonatomic, readonly) BOOL wantsSummary;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXFileBrowserTableViewController.h
|
||||
// FLEXFileBrowserController.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 6/9/14.
|
||||
@@ -10,7 +10,7 @@
|
||||
#import "FLEXGlobalsEntry.h"
|
||||
#import "FLEXFileBrowserSearchOperation.h"
|
||||
|
||||
@interface FLEXFileBrowserTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
|
||||
@interface FLEXFileBrowserController : FLEXTableViewController <FLEXGlobalsEntry>
|
||||
|
||||
+ (instancetype)path:(NSString *)path;
|
||||
- (id)initWithPath:(NSString *)path;
|
||||
+68
-7
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// FLEXFileBrowserTableViewController.m
|
||||
// FLEXFileBrowserController.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 6/9/14.
|
||||
//
|
||||
//
|
||||
|
||||
#import "FLEXFileBrowserTableViewController.h"
|
||||
#import "FLEXFileBrowserController.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXWebViewController.h"
|
||||
#import "FLEXImagePreviewViewController.h"
|
||||
@@ -18,7 +18,13 @@
|
||||
@interface FLEXFileBrowserTableViewCell : UITableViewCell
|
||||
@end
|
||||
|
||||
@interface FLEXFileBrowserTableViewController () <FLEXFileBrowserSearchOperationDelegate>
|
||||
typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
|
||||
FLEXFileBrowserSortAttributeNone = 0,
|
||||
FLEXFileBrowserSortAttributeName,
|
||||
FLEXFileBrowserSortAttributeCreationDate,
|
||||
};
|
||||
|
||||
@interface FLEXFileBrowserController () <FLEXFileBrowserSearchOperationDelegate>
|
||||
|
||||
@property (nonatomic, copy) NSString *path;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *childPaths;
|
||||
@@ -27,10 +33,11 @@
|
||||
@property (nonatomic) NSNumber *searchPathsSize;
|
||||
@property (nonatomic) NSOperationQueue *operationQueue;
|
||||
@property (nonatomic) UIDocumentInteractionController *documentController;
|
||||
@property (nonatomic) FLEXFileBrowserSortAttribute sortAttribute;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXFileBrowserTableViewController
|
||||
@implementation FLEXFileBrowserController
|
||||
|
||||
+ (instancetype)path:(NSString *)path {
|
||||
return [[self alloc] initWithPath:path];
|
||||
@@ -49,7 +56,7 @@
|
||||
|
||||
|
||||
//computing path size
|
||||
FLEXFileBrowserTableViewController *__weak weakSelf = self;
|
||||
FLEXFileBrowserController *__weak weakSelf = self;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSFileManager *fileManager = NSFileManager.defaultManager;
|
||||
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
|
||||
@@ -66,7 +73,7 @@
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
FLEXFileBrowserTableViewController *__strong strongSelf = weakSelf;
|
||||
FLEXFileBrowserController *__strong strongSelf = weakSelf;
|
||||
strongSelf.recursiveSize = @(totalSize);
|
||||
[strongSelf.tableView reloadData];
|
||||
});
|
||||
@@ -84,6 +91,39 @@
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
self.searchBarDebounceInterval = kFLEXDebounceForAsyncSearch;
|
||||
[self addToolbarItems:@[
|
||||
[[UIBarButtonItem alloc] initWithTitle:@"Sort"
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(sortDidTouchUpInside:)]
|
||||
]];
|
||||
}
|
||||
|
||||
- (void)sortDidTouchUpInside:(UIBarButtonItem *)sortButton {
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Sort"
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"None"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
[self sortWithAttribute:FLEXFileBrowserSortAttributeNone];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"Name"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
[self sortWithAttribute:FLEXFileBrowserSortAttributeName];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"Creation Date"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
[self sortWithAttribute:FLEXFileBrowserSortAttributeCreationDate];
|
||||
}]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)sortWithAttribute:(FLEXFileBrowserSortAttribute)attribute {
|
||||
self.sortAttribute = attribute;
|
||||
[self reloadDisplayedPaths];
|
||||
}
|
||||
|
||||
#pragma mark - FLEXGlobalsEntry
|
||||
@@ -321,7 +361,7 @@
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
return [UIContextMenuConfiguration configurationWithIdentifier:nil
|
||||
previewProvider:nil
|
||||
actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
|
||||
@@ -452,6 +492,27 @@
|
||||
for (NSString *subpath in subpaths) {
|
||||
[childPaths addObject:[self.path stringByAppendingPathComponent:subpath]];
|
||||
}
|
||||
if (self.sortAttribute != FLEXFileBrowserSortAttributeNone) {
|
||||
[childPaths sortUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
|
||||
switch (self.sortAttribute) {
|
||||
case FLEXFileBrowserSortAttributeNone:
|
||||
// invalid state
|
||||
return NSOrderedSame;
|
||||
case FLEXFileBrowserSortAttributeName:
|
||||
return [path1 compare:path2];
|
||||
case FLEXFileBrowserSortAttributeCreationDate: {
|
||||
NSDictionary<NSFileAttributeKey, id> *path1Attributes = [NSFileManager.defaultManager attributesOfItemAtPath:path1
|
||||
error:NULL];
|
||||
NSDictionary<NSFileAttributeKey, id> *path2Attributes = [NSFileManager.defaultManager attributesOfItemAtPath:path2
|
||||
error:NULL];
|
||||
NSDate *path1Date = path1Attributes[NSFileCreationDate];
|
||||
NSDate *path2Date = path2Attributes[NSFileCreationDate];
|
||||
|
||||
return [path1Date compare:path2Date];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
self.childPaths = childPaths;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXGlobalsSection.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "UIFont+FLEX.h"
|
||||
|
||||
@interface FLEXGlobalsSection ()
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
@protocol FLEXGlobalsTableViewControllerDelegate;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXGlobalsSectionKind) {
|
||||
FLEXGlobalsSectionCustom,
|
||||
/// NSProcessInfo, Network history, system log,
|
||||
/// heap, address explorer, libraries, app classes
|
||||
FLEXGlobalsSectionProcessAndEvents,
|
||||
@@ -19,7 +20,6 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsSectionKind) {
|
||||
FLEXGlobalsSectionAppShortcuts,
|
||||
/// UIPasteBoard.general, UIScreen, UIDevice
|
||||
FLEXGlobalsSectionMisc,
|
||||
FLEXGlobalsSectionCustom,
|
||||
FLEXGlobalsSectionCount
|
||||
};
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXObjcRuntimeViewController.h"
|
||||
#import "FLEXKeychainTableViewController.h"
|
||||
#import "FLEXKeychainViewController.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXLiveObjectsTableViewController.h"
|
||||
#import "FLEXFileBrowserTableViewController.h"
|
||||
#import "FLEXCookiesTableViewController.h"
|
||||
#import "FLEXLiveObjectsController.h"
|
||||
#import "FLEXFileBrowserController.h"
|
||||
#import "FLEXCookiesViewController.h"
|
||||
#import "FLEXGlobalsEntry.h"
|
||||
#import "FLEXManager+Private.h"
|
||||
#import "FLEXSystemLogViewController.h"
|
||||
@@ -39,14 +39,14 @@
|
||||
|
||||
+ (NSString *)globalsTitleForSection:(FLEXGlobalsSectionKind)section {
|
||||
switch (section) {
|
||||
case FLEXGlobalsSectionCustom:
|
||||
return @"Custom Additions";
|
||||
case FLEXGlobalsSectionProcessAndEvents:
|
||||
return @"Process and Events";
|
||||
case FLEXGlobalsSectionAppShortcuts:
|
||||
return @"App Shortcuts";
|
||||
case FLEXGlobalsSectionMisc:
|
||||
return @"Miscellaneous";
|
||||
case FLEXGlobalsSectionCustom:
|
||||
return @"Custom Additions";
|
||||
|
||||
default:
|
||||
@throw NSInternalInconsistencyException;
|
||||
@@ -56,18 +56,18 @@
|
||||
+ (FLEXGlobalsEntry *)globalsEntryForRow:(FLEXGlobalsRow)row {
|
||||
switch (row) {
|
||||
case FLEXGlobalsRowAppKeychainItems:
|
||||
return [FLEXKeychainTableViewController flex_concreteGlobalsEntry:row];
|
||||
return [FLEXKeychainViewController flex_concreteGlobalsEntry:row];
|
||||
case FLEXGlobalsRowAddressInspector:
|
||||
return [FLEXAddressExplorerCoordinator flex_concreteGlobalsEntry:row];
|
||||
case FLEXGlobalsRowBrowseRuntime:
|
||||
return [FLEXObjcRuntimeViewController flex_concreteGlobalsEntry:row];
|
||||
case FLEXGlobalsRowLiveObjects:
|
||||
return [FLEXLiveObjectsTableViewController flex_concreteGlobalsEntry:row];
|
||||
return [FLEXLiveObjectsController flex_concreteGlobalsEntry:row];
|
||||
case FLEXGlobalsRowCookies:
|
||||
return [FLEXCookiesTableViewController flex_concreteGlobalsEntry:row];
|
||||
return [FLEXCookiesViewController flex_concreteGlobalsEntry:row];
|
||||
case FLEXGlobalsRowBrowseBundle:
|
||||
case FLEXGlobalsRowBrowseContainer:
|
||||
return [FLEXFileBrowserTableViewController flex_concreteGlobalsEntry:row];
|
||||
return [FLEXFileBrowserController flex_concreteGlobalsEntry:row];
|
||||
case FLEXGlobalsRowSystemLog:
|
||||
return [FLEXSystemLogViewController flex_concreteGlobalsEntry:row];
|
||||
case FLEXGlobalsRowNetworkHistory:
|
||||
@@ -104,11 +104,11 @@
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXGlobalsSection *> *)defaultGlobalSections {
|
||||
static NSArray<FLEXGlobalsSection *> *sections = nil;
|
||||
static NSMutableArray<FLEXGlobalsSection *> *sections = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSArray *rowsBySection = @[
|
||||
@[
|
||||
NSDictionary<NSNumber *, NSArray<FLEXGlobalsEntry *> *> *rowsBySection = @{
|
||||
@(FLEXGlobalsSectionProcessAndEvents) : @[
|
||||
[self globalsEntryForRow:FLEXGlobalsRowNetworkHistory],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowSystemLog],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowProcessInfo],
|
||||
@@ -116,7 +116,7 @@
|
||||
[self globalsEntryForRow:FLEXGlobalsRowAddressInspector],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowBrowseRuntime],
|
||||
],
|
||||
@[ // FLEXGlobalsSectionAppShortcuts
|
||||
@(FLEXGlobalsSectionAppShortcuts) : @[
|
||||
[self globalsEntryForRow:FLEXGlobalsRowBrowseBundle],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowBrowseContainer],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowMainBundle],
|
||||
@@ -128,7 +128,7 @@
|
||||
[self globalsEntryForRow:FLEXGlobalsRowRootViewController],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowCookies],
|
||||
],
|
||||
@[ // FLEXGlobalsSectionMisc
|
||||
@(FLEXGlobalsSectionMisc) : @[
|
||||
[self globalsEntryForRow:FLEXGlobalsRowPasteboard],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowMainScreen],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowCurrentDevice],
|
||||
@@ -144,12 +144,13 @@
|
||||
[self globalsEntryForRow:FLEXGlobalsRowMainThread],
|
||||
[self globalsEntryForRow:FLEXGlobalsRowOperationQueue],
|
||||
]
|
||||
];
|
||||
|
||||
sections = [NSArray flex_forEachUpTo:rowsBySection.count map:^FLEXGlobalsSection *(NSUInteger i) {
|
||||
};
|
||||
|
||||
sections = [NSMutableArray array];
|
||||
for (FLEXGlobalsSectionKind i = FLEXGlobalsSectionCustom + 1; i < FLEXGlobalsSectionCount; ++i) {
|
||||
NSString *title = [self globalsTitleForSection:i];
|
||||
return [FLEXGlobalsSection title:title rows:rowsBySection[i]];
|
||||
}];
|
||||
[sections addObject:[FLEXGlobalsSection title:title rows:rowsBySection[@(i)]]];
|
||||
}
|
||||
});
|
||||
|
||||
return sections;
|
||||
@@ -180,8 +181,7 @@
|
||||
}
|
||||
|
||||
- (NSArray<FLEXGlobalsSection *> *)makeSections {
|
||||
NSArray *sections = [self.class defaultGlobalSections];
|
||||
|
||||
NSMutableArray<FLEXGlobalsSection *> *sections = [NSMutableArray array];
|
||||
// Do we have custom sections to add?
|
||||
if (FLEXManager.sharedManager.userGlobalEntries.count) {
|
||||
NSString *title = [[self class] globalsTitleForSection:FLEXGlobalsSectionCustom];
|
||||
@@ -189,9 +189,11 @@
|
||||
title:title
|
||||
rows:FLEXManager.sharedManager.userGlobalEntries
|
||||
];
|
||||
sections = [sections arrayByAddingObject:custom];
|
||||
[sections addObject:custom];
|
||||
}
|
||||
|
||||
|
||||
[sections addObjectsFromArray:[self.class defaultGlobalSections]];
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXKeychainTableViewController.h
|
||||
// FLEXKeychainViewController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by ray on 2019/8/17.
|
||||
@@ -9,6 +9,6 @@
|
||||
#import "FLEXGlobalsEntry.h"
|
||||
#import "FLEXFilteringTableViewController.h"
|
||||
|
||||
@interface FLEXKeychainTableViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
|
||||
@interface FLEXKeychainViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
|
||||
|
||||
@end
|
||||
+10
-13
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXKeychainTableViewController.m
|
||||
// FLEXKeychainViewController.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by ray on 2019/8/17.
|
||||
@@ -8,17 +8,18 @@
|
||||
|
||||
#import "FLEXKeychain.h"
|
||||
#import "FLEXKeychainQuery.h"
|
||||
#import "FLEXKeychainTableViewController.h"
|
||||
#import "FLEXKeychainViewController.h"
|
||||
#import "FLEXTableViewCell.h"
|
||||
#import "FLEXMutableListSection.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "UIPasteboard+FLEX.h"
|
||||
#import "UIBarButtonItem+FLEX.h"
|
||||
|
||||
@interface FLEXKeychainTableViewController ()
|
||||
@interface FLEXKeychainViewController ()
|
||||
@property (nonatomic, readonly) FLEXMutableListSection<NSDictionary *> *section;
|
||||
@end
|
||||
|
||||
@implementation FLEXKeychainTableViewController
|
||||
@implementation FLEXKeychainViewController
|
||||
|
||||
- (id)init {
|
||||
return [self initWithStyle:UITableViewStyleGrouped];
|
||||
@@ -30,12 +31,8 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
self.navigationItem.rightBarButtonItems = @[
|
||||
[[UIBarButtonItem alloc]
|
||||
initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(trashPressed)
|
||||
],
|
||||
[[UIBarButtonItem alloc]
|
||||
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addPressed)
|
||||
],
|
||||
[UIBarButtonItem systemItem:UIBarButtonSystemItemTrash target:self action:@selector(trashPressed:)],
|
||||
[UIBarButtonItem systemItem:UIBarButtonSystemItemAdd target:self action:@selector(addPressed)],
|
||||
];
|
||||
|
||||
[self reloadData];
|
||||
@@ -127,7 +124,7 @@
|
||||
|
||||
#pragma mark Buttons
|
||||
|
||||
- (void)trashPressed {
|
||||
- (void)trashPressed:(UIBarButtonItem *)sender {
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
make.title(@"Clear Keychain");
|
||||
make.message(@"This will remove all keychain items for this app.\n");
|
||||
@@ -140,7 +137,7 @@
|
||||
[self reloadData];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
- (void)addPressed {
|
||||
@@ -170,7 +167,7 @@
|
||||
}
|
||||
|
||||
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
|
||||
FLEXKeychainTableViewController *viewController = [self new];
|
||||
FLEXKeychainViewController *viewController = [self new];
|
||||
viewController.title = [self globalsEntryTitle:row];
|
||||
|
||||
return viewController;
|
||||
@@ -19,6 +19,17 @@
|
||||
/// been loaded since this method was first called.
|
||||
- (void)reloadLibrariesList;
|
||||
|
||||
/// You must call this method on the main thread
|
||||
/// before you attempt to call \c copySafeClassList.
|
||||
+ (void)initializeWebKitLegacy;
|
||||
|
||||
/// Do not call unless you absolutely need all classes. This will cause
|
||||
/// every class in the runtime to initialize itself, which is not common.
|
||||
/// Before you call this method, call \c initializeWebKitLegacy on the main thread.
|
||||
- (NSArray<Class> *)copySafeClassList;
|
||||
|
||||
- (NSArray<Protocol *> *)copyProtocolList;
|
||||
|
||||
/// An array of strings representing the currently loaded libraries.
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *imageDisplayNames;
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeClient.h"
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "FLEXMethod.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXRuntimeSafety.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define Equals(a, b) ([a compare:b options:NSCaseInsensitiveSearch] == NSOrderedSame)
|
||||
#define Contains(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
@@ -187,6 +188,38 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
+ (void)initializeWebKitLegacy {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
void *handle = dlopen(
|
||||
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
|
||||
RTLD_LAZY
|
||||
);
|
||||
void (*WebKitInitialize)() = dlsym(handle, "WebKitInitialize");
|
||||
if (WebKitInitialize) {
|
||||
NSAssert(NSThread.isMainThread,
|
||||
@"WebKitInitialize can only be called on the main thread"
|
||||
);
|
||||
WebKitInitialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray<Class> *)copySafeClassList {
|
||||
unsigned int count = 0;
|
||||
Class *classes = objc_copyClassList(&count);
|
||||
return [NSArray flex_forEachUpTo:count map:^id(NSUInteger i) {
|
||||
Class cls = classes[i];
|
||||
return FLEXClassIsSafe(cls) ? cls : nil;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSArray<Protocol *> *)copyProtocolList {
|
||||
unsigned int count = 0;
|
||||
Protocol *__unsafe_unretained *protocols = objc_copyProtocolList(&count);
|
||||
return [NSArray arrayWithObjects:protocols count:count];
|
||||
}
|
||||
|
||||
- (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token {
|
||||
if (self.imagePaths.count) {
|
||||
TBWildcardOptions options = token.options;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import "FLEXRuntimeKeyPath.h"
|
||||
|
||||
/// Wraps FLEXRuntimeClient and provides caching mechanisms
|
||||
/// Wraps FLEXRuntimeClient and provides extra caching mechanisms
|
||||
@interface FLEXRuntimeController : NSObject
|
||||
|
||||
/// @return An array of strings if the key path only evaluates
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
+ (NSString *)imagePathWithShortName:(NSString *)suffix;
|
||||
|
||||
/// Gives back short names. For example, "Foundation.framework"
|
||||
+ (NSArray<NSString*> *)allBundleNames;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// FLEXRuntimeExporter.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 3/26/20.
|
||||
// Copyright (c) 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// A class for exporting all runtime metadata to an SQLite database.
|
||||
//API_AVAILABLE(ios(10.0))
|
||||
@interface FLEXRuntimeExporter : NSObject
|
||||
|
||||
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
|
||||
progressHandler:(void(^)(NSString *status))progress
|
||||
completion:(void(^)(NSString *_Nullable error))completion;
|
||||
|
||||
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
|
||||
forImages:(nullable NSArray<NSString *> *)images
|
||||
progressHandler:(void(^)(NSString *status))progress
|
||||
completion:(void(^)(NSString *_Nullable error))completion;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,875 @@
|
||||
//
|
||||
// FLEXRuntimeExporter.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 3/26/20.
|
||||
// Copyright (c) 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeExporter.h"
|
||||
#import "FLEXSQLiteDatabaseManager.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "FLEXRuntimeController.h"
|
||||
#import "FLEXRuntimeClient.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
#import <sqlite3.h>
|
||||
|
||||
#import "FLEXProtocol.h"
|
||||
#import "FLEXProperty.h"
|
||||
#import "FLEXIvar.h"
|
||||
#import "FLEXMethodBase.h"
|
||||
#import "FLEXMethod.h"
|
||||
#import "FLEXPropertyAttributes.h"
|
||||
|
||||
NSString * const kFREEnableForeignKeys = @"PRAGMA foreign_keys = ON;";
|
||||
|
||||
/// Loaded images
|
||||
NSString * const kFRECreateTableMachOCommand = @"CREATE TABLE MachO( "
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"shortName TEXT, "
|
||||
"imagePath TEXT, "
|
||||
"bundleID TEXT "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertImage = @"INSERT INTO MachO ( "
|
||||
"shortName, imagePath, bundleID "
|
||||
") VALUES ( "
|
||||
"$shortName, $imagePath, $bundleID "
|
||||
");";
|
||||
|
||||
/// Objc classes
|
||||
NSString * const kFRECreateTableClassCommand = @"CREATE TABLE Class( "
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"className TEXT, "
|
||||
"superclass INTEGER, "
|
||||
"instanceSize INTEGER, "
|
||||
"version INTEGER, "
|
||||
"image INTEGER, "
|
||||
|
||||
"FOREIGN KEY(superclass) REFERENCES Class(id), "
|
||||
"FOREIGN KEY(image) REFERENCES MachO(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertClass = @"INSERT INTO Class ( "
|
||||
"className, instanceSize, version, image "
|
||||
") VALUES ( "
|
||||
"$className, $instanceSize, $version, $image "
|
||||
");";
|
||||
|
||||
NSString * const kFREUpdateClassSetSuper = @"UPDATE Class SET superclass = $super WHERE id = $id;";
|
||||
|
||||
/// Unique objc selectors
|
||||
NSString * const kFRECreateTableSelectorCommand = @"CREATE TABLE Selector( "
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
|
||||
"name text NOT NULL UNIQUE "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertSelector = @"INSERT OR IGNORE INTO Selector (name) VALUES ($name);";
|
||||
|
||||
/// Unique objc type encodings
|
||||
NSString * const kFRECreateTableTypeEncodingCommand = @"CREATE TABLE TypeEncoding( "
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
|
||||
"string text NOT NULL UNIQUE, "
|
||||
"size integer "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertTypeEncoding = @"INSERT OR IGNORE INTO TypeEncoding "
|
||||
"(string, size) VALUES ($type, $size);";
|
||||
|
||||
/// Unique objc type signatures
|
||||
NSString * const kFRECreateTableTypeSignatureCommand = @"CREATE TABLE TypeSignature( "
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
|
||||
"string text NOT NULL UNIQUE "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertTypeSignature = @"INSERT OR IGNORE INTO TypeSignature "
|
||||
"(string) VALUES ($type);";
|
||||
|
||||
NSString * const kFRECreateTableMethodSignatureCommand = @"CREATE TABLE MethodSignature( "
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
|
||||
"typeEncoding TEXT, "
|
||||
"argc INTEGER, "
|
||||
"returnType INTEGER, "
|
||||
"frameLength INTEGER, "
|
||||
|
||||
"FOREIGN KEY(returnType) REFERENCES TypeEncoding(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertMethodSignature = @"INSERT INTO MethodSignature ( "
|
||||
"typeEncoding, argc, returnType, frameLength "
|
||||
") VALUES ( "
|
||||
"$typeEncoding, $argc, $returnType, $frameLength "
|
||||
");";
|
||||
|
||||
NSString * const kFRECreateTableMethodCommand = @"CREATE TABLE Method( "
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"sel INTEGER, "
|
||||
"class INTEGER, "
|
||||
"instance INTEGER, " // 0 if class method, 1 if instance method
|
||||
"signature INTEGER, "
|
||||
"image INTEGER, "
|
||||
|
||||
"FOREIGN KEY(sel) REFERENCES Selector(id), "
|
||||
"FOREIGN KEY(class) REFERENCES Class(id), "
|
||||
"FOREIGN KEY(signature) REFERENCES MethodSignature(id), "
|
||||
"FOREIGN KEY(image) REFERENCES MachO(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertMethod = @"INSERT INTO Method ( "
|
||||
"sel, class, instance, signature, image "
|
||||
") VALUES ( "
|
||||
"$sel, $class, $instance, $signature, $image "
|
||||
");";
|
||||
|
||||
NSString * const kFRECreateTablePropertyCommand = @"CREATE TABLE Property( "
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"name TEXT, "
|
||||
"class INTEGER, "
|
||||
"instance INTEGER, " // 0 if class prop, 1 if instance prop
|
||||
"image INTEGER, "
|
||||
"attributes TEXT, "
|
||||
|
||||
"customGetter INTEGER, "
|
||||
"customSetter INTEGER, "
|
||||
|
||||
"type INTEGER, "
|
||||
"ivar TEXT, "
|
||||
"readonly INTEGER, "
|
||||
"copy INTEGER, "
|
||||
"retained INTEGER, "
|
||||
"nonatomic INTEGER, "
|
||||
"dynamic INTEGER, "
|
||||
"weak INTEGER, "
|
||||
"canGC INTEGER, "
|
||||
|
||||
"FOREIGN KEY(class) REFERENCES Class(id), "
|
||||
"FOREIGN KEY(customGetter) REFERENCES Selector(id), "
|
||||
"FOREIGN KEY(customSetter) REFERENCES Selector(id), "
|
||||
"FOREIGN KEY(image) REFERENCES MachO(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertProperty = @"INSERT INTO Property ( "
|
||||
"name, class, instance, attributes, image, "
|
||||
"customGetter, customSetter, type, ivar, readonly, "
|
||||
"copy, retained, nonatomic, dynamic, weak, canGC "
|
||||
") VALUES ( "
|
||||
"$name, $class, $instance, $attributes, $image, "
|
||||
"$customGetter, $customSetter, $type, $ivar, $readonly, "
|
||||
"$copy, $retained, $nonatomic, $dynamic, $weak, $canGC "
|
||||
");";
|
||||
|
||||
NSString * const kFRECreateTableIvarCommand = @"CREATE TABLE Ivar( "
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"name TEXT, "
|
||||
"offset INTEGER, "
|
||||
"type INTEGER, "
|
||||
"class INTEGER, "
|
||||
"image INTEGER, "
|
||||
|
||||
"FOREIGN KEY(type) REFERENCES TypeEncoding(id), "
|
||||
"FOREIGN KEY(class) REFERENCES Class(id), "
|
||||
"FOREIGN KEY(image) REFERENCES MachO(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertIvar = @"INSERT INTO Ivar ( "
|
||||
"name, offset, type, class, image "
|
||||
") VALUES ( "
|
||||
"$name, $offset, $type, $class, $image "
|
||||
");";
|
||||
|
||||
NSString * const kFRECreateTableProtocolCommand = @"CREATE TABLE Protocol( "
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"name TEXT, "
|
||||
"image INTEGER, "
|
||||
|
||||
"FOREIGN KEY(image) REFERENCES MachO(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertProtocol = @"INSERT INTO Protocol "
|
||||
"(name, image) VALUES ($name, $image);";
|
||||
|
||||
NSString * const kFRECreateTableProtocolPropertyCommand = @"CREATE TABLE ProtocolMember( "
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"protocol INTEGER, "
|
||||
"required INTEGER, "
|
||||
"instance INTEGER, " // 0 if class member, 1 if instance member
|
||||
|
||||
// Only of the two below is used
|
||||
"property TEXT, "
|
||||
"method TEXT, "
|
||||
|
||||
"image INTEGER, "
|
||||
|
||||
"FOREIGN KEY(protocol) REFERENCES Protocol(id), "
|
||||
"FOREIGN KEY(image) REFERENCES MachO(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertProtocolMember = @"INSERT INTO ProtocolMember ( "
|
||||
"protocol, required, instance, property, method, image "
|
||||
") VALUES ( "
|
||||
"$protocol, $required, $instance, $property, $method, $image "
|
||||
");";
|
||||
|
||||
/// For protocols conforming to other protocols
|
||||
NSString * const kFRECreateTableProtocolConformanceCommand = @"CREATE TABLE ProtocolConformance( "
|
||||
"protocol INTEGER, "
|
||||
"conformance INTEGER, "
|
||||
|
||||
"FOREIGN KEY(protocol) REFERENCES Protocol(id), "
|
||||
"FOREIGN KEY(conformance) REFERENCES Protocol(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertProtocolConformance = @"INSERT INTO ProtocolConformance "
|
||||
"(protocol, conformance) VALUES ($protocol, $conformance);";
|
||||
|
||||
/// For classes conforming to protocols
|
||||
NSString * const kFRECreateTableClassConformanceCommand = @"CREATE TABLE ClassConformance( "
|
||||
"class INTEGER, "
|
||||
"conformance INTEGER, "
|
||||
|
||||
"FOREIGN KEY(class) REFERENCES Class(id), "
|
||||
"FOREIGN KEY(conformance) REFERENCES Protocol(id) "
|
||||
");";
|
||||
|
||||
NSString * const kFREInsertClassConformance = @"INSERT INTO ClassConformance "
|
||||
"(class, conformance) VALUES ($class, $conformance);";
|
||||
|
||||
@interface FLEXRuntimeExporter ()
|
||||
@property (nonatomic, readonly) FLEXSQLiteDatabaseManager *db;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *loadedShortBundleNames;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *loadedBundlePaths;
|
||||
@property (nonatomic, copy) NSArray<FLEXProtocol *> *protocols;
|
||||
@property (nonatomic, copy) NSArray<Class> *classes;
|
||||
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *bundlePathsToIDs;
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *protocolsToIDs;
|
||||
@property (nonatomic) NSMutableDictionary<Class, NSNumber *> *classesToIDs;
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *typeEncodingsToIDs;
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *methodSignaturesToIDs;
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *selectorsToIDs;
|
||||
@end
|
||||
|
||||
@implementation FLEXRuntimeExporter
|
||||
|
||||
+ (NSString *)tempFilename {
|
||||
NSString *temp = NSTemporaryDirectory();
|
||||
NSString *uuid = [NSUUID.UUID.UUIDString substringToIndex:8];
|
||||
NSString *filename = [NSString stringWithFormat:@"FLEXRuntimeDatabase-%@.db", uuid];
|
||||
return [temp stringByAppendingPathComponent:filename];
|
||||
}
|
||||
|
||||
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
|
||||
progressHandler:(void(^)(NSString *status))progress
|
||||
completion:(void (^)(NSString *))completion {
|
||||
[self createRuntimeDatabaseAtPath:path forImages:nil progressHandler:progress completion:completion];
|
||||
}
|
||||
|
||||
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
|
||||
forImages:(NSArray<NSString *> *)images
|
||||
progressHandler:(void(^)(NSString *status))progress
|
||||
completion:(void(^)(NSString *_Nullable error))completion {
|
||||
__typeof(completion) callback = ^(NSString *error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completion(error);
|
||||
});
|
||||
};
|
||||
|
||||
// This must be called on the main thread first
|
||||
if (NSThread.isMainThread) {
|
||||
[FLEXRuntimeClient initializeWebKitLegacy];
|
||||
} else {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[FLEXRuntimeClient initializeWebKitLegacy];
|
||||
});
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
NSError *error = nil;
|
||||
NSString *errorMessage = nil;
|
||||
|
||||
// Get unused temp filename, remove existing database if any
|
||||
NSString *tempPath = [self tempFilename];
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
|
||||
[NSFileManager.defaultManager removeItemAtPath:tempPath error:&error];
|
||||
if (error) {
|
||||
callback(error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to create and populate the database, abort if we fail
|
||||
FLEXRuntimeExporter *exporter = [self new];
|
||||
exporter.loadedBundlePaths = images;
|
||||
if (![exporter createAndPopulateDatabaseAtPath:tempPath
|
||||
progressHandler:progress
|
||||
error:&errorMessage]) {
|
||||
// Remove temp database if it was not moved
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
|
||||
[NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
|
||||
}
|
||||
|
||||
callback(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old database at given path
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:path]) {
|
||||
[NSFileManager.defaultManager removeItemAtPath:path error:&error];
|
||||
if (error) {
|
||||
callback(error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Move new database to desired path
|
||||
[NSFileManager.defaultManager moveItemAtPath:tempPath toPath:path error:&error];
|
||||
if (error) {
|
||||
callback(error.localizedDescription);
|
||||
}
|
||||
|
||||
// Remove temp database if it was not moved
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
|
||||
[NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
|
||||
}
|
||||
|
||||
callback(nil);
|
||||
});
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_bundlePathsToIDs = [NSMutableDictionary new];
|
||||
_protocolsToIDs = [NSMutableDictionary new];
|
||||
_classesToIDs = [NSMutableDictionary new];
|
||||
_typeEncodingsToIDs = [NSMutableDictionary new];
|
||||
_methodSignaturesToIDs = [NSMutableDictionary new];
|
||||
_selectorsToIDs = [NSMutableDictionary new];
|
||||
|
||||
_bundlePathsToIDs[NSNull.null] = (id)NSNull.null;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)createAndPopulateDatabaseAtPath:(NSString *)path
|
||||
progressHandler:(void(^)(NSString *status))step
|
||||
error:(NSString **)error {
|
||||
_db = [FLEXSQLiteDatabaseManager managerForDatabase:path];
|
||||
|
||||
[self loadMetadata:step];
|
||||
|
||||
if ([self createTables] && [self addImages:step] && [self addProtocols:step] &&
|
||||
[self addClasses:step] && [self setSuperclasses:step] &&
|
||||
[self addProtocolConformances:step] && [self addClassConformances:step] &&
|
||||
[self addIvars:step] && [self addMethods:step] && [self addProperties:step]) {
|
||||
_db = nil; // Close the database
|
||||
return YES;
|
||||
}
|
||||
|
||||
*error = self.db.lastResult.message;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)loadMetadata:(void(^)(NSString *status))progress {
|
||||
progress(@"Loading metadata…");
|
||||
|
||||
FLEXRuntimeClient *runtime = FLEXRuntimeClient.runtime;
|
||||
|
||||
// Only load metadata for the existing paths if any
|
||||
if (self.loadedBundlePaths) {
|
||||
// Images
|
||||
self.loadedShortBundleNames = [self.loadedBundlePaths flex_mapped:^id(NSString *path, NSUInteger idx) {
|
||||
return [runtime shortNameForImageName:path];
|
||||
}];
|
||||
|
||||
// Classes
|
||||
self.classes = [[runtime classesForToken:FLEXSearchToken.any
|
||||
inBundles:self.loadedBundlePaths.mutableCopy
|
||||
] flex_mapped:^id(NSString *cls, NSUInteger idx) {
|
||||
return NSClassFromString(cls);
|
||||
}];
|
||||
} else {
|
||||
// Images
|
||||
self.loadedShortBundleNames = runtime.imageDisplayNames;
|
||||
self.loadedBundlePaths = [self.loadedShortBundleNames flex_mapped:^id(NSString *name, NSUInteger idx) {
|
||||
return [runtime imageNameForShortName:name];
|
||||
}];
|
||||
|
||||
// Classes
|
||||
self.classes = [runtime copySafeClassList];
|
||||
}
|
||||
|
||||
// ...except protocols, because there's not a lot of them
|
||||
// and there's no way load the protocols for a given image
|
||||
self.protocols = [[runtime copyProtocolList] flex_mapped:^id(Protocol *proto, NSUInteger idx) {
|
||||
return [FLEXProtocol protocol:proto];
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)createTables {
|
||||
NSArray<NSString *> *commands = @[
|
||||
kFREEnableForeignKeys,
|
||||
kFRECreateTableMachOCommand,
|
||||
kFRECreateTableClassCommand,
|
||||
kFRECreateTableSelectorCommand,
|
||||
kFRECreateTableTypeEncodingCommand,
|
||||
kFRECreateTableTypeSignatureCommand,
|
||||
kFRECreateTableMethodSignatureCommand,
|
||||
kFRECreateTableMethodCommand,
|
||||
kFRECreateTablePropertyCommand,
|
||||
kFRECreateTableIvarCommand,
|
||||
kFRECreateTableProtocolCommand,
|
||||
kFRECreateTableProtocolPropertyCommand,
|
||||
kFRECreateTableProtocolConformanceCommand,
|
||||
kFRECreateTableClassConformanceCommand
|
||||
];
|
||||
|
||||
for (NSString *command in commands) {
|
||||
if (![self.db executeStatement:command]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addImages:(void(^)(NSString *status))progress {
|
||||
progress(@"Adding loaded images…");
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSArray *shortNames = self.loadedShortBundleNames;
|
||||
NSArray *fullPaths = self.loadedBundlePaths;
|
||||
NSParameterAssert(shortNames.count == fullPaths.count);
|
||||
|
||||
NSInteger count = shortNames.count;
|
||||
for (NSInteger i = 0; i < count; i++) {
|
||||
// Grab bundle ID
|
||||
NSString *bundleID = [NSBundle
|
||||
bundleWithPath:fullPaths[i]
|
||||
].bundleIdentifier;
|
||||
|
||||
[database executeStatement:kFREInsertImage arguments:@{
|
||||
@"$shortName": shortNames[i],
|
||||
@"$imagePath": fullPaths[i],
|
||||
@"$bundleID": bundleID ?: NSNull.null
|
||||
}];
|
||||
|
||||
if (database.lastResult.isError) {
|
||||
return NO;
|
||||
} else {
|
||||
self.bundlePathsToIDs[fullPaths[i]] = @(database.lastRowID);
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
NS_INLINE BOOL FREInsertProtocolMember(FLEXSQLiteDatabaseManager *db,
|
||||
id proto, id required, id instance,
|
||||
id prop, id methSel, id image) {
|
||||
return ![db executeStatement:kFREInsertProtocolMember arguments:@{
|
||||
@"$protocol": proto,
|
||||
@"$required": required,
|
||||
@"$instance": instance ?: NSNull.null,
|
||||
@"$property": prop ?: NSNull.null,
|
||||
@"$method": methSel ?: NSNull.null,
|
||||
@"$image": image
|
||||
}].isError;
|
||||
}
|
||||
|
||||
- (BOOL)addProtocols:(void(^)(NSString *status))progress {
|
||||
progress([NSString stringWithFormat:@"Adding %@ protocols…", @(self.protocols.count)]);
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSDictionary *imageIDs = self.bundlePathsToIDs;
|
||||
|
||||
for (FLEXProtocol *proto in self.protocols) {
|
||||
id imagePath = proto.imagePath ?: NSNull.null;
|
||||
NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
|
||||
NSNumber *pid = nil;
|
||||
|
||||
// Insert protocol
|
||||
BOOL failed = [database executeStatement:kFREInsertProtocol arguments:@{
|
||||
@"$name": proto.name, @"$image": image
|
||||
}].isError;
|
||||
|
||||
// Cache rowid
|
||||
if (failed) {
|
||||
return NO;
|
||||
} else {
|
||||
self.protocolsToIDs[proto.name] = pid = @(database.lastRowID);
|
||||
}
|
||||
|
||||
// Insert its members //
|
||||
|
||||
// Required methods
|
||||
for (FLEXMethodDescription *method in proto.requiredMethods) {
|
||||
NSString *selector = NSStringFromSelector(method.selector);
|
||||
if (!FREInsertProtocolMember(database, pid, @YES, method.instance, nil, selector, image)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
// Optional methods
|
||||
for (FLEXMethodDescription *method in proto.optionalMethods) {
|
||||
NSString *selector = NSStringFromSelector(method.selector);
|
||||
if (!FREInsertProtocolMember(database, pid, @NO, method.instance, nil, selector, image)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (@available(iOS 10, *)) {
|
||||
// Required properties
|
||||
for (FLEXProperty *property in proto.requiredProperties) {
|
||||
BOOL success = FREInsertProtocolMember(
|
||||
database, pid, @YES, @(property.isClassProperty), property.name, NSNull.null, image
|
||||
);
|
||||
|
||||
if (!success) return NO;
|
||||
}
|
||||
// Optional properties
|
||||
for (FLEXProperty *property in proto.optionalProperties) {
|
||||
BOOL success = FREInsertProtocolMember(
|
||||
database, pid, @NO, @(property.isClassProperty), property.name, NSNull.null, image
|
||||
);
|
||||
|
||||
if (!success) return NO;
|
||||
}
|
||||
} else {
|
||||
// Just... properties.
|
||||
for (FLEXProperty *property in proto.properties) {
|
||||
BOOL success = FREInsertProtocolMember(
|
||||
database, pid, nil, @(property.isClassProperty), property.name, NSNull.null, image
|
||||
);
|
||||
|
||||
if (!success) return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addProtocolConformances:(void(^)(NSString *status))progress {
|
||||
progress(@"Adding protocol-to-protocol conformances…");
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSDictionary *protocolIDs = self.protocolsToIDs;
|
||||
|
||||
for (FLEXProtocol *proto in self.protocols) {
|
||||
id protoID = protocolIDs[proto.name];
|
||||
|
||||
for (FLEXProtocol *conform in proto.protocols) {
|
||||
BOOL failed = [database executeStatement:kFREInsertProtocolConformance arguments:@{
|
||||
@"$protocol": protoID,
|
||||
@"$conformance": protocolIDs[conform.name]
|
||||
}].isError;
|
||||
|
||||
if (failed) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addClasses:(void(^)(NSString *status))progress {
|
||||
progress([NSString stringWithFormat:@"Adding %@ classes…", @(self.classes.count)]);
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSDictionary *imageIDs = self.bundlePathsToIDs;
|
||||
|
||||
for (Class cls in self.classes) {
|
||||
const char *imageName = class_getImageName(cls);
|
||||
id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
|
||||
image = image ?: NSNull.null;
|
||||
|
||||
BOOL failed = [database executeStatement:kFREInsertClass arguments:@{
|
||||
@"$className": NSStringFromClass(cls),
|
||||
@"$instanceSize": @(class_getInstanceSize(cls)),
|
||||
@"$version": @(class_getVersion(cls)),
|
||||
@"$image": image
|
||||
}].isError;
|
||||
|
||||
if (failed) {
|
||||
return NO;
|
||||
} else {
|
||||
self.classesToIDs[(id)cls] = @(database.lastRowID);
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)setSuperclasses:(void(^)(NSString *status))progress {
|
||||
progress(@"Setting superclasses…");
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
|
||||
for (Class cls in self.classes) {
|
||||
// Grab superclass ID
|
||||
Class superclass = class_getSuperclass(cls);
|
||||
NSNumber *superclassID = _classesToIDs[class_getSuperclass(cls)];
|
||||
|
||||
// ... or add the superclass and cache its ID if the
|
||||
// superclass does not reside in the target image(s)
|
||||
if (!superclassID) {
|
||||
NSDictionary *args = @{ @"$className": NSStringFromClass(superclass) };
|
||||
BOOL failed = [database executeStatement:kFREInsertClass arguments:args].isError;
|
||||
if (failed) { return NO; }
|
||||
|
||||
_classesToIDs[(id)superclass] = superclassID = @(database.lastRowID);
|
||||
}
|
||||
|
||||
if (superclass) {
|
||||
BOOL failed = [database executeStatement:kFREUpdateClassSetSuper arguments:@{
|
||||
@"$super": superclassID, @"$id": _classesToIDs[cls]
|
||||
}].isError;
|
||||
|
||||
if (failed) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addClassConformances:(void(^)(NSString *status))progress {
|
||||
progress(@"Adding class-to-protocol conformances…");
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSDictionary *protocolIDs = self.protocolsToIDs;
|
||||
NSDictionary *classIDs = self.classesToIDs;
|
||||
|
||||
for (Class cls in self.classes) {
|
||||
id classID = classIDs[(id)cls];
|
||||
|
||||
for (FLEXProtocol *conform in FLEXGetConformedProtocols(cls)) {
|
||||
BOOL failed = [database executeStatement:kFREInsertClassConformance arguments:@{
|
||||
@"$class": classID,
|
||||
@"$conformance": protocolIDs[conform.name]
|
||||
}].isError;
|
||||
|
||||
if (failed) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addIvars:(void(^)(NSString *status))progress {
|
||||
progress(@"Adding ivars…");
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSDictionary *imageIDs = self.bundlePathsToIDs;
|
||||
|
||||
for (Class cls in self.classes) {
|
||||
for (FLEXIvar *ivar in FLEXGetAllIvars(cls)) {
|
||||
// Insert type first
|
||||
if (![self addTypeEncoding:ivar.typeEncoding size:ivar.size]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
id imagePath = ivar.imagePath ?: NSNull.null;
|
||||
NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
|
||||
|
||||
BOOL failed = [database executeStatement:kFREInsertIvar arguments:@{
|
||||
@"$name": ivar.name,
|
||||
@"$offset": @(ivar.offset),
|
||||
@"$type": _typeEncodingsToIDs[ivar.typeEncoding],
|
||||
@"$class": _classesToIDs[cls],
|
||||
@"$image": image
|
||||
}].isError;
|
||||
|
||||
if (failed) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addMethods:(void(^)(NSString *status))progress {
|
||||
progress(@"Adding methods…");
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSDictionary *imageIDs = self.bundlePathsToIDs;
|
||||
|
||||
// Loop over all classes
|
||||
for (Class cls in self.classes) {
|
||||
NSNumber *classID = _classesToIDs[(id)cls];
|
||||
const char *imageName = class_getImageName(cls);
|
||||
id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
|
||||
image = image ?: NSNull.null;
|
||||
|
||||
// Block used to process each message
|
||||
BOOL (^insert)(FLEXMethod *, NSNumber *) = ^BOOL(FLEXMethod *method, NSNumber *instance) {
|
||||
// Insert selector and signature first
|
||||
if (![self addSelector:method.selectorString]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self addMethodSignature:method]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return ![database executeStatement:kFREInsertMethod arguments:@{
|
||||
@"$sel": self->_selectorsToIDs[method.selectorString],
|
||||
@"$class": classID,
|
||||
@"$instance": instance,
|
||||
@"$signature": self->_methodSignaturesToIDs[method.signatureString],
|
||||
@"$image": image
|
||||
}].isError;
|
||||
};
|
||||
|
||||
// Loop over all instance and class methods of that class //
|
||||
|
||||
for (FLEXMethod *method in FLEXGetAllMethods(cls, YES)) {
|
||||
if (!insert(method, @YES)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
for (FLEXMethod *method in FLEXGetAllMethods(object_getClass(cls), NO)) {
|
||||
if (!insert(method, @NO)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addProperties:(void(^)(NSString *status))progress {
|
||||
progress(@"Adding properties…");
|
||||
|
||||
FLEXSQLiteDatabaseManager *database = self.db;
|
||||
NSDictionary *imageIDs = self.bundlePathsToIDs;
|
||||
|
||||
// Loop over all classes
|
||||
for (Class cls in self.classes) {
|
||||
NSNumber *classID = _classesToIDs[(id)cls];
|
||||
|
||||
// Block used to process each message
|
||||
BOOL (^insert)(FLEXProperty *, NSNumber *) = ^BOOL(FLEXProperty *property, NSNumber *instance) {
|
||||
FLEXPropertyAttributes *attrs = property.attributes;
|
||||
NSString *customGetter = attrs.customGetterString;
|
||||
NSString *customSetter = attrs.customSetterString;
|
||||
|
||||
// Insert selectors first
|
||||
if (customGetter) {
|
||||
if (![self addSelector:customGetter]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
if (customSetter) {
|
||||
if (![self addSelector:customSetter]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert type encoding first
|
||||
NSInteger size = [FLEXTypeEncodingParser
|
||||
sizeForTypeEncoding:attrs.typeEncoding alignment:nil
|
||||
];
|
||||
if (![self addTypeEncoding:attrs.typeEncoding size:size]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
id imagePath = property.imagePath ?: NSNull.null;
|
||||
id image = imageIDs[imagePath] ?: NSNull.null;
|
||||
return ![database executeStatement:kFREInsertProperty arguments:@{
|
||||
@"$name": property.name,
|
||||
@"$class": classID,
|
||||
@"$instance": instance,
|
||||
@"$image": image,
|
||||
@"$attributes": attrs.string,
|
||||
|
||||
@"$customGetter": self->_selectorsToIDs[customGetter] ?: NSNull.null,
|
||||
@"$customSetter": self->_selectorsToIDs[customSetter] ?: NSNull.null,
|
||||
|
||||
@"$type": self->_typeEncodingsToIDs[attrs.typeEncoding] ?: NSNull.null,
|
||||
@"$ivar": attrs.backingIvar ?: NSNull.null,
|
||||
@"$readonly": @(attrs.isReadOnly),
|
||||
@"$copy": @(attrs.isCopy),
|
||||
@"$retained": @(attrs.isRetained),
|
||||
@"$nonatomic": @(attrs.isNonatomic),
|
||||
@"$dynamic": @(attrs.isDynamic),
|
||||
@"$weak": @(attrs.isWeak),
|
||||
@"$canGC": @(attrs.isGarbageCollectable),
|
||||
}].isError;
|
||||
};
|
||||
|
||||
// Loop over all instance and class methods of that class //
|
||||
|
||||
for (FLEXProperty *property in FLEXGetAllProperties(cls)) {
|
||||
if (!insert(property, @YES)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
for (FLEXProperty *property in FLEXGetAllProperties(object_getClass(cls))) {
|
||||
if (!insert(property, @NO)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)addSelector:(NSString *)sel {
|
||||
return [self executeInsert:kFREInsertSelector args:@{
|
||||
@"$name": sel
|
||||
} key:sel cacheResult:_selectorsToIDs];
|
||||
}
|
||||
|
||||
- (BOOL)addTypeEncoding:(NSString *)type size:(NSInteger)size {
|
||||
return [self executeInsert:kFREInsertTypeEncoding args:@{
|
||||
@"$type": type, @"$size": @(size)
|
||||
} key:type cacheResult:_typeEncodingsToIDs];
|
||||
}
|
||||
|
||||
- (BOOL)addMethodSignature:(FLEXMethod *)method {
|
||||
NSString *signature = method.signatureString;
|
||||
NSString *returnType = @((char *)method.returnType);
|
||||
|
||||
// Insert return type first
|
||||
if (![self addTypeEncoding:returnType size:method.returnSize]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [self executeInsert:kFREInsertMethodSignature args:@{
|
||||
@"$typeEncoding": signature,
|
||||
@"$returnType": _typeEncodingsToIDs[returnType],
|
||||
@"$argc": @(method.numberOfArguments),
|
||||
@"$frameLength": @(method.signature.frameLength)
|
||||
} key:signature cacheResult:_methodSignaturesToIDs];
|
||||
}
|
||||
|
||||
- (BOOL)executeInsert:(NSString *)statement
|
||||
args:(NSDictionary *)args
|
||||
key:(NSString *)cacheKey
|
||||
cacheResult:(NSMutableDictionary<NSString *, NSNumber *> *)rowids {
|
||||
// Check if already inserted
|
||||
if (rowids[cacheKey]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Insert
|
||||
FLEXSQLiteDatabaseManager *database = _db;
|
||||
[database executeStatement:statement arguments:args];
|
||||
|
||||
if (database.lastResult.isError) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Cache rowid
|
||||
rowids[cacheKey] = @(database.lastRowID);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -10,9 +10,9 @@
|
||||
#import "FLEXRuntimeKeyPathTokenizer.h"
|
||||
#import "FLEXRuntimeController.h"
|
||||
#import "NSString+FLEX.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "UITextField+Range.h"
|
||||
#import "NSTimer+Blocks.h"
|
||||
#import "NSTimer+FLEX.h"
|
||||
#import "FLEXTableView.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeKeyPath.h"
|
||||
#include <dlfcn.h>
|
||||
#import "FLEXRuntimeClient.h"
|
||||
|
||||
@interface FLEXRuntimeKeyPath () {
|
||||
NSString *flex_description;
|
||||
@@ -49,29 +49,12 @@
|
||||
keyPath->flex_description = keyPathString;
|
||||
|
||||
if (bundle.isAny && cls.isAny && method.isAny) {
|
||||
[self initializeWebKitLegacy];
|
||||
[FLEXRuntimeClient initializeWebKitLegacy];
|
||||
}
|
||||
|
||||
return keyPath;
|
||||
}
|
||||
|
||||
+ (void)initializeWebKitLegacy {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
void *handle = dlopen(
|
||||
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
|
||||
RTLD_LAZY
|
||||
);
|
||||
void (*WebKitInitialize)() = dlsym(handle, "WebKitInitialize");
|
||||
if (WebKitInitialize) {
|
||||
NSAssert(NSThread.isMainThread,
|
||||
@"WebKitInitialize can only be called on the main thread"
|
||||
);
|
||||
WebKitInitialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return flex_description;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#ifndef ActivityStreamSPI_h
|
||||
#define ActivityStreamSPI_h
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include <sys/time.h>
|
||||
// #include <xpc/xpc.h>
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
#pragma mark - Initialization
|
||||
|
||||
+ (void)load {
|
||||
// User must opt-into disabling os_log
|
||||
if (!NSUserDefaults.standardUserDefaults.flex_disableOSLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Thanks to @Ram4096 on GitHub for telling me that
|
||||
// os_log is conditionally enabled by the SDK version
|
||||
void *addr = __builtin_return_address(0);
|
||||
@@ -51,28 +56,28 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
FLEXDidHookNSLog = rebind_symbols((struct rebinding[1]) {{
|
||||
FLEXDidHookNSLog = flex_rebind_symbols((struct rebinding[1]) {{
|
||||
"os_log_shim_enabled",
|
||||
(void *)my_os_log_shim_enabled,
|
||||
(void **)&orig_os_log_shim_enabled
|
||||
}}, 1) == 0;
|
||||
|
||||
|
||||
if (FLEXDidHookNSLog && orig_os_log_shim_enabled != nil) {
|
||||
// Check if our rebinding worked
|
||||
FLEXNSLogHookWorks = my_os_log_shim_enabled(addr) == NO;
|
||||
}
|
||||
|
||||
|
||||
// So, just because we rebind the lazily loaded symbol for
|
||||
// this function doesn't mean it's even going to be used.
|
||||
// While it seems to be sufficient for the simulator, for
|
||||
// whatever reason it is not sufficient on-device. We need
|
||||
// to actually hook the function with something like Substrate.
|
||||
|
||||
|
||||
// Check if we have substrate, and if so use that instead
|
||||
void *handle = dlopen("/usr/lib/libsubstrate.dylib", RTLD_LAZY);
|
||||
if (handle) {
|
||||
MSHookFunction = dlsym(handle, "MSHookFunction");
|
||||
|
||||
|
||||
if (MSHookFunction) {
|
||||
// Set the hook and check if it worked
|
||||
void *unused;
|
||||
@@ -91,16 +96,16 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
self.showSearchBarInitially = NO;
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) {
|
||||
__strong __typeof(weakSelf) self = weakSelf;
|
||||
[self handleUpdateWithNewMessages:newMessages];
|
||||
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf handleUpdateWithNewMessages:newMessages];
|
||||
};
|
||||
|
||||
|
||||
if (FLEXOSLogAvailable() && !FLEXNSLogHookWorks) {
|
||||
_logController = [FLEXOSLogController withUpdateHandler:logHandler];
|
||||
} else {
|
||||
@@ -108,10 +113,10 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
}
|
||||
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.title = @"Loading...";
|
||||
|
||||
self.title = @"Waiting for Logs...";
|
||||
|
||||
// Toolbar buttons //
|
||||
|
||||
|
||||
UIBarButtonItem *scrollDown = [UIBarButtonItem
|
||||
itemWithImage:FLEXResources.scrollToBottomIcon
|
||||
target:self
|
||||
@@ -122,12 +127,8 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
target:self
|
||||
action:@selector(showLogSettings)
|
||||
];
|
||||
|
||||
if (FLEXOSLogAvailable() && !FLEXNSLogHookWorks) {
|
||||
[self addToolbarItems:@[scrollDown, settings]];
|
||||
} else {
|
||||
[self addToolbarItems:@[scrollDown]];
|
||||
}
|
||||
|
||||
[self addToolbarItems:@[scrollDown, settings]];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
@@ -137,26 +138,30 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
}
|
||||
|
||||
- (NSArray<FLEXTableViewSection *> *)makeSections {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
_logMessages = [FLEXMutableListSection list:@[]
|
||||
cellConfiguration:^(FLEXSystemLogCell *cell, FLEXSystemLogMessage *message, NSInteger row) {
|
||||
cell.logMessage = message;
|
||||
cell.highlightedText = self.filterText;
|
||||
|
||||
if (row % 2 == 0) {
|
||||
cell.backgroundColor = FLEXColor.primaryBackgroundColor;
|
||||
} else {
|
||||
cell.backgroundColor = FLEXColor.secondaryBackgroundColor;
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
cell.logMessage = message;
|
||||
cell.highlightedText = strongSelf.filterText;
|
||||
|
||||
if (row % 2 == 0) {
|
||||
cell.backgroundColor = FLEXColor.primaryBackgroundColor;
|
||||
} else {
|
||||
cell.backgroundColor = FLEXColor.secondaryBackgroundColor;
|
||||
}
|
||||
}
|
||||
} filterMatcher:^BOOL(NSString *filterText, FLEXSystemLogMessage *message) {
|
||||
NSString *displayedText = [FLEXSystemLogCell displayedTextForLogMessage:message];
|
||||
return [displayedText localizedCaseInsensitiveContainsString:filterText];
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
self.logMessages.cellRegistrationMapping = @{
|
||||
kFLEXSystemLogCellIdentifier : [FLEXSystemLogCell class]
|
||||
};
|
||||
|
||||
|
||||
return @[self.logMessages];
|
||||
}
|
||||
|
||||
@@ -191,17 +196,35 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
}
|
||||
|
||||
- (void)showLogSettings {
|
||||
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
||||
BOOL disableOSLog = defaults.flex_disableOSLog;
|
||||
BOOL persistent = defaults.flex_cacheOSLogMessages;
|
||||
|
||||
NSString *aslToggle = disableOSLog ? @"Enable os_log (default)" : @"Disable os_log";
|
||||
NSString *persistence = persistent ? @"Disable persistent logging" : @"Enable persistent logging";
|
||||
|
||||
NSString *title = @"System Log Settings";
|
||||
NSString *body = @"In iOS 10 and up, ASL has been replaced by os_log. "
|
||||
"The os_log API is much more limited. Below, you can opt-into the old behavior "
|
||||
"if you want cleaner, more reliable logs within FLEX, but this will break "
|
||||
"anything that expects os_log to be working, such as Console.app. "
|
||||
"This setting requires the app to restart to take effect. \n\n"
|
||||
|
||||
"To get as close to the old behavior as possible with os_log enabled, logs must "
|
||||
"be collected manually at launch and stored. This setting has no effect "
|
||||
"on iOS 9 and below, or if os_log is disabled. "
|
||||
"You should only enable persistent logging when you need it.";
|
||||
|
||||
FLEXOSLogController *logController = (FLEXOSLogController *)self.logController;
|
||||
BOOL persistent = NSUserDefaults.standardUserDefaults.flex_cacheOSLogMessages;
|
||||
NSString *toggle = persistent ? @"Disable" : @"Enable";
|
||||
NSString *title = [@"Persistent logging: " stringByAppendingString:persistent ? @"ON" : @"OFF"];
|
||||
NSString *body = @"In iOS 10 and up, ASL is gone. The OS Log API is much more limited. "
|
||||
"To get as close to the old behavior as possible, logs must be collected manually at launch and stored.\n\n"
|
||||
"Turn this feature on only when you need it.";
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(title).message(body).button(toggle).handler(^(NSArray<NSString *> *strings) {
|
||||
NSUserDefaults.standardUserDefaults.flex_cacheOSLogMessages = !persistent;
|
||||
make.title(title).message(body);
|
||||
make.button(aslToggle).destructiveStyle().handler(^(NSArray<NSString *> *strings) {
|
||||
[defaults toggleBoolForKey:kFLEXDefaultsDisableOSLogForceASLKey];
|
||||
});
|
||||
|
||||
make.button(persistence).handler(^(NSArray<NSString *> *strings) {
|
||||
[defaults toggleBoolForKey:kFLEXDefaultsiOSPersistentOSLogKey];
|
||||
logController.persistent = !persistent;
|
||||
[logController.messages addObjectsFromArray:self.logMessages.list];
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#import "FLEXExplorerViewController.h"
|
||||
#import "FLEXNetworkMITMViewController.h"
|
||||
#import "FLEXKeyboardHelpViewController.h"
|
||||
#import "FLEXFileBrowserTableViewController.h"
|
||||
#import "FLEXFileBrowserController.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
@interface FLEXManager (ExtensibilityPrivate)
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description {
|
||||
#if TARGET_OS_SIMULATOR
|
||||
[FLEXKeyboardShortcutManager.sharedManager registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description];
|
||||
[FLEXKeyboardShortcutManager.sharedManager registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description allowOverride:YES];
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -81,37 +81,47 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Shortcuts Defaults
|
||||
|
||||
- (void)registerDefaultSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description {
|
||||
#if TARGET_OS_SIMULATOR
|
||||
// Don't allow override to avoid changing keys registered by the app
|
||||
[FLEXKeyboardShortcutManager.sharedManager registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description allowOverride:NO];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)registerDefaultSimulatorShortcuts {
|
||||
[self registerSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
|
||||
[self toggleExplorer];
|
||||
} description:@"Toggle FLEX toolbar"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleMenuTool];
|
||||
} description:@"Toggle FLEX globals menu"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleViewsTool];
|
||||
} description:@"Toggle view hierarchy menu"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleSelectTool];
|
||||
} description:@"Toggle select tool"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleMoveTool];
|
||||
} description:@"Toggle move tool"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXNetworkMITMViewController class]];
|
||||
} description:@"Toggle network history view"];
|
||||
|
||||
// 't' is for testing: quickly present an object explorer for debugging
|
||||
[self registerSimulatorShortcutWithKey:@"t" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"t" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
|
||||
[self.explorerViewController toggleToolWithViewControllerProvider:^UINavigationController *{
|
||||
@@ -121,25 +131,25 @@
|
||||
} completion:nil];
|
||||
} description:@"Present an object explorer for debugging"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
|
||||
if (self.isHidden || ![self.explorerViewController handleDownArrowKeyPressed]) {
|
||||
[self tryScrollDown];
|
||||
}
|
||||
} description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
|
||||
if (self.isHidden || ![self.explorerViewController handleUpArrowKeyPressed]) {
|
||||
[self tryScrollUp];
|
||||
}
|
||||
} description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
|
||||
if (!self.isHidden) {
|
||||
[self.explorerViewController handleRightArrowKeyPressed];
|
||||
}
|
||||
} description:@"Move selected view right"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
|
||||
if (self.isHidden) {
|
||||
[self tryGoBack];
|
||||
} else {
|
||||
@@ -147,16 +157,16 @@
|
||||
}
|
||||
} description:@"Move selected view left"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
|
||||
} description:@"Toggle (this) help menu"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
|
||||
[[self.topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
|
||||
} description:@"End editing text\n\t\tDismiss top view controller"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXFileBrowserTableViewController class]];
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXFileBrowserController class]];
|
||||
} description:@"Toggle file browser menu"];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,21 +11,25 @@
|
||||
#import "FLEXNetworkObserver.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "NSUserDefaults+FLEX.h"
|
||||
|
||||
@implementation FLEXManager (Networking)
|
||||
|
||||
+ (void)load {
|
||||
// Register array/dictionary viewer for JSON responses
|
||||
[self.sharedManager setCustomViewerForContentType:@"application/json"
|
||||
viewControllerFutureBlock:^UIViewController *(NSData *data) {
|
||||
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
if (jsonObject) {
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:jsonObject];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
];
|
||||
if (NSUserDefaults.standardUserDefaults.flex_registerDictionaryJSONViewerOnLaunch) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Register array/dictionary viewer for JSON responses
|
||||
[self.sharedManager setCustomViewerForContentType:@"application/json"
|
||||
viewControllerFutureBlock:^UIViewController *(NSData *data) {
|
||||
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
if (jsonObject) {
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:jsonObject];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isNetworkDebuggingEnabled {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#import "FLEXExplorerViewController.h"
|
||||
#import "FLEXWindow.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import "FLEXFileBrowserTableViewController.h"
|
||||
#import "FLEXFileBrowserController.h"
|
||||
|
||||
@interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
#import "FLEXNetworkTransaction.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXNetworkObserver.h"
|
||||
#import "FLEXNetworkTransactionTableViewCell.h"
|
||||
#import "FLEXNetworkTransactionDetailTableViewController.h"
|
||||
#import "FLEXNetworkTransactionCell.h"
|
||||
#import "FLEXNetworkTransactionDetailController.h"
|
||||
#import "FLEXNetworkSettingsController.h"
|
||||
#import "FLEXGlobalsViewController.h"
|
||||
#import "UIBarButtonItem+FLEX.h"
|
||||
@@ -61,11 +61,11 @@
|
||||
]];
|
||||
|
||||
[self.tableView
|
||||
registerClass:[FLEXNetworkTransactionTableViewCell class]
|
||||
registerClass:[FLEXNetworkTransactionCell class]
|
||||
forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier
|
||||
];
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.tableView.rowHeight = FLEXNetworkTransactionTableViewCell.preferredCellHeight;
|
||||
self.tableView.rowHeight = FLEXNetworkTransactionCell.preferredCellHeight;
|
||||
|
||||
[self registerForNotifications];
|
||||
[self updateTransactions];
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
#pragma mark Button Actions
|
||||
|
||||
- (void)settingsButtonTapped:(id)sender {
|
||||
- (void)settingsButtonTapped:(UIBarButtonItem *)sender {
|
||||
UIViewController *settings = [FLEXNetworkSettingsController new];
|
||||
settings.navigationItem.rightBarButtonItem = FLEXBarButtonItemSystem(
|
||||
Done, self, @selector(settingsViewControllerDoneTapped:)
|
||||
@@ -121,7 +121,7 @@
|
||||
[self presentViewController:nav animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)trashButtonTapped:(id)sender {
|
||||
- (void)trashButtonTapped:(UIBarButtonItem *)sender {
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
make.title(@"Clear All Recorded Requests?");
|
||||
make.message(@"This cannot be undone.");
|
||||
@@ -130,7 +130,7 @@
|
||||
make.button(@"Clear All").destructiveStyle().handler(^(NSArray *strings) {
|
||||
[FLEXNetworkRecorder.defaultRecorder clearRecordedActivity];
|
||||
});
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
- (void)settingsViewControllerDoneTapped:(id)sender {
|
||||
@@ -311,7 +311,7 @@
|
||||
FLEXNetworkTransaction *transaction = notification.userInfo[kFLEXNetworkRecorderUserInfoTransactionKey];
|
||||
|
||||
// Update both the main table view and search table view if needed.
|
||||
for (FLEXNetworkTransactionTableViewCell *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.
|
||||
@@ -352,7 +352,7 @@
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
FLEXNetworkTransactionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath];
|
||||
FLEXNetworkTransactionCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier 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,7 +367,7 @@
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
FLEXNetworkTransactionDetailTableViewController *detailViewController = [FLEXNetworkTransactionDetailTableViewController new];
|
||||
FLEXNetworkTransactionDetailController *detailViewController = [FLEXNetworkTransactionDetailController new];
|
||||
detailViewController.transaction = [self transactionAtIndexPath:indexPath];
|
||||
[self.navigationController pushViewController:detailViewController animated:YES];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXTableView.h"
|
||||
#import "FLEXColor.h"
|
||||
#import "NSUserDefaults+FLEX.h"
|
||||
|
||||
@interface FLEXNetworkSettingsController () <UIActionSheetDelegate>
|
||||
@property (nonatomic) float cacheLimitValue;
|
||||
@@ -18,6 +19,7 @@
|
||||
|
||||
@property (nonatomic, readonly) UISwitch *observerSwitch;
|
||||
@property (nonatomic, readonly) UISwitch *cacheMediaSwitch;
|
||||
@property (nonatomic, readonly) UISwitch *jsonViewerSwitch;
|
||||
@property (nonatomic, readonly) UISlider *cacheLimitSlider;
|
||||
@property (nonatomic) UILabel *cacheLimitLabel;
|
||||
|
||||
@@ -32,8 +34,11 @@
|
||||
[self disableToolbar];
|
||||
self.hostBlacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist.mutableCopy;
|
||||
|
||||
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
||||
|
||||
_observerSwitch = [UISwitch new];
|
||||
_cacheMediaSwitch = [UISwitch new];
|
||||
_jsonViewerSwitch = [UISwitch new];
|
||||
_cacheLimitSlider = [UISlider new];
|
||||
|
||||
self.observerSwitch.on = FLEXNetworkObserver.enabled;
|
||||
@@ -48,6 +53,12 @@
|
||||
forControlEvents:UIControlEventValueChanged
|
||||
];
|
||||
|
||||
self.jsonViewerSwitch.on = defaults.flex_registerDictionaryJSONViewerOnLaunch;
|
||||
[self.jsonViewerSwitch addTarget:self
|
||||
action:@selector(jsonViewerSettingToggled:)
|
||||
forControlEvents:UIControlEventValueChanged
|
||||
];
|
||||
|
||||
[self.cacheLimitSlider addTarget:self
|
||||
action:@selector(cacheLimitAdjusted:)
|
||||
forControlEvents:UIControlEventValueChanged
|
||||
@@ -84,12 +95,16 @@
|
||||
FLEXNetworkRecorder.defaultRecorder.shouldCacheMediaResponses = sender.isOn;
|
||||
}
|
||||
|
||||
- (void)jsonViewerSettingToggled:(UISwitch *)sender {
|
||||
[NSUserDefaults.standardUserDefaults toggleBoolForKey:kFLEXDefaultsRegisterJSONExplorerKey];
|
||||
}
|
||||
|
||||
- (void)cacheLimitAdjusted:(UISlider *)sender {
|
||||
self.cacheLimitValue = sender.value;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Table View
|
||||
#pragma mark - Table View Data Source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.hostBlacklist.count ? 2 : 1;
|
||||
@@ -97,7 +112,7 @@
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
switch (section) {
|
||||
case 0: return 4;
|
||||
case 0: return 5;
|
||||
case 1: return self.hostBlacklist.count;
|
||||
default: return 0;
|
||||
}
|
||||
@@ -111,6 +126,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
|
||||
if (section == 0) {
|
||||
return @"By default, JSON is rendered in a webview. Turn on "
|
||||
"\"View JSON as a dictionary/array\" to convert JSON payloads "
|
||||
"to objects and view them in an object explorer. "
|
||||
"This setting requires a restart of the app.";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UITableViewCell *cell = [self.tableView
|
||||
dequeueReusableCellWithIdentifier:kFLEXDefaultCell forIndexPath:indexPath
|
||||
@@ -132,10 +158,14 @@
|
||||
cell.accessoryView = self.cacheMediaSwitch;
|
||||
break;
|
||||
case 2:
|
||||
cell.textLabel.text = @"View JSON as a dictionary/array";
|
||||
cell.accessoryView = self.jsonViewerSwitch;
|
||||
break;
|
||||
case 3:
|
||||
cell.textLabel.text = @"Reset Host Blacklist";
|
||||
cell.textLabel.textColor = tableView.tintColor;
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
cell.textLabel.text = self.cacheLimitCellTitle;
|
||||
self.cacheLimitLabel = cell.textLabel;
|
||||
[self.cacheLimitSlider removeFromSuperview];
|
||||
@@ -179,6 +209,8 @@
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - Table View Delegate
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)ip {
|
||||
// Can only select the "Reset Host Blacklist" row
|
||||
return ip.section == 0 && ip.row == 2;
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXNetworkTransactionTableViewCell.h
|
||||
// FLEXNetworkTransactionCell.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 2/8/15.
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
extern NSString *const kFLEXNetworkTransactionCellIdentifier;
|
||||
extern NSString * const kFLEXNetworkTransactionCellIdentifier;
|
||||
|
||||
@class FLEXNetworkTransaction;
|
||||
|
||||
@interface FLEXNetworkTransactionTableViewCell : UITableViewCell
|
||||
@interface FLEXNetworkTransactionCell : UITableViewCell
|
||||
|
||||
@property (nonatomic) FLEXNetworkTransaction *transaction;
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXNetworkTransactionTableViewCell.m
|
||||
// FLEXNetworkTransactionCell.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 2/8/15.
|
||||
@@ -7,14 +7,14 @@
|
||||
//
|
||||
|
||||
#import "FLEXColor.h"
|
||||
#import "FLEXNetworkTransactionTableViewCell.h"
|
||||
#import "FLEXNetworkTransactionCell.h"
|
||||
#import "FLEXNetworkTransaction.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXResources.h"
|
||||
|
||||
NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactionCellIdentifier";
|
||||
|
||||
@interface FLEXNetworkTransactionTableViewCell ()
|
||||
@interface FLEXNetworkTransactionCell ()
|
||||
|
||||
@property (nonatomic) UIImageView *thumbnailImageView;
|
||||
@property (nonatomic) UILabel *nameLabel;
|
||||
@@ -23,7 +23,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXNetworkTransactionTableViewCell
|
||||
@implementation FLEXNetworkTransactionCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXNetworkTransactionDetailTableViewController.h
|
||||
// FLEXNetworkTransactionDetailController.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 2/10/15.
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
@class FLEXNetworkTransaction;
|
||||
|
||||
@interface FLEXNetworkTransactionDetailTableViewController : UITableViewController
|
||||
@interface FLEXNetworkTransactionDetailController : UITableViewController
|
||||
|
||||
@property (nonatomic) FLEXNetworkTransaction *transaction;
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// FLEXNetworkTransactionDetailTableViewController.m
|
||||
// FLEXNetworkTransactionDetailController.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 2/10/15.
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXColor.h"
|
||||
#import "FLEXNetworkTransactionDetailTableViewController.h"
|
||||
#import "FLEXNetworkTransactionDetailController.h"
|
||||
#import "FLEXNetworkCurlLogger.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXNetworkTransaction.h"
|
||||
@@ -44,13 +44,13 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXNetworkTransactionDetailTableViewController ()
|
||||
@interface FLEXNetworkTransactionDetailController ()
|
||||
|
||||
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXNetworkTransactionDetailTableViewController
|
||||
@implementation FLEXNetworkTransactionDetailController
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewStyle)style {
|
||||
// Force grouped style
|
||||
@@ -15,7 +15,7 @@
|
||||
#import "FLEXNetworkObserver.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "FLEXMethod.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
@@ -278,17 +278,18 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
|
||||
// In iOS 7 resume lives in __NSCFLocalSessionTask
|
||||
// In iOS 8 resume lives in NSURLSessionTask
|
||||
// In iOS 9 resume lives in __NSCFURLSessionTask
|
||||
// In iOS 14 resume lives in NSURLSessionTask
|
||||
Class baseResumeClass = Nil;
|
||||
if (![NSProcessInfo.processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
|
||||
// iOS ... 7
|
||||
baseResumeClass = NSClassFromString(@"__NSCFLocalSessionTask");
|
||||
} else {
|
||||
NSInteger majorVersion = NSProcessInfo.processInfo.operatingSystemVersion.majorVersion;
|
||||
if (majorVersion < 9) {
|
||||
// iOS 8
|
||||
if (majorVersion < 9 || majorVersion >= 14) {
|
||||
// iOS 8 or iOS 14+
|
||||
baseResumeClass = [NSURLSessionTask class];
|
||||
} else {
|
||||
// iOS 9+
|
||||
// iOS 9 ... 13
|
||||
baseResumeClass = NSClassFromString(@"__NSCFURLSessionTask");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,22 @@
|
||||
|
||||
#import "FLEXRuntime+UIKitHelpers.h"
|
||||
|
||||
/// Carries state about the current user defaults settings
|
||||
@interface FLEXObjectExplorerDefaults : NSObject
|
||||
+ (instancetype)canEdit:(BOOL)editable wantsPreviews:(BOOL)showPreviews;
|
||||
|
||||
/// Only \c YES for properties and ivars
|
||||
@property (nonatomic, readonly) BOOL isEditable;
|
||||
/// Only affects properties and ivars
|
||||
@property (nonatomic, readonly) BOOL wantsDynamicPreviews;
|
||||
@end
|
||||
|
||||
@interface FLEXObjectExplorer : NSObject
|
||||
|
||||
+ (instancetype)forObject:(id)objectOrClass;
|
||||
|
||||
+ (void)configureDefaultsForItems:(NSArray<id<FLEXObjectExplorerItem>> *)items;
|
||||
|
||||
@property (nonatomic, readonly) id object;
|
||||
/// Subclasses can override to provide a more useful description
|
||||
@property (nonatomic, readonly) NSString *objectDescription;
|
||||
|
||||
@@ -9,14 +9,24 @@
|
||||
#import "FLEXObjectExplorer.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "FLEXRuntime+Compare.h"
|
||||
#import "FLEXRuntime+UIKitHelpers.h"
|
||||
#import "FLEXPropertyAttributes.h"
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "FLEXMetadataSection.h"
|
||||
#import "NSUserDefaults+FLEX.h"
|
||||
|
||||
@implementation FLEXObjectExplorerDefaults
|
||||
|
||||
+ (instancetype)canEdit:(BOOL)editable wantsPreviews:(BOOL)showPreviews {
|
||||
FLEXObjectExplorerDefaults *defaults = [self new];
|
||||
defaults->_isEditable = editable;
|
||||
defaults->_wantsDynamicPreviews = showPreviews;
|
||||
return defaults;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXObjectExplorer () {
|
||||
NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
|
||||
NSMutableArray<NSArray<FLEXProperty *> *> *_allClassProperties;
|
||||
@@ -55,6 +65,24 @@
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
+ (void)configureDefaultsForItems:(NSArray<id<FLEXObjectExplorerItem>> *)items {
|
||||
BOOL hidePreviews = NSUserDefaults.standardUserDefaults.flex_explorerHidesVariablePreviews;
|
||||
FLEXObjectExplorerDefaults *mutable = [FLEXObjectExplorerDefaults
|
||||
canEdit:YES wantsPreviews:!hidePreviews
|
||||
];
|
||||
FLEXObjectExplorerDefaults *immutable = [FLEXObjectExplorerDefaults
|
||||
canEdit:NO wantsPreviews:!hidePreviews
|
||||
];
|
||||
|
||||
// .tag is used to cache whether the value of .isEditable;
|
||||
// This could change at runtime so it is important that
|
||||
// it is cached every time shortcuts are requeted and not
|
||||
// just once at as shortcuts are initially registered
|
||||
for (id<FLEXObjectExplorerItem> metadata in items) {
|
||||
metadata.defaults = metadata.isEditable ? mutable : immutable;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)objectDescription {
|
||||
if (!_objectDescription) {
|
||||
// Hard-code UIColor description
|
||||
@@ -222,11 +250,13 @@
|
||||
// Set up UIKit helper data
|
||||
// Really, we only need to call this on properties and ivars
|
||||
// because no other metadata types support editing.
|
||||
for (NSArray *matrix in @[_allProperties, _allIvars, /* _allMethods, _allClassMethods, _allConformedProtocols */]) {
|
||||
NSArray<NSArray *>*metadatas = @[
|
||||
_allProperties, _allClassProperties, _allIvars,
|
||||
/* _allMethods, _allClassMethods, _allConformedProtocols */
|
||||
];
|
||||
for (NSArray *matrix in metadatas) {
|
||||
for (NSArray *metadataByClass in matrix) {
|
||||
for (id<FLEXRuntimeMetadata> metadata in metadataByClass) {
|
||||
metadata.tag = metadata.isEditable ? @YES : nil;
|
||||
}
|
||||
[FLEXObjectExplorer configureDefaultsForItems:metadataByClass];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "FLEXClassShortcuts.h"
|
||||
#import "FLEXViewShortcuts.h"
|
||||
#import "FLEXViewControllerShortcuts.h"
|
||||
#import "FLEXUIAppShortcuts.h"
|
||||
#import "FLEXImageShortcuts.h"
|
||||
#import "FLEXLayerShortcuts.h"
|
||||
#import "FLEXColorPreviewSection.h"
|
||||
@@ -35,6 +36,7 @@ static NSMutableDictionary<Class, Class> *classesToRegisteredSections = nil;
|
||||
ClassKey(NSOrderedSet) : [FLEXCollectionContentSection class],
|
||||
ClassKey(NSUserDefaults) : [FLEXDefaultsContentSection class],
|
||||
ClassKey(UIViewController) : [FLEXViewControllerShortcuts class],
|
||||
ClassKey(UIApplication) : [FLEXUIAppShortcuts class],
|
||||
ClassKey(UIView) : [FLEXViewShortcuts class],
|
||||
ClassKey(UIImage) : [FLEXImageShortcuts class],
|
||||
ClassKey(CALayer) : [FLEXLayerShortcuts class],
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
kFLEXDefaultsHidePropertyIvarsKey,
|
||||
kFLEXDefaultsHidePropertyMethodsKey,
|
||||
kFLEXDefaultsHideMethodOverridesKey,
|
||||
kFLEXDefaultsHideVariablePreviewsKey,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@
|
||||
|
||||
// ... button for extra options
|
||||
[self addToolbarItems:@[[UIBarButtonItem
|
||||
itemWithImage:FLEXResources.moreIcon target:self action:@selector(moreButtonPressed)
|
||||
itemWithImage:FLEXResources.moreIcon target:self action:@selector(moreButtonPressed:)
|
||||
]]];
|
||||
|
||||
// Swipe gestures to swipe between classes in the hierarchy
|
||||
@@ -122,12 +123,7 @@
|
||||
//
|
||||
// "If your app targets iOS 9.0 and later or macOS 10.11 and later,
|
||||
// you don't need to unregister an observer in its dealloc method."
|
||||
NSArray<NSString *> *observedNotifications = @[
|
||||
kFLEXDefaultsHidePropertyIvarsKey,
|
||||
kFLEXDefaultsHidePropertyMethodsKey,
|
||||
kFLEXDefaultsHideMethodOverridesKey,
|
||||
];
|
||||
for (NSString *pref in observedNotifications) {
|
||||
for (NSString *pref in self.observedNotifications) {
|
||||
[NSNotificationCenter.defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(fullyReloadData)
|
||||
@@ -221,7 +217,7 @@
|
||||
[super reloadData];
|
||||
}
|
||||
|
||||
- (void)shareButtonPressed {
|
||||
- (void)shareButtonPressed:(UIBarButtonItem *)sender {
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
make.button(@"Add to Bookmarks").handler(^(NSArray<NSString *> *strings) {
|
||||
[FLEXBookmarkManager.bookmarks addObject:self.object];
|
||||
@@ -233,7 +229,7 @@
|
||||
UIPasteboard.generalPasteboard.string = [FLEXUtility addressOfObject:self.object];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
|
||||
@@ -289,13 +285,14 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)moreButtonPressed {
|
||||
- (void)moreButtonPressed:(UIBarButtonItem *)sender {
|
||||
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
||||
// Maps preference keys to a description of what they affect
|
||||
NSDictionary<NSString *, NSString *> *explorerToggles = @{
|
||||
kFLEXDefaultsHidePropertyIvarsKey: @"Property-Backing Ivars",
|
||||
kFLEXDefaultsHidePropertyMethodsKey: @"Property-Backing Methods",
|
||||
kFLEXDefaultsHideMethodOverridesKey: @"Method Overrides",
|
||||
kFLEXDefaultsHidePropertyIvarsKey: @"Property-Backing Ivars",
|
||||
kFLEXDefaultsHidePropertyMethodsKey: @"Property-Backing Methods",
|
||||
kFLEXDefaultsHideMethodOverridesKey: @"Method Overrides",
|
||||
kFLEXDefaultsHideVariablePreviewsKey: @"Variable Previews"
|
||||
};
|
||||
|
||||
// Maps the key of the action itself to a map of a description
|
||||
@@ -303,9 +300,10 @@
|
||||
//
|
||||
// So keys that are hidden by default have NO mapped to "Show"
|
||||
NSDictionary<NSString *, NSDictionary *> *nextStateDescriptions = @{
|
||||
kFLEXDefaultsHidePropertyIvarsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
kFLEXDefaultsHidePropertyMethodsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
kFLEXDefaultsHideMethodOverridesKey: @{ @NO: @"Show ", @YES: @"Hide " },
|
||||
kFLEXDefaultsHidePropertyIvarsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
kFLEXDefaultsHidePropertyMethodsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
kFLEXDefaultsHideMethodOverridesKey: @{ @NO: @"Show ", @YES: @"Hide " },
|
||||
kFLEXDefaultsHideVariablePreviewsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
};
|
||||
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
@@ -323,7 +321,7 @@
|
||||
}
|
||||
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
@@ -77,7 +77,7 @@ typedef id<FLEXCollection>(^FLEXCollectionContentFuture)(__kindof FLEXCollection
|
||||
/// Defaults to \c NO
|
||||
@property (nonatomic) BOOL hideSectionTitle;
|
||||
/// Defaults to \c nil
|
||||
@property (nonatomic) NSString *customTitle;
|
||||
@property (nonatomic, copy) NSString *customTitle;
|
||||
/// Defaults to \c NO
|
||||
///
|
||||
/// Settings this to \c NO will not display the element index for ordered collections.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#import "FLEXFieldEditorViewController.h"
|
||||
#import "FLEXMethodCallingViewController.h"
|
||||
#import "FLEXIvar.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXRuntime+UIKitHelpers.h"
|
||||
|
||||
@interface FLEXMetadataSection ()
|
||||
|
||||
@@ -36,7 +36,7 @@ typedef void (^FLEXMutableListCellForElement)(__kindof UITableViewCell *cell, id
|
||||
|
||||
/// By default, rows are not selectable. If you want rows
|
||||
/// to be selectable, provide a selection handler here.
|
||||
@property (nonatomic) void (^selectionHandler)(__kindof UIViewController * host, id element);
|
||||
@property (nonatomic, copy) void (^selectionHandler)(__kindof UIViewController *host, id element);
|
||||
|
||||
/// The objects representing all possible rows in the section.
|
||||
@property (nonatomic) NSArray<ObjectType> *list;
|
||||
|
||||
@@ -29,12 +29,12 @@ configurationBlock:(FLEXMutableListCellForElement)cellConfig
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_configureCell = cellConfig;
|
||||
|
||||
|
||||
self.list = list.mutableCopy;
|
||||
self.customFilter = filterBlock;
|
||||
self.hideSectionTitle = YES;
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ configurationBlock:(FLEXMutableListCellForElement)cellConfig
|
||||
- (void)setList:(NSMutableArray *)list {
|
||||
NSParameterAssert(list);
|
||||
_collection = list;
|
||||
|
||||
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
@@ -79,11 +79,15 @@ configurationBlock:(FLEXMutableListCellForElement)cellConfig
|
||||
|
||||
- (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row {
|
||||
if (self.selectionHandler) {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
return ^(UIViewController *host) {
|
||||
self.selectionHandler(host, self.filteredList[row]);
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
strongSelf.selectionHandler(host, strongSelf.filteredList[row]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -95,7 +99,7 @@ configurationBlock:(FLEXMutableListCellForElement)cellConfig
|
||||
if (self.cellRegistrationMapping.count) {
|
||||
return self.cellRegistrationMapping.allKeys.firstObject;
|
||||
}
|
||||
|
||||
|
||||
return [super reuseIdentifierForRow:row];
|
||||
}
|
||||
|
||||
|
||||
@@ -8,23 +8,110 @@
|
||||
|
||||
#import "FLEXBundleShortcuts.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXFileBrowserTableViewController.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXRuntimeExporter.h"
|
||||
#import "FLEXTableListViewController.h"
|
||||
#import "FLEXFileBrowserController.h"
|
||||
|
||||
#pragma mark -
|
||||
@implementation FLEXBundleShortcuts
|
||||
#pragma mark Overrides
|
||||
|
||||
+ (instancetype)forObject:(NSBundle *)bundle {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
return [self forObject:bundle additionalRows:@[
|
||||
[FLEXActionShortcut title:@"Browse Bundle Directory" subtitle:nil
|
||||
viewer:^UIViewController *(id view) {
|
||||
return [FLEXFileBrowserTableViewController path:bundle.bundlePath];
|
||||
[FLEXActionShortcut
|
||||
title:@"Browse Bundle Directory" subtitle:nil
|
||||
viewer:^UIViewController *(NSBundle *bundle) {
|
||||
return [FLEXFileBrowserController path:bundle.bundlePath];
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(id view) {
|
||||
accessoryType:^UITableViewCellAccessoryType(NSBundle *bundle) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
]
|
||||
],
|
||||
[FLEXActionShortcut title:@"Browse Bundle as Database…" subtitle:nil
|
||||
selectionHandler:^(UIViewController *host, NSBundle *bundle) {
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
[strongSelf promptToExportBundleAsDatabase:bundle host:host];
|
||||
}
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(NSBundle *bundle) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
],
|
||||
]];
|
||||
}
|
||||
|
||||
+ (void)promptToExportBundleAsDatabase:(NSBundle *)bundle host:(UIViewController *)host {
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Save As…").message(
|
||||
@"The database be saved in the Library folder. "
|
||||
"Depending on the number of classes, it may take "
|
||||
"10 minutes or more to finish exporting. 20,000 "
|
||||
"classes takes about 7 minutes."
|
||||
);
|
||||
make.configuredTextField(^(UITextField *field) {
|
||||
field.placeholder = @"FLEXRuntimeExport.objc.db";
|
||||
field.text = [NSString stringWithFormat:
|
||||
@"%@.objc.db", bundle.executablePath.lastPathComponent
|
||||
];
|
||||
});
|
||||
make.button(@"Start").handler(^(NSArray<NSString *> *strings) {
|
||||
[self browseBundleAsDatabase:bundle host:host name:strings[0]];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:host];
|
||||
}
|
||||
|
||||
+ (void)browseBundleAsDatabase:(NSBundle *)bundle host:(UIViewController *)host name:(NSString *)name {
|
||||
NSParameterAssert(name.length);
|
||||
|
||||
UIAlertController *progress = [FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Generating Database");
|
||||
// Some iOS version glitch out of there is
|
||||
// no initial message and you add one later
|
||||
make.message(@"…");
|
||||
}];
|
||||
|
||||
[host presentViewController:progress animated:YES completion:^{
|
||||
// Generate path to store db
|
||||
NSString *path = [NSSearchPathForDirectoriesInDomains(
|
||||
NSLibraryDirectory, NSUserDomainMask, YES
|
||||
)[0] stringByAppendingPathComponent:name];
|
||||
|
||||
progress.message = [path stringByAppendingString:@"\n\nCreating database…"];
|
||||
|
||||
// Generate db and show progress
|
||||
[FLEXRuntimeExporter createRuntimeDatabaseAtPath:path
|
||||
forImages:@[bundle.executablePath]
|
||||
progressHandler:^(NSString *status) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
progress.message = [progress.message
|
||||
stringByAppendingFormat:@"\n%@", status
|
||||
];
|
||||
[progress.view setNeedsLayout];
|
||||
[progress.view layoutIfNeeded];
|
||||
});
|
||||
} completion:^(NSString *error) {
|
||||
// Display error if any
|
||||
if (error) {
|
||||
progress.title = @"Error";
|
||||
progress.message = error;
|
||||
[progress addAction:[UIAlertAction
|
||||
actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]
|
||||
];
|
||||
}
|
||||
// Browse database
|
||||
else {
|
||||
[progress dismissViewControllerAnimated:YES completion:nil];
|
||||
[host.navigationController pushViewController:[
|
||||
[FLEXTableListViewController alloc] initWithPath:path
|
||||
] animated:YES];
|
||||
}
|
||||
}
|
||||
];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXObjectListViewController.h"
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
|
||||
@interface FLEXClassShortcuts ()
|
||||
@property (nonatomic, readonly) Class cls;
|
||||
|
||||
@@ -8,57 +8,60 @@
|
||||
|
||||
#import "FLEXImageShortcuts.h"
|
||||
#import "FLEXImagePreviewViewController.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXAlert.h"
|
||||
|
||||
@interface FLEXImageShortcuts ()
|
||||
@property (nonatomic, readonly) UIImage *image;
|
||||
@interface UIAlertController (FLEXImageShortcuts)
|
||||
- (void)flex_image:(UIImage *)image disSaveWithError:(NSError *)error :(void *)context;
|
||||
@end
|
||||
|
||||
@implementation FLEXImageShortcuts
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
- (UIImage *)image {
|
||||
return self.object;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
+ (instancetype)forObject:(UIImage *)image {
|
||||
// These additional rows will appear at the beginning of the shortcuts section.
|
||||
// The methods below are written in such a way that they will not interfere
|
||||
// with properties/etc being registered alongside these
|
||||
return [self forObject:image additionalRows:@[@"View Image", @"Save Image"]];
|
||||
}
|
||||
|
||||
/// View image
|
||||
- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
|
||||
if (row == 0) {
|
||||
return [FLEXImagePreviewViewController forImage:self.image];
|
||||
}
|
||||
|
||||
return [super viewControllerToPushForRow:row];
|
||||
}
|
||||
|
||||
/// Save image
|
||||
- (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row {
|
||||
if (row == 1) {
|
||||
return ^(UIViewController *host) {
|
||||
UIImageWriteToSavedPhotosAlbum(self.image, nil, nil, nil);
|
||||
};
|
||||
}
|
||||
|
||||
return [super didSelectRowAction:row];
|
||||
}
|
||||
|
||||
/// "Save Image" does not need a disclosure indicator
|
||||
- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
|
||||
switch (row) {
|
||||
case 0: return UITableViewCellAccessoryDisclosureIndicator;
|
||||
case 1: return UITableViewCellAccessoryNone;
|
||||
default: return [super accessoryTypeForRow:row];
|
||||
}
|
||||
return [self forObject:image additionalRows:@[
|
||||
[FLEXActionShortcut title:@"View Image" subtitle:nil
|
||||
viewer:^UIViewController *(id image) {
|
||||
return [FLEXImagePreviewViewController forImage:image];
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(id image) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
],
|
||||
[FLEXActionShortcut title:@"Save Image" subtitle:nil
|
||||
selectionHandler:^(UIViewController *host, id image) {
|
||||
// Present modal alerting user about saving
|
||||
UIAlertController *alert = [FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Saving Image…");
|
||||
}];
|
||||
[host presentViewController:alert animated:YES completion:nil];
|
||||
|
||||
// Save the image
|
||||
UIImageWriteToSavedPhotosAlbum(
|
||||
image, alert, @selector(flex_image:disSaveWithError::), nil
|
||||
);
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(id image) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
]
|
||||
]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation UIAlertController (FLEXImageShortcuts)
|
||||
|
||||
- (void)flex_image:(UIImage *)image disSaveWithError:(NSError *)error :(void *)context {
|
||||
self.title = @"Image Saved";
|
||||
flex_dispatch_after(1, dispatch_get_main_queue(), ^{
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// Copyright © 2019 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXObjectExplorer.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
///
|
||||
/// It is useful to make your own shortcuts to append/prepend
|
||||
/// them to the existing list of shortcuts for a class.
|
||||
@protocol FLEXShortcut <NSObject>
|
||||
@protocol FLEXShortcut <FLEXObjectExplorerItem>
|
||||
|
||||
- (nonnull NSString *)titleWith:(id)object;
|
||||
- (nullable NSString *)subtitleWith:(id)object;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
@end
|
||||
|
||||
@implementation FLEXShortcut
|
||||
@synthesize defaults = _defaults;
|
||||
|
||||
+ (id<FLEXShortcut>)shortcutFor:(id)item {
|
||||
if ([item conformsToProtocol:@protocol(FLEXShortcut)]) {
|
||||
@@ -85,7 +86,7 @@
|
||||
|
||||
- (NSString *)subtitleWith:(id)object {
|
||||
if (self.metadataKind) {
|
||||
return [self.metadata previewWithTarget:object] ?: @"nil";
|
||||
return [self.metadata previewWithTarget:object];
|
||||
}
|
||||
|
||||
// Item is probably a string; must return empty string since
|
||||
@@ -124,6 +125,32 @@
|
||||
return kFLEXMultilineCell;
|
||||
}
|
||||
|
||||
#pragma mark FLEXObjectExplorerDefaults
|
||||
|
||||
- (void)setDefaults:(FLEXObjectExplorerDefaults *)defaults {
|
||||
_defaults = defaults;
|
||||
|
||||
if (_metadataKind) {
|
||||
self.metadata.defaults = defaults;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isEditable {
|
||||
if (_metadataKind) {
|
||||
return self.metadata.isEditable;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isCallable {
|
||||
if (_metadataKind) {
|
||||
return self.metadata.isCallable;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (FLEXProperty *)property { return _item; }
|
||||
@@ -145,6 +172,7 @@
|
||||
@end
|
||||
|
||||
@implementation FLEXActionShortcut
|
||||
@synthesize defaults = _defaults;
|
||||
|
||||
+ (instancetype)title:(NSString *)title
|
||||
subtitle:(NSString *(^)(id))subtitle
|
||||
@@ -186,7 +214,11 @@
|
||||
}
|
||||
|
||||
- (NSString *)subtitleWith:(id)object {
|
||||
return self.subtitleFuture(object);
|
||||
if (self.defaults.wantsDynamicPreviews) {
|
||||
return self.subtitleFuture(object);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void (^)(UIViewController *))didSelectActionWith:(id)object {
|
||||
@@ -216,4 +248,7 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isEditable { return NO; }
|
||||
- (BOOL)isCallable { return NO; }
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#import "FLEXShortcutsSection.h"
|
||||
|
||||
@interface FLEXShortcutsFactory (UIApplication) @end
|
||||
|
||||
@interface FLEXShortcutsFactory (Views) @end
|
||||
|
||||
@interface FLEXShortcutsFactory (ViewControllers) @end
|
||||
|
||||
@@ -9,13 +9,38 @@
|
||||
#import "FLEXShortcutsFactory+Defaults.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
|
||||
#pragma mark - UIApplication
|
||||
|
||||
@implementation FLEXShortcutsFactory (UIApplication)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
// sharedApplication class property possibly not added
|
||||
// as a literal class property until iOS 10
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(
|
||||
2, sharedApplication, UIApplication.flex_metaclass, UIApplication, PropertyKey(ReadOnly)
|
||||
);
|
||||
|
||||
self.append.classProperties(@[@"sharedApplication"]).forClass(UIApplication.flex_metaclass);
|
||||
self.append.properties(@[
|
||||
@"delegate", @"keyWindow", @"windows"
|
||||
]).forClass(UIApplication.class);
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
self.append.properties(@[
|
||||
@"connectedScenes", @"openSessions", @"supportsMultipleScenes"
|
||||
]).forClass(UIApplication.class);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Views
|
||||
|
||||
@implementation FLEXShortcutsFactory (Views)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
// A quirk of UIView and some other classes: a lot of the `@property`s are
|
||||
// not actually properties from the perspective of the runtime.
|
||||
//
|
||||
@@ -90,6 +115,13 @@
|
||||
@"currentTitle", @"currentImage", @"enabled", @"frame",
|
||||
@"superview", @"subviews"
|
||||
]).forClass(UIButton.class);
|
||||
|
||||
// UIImageView
|
||||
self.append.properties(@[
|
||||
@"image", @"animationImages", @"frame", @"bounds", @"center",
|
||||
@"transform", @"alpha", @"hidden", @"clipsToBounds",
|
||||
@"userInteractionEnabled", @"layer", @"superview", @"subviews",
|
||||
]).forClass(UIImageView.class);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -99,7 +131,7 @@
|
||||
|
||||
@implementation FLEXShortcutsFactory (ViewControllers)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
// toolbarItems is not really a property, make it one
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(3, toolbarItems, UIViewController.class, NSArray);
|
||||
|
||||
@@ -119,7 +151,7 @@
|
||||
|
||||
@implementation FLEXShortcutsFactory (UIImage)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
self.append.methods(@[
|
||||
@"CGImage", @"CIImage"
|
||||
]).properties(@[
|
||||
@@ -128,7 +160,7 @@
|
||||
]).forClass(UIImage.class);
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
self.append.properties(@[@"symbolImage"]);
|
||||
self.append.properties(@[@"symbolImage"]).forClass(UIImage.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +171,7 @@
|
||||
|
||||
@implementation FLEXShortcutsFactory (NSBundle)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
self.append.properties(@[
|
||||
@"bundleIdentifier", @"principalClass",
|
||||
@"infoDictionary", @"bundlePath",
|
||||
@@ -154,7 +186,7 @@
|
||||
|
||||
@implementation FLEXShortcutsFactory (Classes)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
self.append.classMethods(@[@"new", @"alloc"]).forClass(NSObject.flex_metaclass);
|
||||
}
|
||||
|
||||
@@ -165,7 +197,7 @@
|
||||
|
||||
@implementation FLEXShortcutsFactory (Activities)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
// Property was added in iOS 10 but we want it on iOS 9 too
|
||||
FLEXRuntimeUtilityTryAddNonatomicProperty(9, item, UIActivityItemProvider.class, id, PropertyKey(ReadOnly));
|
||||
|
||||
@@ -185,7 +217,7 @@
|
||||
|
||||
@implementation FLEXShortcutsFactory (Blocks)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
self.append.methods(@[@"invoke"]).forClass(NSClassFromString(@"NSBlock"));
|
||||
}
|
||||
|
||||
@@ -195,7 +227,7 @@
|
||||
|
||||
@implementation FLEXShortcutsFactory (Foundation)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
self.append.properties(@[
|
||||
@"configuration", @"delegate", @"delegateQueue", @"sessionDescription",
|
||||
]).methods(@[
|
||||
@@ -230,24 +262,6 @@
|
||||
self.append.classProperties(@[
|
||||
@"defaultTimeZone", @"systemTimeZone", @"localTimeZone"
|
||||
]).forClass(NSTimeZone.class);
|
||||
|
||||
|
||||
// self.append.<#type#>(@[@"<#value#>"]).forClass(NSURLSession.class);
|
||||
//
|
||||
//
|
||||
// self.append.<#type#>(@[@"<#value#>"]).forClass(NSURLSession.class);
|
||||
//
|
||||
//
|
||||
// self.append.<#type#>(@[@"<#value#>"]).forClass(NSURLSession.class);
|
||||
//
|
||||
//
|
||||
// self.append.<#type#>(@[@"<#value#>"]).forClass(NSURLSession.class);
|
||||
//
|
||||
//
|
||||
// self.append.<#type#>(@[@"<#value#>"]).forClass(NSURLSession.class);
|
||||
//
|
||||
//
|
||||
// self.append.<#type#>(@[@"<#value#>"]).forClass(NSURLSession.class);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
/// Subclasses \e may override this to hide the disclosure indicator
|
||||
/// for some rows. It is shown for all rows by default, unless
|
||||
/// you initialize it with \c forObject:rowTitles:rowSubtitles:
|
||||
///
|
||||
/// When you hide the disclosure indicator, the row is not selectable.
|
||||
- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row;
|
||||
|
||||
/// The number of lines for the title and subtitle labels. Defaults to 1.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#import "FLEXIvar.h"
|
||||
#import "FLEXMethod.h"
|
||||
#import "FLEXRuntime+UIKitHelpers.h"
|
||||
#import "FLEXObjectExplorer.h"
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
@@ -86,6 +87,7 @@
|
||||
return [FLEXShortcut shortcutFor:obj];
|
||||
}];
|
||||
_numberOfLines = 1;
|
||||
|
||||
// Populate titles and subtitles
|
||||
[self reloadData];
|
||||
}
|
||||
@@ -156,6 +158,8 @@
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
[FLEXObjectExplorer configureDefaultsForItems:self.allShortcuts];
|
||||
|
||||
// Generate all (sub)titles from shortcuts
|
||||
if (self.allShortcuts) {
|
||||
self.allTitles = [self.allShortcuts flex_mapped:^id(FLEXShortcut *s, NSUInteger idx) {
|
||||
@@ -303,15 +307,8 @@ static RegistrationBuckets *mMethods = nil;
|
||||
classKey = class_getSuperclass(classKey);
|
||||
}
|
||||
}
|
||||
|
||||
// .tag is used to cache whether the value of .isEditable;
|
||||
// This could change at runtime so it is important that
|
||||
// it is cached every time shortcuts are requeted and not
|
||||
// just once at as shortcuts are initially registered
|
||||
for (id<FLEXRuntimeMetadata> metadata in shortcuts) {
|
||||
metadata.tag = metadata.isEditable ? @YES : nil;
|
||||
}
|
||||
|
||||
|
||||
[FLEXObjectExplorer configureDefaultsForItems:shortcuts];
|
||||
return shortcuts;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FLEXUIAppShortcuts.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 5/25/20.
|
||||
// Copyright © 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXShortcutsSection.h"
|
||||
|
||||
@interface FLEXUIAppShortcuts : FLEXShortcutsSection
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// FLEXUIAppShortcuts.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 5/25/20.
|
||||
// Copyright © 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXUIAppShortcuts.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXAlert.h"
|
||||
|
||||
@implementation FLEXUIAppShortcuts
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
+ (instancetype)forObject:(UIApplication *)application {
|
||||
return [self forObject:application additionalRows:@[
|
||||
[FLEXActionShortcut title:@"Open URL…"
|
||||
subtitle:^NSString *(UIViewController *controller) {
|
||||
return nil;
|
||||
}
|
||||
selectionHandler:^void(UIViewController *host, UIApplication *app) {
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Open URL");
|
||||
make.message(
|
||||
@"This will call openURL: or openURL:options:completion: "
|
||||
"with the string below. 'Open if Universal' will only open "
|
||||
"the URL if it is a registered Universal Link."
|
||||
);
|
||||
|
||||
make.textField(@"twitter://user?id=12345");
|
||||
make.button(@"Open").handler(^(NSArray<NSString *> *strings) {
|
||||
[self openURL:strings[0] inApp:app onlyIfUniveral:NO host:host];
|
||||
});
|
||||
make.button(@"Open if Universal").handler(^(NSArray<NSString *> *strings) {
|
||||
[self openURL:strings[0] inApp:app onlyIfUniveral:YES host:host];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:host];
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(UIViewController *controller) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
]
|
||||
]];
|
||||
}
|
||||
|
||||
+ (void)openURL:(NSString *)urlString
|
||||
inApp:(UIApplication *)app
|
||||
onlyIfUniveral:(BOOL)universalOnly
|
||||
host:(UIViewController *)host {
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
|
||||
if (url) {
|
||||
if (@available(iOS 10, *)) {
|
||||
[app openURL:url options:@{
|
||||
UIApplicationOpenURLOptionUniversalLinksOnly: @(universalOnly)
|
||||
} completionHandler:^(BOOL success) {
|
||||
if (!success) {
|
||||
[FLEXAlert showAlert:@"No Universal Link Handler"
|
||||
message:@"No installed application is registered to handle this link."
|
||||
from:host
|
||||
];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[app openURL:url];
|
||||
}
|
||||
} else {
|
||||
[FLEXAlert showAlert:@"Error" message:@"Invalid URL" from:host];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,34 +13,15 @@
|
||||
#import "FLEXAlert.h"
|
||||
|
||||
@interface FLEXViewControllerShortcuts ()
|
||||
@property (nonatomic, readonly) UIViewController *viewController;
|
||||
@property (nonatomic, readonly) BOOL viewControllerIsInUse;
|
||||
@end
|
||||
|
||||
@implementation FLEXViewControllerShortcuts
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
- (UIViewController *)viewController {
|
||||
return self.object;
|
||||
}
|
||||
|
||||
/// A view controller is "in use" if it's view is in a window,
|
||||
/// or if it belongs to a navigation stack which is in use.
|
||||
- (BOOL)viewControllerIsInUse {
|
||||
if (self.viewController.view.window) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return self.viewController.navigationController != nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
+ (instancetype)forObject:(UIViewController *)viewController {
|
||||
BOOL (^vcIsInuse)(UIViewController *) = ^BOOL(UIViewController *controller) {
|
||||
if (controller.view.window) {
|
||||
if (controller.viewIfLoaded.window) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,25 @@
|
||||
#import "FLEXMethod.h"
|
||||
#import "FLEXProtocol.h"
|
||||
|
||||
@protocol FLEXRuntimeMetadata <NSObject>
|
||||
/// Used as the main title of the row
|
||||
- (NSString *)description;
|
||||
/// Used to compare metadata objects for uniqueness
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
@class FLEXObjectExplorerDefaults;
|
||||
|
||||
/// Model objects of an object explorer screen adopt this
|
||||
/// protocol in order respond to user defaults changes
|
||||
@protocol FLEXObjectExplorerItem <NSObject>
|
||||
/// Current explorer settings. Set when settings change.
|
||||
@property (nonatomic) FLEXObjectExplorerDefaults *defaults;
|
||||
|
||||
/// YES for properties and ivars which surely support editing, NO for all methods.
|
||||
@property (nonatomic, readonly) BOOL isEditable;
|
||||
/// NO for ivars, YES for supported methods and properties
|
||||
@property (nonatomic, readonly) BOOL isCallable;
|
||||
@end
|
||||
|
||||
@protocol FLEXRuntimeMetadata <FLEXObjectExplorerItem>
|
||||
/// Used as the main title of the row
|
||||
- (NSString *)description;
|
||||
/// Used to compare metadata objects for uniqueness
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
|
||||
/// For internal use
|
||||
@property (nonatomic) id tag;
|
||||
@@ -76,3 +86,7 @@ typedef NS_ENUM(NSUInteger, FLEXStaticMetadataRowStyle) {
|
||||
+ (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/// This is assigned to the \c tag property of each metadata.
|
||||
|
||||
|
||||
@@ -15,11 +15,21 @@
|
||||
#import "FLEXMethodCallingViewController.h"
|
||||
#import "FLEXTableView.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "NSString+FLEX.h"
|
||||
|
||||
#define FLEXObjectExplorerDefaultsImpl \
|
||||
- (FLEXObjectExplorerDefaults *)defaults { \
|
||||
return self.tag; \
|
||||
} \
|
||||
\
|
||||
- (void)setDefaults:(FLEXObjectExplorerDefaults *)defaults { \
|
||||
self.tag = defaults; \
|
||||
}
|
||||
|
||||
#pragma mark FLEXProperty
|
||||
@implementation FLEXProperty (UIKitHelpers)
|
||||
FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
/// Decide whether to use potentialTarget or [potentialTarget class] to get or set property
|
||||
- (id)appropriateTargetForPropertyType:(id)potentialTarget {
|
||||
@@ -67,11 +77,13 @@
|
||||
- (NSString *)previewWithTarget:(id)object {
|
||||
if (object_isClass(object) && !self.isClassProperty) {
|
||||
return self.attributes.fullDeclaration;
|
||||
} else {
|
||||
} else if (self.defaults.wantsDynamicPreviews) {
|
||||
return [FLEXRuntimeUtility
|
||||
summaryForObject:[self currentValueWithTarget:object]
|
||||
];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIViewController *)viewerWithTarget:(id)object {
|
||||
@@ -94,7 +106,7 @@
|
||||
// We use .tag to store the cached value of .isEditable that is
|
||||
// initialized by FLEXObjectExplorer in -reloadMetada
|
||||
if ([self getPotentiallyUnboxedValue:targetForValueCheck]) {
|
||||
if (self.tag) {
|
||||
if (self.defaults.isEditable) {
|
||||
// Editable non-nil value, both
|
||||
return UITableViewCellAccessoryDetailDisclosureButton;
|
||||
} else {
|
||||
@@ -102,7 +114,7 @@
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
} else {
|
||||
if (self.tag) {
|
||||
if (self.defaults.isEditable) {
|
||||
// Editable nil value, just (i)
|
||||
return UITableViewCellAccessoryDetailButton;
|
||||
} else {
|
||||
@@ -144,7 +156,7 @@
|
||||
if (targetNotNil) {
|
||||
id value = [self currentValueBeforeUnboxingWithTarget:object];
|
||||
[items addObjectsFromArray:@[
|
||||
@"Value Preview", [self previewWithTarget:object],
|
||||
@"Value Preview", [self previewWithTarget:object] ?: @"",
|
||||
@"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
|
||||
]];
|
||||
}
|
||||
@@ -177,6 +189,7 @@
|
||||
|
||||
#pragma mark FLEXIvar
|
||||
@implementation FLEXIvar (UIKitHelpers)
|
||||
FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
- (BOOL)isEditable {
|
||||
const FLEXTypeEncoding *typeEncoding = self.typeEncoding.UTF8String;
|
||||
@@ -198,10 +211,13 @@
|
||||
- (NSString *)previewWithTarget:(id)object {
|
||||
if (object_isClass(object)) {
|
||||
return self.details;
|
||||
} else if (self.defaults.wantsDynamicPreviews) {
|
||||
return [FLEXRuntimeUtility
|
||||
summaryForObject:[self currentValueWithTarget:object]
|
||||
];
|
||||
}
|
||||
return [FLEXRuntimeUtility
|
||||
summaryForObject:[self currentValueWithTarget:object]
|
||||
];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIViewController *)viewerWithTarget:(id)object {
|
||||
@@ -222,7 +238,7 @@
|
||||
|
||||
// Could use .isEditable here, but we use .tag for speed since it is cached
|
||||
if ([self getPotentiallyUnboxedValue:object]) {
|
||||
if (self.tag) {
|
||||
if (self.defaults.isEditable) {
|
||||
// Editable non-nil value, both
|
||||
return UITableViewCellAccessoryDetailDisclosureButton;
|
||||
} else {
|
||||
@@ -230,7 +246,7 @@
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
} else {
|
||||
if (self.tag) {
|
||||
if (self.defaults.isEditable) {
|
||||
// Editable nil value, just (i)
|
||||
return UITableViewCellAccessoryDetailButton;
|
||||
} else {
|
||||
@@ -301,6 +317,7 @@
|
||||
|
||||
#pragma mark FLEXMethod
|
||||
@implementation FLEXMethodBase (UIKitHelpers)
|
||||
FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
- (BOOL)isEditable {
|
||||
return NO;
|
||||
@@ -402,6 +419,7 @@
|
||||
|
||||
#pragma mark FLEXProtocol
|
||||
@implementation FLEXProtocol (UIKitHelpers)
|
||||
FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
- (BOOL)isEditable {
|
||||
return NO;
|
||||
@@ -444,7 +462,7 @@
|
||||
NSString *conformances = [conformanceNames componentsJoinedByString:@"\n"];
|
||||
return @[
|
||||
@"Name", self.name ?: @"",
|
||||
@"Conformances", conformances,
|
||||
@"Conformances", conformances ?: @"",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -475,6 +493,8 @@
|
||||
@synthesize name = _name;
|
||||
@synthesize tag = _tag;
|
||||
|
||||
FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
+ (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes {
|
||||
return [classes flex_mapped:^id(Class cls, NSUInteger idx) {
|
||||
return [FLEXStaticMetadata_Class withClass:cls];
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// NSArray+Functional.h
|
||||
// NSArray+FLEX.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 9/25/19.
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// NSArray+Functional.m
|
||||
// NSArray+FLEX.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 9/25/19.
|
||||
// Copyright © 2019 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
|
||||
#define FLEXArrayClassIsMutable(me) ([[self class] isSubclassOfClass:[NSMutableArray class]])
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@interface NSDictionary (ObjcRuntime)
|
||||
|
||||
/// \c kFLEXPropertyAttributeKeyTypeEncoding is the only required key.
|
||||
/// Keys representing a boolean value should have a value of \c @YES instead of an empty string.
|
||||
/// Keys representing a boolean value should have a value of \c YES instead of an empty string.
|
||||
- (NSString *)propertyAttributesString;
|
||||
|
||||
+ (instancetype)attributesDictionaryForProperty:(objc_property_t)property;
|
||||
|
||||
+14
-1
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// NSObject+Reflection.h
|
||||
// NSObject+FLEX_Reflection.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
@@ -25,6 +25,19 @@ NSArray<Class> *FLEXGetAllSubclasses(_Nullable Class cls, BOOL includeSelf);
|
||||
NSArray<Class> *FLEXGetClassHierarchy(_Nullable Class cls, BOOL includeSelf);
|
||||
NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(_Nullable Class cls);
|
||||
|
||||
NSArray<FLEXIvar *> *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);
|
||||
/// @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);
|
||||
/// @param cls a class object to get all instance and class methods.
|
||||
NSArray<FLEXMethod *> *FLEXGetAllInstanceAndClassMethods(_Nullable Class cls);
|
||||
|
||||
|
||||
|
||||
#pragma mark Reflection
|
||||
@interface NSObject (Reflection)
|
||||
+55
-63
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// NSObject+Reflection.m
|
||||
// NSObject+FLEX_Reflection.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
@@ -7,7 +7,7 @@
|
||||
// Copyright (c) 2015 Tanner Bennett. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSObject+Reflection.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "FLEXClassBuilder.h"
|
||||
#import "FLEXMirror.h"
|
||||
#import "FLEXProperty.h"
|
||||
@@ -15,7 +15,7 @@
|
||||
#import "FLEXIvar.h"
|
||||
#import "FLEXProtocol.h"
|
||||
#import "FLEXPropertyAttributes.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
|
||||
@@ -101,13 +101,52 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
|
||||
}];
|
||||
}
|
||||
|
||||
NSArray<FLEXIvar *> *FLEXGetAllIvars(_Nullable Class cls) {
|
||||
if (!cls) return nil;
|
||||
|
||||
unsigned int ivcount;
|
||||
Ivar *objcivars = class_copyIvarList(cls, &ivcount);
|
||||
NSArray *ivars = [NSArray flex_forEachUpTo:ivcount map:^id(NSUInteger i) {
|
||||
return [FLEXIvar ivar:objcivars[i]];
|
||||
}];
|
||||
|
||||
free(objcivars);
|
||||
return ivars;
|
||||
}
|
||||
|
||||
NSArray<FLEXProperty *> *FLEXGetAllProperties(_Nullable Class cls) {
|
||||
if (!cls) return nil;
|
||||
|
||||
unsigned int pcount;
|
||||
objc_property_t *objcproperties = class_copyPropertyList(cls, &pcount);
|
||||
NSArray *properties = [NSArray flex_forEachUpTo:pcount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:objcproperties[i] onClass:cls];
|
||||
}];
|
||||
|
||||
free(objcproperties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
|
||||
if (!cls) return nil;
|
||||
|
||||
unsigned int mcount;
|
||||
Method *objcmethods = class_copyMethodList(cls, &mcount);
|
||||
NSArray *methods = [NSArray flex_forEachUpTo:mcount map:^id(NSUInteger i) {
|
||||
return [FLEXMethod method:objcmethods[i] isInstanceMethod:instance];
|
||||
}];
|
||||
|
||||
free(objcmethods);
|
||||
return methods;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark NSProxy
|
||||
|
||||
@interface NSProxy (AnyObjectAdditions) @end
|
||||
@implementation NSProxy (AnyObjectAdditions)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_TESTING()
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
// We need to get all of the methods in this file and add them to NSProxy.
|
||||
// To do this we we need the class itself and it's metaclass.
|
||||
// Edit: also add them to Swift._SwiftObject
|
||||
@@ -135,6 +174,11 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
|
||||
FLEXClassBuilder *swiftObjectMeta = [FLEXClassBuilder builderForClass:SwiftObject_meta];
|
||||
[swiftObject addMethods:instanceMethods];
|
||||
[swiftObjectMeta addMethods:classMethods];
|
||||
|
||||
// So we can put Swift objects into dictionaries...
|
||||
[swiftObjectMeta addMethods:@[
|
||||
[NSObject flex_classMethodNamed:@"copyWithZone:"]]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,41 +236,17 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
|
||||
@implementation NSObject (Methods)
|
||||
|
||||
+ (NSArray<FLEXMethod *> *)flex_allMethods {
|
||||
NSMutableArray *instanceMethods = (id)self.flex_allInstanceMethods;
|
||||
NSMutableArray *instanceMethods = self.flex_allInstanceMethods.mutableCopy;
|
||||
[instanceMethods addObjectsFromArray:self.flex_allClassMethods];
|
||||
return instanceMethods;
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXMethod *> *)flex_allInstanceMethods {
|
||||
unsigned int mcount;
|
||||
Method *objcmethods = class_copyMethodList([self class], &mcount);
|
||||
|
||||
NSMutableArray *methods = [NSMutableArray new];
|
||||
for (int i = 0; i < mcount; i++) {
|
||||
FLEXMethod *m = [FLEXMethod method:objcmethods[i] isInstanceMethod:YES];
|
||||
if (m) {
|
||||
[methods addObject:m];
|
||||
}
|
||||
}
|
||||
|
||||
free(objcmethods);
|
||||
return methods;
|
||||
return FLEXGetAllMethods(self, YES);
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXMethod *> *)flex_allClassMethods {
|
||||
unsigned int mcount;
|
||||
Method *objcmethods = class_copyMethodList(self.flex_metaclass, &mcount);
|
||||
|
||||
NSMutableArray *methods = [NSMutableArray new];
|
||||
for (int i = 0; i < mcount; i++) {
|
||||
FLEXMethod *m = [FLEXMethod method:objcmethods[i] isInstanceMethod:NO];
|
||||
if (m) {
|
||||
[methods addObject:m];
|
||||
}
|
||||
}
|
||||
|
||||
free(objcmethods);
|
||||
return methods;
|
||||
return FLEXGetAllMethods(self.flex_metaclass, NO);
|
||||
}
|
||||
|
||||
+ (FLEXMethod *)flex_methodNamed:(NSString *)name {
|
||||
@@ -292,16 +312,7 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
|
||||
@implementation NSObject (Ivars)
|
||||
|
||||
+ (NSArray<FLEXIvar *> *)flex_allIvars {
|
||||
unsigned int ivcount;
|
||||
Ivar *objcivars = class_copyIvarList([self class], &ivcount);
|
||||
|
||||
NSMutableArray *ivars = [NSMutableArray new];
|
||||
for (int i = 0; i < ivcount; i++) {
|
||||
[ivars addObject:[FLEXIvar ivar:objcivars[i]]];
|
||||
}
|
||||
|
||||
free(objcivars);
|
||||
return ivars;
|
||||
return FLEXGetAllIvars(self);
|
||||
}
|
||||
|
||||
+ (FLEXIvar *)flex_ivarNamed:(NSString *)name {
|
||||
@@ -373,36 +384,17 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
|
||||
@implementation NSObject (Properties)
|
||||
|
||||
+ (NSArray<FLEXProperty *> *)flex_allProperties {
|
||||
NSMutableArray *instanceProperties = (id)self.flex_allInstanceProperties;
|
||||
NSMutableArray *instanceProperties = self.flex_allInstanceProperties.mutableCopy;
|
||||
[instanceProperties addObjectsFromArray:self.flex_allClassProperties];
|
||||
return instanceProperties;
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXProperty *> *)flex_allInstanceProperties {
|
||||
unsigned int pcount;
|
||||
objc_property_t *objcproperties = class_copyPropertyList(self, &pcount);
|
||||
|
||||
NSMutableArray *properties = [NSMutableArray new];
|
||||
for (int i = 0; i < pcount; i++) {
|
||||
[properties addObject:[FLEXProperty property:objcproperties[i] onClass:self]];
|
||||
}
|
||||
|
||||
free(objcproperties);
|
||||
return properties;
|
||||
return FLEXGetAllProperties(self);
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXProperty *> *)flex_allClassProperties {
|
||||
Class metaclass = self.flex_metaclass;
|
||||
unsigned int pcount;
|
||||
objc_property_t *objcproperties = class_copyPropertyList(metaclass, &pcount);
|
||||
|
||||
NSMutableArray *properties = [NSMutableArray new];
|
||||
for (int i = 0; i < pcount; i++) {
|
||||
[properties addObject:[FLEXProperty property:objcproperties[i] onClass:metaclass]];
|
||||
}
|
||||
|
||||
free(objcproperties);
|
||||
return properties;
|
||||
return FLEXGetAllProperties(self.flex_metaclass);
|
||||
}
|
||||
|
||||
+ (FLEXProperty *)flex_propertyNamed:(NSString *)name {
|
||||
@@ -14,6 +14,8 @@
|
||||
@property (nonatomic, readonly) BOOL flex_typeIsConst;
|
||||
/// @return the first char in the type encoding that is not the const specifier
|
||||
@property (nonatomic, readonly) FLEXTypeEncoding flex_firstNonConstType;
|
||||
/// @return the first char in the type encoding after the pointer specifier, if it is a pointer
|
||||
@property (nonatomic, readonly) FLEXTypeEncoding flex_pointeeType;
|
||||
/// @return whether this type is an objc object of any kind, even if it's const
|
||||
@property (nonatomic, readonly) BOOL flex_typeIsObjectOrClass;
|
||||
/// @return the class named in this type encoding if it is of the form \c @"MYClass"
|
||||
|
||||
@@ -75,6 +75,16 @@
|
||||
return [self characterAtIndex:(self.flex_typeIsConst ? 1 : 0)];
|
||||
}
|
||||
|
||||
- (FLEXTypeEncoding)flex_pointeeType {
|
||||
if (!self.length) return FLEXTypeEncodingNull;
|
||||
|
||||
if (self.flex_firstNonConstType == FLEXTypeEncodingPointer) {
|
||||
return [self characterAtIndex:(self.flex_typeIsConst ? 2 : 1)];
|
||||
}
|
||||
|
||||
return FLEXTypeEncodingNull;
|
||||
}
|
||||
|
||||
- (BOOL)flex_typeIsObjectOrClass {
|
||||
FLEXTypeEncoding type = self.flex_firstNonConstType;
|
||||
return type == FLEXTypeEncodingObjcObject || type == FLEXTypeEncodingObjcClass;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
@interface NSString (Utilities)
|
||||
|
||||
/// A dictionary of property attributes if the receiver is a valid property attributes string.
|
||||
/// Values are either a string or \c @YES. Boolean attributes which are false will not be
|
||||
/// Values are either a string or \c YES. Boolean attributes which are false will not be
|
||||
/// present in the dictionary. See this link on how to construct a proper attributes string:
|
||||
/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
|
||||
///
|
||||
|
||||
+1
@@ -8,6 +8,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void (^VoidBlock)(void);
|
||||
|
||||
@interface NSTimer (Blocks)
|
||||
|
||||
+ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
// Created by Tanner on 3/23/17.
|
||||
//
|
||||
|
||||
#import "NSTimer+Blocks.h"
|
||||
#import "NSTimer+FLEX.h"
|
||||
|
||||
@interface Block : NSObject
|
||||
- (void)invoke;
|
||||
@@ -8,31 +8,37 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Only use these if the getters and setters aren't good enough for whatever reaso
|
||||
// Only use these if the getters and setters aren't good enough for whatever reason
|
||||
extern NSString * const kFLEXDefaultsToolbarTopMarginKey;
|
||||
extern NSString * const kFLEXDefaultsiOSPersistentOSLogKey;
|
||||
extern NSString * const kFLEXDefaultsHidePropertyIvarsKey;
|
||||
extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
|
||||
extern NSString * const kFLEXDefaultsHideMethodOverridesKey;
|
||||
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
|
||||
extern NSString * const kFLEXDefaultsNetworkHostBlacklistKey;
|
||||
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
|
||||
extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
|
||||
|
||||
/// All BOOL preferences are NO by default
|
||||
@interface NSUserDefaults (FLEX)
|
||||
|
||||
- (void)toggleBoolForKey:(NSString *)key;
|
||||
|
||||
@property (nonatomic) double flex_toolbarTopMargin;
|
||||
|
||||
/// NO by default
|
||||
@property (nonatomic) BOOL flex_cacheOSLogMessages;
|
||||
|
||||
/// NO by default
|
||||
@property (nonatomic) BOOL flex_explorerHidesPropertyIvars;
|
||||
/// NO by default
|
||||
@property (nonatomic) BOOL flex_explorerHidesPropertyMethods;
|
||||
/// NO by default
|
||||
@property (nonatomic) BOOL flex_explorerShowsMethodOverrides;
|
||||
|
||||
// Not actually stored in defaults, but written to a file
|
||||
@property (nonatomic) NSArray<NSString *> *flex_networkHostBlacklist;
|
||||
|
||||
/// Whether or not to register the object explorer as a JSON viewer on launch
|
||||
@property (nonatomic) BOOL flex_registerDictionaryJSONViewerOnLaunch;
|
||||
|
||||
/// Disable os_log and re-enable ASL. May break Console.app output.
|
||||
@property (nonatomic) BOOL flex_disableOSLog;
|
||||
@property (nonatomic) BOOL flex_cacheOSLogMessages;
|
||||
|
||||
@property (nonatomic) BOOL flex_explorerHidesPropertyIvars;
|
||||
@property (nonatomic) BOOL flex_explorerHidesPropertyMethods;
|
||||
@property (nonatomic) BOOL flex_explorerShowsMethodOverrides;
|
||||
@property (nonatomic) BOOL flex_explorerHidesVariablePreviews;
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,17 +13,22 @@ NSString * const kFLEXDefaultsiOSPersistentOSLogKey = @"com.flipborad.flex.enabl
|
||||
NSString * const kFLEXDefaultsHidePropertyIvarsKey = @"com.flipboard.FLEX.hide_property_ivars";
|
||||
NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide_property_methods";
|
||||
NSString * const kFLEXDefaultsHideMethodOverridesKey = @"com.flipboard.FLEX.hide_method_overrides";
|
||||
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
|
||||
NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.network_host_blacklist";
|
||||
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
|
||||
NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.view_json_as_object";
|
||||
|
||||
#define FLEXDefaultsPathForFile(name) ({ \
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains( \
|
||||
NSLibraryDirectory, NSUserDomainMask, NO \
|
||||
NSLibraryDirectory, NSUserDomainMask, YES \
|
||||
); \
|
||||
[paths[0] stringByAppendingPathComponent:@"Preferences"]; \
|
||||
})
|
||||
|
||||
@implementation NSUserDefaults (FLEX)
|
||||
|
||||
#pragma mark Internal
|
||||
|
||||
/// @param filename the name of a plist file without any extension
|
||||
- (NSString *)flex_defaultsPathForFile:(NSString *)filename {
|
||||
filename = [filename stringByAppendingPathExtension:@"plist"];
|
||||
@@ -35,11 +40,15 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
|
||||
return [preferences stringByAppendingPathComponent:filename];
|
||||
}
|
||||
|
||||
#pragma mark Helper
|
||||
|
||||
- (void)toggleBoolForKey:(NSString *)key {
|
||||
[self setBool:![self boolForKey:key] forKey:key];
|
||||
[NSNotificationCenter.defaultCenter postNotificationName:key object:nil];
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
- (double)flex_toolbarTopMargin {
|
||||
if ([self objectForKey:kFLEXDefaultsToolbarTopMarginKey]) {
|
||||
return [self doubleForKey:kFLEXDefaultsToolbarTopMarginKey];
|
||||
@@ -52,6 +61,37 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
|
||||
[self setDouble:margin forKey:kFLEXDefaultsToolbarTopMarginKey];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)flex_networkHostBlacklist {
|
||||
return [NSArray arrayWithContentsOfFile:[
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
|
||||
]] ?: @[];
|
||||
}
|
||||
|
||||
- (void)setFlex_networkHostBlacklist:(NSArray<NSString *> *)blacklist {
|
||||
NSParameterAssert(blacklist);
|
||||
[blacklist writeToFile:[
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
|
||||
] atomically:YES];
|
||||
}
|
||||
|
||||
- (BOOL)flex_registerDictionaryJSONViewerOnLaunch {
|
||||
return [self boolForKey:kFLEXDefaultsRegisterJSONExplorerKey];
|
||||
}
|
||||
|
||||
- (void)setFlex_registerDictionaryJSONViewerOnLaunch:(BOOL)enable {
|
||||
[self setBool:enable forKey:kFLEXDefaultsRegisterJSONExplorerKey];
|
||||
}
|
||||
|
||||
#pragma mark System Log
|
||||
|
||||
- (BOOL)flex_disableOSLog {
|
||||
return [self boolForKey:kFLEXDefaultsDisableOSLogForceASLKey];
|
||||
}
|
||||
|
||||
- (void)setFlex_disableOSLog:(BOOL)disable {
|
||||
[self setBool:disable forKey:kFLEXDefaultsDisableOSLogForceASLKey];
|
||||
}
|
||||
|
||||
- (BOOL)flex_cacheOSLogMessages {
|
||||
return [self boolForKey:kFLEXDefaultsiOSPersistentOSLogKey];
|
||||
}
|
||||
@@ -64,6 +104,8 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
|
||||
];
|
||||
}
|
||||
|
||||
#pragma mark Object Explorer
|
||||
|
||||
- (BOOL)flex_explorerHidesPropertyIvars {
|
||||
return [self boolForKey:kFLEXDefaultsHidePropertyIvarsKey];
|
||||
}
|
||||
@@ -100,17 +142,16 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
|
||||
];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)flex_networkHostBlacklist {
|
||||
return [NSArray arrayWithContentsOfFile:[
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
|
||||
]] ?: @[];
|
||||
- (BOOL)flex_explorerHidesVariablePreviews {
|
||||
return [self boolForKey:kFLEXDefaultsHideVariablePreviewsKey];
|
||||
}
|
||||
|
||||
- (void)setFlex_networkHostBlacklist:(NSArray<NSString *> *)blacklist {
|
||||
NSParameterAssert(blacklist);
|
||||
[blacklist writeToFile:[
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
|
||||
] atomically:YES];
|
||||
- (void)setFlex_explorerHidesVariablePreviews:(BOOL)hide {
|
||||
[self setBool:hide forKey:kFLEXDefaultsHideVariablePreviewsKey];
|
||||
[NSNotificationCenter.defaultCenter
|
||||
postNotificationName:kFLEXDefaultsHideVariablePreviewsKey
|
||||
object:nil
|
||||
];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,7 +29,9 @@ typedef FLEXAlertAction *(^FLEXAlertActionHandler)(void(^handler)(NSArray<NSStri
|
||||
/// Construct and display an alert
|
||||
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController;
|
||||
/// Construct and display an action sheet-style alert
|
||||
+ (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController;
|
||||
+ (void)makeSheet:(FLEXAlertBuilder)block
|
||||
showFrom:(UIViewController *)viewController
|
||||
source:(id)viewOrBarItem;
|
||||
|
||||
/// Construct an alert
|
||||
+ (UIAlertController *)makeAlert:(FLEXAlertBuilder)block;
|
||||
|
||||
@@ -62,17 +62,42 @@ NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAle
|
||||
return alert._controller;
|
||||
}
|
||||
|
||||
+ (void)make:(FLEXAlertBuilder)block withStyle:(UIAlertControllerStyle)style showFrom:(UIViewController *)viewController {
|
||||
+ (void)make:(FLEXAlertBuilder)block
|
||||
withStyle:(UIAlertControllerStyle)style
|
||||
showFrom:(UIViewController *)viewController
|
||||
source:(id)viewOrBarItem {
|
||||
UIAlertController *alert = [self make:block withStyle:style];
|
||||
if ([viewOrBarItem isKindOfClass:[UIBarButtonItem class]]) {
|
||||
alert.popoverPresentationController.barButtonItem = viewOrBarItem;
|
||||
} else if ([viewOrBarItem isKindOfClass:[UIView class]]) {
|
||||
alert.popoverPresentationController.sourceView = viewOrBarItem;
|
||||
alert.popoverPresentationController.sourceRect = [viewOrBarItem bounds];
|
||||
} else if (viewOrBarItem) {
|
||||
NSParameterAssert(
|
||||
[viewOrBarItem isKindOfClass:[UIBarButtonItem class]] ||
|
||||
[viewOrBarItem isKindOfClass:[UIView class]] ||
|
||||
!viewOrBarItem
|
||||
);
|
||||
}
|
||||
[viewController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController {
|
||||
[self make:block withStyle:UIAlertControllerStyleAlert showFrom:viewController];
|
||||
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
|
||||
[self make:block withStyle:UIAlertControllerStyleAlert showFrom:controller source:nil];
|
||||
}
|
||||
|
||||
+ (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController {
|
||||
[self make:block withStyle:UIAlertControllerStyleActionSheet showFrom:viewController];
|
||||
+ (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
|
||||
[self make:block withStyle:UIAlertControllerStyleActionSheet showFrom:controller source:nil];
|
||||
}
|
||||
|
||||
/// Construct and display an action sheet-style alert
|
||||
+ (void)makeSheet:(FLEXAlertBuilder)block
|
||||
showFrom:(UIViewController *)controller
|
||||
source:(id)viewOrBarItem {
|
||||
[self make:block
|
||||
withStyle:UIAlertControllerStyleActionSheet
|
||||
showFrom:controller
|
||||
source:viewOrBarItem];
|
||||
}
|
||||
|
||||
+ (UIAlertController *)makeAlert:(FLEXAlertBuilder)block {
|
||||
@@ -88,7 +113,7 @@ NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAle
|
||||
- (FLEXAlertStringProperty)title {
|
||||
return ^FLEXAlert *(NSString *title) {
|
||||
if (self._controller.title) {
|
||||
self._controller.title = [self._controller.title stringByAppendingString:title];
|
||||
self._controller.title = [self._controller.title stringByAppendingString:title ?: @""];
|
||||
} else {
|
||||
self._controller.title = title;
|
||||
}
|
||||
@@ -99,7 +124,7 @@ NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAle
|
||||
- (FLEXAlertStringProperty)message {
|
||||
return ^FLEXAlert *(NSString *message) {
|
||||
if (self._controller.message) {
|
||||
self._controller.message = [self._controller.message stringByAppendingString:message];
|
||||
self._controller.message = [self._controller.message stringByAppendingString:message ?: @""];
|
||||
} else {
|
||||
self._controller.message = message;
|
||||
}
|
||||
@@ -141,7 +166,7 @@ NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAle
|
||||
return ^FLEXAlertAction *(NSString *title) {
|
||||
FLEXAlertActionMutationAssertion();
|
||||
if (self._title) {
|
||||
self._title = [self._title stringByAppendingString:title];
|
||||
self._title = [self._title stringByAppendingString:title ?: @""];
|
||||
} else {
|
||||
self._title = title;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXHeapEnumerator.h"
|
||||
#import "FLEXObjcInternal.h"
|
||||
#import <malloc/malloc.h>
|
||||
#import <mach/mach.h>
|
||||
#import <objc/runtime.h>
|
||||
@@ -84,26 +85,14 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
|
||||
block(object, actualClass);
|
||||
lock_zone(zone);
|
||||
};
|
||||
|
||||
// The largest realistic memory address varies by platform.
|
||||
// Only 48 bits are used by 64 bit machines while
|
||||
// 32 bit machines use all bits.
|
||||
//
|
||||
// __LP64__ is defined as 1 for both arm64 and x86_64
|
||||
// via: clang -dM -arch [arm64|x86_64] -E -x c /dev/null | grep LP
|
||||
#if __LP64__
|
||||
static uintptr_t MAX_REALISTIC_ADDRESS = 0x0000FFFFFFFFFFFF;
|
||||
BOOL lockZoneValid = lock_zone != nil && (uintptr_t)lock_zone < MAX_REALISTIC_ADDRESS;
|
||||
BOOL unlockZoneValid = unlock_zone != nil && (uintptr_t)unlock_zone < MAX_REALISTIC_ADDRESS;
|
||||
#else
|
||||
BOOL lockZoneValid = lock_zone != nil;
|
||||
BOOL unlockZoneValid = unlock_zone != nil;
|
||||
#endif
|
||||
|
||||
BOOL lockZoneValid = FLEXPointerIsReadable(lock_zone);
|
||||
BOOL unlockZoneValid = FLEXPointerIsReadable(unlock_zone);
|
||||
|
||||
// There is little documentation on when and why
|
||||
// any of these function pointers might be NULL
|
||||
// or garbage, so we resort to checking for NULL
|
||||
// and impossible memory addresses at least
|
||||
// and whether the pointer is readable
|
||||
if (introspection->enumerator && lockZoneValid && unlockZoneValid) {
|
||||
lock_zone(zone);
|
||||
introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
|
||||
|
||||
@@ -9,8 +9,18 @@
|
||||
#ifndef FLEXMacros_h
|
||||
#define FLEXMacros_h
|
||||
|
||||
// Used to prevent loading of pre-registered shortcuts and runtime categories in a test environment
|
||||
#define FLEX_EXIT_IF_TESTING() if (NSClassFromString(@"XCTest")) return;
|
||||
#define flex_keywordify class NSObject;
|
||||
#define ctor flex_keywordify __attribute__((constructor)) void __flex_ctor_##__LINE__()
|
||||
#define dtor flex_keywordify __attribute__((destructor)) void __flex_dtor_##__LINE__()
|
||||
|
||||
// A macro to check if we are running in a test environment
|
||||
#define FLEX_IS_TESTING() (NSClassFromString(@"XCTest") != nil)
|
||||
|
||||
/// Whether we want the majority of constructors to run upon load or not.
|
||||
extern BOOL FLEXConstructorsShouldRun();
|
||||
|
||||
/// A macro to return from the current procedure if we don't want to run constructors
|
||||
#define FLEX_EXIT_IF_NO_CTORS() if (!FLEXConstructorsShouldRun()) return;
|
||||
|
||||
/// Rounds down to the nearest "point" coordinate
|
||||
NS_INLINE CGFloat FLEXFloor(CGFloat x) {
|
||||
@@ -71,4 +81,8 @@ NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
|
||||
stringWithFormat:(count == 1 ? singularFormat : pluralFormat), @(count) \
|
||||
]
|
||||
|
||||
#define flex_dispatch_after(nSeconds, onQueue, block) \
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, \
|
||||
(int64_t)(nSeconds * NSEC_PER_SEC)), onQueue, block)
|
||||
|
||||
#endif /* FLEXMacros_h */
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#import <objc/runtime.h>
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "NSArray+Functional.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "UIFont+FLEX.h"
|
||||
#import "NSMapTable+FLEX_Subscripting.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
@@ -11,8 +11,22 @@
|
||||
#import "FLEXResources.h"
|
||||
#import "FLEXWindow.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <zlib.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <zlib.h>
|
||||
|
||||
BOOL FLEXConstructorsShouldRun() {
|
||||
static BOOL _FLEXConstructorsShouldRun_storage = YES;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *key = @"FLEX_SKIP_INIT";
|
||||
if (getenv(key.UTF8String) || [NSUserDefaults.standardUserDefaults boolForKey:key]) {
|
||||
_FLEXConstructorsShouldRun_storage = NO;
|
||||
}
|
||||
});
|
||||
|
||||
return _FLEXConstructorsShouldRun_storage;
|
||||
}
|
||||
|
||||
@implementation FLEXUtility
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user