Compare commits
32 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 |
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -101,22 +101,22 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
- (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 {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
[super viewDidLoad];
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
self.showSearchBarInitially = YES;
|
||||
self.searchBarDebounceInterval = kFLEXDebounceInstant;
|
||||
self.showsCarousel = YES;
|
||||
self.carousel.items = @[@"A→Z", @"Count", @"Size"];
|
||||
|
||||
@@ -125,14 +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;
|
||||
@@ -145,7 +145,7 @@
|
||||
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%lu)",
|
||||
className, (unsigned long)classes.count
|
||||
];
|
||||
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -158,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.
|
||||
@@ -167,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]];
|
||||
@@ -183,7 +183,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tryClass = class_getSuperclass(tryClass);
|
||||
}
|
||||
}];
|
||||
@@ -206,7 +206,7 @@
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
}
|
||||
|
||||
@@ -224,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]];
|
||||
@@ -241,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
@interface FLEXFileBrowserTableViewCell : UITableViewCell
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
|
||||
FLEXFileBrowserSortAttributeNone = 0,
|
||||
FLEXFileBrowserSortAttributeName,
|
||||
FLEXFileBrowserSortAttributeCreationDate,
|
||||
};
|
||||
|
||||
@interface FLEXFileBrowserController () <FLEXFileBrowserSearchOperationDelegate>
|
||||
|
||||
@property (nonatomic, copy) NSString *path;
|
||||
@@ -27,6 +33,7 @@
|
||||
@property (nonatomic) NSNumber *searchPathsSize;
|
||||
@property (nonatomic) NSOperationQueue *operationQueue;
|
||||
@property (nonatomic) UIDocumentInteractionController *documentController;
|
||||
@property (nonatomic) FLEXFileBrowserSortAttribute sortAttribute;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#ifndef ActivityStreamSPI_h
|
||||
#define ActivityStreamSPI_h
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include <sys/time.h>
|
||||
// #include <xpc/xpc.h>
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
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);
|
||||
@@ -56,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;
|
||||
@@ -96,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 {
|
||||
@@ -114,9 +114,9 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.title = @"Waiting for Logs...";
|
||||
|
||||
|
||||
// Toolbar buttons //
|
||||
|
||||
|
||||
UIBarButtonItem *scrollDown = [UIBarButtonItem
|
||||
itemWithImage:FLEXResources.scrollToBottomIcon
|
||||
target:self
|
||||
@@ -127,7 +127,7 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
target:self
|
||||
action:@selector(showLogSettings)
|
||||
];
|
||||
|
||||
|
||||
[self addToolbarItems:@[scrollDown, settings]];
|
||||
}
|
||||
|
||||
@@ -138,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];
|
||||
}
|
||||
|
||||
@@ -198,27 +202,27 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
|
||||
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;
|
||||
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
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;
|
||||
|
||||
@@ -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,15 +157,15 @@
|
||||
}
|
||||
} 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 registerDefaultSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXFileBrowserController class]];
|
||||
} description:@"Toggle file browser menu"];
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,11 @@
|
||||
// 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) {
|
||||
[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],
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#pragma mark Overrides
|
||||
|
||||
+ (instancetype)forObject:(NSBundle *)bundle {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
return [self forObject:bundle additionalRows:@[
|
||||
[FLEXActionShortcut
|
||||
title:@"Browse Bundle Directory" subtitle:nil
|
||||
@@ -30,7 +31,10 @@
|
||||
],
|
||||
[FLEXActionShortcut title:@"Browse Bundle as Database…" subtitle:nil
|
||||
selectionHandler:^(UIViewController *host, NSBundle *bundle) {
|
||||
[self promptToExportBundleAsDatabase:bundle host:host];
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
[strongSelf promptToExportBundleAsDatabase:bundle host:host];
|
||||
}
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(NSBundle *bundle) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
@@ -62,22 +66,22 @@
|
||||
|
||||
+ (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]
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#import "FLEXShortcutsSection.h"
|
||||
|
||||
@interface FLEXShortcutsFactory (UIApplication) @end
|
||||
|
||||
@interface FLEXShortcutsFactory (Views) @end
|
||||
|
||||
@interface FLEXShortcutsFactory (ViewControllers) @end
|
||||
|
||||
@@ -11,11 +11,36 @@
|
||||
#import "FLEXRuntimeUtility.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.
|
||||
//
|
||||
@@ -106,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);
|
||||
|
||||
@@ -126,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(@[
|
||||
@@ -135,7 +160,7 @@
|
||||
]).forClass(UIImage.class);
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
self.append.properties(@[@"symbolImage"]);
|
||||
self.append.properties(@[@"symbolImage"]).forClass(UIImage.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,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",
|
||||
@@ -161,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);
|
||||
}
|
||||
|
||||
@@ -172,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));
|
||||
|
||||
@@ -192,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"));
|
||||
}
|
||||
|
||||
@@ -202,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(@[
|
||||
@@ -237,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.
|
||||
|
||||
@@ -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,29 +13,10 @@
|
||||
#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 {
|
||||
|
||||
@@ -156,7 +156,7 @@ FLEXObjectExplorerDefaultsImpl
|
||||
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] : @"",
|
||||
]];
|
||||
}
|
||||
@@ -462,7 +462,7 @@ FLEXObjectExplorerDefaultsImpl
|
||||
NSString *conformances = [conformanceNames componentsJoinedByString:@"\n"];
|
||||
return @[
|
||||
@"Name", self.name ?: @"",
|
||||
@"Conformances", conformances,
|
||||
@"Conformances", conformances ?: @"",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -34,6 +34,8 @@ NSArray<FLEXProperty *> *FLEXGetAllProperties(_Nullable Class cls);
|
||||
/// @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);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
|
||||
@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
|
||||
|
||||
@@ -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
|
||||
///
|
||||
|
||||
@@ -113,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;
|
||||
}
|
||||
@@ -124,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;
|
||||
}
|
||||
@@ -166,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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -12,10 +12,16 @@
|
||||
|
||||
@property (nonatomic, readonly, class) FLEXKeyboardShortcutManager *sharedManager;
|
||||
|
||||
/// @param key A single character string matching a key on the keyboard
|
||||
/// @param modifiers Modifier keys such as shift, command, or alt/option
|
||||
/// @param action The block to run on the main thread when the key & modifier combination is recognized.
|
||||
/// @param description Shown the the keyboard shortcut help menu, which is accessed via the '?' key.
|
||||
/// @param allowOverride Allow registering even if there's an existing action associated with that key/modifier.
|
||||
- (void)registerSimulatorShortcutWithKey:(NSString *)key
|
||||
modifiers:(UIKeyModifierFlags)modifiers
|
||||
action:(dispatch_block_t)action
|
||||
description:(NSString *)description;
|
||||
description:(NSString *)description
|
||||
allowOverride:(BOOL)allowOverride;
|
||||
|
||||
@property (nonatomic, getter=isEnabled) BOOL enabled;
|
||||
@property (nonatomic, readonly) NSString *keyboardShortcutsDescription;
|
||||
|
||||
@@ -212,9 +212,14 @@
|
||||
- (void)registerSimulatorShortcutWithKey:(NSString *)key
|
||||
modifiers:(UIKeyModifierFlags)modifiers
|
||||
action:(dispatch_block_t)action
|
||||
description:(NSString *)description {
|
||||
description:(NSString *)description
|
||||
allowOverride:(BOOL)allowOverride {
|
||||
FLEXKeyInput *keyInput = [FLEXKeyInput keyInputForKey:key flags:modifiers helpDescription:description];
|
||||
[self.actionsForKeyInputs setObject:action forKey:keyInput];
|
||||
if (!allowOverride && self.actionsForKeyInputs[keyInput] != nil) {
|
||||
return;
|
||||
} else {
|
||||
[self.actionsForKeyInputs setObject:action forKey:keyInput];
|
||||
}
|
||||
}
|
||||
|
||||
static const long kFLEXControlKeyCode = 0xe0;
|
||||
|
||||
@@ -11,6 +11,56 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// The macros below are copied straight from
|
||||
// objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with
|
||||
// as few modifications as possible. Changes are noted in boxed comments.
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html
|
||||
|
||||
/////////////////////
|
||||
// objc-internal.h //
|
||||
/////////////////////
|
||||
|
||||
#if __LP64__
|
||||
#define OBJC_HAVE_TAGGED_POINTERS 1
|
||||
#endif
|
||||
|
||||
#if OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
#if TARGET_OS_OSX && __x86_64__
|
||||
// 64-bit Mac - tag bit is LSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 0
|
||||
#else
|
||||
// Everything else - tag bit is MSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 1
|
||||
#endif
|
||||
|
||||
#if OBJC_MSB_TAGGED_POINTERS
|
||||
# define _OBJC_TAG_MASK (1UL<<63)
|
||||
# define _OBJC_TAG_EXT_MASK (0xfUL<<60)
|
||||
#else
|
||||
# define _OBJC_TAG_MASK 1UL
|
||||
# define _OBJC_TAG_EXT_MASK 0xfUL
|
||||
#endif
|
||||
|
||||
#endif // OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
//////////////////////////////////////
|
||||
// originally _objc_isTaggedPointer //
|
||||
//////////////////////////////////////
|
||||
NS_INLINE BOOL flex_isTaggedPointer(const void *ptr) {
|
||||
#if OBJC_HAVE_TAGGED_POINTERS
|
||||
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
|
||||
#else
|
||||
return NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
#define FLEXPointerIsTaggedPointer(obj) flex_isTaggedPointer((__bridge void *)obj)
|
||||
|
||||
BOOL FLEXPointerIsReadable(const void * ptr);
|
||||
|
||||
/// @brief Assumes memory is valid and readable.
|
||||
/// @discussion objc-internal.h, objc-private.h, and objc-config.h
|
||||
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005-2007 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
@@ -24,7 +24,7 @@
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
// For vm_region_64
|
||||
#include <mach/mach.h>
|
||||
|
||||
#if __arm64e__
|
||||
#include <ptrauth.h>
|
||||
#endif
|
||||
|
||||
#define ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#define NEVER_INLINE inline __attribute__((noinline))
|
||||
|
||||
@@ -49,35 +53,8 @@
|
||||
// objc-internal.h //
|
||||
/////////////////////
|
||||
|
||||
#if __LP64__
|
||||
#define OBJC_HAVE_TAGGED_POINTERS 1
|
||||
#endif
|
||||
|
||||
#if OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
#if TARGET_OS_OSX && __x86_64__
|
||||
// 64-bit Mac - tag bit is LSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 0
|
||||
#else
|
||||
// Everything else - tag bit is MSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 1
|
||||
#endif
|
||||
|
||||
#if OBJC_MSB_TAGGED_POINTERS
|
||||
# define _OBJC_TAG_MASK (1UL<<63)
|
||||
# define _OBJC_TAG_EXT_MASK (0xfUL<<60)
|
||||
#else
|
||||
# define _OBJC_TAG_MASK 1UL
|
||||
# define _OBJC_TAG_EXT_MASK 0xfUL
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////
|
||||
// originally _objc_isTaggedPointer //
|
||||
//////////////////////////////////////
|
||||
static BOOL flex_isTaggedPointer(const void *ptr) {
|
||||
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// objc-object.h //
|
||||
///////////////////
|
||||
@@ -85,11 +62,11 @@ static BOOL flex_isTaggedPointer(const void *ptr) {
|
||||
////////////////////////////////////////////////
|
||||
// originally objc_object::isExtTaggedPointer //
|
||||
////////////////////////////////////////////////
|
||||
static BOOL flex_isExtTaggedPointer(const void *ptr) {
|
||||
NS_INLINE BOOL flex_isExtTaggedPointer(const void *ptr) {
|
||||
return ((uintptr_t)ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
/////////////////////////////////////
|
||||
// FLEXObjectInternal //
|
||||
@@ -98,11 +75,16 @@ static BOOL flex_isExtTaggedPointer(const void *ptr) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
static BOOL FLEXPointerIsReadable(const void *inPtr) {
|
||||
BOOL FLEXPointerIsReadable(const void *inPtr) {
|
||||
kern_return_t error = KERN_SUCCESS;
|
||||
|
||||
vm_size_t vmsize;
|
||||
#if __arm64e__
|
||||
// On arm64e, we need to strip the PAC from the pointer so the adress is readable
|
||||
vm_address_t address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
|
||||
#else
|
||||
vm_address_t address = (vm_address_t)inPtr;
|
||||
#endif
|
||||
vm_region_basic_info_data_t info;
|
||||
mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
memory_object_name_t object;
|
||||
@@ -127,7 +109,11 @@ static BOOL FLEXPointerIsReadable(const void *inPtr) {
|
||||
// Read the memory
|
||||
vm_offset_t readMem = 0;
|
||||
mach_msg_type_number_t size = 0;
|
||||
#if __arm64e__
|
||||
address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
|
||||
#else
|
||||
address = (vm_address_t)inPtr;
|
||||
#endif
|
||||
error = vm_read(mach_task_self(), address, sizeof(uintptr_t), &readMem, &size);
|
||||
if (error != KERN_SUCCESS) {
|
||||
// vm_read returned an error
|
||||
@@ -194,5 +180,5 @@ BOOL FLEXPointerIsValidObjcObject(const void *ptr) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // End extern "C"
|
||||
|
||||
@@ -546,10 +546,15 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
|
||||
}
|
||||
|
||||
- (BOOL)canScanChar:(char)c {
|
||||
NSScanner *scan = self.scan;
|
||||
if (scan.scanLocation >= scan.string.length) return NO;
|
||||
// By avoiding any ARC calls on these two objects which we know won't be
|
||||
// free'd out from under us, we're making HUGE performance savings in this
|
||||
// parser, because this method is one of the most-used methods of the parser.
|
||||
// This is probably the most performance-critical method in this class.
|
||||
__unsafe_unretained NSScanner *scan = self.scan;
|
||||
__unsafe_unretained NSString *string = scan.string;
|
||||
if (scan.scanLocation >= string.length) return NO;
|
||||
|
||||
return [scan.string characterAtIndex:scan.scanLocation] == c;
|
||||
return [string characterAtIndex:scan.scanLocation] == c;
|
||||
}
|
||||
|
||||
- (BOOL)scanChar:(char)c {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXRuntimeSafety.h"
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
#import "NSString+FLEX.h"
|
||||
#include "FLEXObjcInternal.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
@interface FLEXIvar () {
|
||||
@@ -35,19 +37,20 @@
|
||||
}
|
||||
|
||||
+ (instancetype)named:(NSString *)name onClass:(Class)cls {
|
||||
Ivar ivar = class_getInstanceVariable(cls, name.UTF8String);
|
||||
Ivar _Nullable ivar = class_getInstanceVariable(cls, name.UTF8String);
|
||||
NSAssert(ivar, @"Cannot find ivar with name %@ on class %@", name, cls);
|
||||
return [self ivar:ivar];
|
||||
}
|
||||
|
||||
- (id)initWithIvar:(Ivar)ivar {
|
||||
NSParameterAssert(ivar);
|
||||
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_objc_ivar = ivar;
|
||||
[self examine];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -71,7 +74,7 @@
|
||||
_name = @(ivar_getName(self.objc_ivar) ?: "(nil)");
|
||||
_offset = ivar_getOffset(self.objc_ivar);
|
||||
_typeEncoding = @(ivar_getTypeEncoding(self.objc_ivar) ?: "");
|
||||
|
||||
|
||||
NSString *typeForDetails = _typeEncoding;
|
||||
NSString *sizeForDetails = nil;
|
||||
if (_typeEncoding.length) {
|
||||
@@ -83,7 +86,7 @@
|
||||
typeForDetails = @"no type info";
|
||||
sizeForDetails = @"unknown size";
|
||||
}
|
||||
|
||||
|
||||
Dl_info exeInfo;
|
||||
if (dladdr(_objc_ivar, &exeInfo)) {
|
||||
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
|
||||
@@ -97,7 +100,9 @@
|
||||
|
||||
- (id)getValue:(id)target {
|
||||
id value = nil;
|
||||
if (!FLEXIvarIsSafe(_objc_ivar) || _type == FLEXTypeEncodingNull) {
|
||||
if (!FLEXIvarIsSafe(_objc_ivar) ||
|
||||
_type == FLEXTypeEncodingNull ||
|
||||
FLEXPointerIsTaggedPointer(target)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -147,9 +152,14 @@
|
||||
}
|
||||
|
||||
- (id)getPotentiallyUnboxedValue:(id)target {
|
||||
NSString *type = self.typeEncoding;
|
||||
if (type.flex_typeIsNonObjcPointer && type.flex_pointeeType != FLEXTypeEncodingVoid) {
|
||||
return [self getValue:target];
|
||||
}
|
||||
|
||||
return [FLEXRuntimeUtility
|
||||
potentiallyUnwrapBoxedPointer:[self getValue:target]
|
||||
type:self.typeEncoding.UTF8String
|
||||
type:type.UTF8String
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <dlfcn.h>
|
||||
|
||||
@implementation FLEXMethod
|
||||
@synthesize imagePath = _imagePath;
|
||||
@dynamic implementation;
|
||||
|
||||
+ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation {
|
||||
@@ -156,11 +157,6 @@
|
||||
_name = NSStringFromSelector(_selector);
|
||||
_returnType = (FLEXTypeEncoding *)_signature.methodReturnType ?: "";
|
||||
_returnSize = _signature.methodReturnLength;
|
||||
|
||||
Dl_info exeInfo;
|
||||
if (dladdr(_objc_method, &exeInfo)) {
|
||||
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Public
|
||||
@@ -184,6 +180,17 @@
|
||||
return _typeEncoding;
|
||||
}
|
||||
|
||||
- (NSString *)imagePath {
|
||||
if (!_imagePath) {
|
||||
Dl_info exeInfo;
|
||||
if (dladdr(_implementation, &exeInfo)) {
|
||||
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : @"";
|
||||
}
|
||||
}
|
||||
|
||||
return _imagePath;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
- (void)swapImplementations:(FLEXMethod *)method {
|
||||
|
||||
@@ -45,8 +45,9 @@
|
||||
}
|
||||
|
||||
+ (instancetype)named:(NSString *)name onClass:(Class)cls {
|
||||
NSParameterAssert(class_getProperty(cls, name.UTF8String));
|
||||
return [self property:class_getProperty(cls, name.UTF8String) onClass:cls];
|
||||
objc_property_t _Nullable property = class_getProperty(cls, name.UTF8String);
|
||||
NSAssert(property, @"Cannot find property with name %@ on class %@", name, cls);
|
||||
return [self property:property onClass:cls];
|
||||
}
|
||||
|
||||
+ (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
|
||||
|
||||
@@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// A human-readable version of the property attributes.
|
||||
@property (nonatomic, readonly) NSString *fullDeclaration;
|
||||
/// A dictionary of the property attributes.
|
||||
/// Values are either a string or \c @YES. Boolean attributes
|
||||
/// Values are either a string or \c YES. Boolean attributes
|
||||
/// which are false will not be present in the dictionary.
|
||||
@property (nonatomic, readonly) NSDictionary *dictionary;
|
||||
|
||||
|
||||
@@ -64,6 +64,6 @@
|
||||
@property (nonatomic, readonly) NSString *typeEncoding;
|
||||
/// The method's return type.
|
||||
@property (nonatomic, readonly) FLEXTypeEncoding returnType;
|
||||
/// \c @YES if this is an instance method, \c @NO if it is a class method, or \c nil if unspecified
|
||||
/// \c YES if this is an instance method, \c NO if it is a class method, or \c nil if unspecified
|
||||
@property (nonatomic, readonly) NSNumber *instance;
|
||||
@end
|
||||
|
||||
@@ -60,10 +60,10 @@ struct rebindings_entry {
|
||||
struct rebindings_entry *next;
|
||||
};
|
||||
|
||||
static struct rebindings_entry *_rebindings_head;
|
||||
static struct rebindings_entry *_flex_rebindings_head;
|
||||
|
||||
/// @return 0 on success
|
||||
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
|
||||
static int flex_prepend_rebindings(struct rebindings_entry **rebindings_head,
|
||||
struct rebinding rebindings[],
|
||||
size_t nel) {
|
||||
struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
|
||||
@@ -85,7 +85,7 @@ static int prepend_rebindings(struct rebindings_entry **rebindings_head,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static vm_prot_t get_protection(void *sectionStart) {
|
||||
static vm_prot_t flex_get_protection(void *sectionStart) {
|
||||
mach_port_t task = mach_task_self();
|
||||
vm_size_t size = 0;
|
||||
vm_address_t address = (vm_address_t)sectionStart;
|
||||
@@ -110,19 +110,19 @@ static vm_prot_t get_protection(void *sectionStart) {
|
||||
return VM_PROT_READ;
|
||||
}
|
||||
}
|
||||
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
|
||||
section_t *section,
|
||||
intptr_t slide,
|
||||
nlist_t *symtab,
|
||||
char *strtab,
|
||||
uint32_t *indirect_symtab) {
|
||||
static void flex_perform_rebinding_with_section(struct rebindings_entry *rebindings,
|
||||
section_t *section,
|
||||
intptr_t slide,
|
||||
nlist_t *symtab,
|
||||
char *strtab,
|
||||
uint32_t *indirect_symtab) {
|
||||
const bool isDataConst = strcmp(section->segname, "__DATA_CONST") == 0;
|
||||
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
|
||||
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
|
||||
vm_prot_t oldProtection = VM_PROT_READ;
|
||||
|
||||
if (isDataConst) {
|
||||
oldProtection = get_protection(rebindings);
|
||||
oldProtection = flex_get_protection(rebindings);
|
||||
mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE);
|
||||
}
|
||||
|
||||
@@ -177,9 +177,9 @@ static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
|
||||
}
|
||||
}
|
||||
|
||||
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
|
||||
const struct mach_header *header,
|
||||
intptr_t slide) {
|
||||
static void flex_rebind_symbols_for_image(struct rebindings_entry *rebindings,
|
||||
const struct mach_header *header,
|
||||
intptr_t slide) {
|
||||
Dl_info info;
|
||||
if (dladdr(header, &info) == 0) {
|
||||
return;
|
||||
@@ -232,12 +232,12 @@ static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
|
||||
section_t *sect = (section_t *)(cur + sizeof(segment_command_t)) + j;
|
||||
|
||||
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
|
||||
perform_rebinding_with_section(
|
||||
flex_perform_rebinding_with_section(
|
||||
rebindings, sect, slide, symtab, strtab, indirect_symtab
|
||||
);
|
||||
}
|
||||
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
|
||||
perform_rebinding_with_section(
|
||||
flex_perform_rebinding_with_section(
|
||||
rebindings, sect, slide, symtab, strtab, indirect_symtab
|
||||
);
|
||||
}
|
||||
@@ -246,19 +246,19 @@ static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
|
||||
}
|
||||
}
|
||||
|
||||
static void _rebind_symbols_for_image(const struct mach_header *header,
|
||||
intptr_t slide) {
|
||||
rebind_symbols_for_image(_rebindings_head, header, slide);
|
||||
static void _flex_rebind_symbols_for_image(const struct mach_header *header,
|
||||
intptr_t slide) {
|
||||
flex_rebind_symbols_for_image(_flex_rebindings_head, header, slide);
|
||||
}
|
||||
|
||||
int rebind_symbols_image(void *header,
|
||||
intptr_t slide,
|
||||
struct rebinding rebindings[],
|
||||
size_t rebindings_nel) {
|
||||
int flex_rebind_symbols_image(void *header,
|
||||
intptr_t slide,
|
||||
struct rebinding rebindings[],
|
||||
size_t rebindings_nel) {
|
||||
struct rebindings_entry *rebindings_head = NULL;
|
||||
|
||||
int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
|
||||
rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
|
||||
int retval = flex_prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
|
||||
flex_rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
|
||||
|
||||
if (rebindings_head) {
|
||||
free(rebindings_head->rebindings);
|
||||
@@ -269,20 +269,20 @@ int rebind_symbols_image(void *header,
|
||||
}
|
||||
|
||||
/// @return 0 on success
|
||||
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
|
||||
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
|
||||
int flex_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
|
||||
int retval = flex_prepend_rebindings(&_flex_rebindings_head, rebindings, rebindings_nel);
|
||||
if (retval < 0) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
// If this was the first call, register callback for image additions (which is also invoked for
|
||||
// existing images, otherwise, just run on existing images
|
||||
if (!_rebindings_head->next) {
|
||||
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
|
||||
if (!_flex_rebindings_head->next) {
|
||||
_dyld_register_func_for_add_image(_flex_rebind_symbols_for_image);
|
||||
} else {
|
||||
uint32_t c = _dyld_image_count();
|
||||
for (uint32_t i = 0; i < c; i++) {
|
||||
_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
|
||||
_flex_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ struct rebinding {
|
||||
* @return 0 on success
|
||||
*/
|
||||
FISHHOOK_VISIBILITY
|
||||
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
|
||||
int flex_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
|
||||
|
||||
/**
|
||||
* Rebinds as above, but only in the specified image. The header should point
|
||||
@@ -65,10 +65,10 @@ int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
|
||||
* @return 0 on success
|
||||
*/
|
||||
FISHHOOK_VISIBILITY
|
||||
int rebind_symbols_image(void *header,
|
||||
intptr_t slide,
|
||||
struct rebinding rebindings[],
|
||||
size_t rebindings_nel);
|
||||
int flex_rebind_symbols_image(void *header,
|
||||
intptr_t slide,
|
||||
struct rebinding rebindings[],
|
||||
size_t rebindings_nel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "FLEX"
|
||||
spec.version = "4.1.0"
|
||||
spec.version = "4.1.1"
|
||||
spec.summary = "A set of in-app debugging and exploration tools for iOS"
|
||||
spec.description = <<-DESC
|
||||
- Inspect and modify views in the hierarchy.
|
||||
@@ -32,7 +32,7 @@ Pod::Spec.new do |spec|
|
||||
spec.platform = :ios, "9.0"
|
||||
spec.source = { :git => "https://github.com/Flipboard/FLEX.git", :tag => "#{spec.version}" }
|
||||
spec.source_files = "Classes/**/*.{h,c,m,mm}"
|
||||
spec.frameworks = [ "Foundation", "UIKit", "CoreGraphics", "ImageIO", "QuartzCore", "WebKit", "Security" ]
|
||||
spec.frameworks = [ "Foundation", "UIKit", "CoreGraphics", "ImageIO", "QuartzCore", "WebKit", "Security", "SceneKit" ]
|
||||
spec.libraries = [ "z", "sqlite3" ]
|
||||
spec.requires_arc = true
|
||||
spec.compiler_flags = "-Wno-unsupported-availability-guard -Wno-strict-prototypes"
|
||||
|
||||
@@ -164,6 +164,8 @@
|
||||
C32A195F231732E800EB02AC /* FLEXCollectionContentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C32A195D231732E800EB02AC /* FLEXCollectionContentSection.m */; };
|
||||
C32A19622317378C00EB02AC /* FLEXDefaultsContentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C32A19602317378C00EB02AC /* FLEXDefaultsContentSection.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C32A19632317378C00EB02AC /* FLEXDefaultsContentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C32A19612317378C00EB02AC /* FLEXDefaultsContentSection.m */; };
|
||||
C32F3A18247C6B3E0063542D /* FLEXUIAppShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C32F3A16247C6B3E0063542D /* FLEXUIAppShortcuts.h */; };
|
||||
C32F3A19247C6B3E0063542D /* FLEXUIAppShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C32F3A17247C6B3E0063542D /* FLEXUIAppShortcuts.m */; };
|
||||
C33C825B23159EAF00DD2451 /* FLEXTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C33C825A23159EAF00DD2451 /* FLEXTests.m */; };
|
||||
C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */ = {isa = PBXBuildFile; fileRef = C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */ = {isa = PBXBuildFile; fileRef = C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */; };
|
||||
@@ -523,6 +525,8 @@
|
||||
C32A195D231732E800EB02AC /* FLEXCollectionContentSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXCollectionContentSection.m; sourceTree = "<group>"; };
|
||||
C32A19602317378C00EB02AC /* FLEXDefaultsContentSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXDefaultsContentSection.h; sourceTree = "<group>"; };
|
||||
C32A19612317378C00EB02AC /* FLEXDefaultsContentSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXDefaultsContentSection.m; sourceTree = "<group>"; };
|
||||
C32F3A16247C6B3E0063542D /* FLEXUIAppShortcuts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXUIAppShortcuts.h; sourceTree = "<group>"; };
|
||||
C32F3A17247C6B3E0063542D /* FLEXUIAppShortcuts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXUIAppShortcuts.m; sourceTree = "<group>"; };
|
||||
C33C825A23159EAF00DD2451 /* FLEXTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTests.m; sourceTree = "<group>"; };
|
||||
C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FLEXObjectExplorer.h; path = Classes/ObjectExplorers/FLEXObjectExplorer.h; sourceTree = SOURCE_ROOT; };
|
||||
C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FLEXObjectExplorer.m; path = Classes/ObjectExplorers/FLEXObjectExplorer.m; sourceTree = SOURCE_ROOT; };
|
||||
@@ -1363,6 +1367,8 @@
|
||||
C34D4EB723A2B17900C1F903 /* FLEXBundleShortcuts.m */,
|
||||
C38EF26123A2FCD20047A7EC /* FLEXViewControllerShortcuts.h */,
|
||||
C38EF26023A2FCD20047A7EC /* FLEXViewControllerShortcuts.m */,
|
||||
C32F3A16247C6B3E0063542D /* FLEXUIAppShortcuts.h */,
|
||||
C32F3A17247C6B3E0063542D /* FLEXUIAppShortcuts.m */,
|
||||
C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */,
|
||||
C31D93E323E38CBE005517BF /* FLEXBlockShortcuts.m */,
|
||||
);
|
||||
@@ -1489,6 +1495,7 @@
|
||||
C3BFD070233C23ED0015FB82 /* NSArray+FLEX.h in Headers */,
|
||||
C398682923AC370100E9E391 /* FLEXViewShortcuts.h in Headers */,
|
||||
C3474C4023DA496400466532 /* FLEXKeyValueTableViewCell.h in Headers */,
|
||||
C32F3A18247C6B3E0063542D /* FLEXUIAppShortcuts.h in Headers */,
|
||||
779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */,
|
||||
C3DFCDB82418336D00BB7084 /* NSUserDefaults+FLEX.h in Headers */,
|
||||
3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */,
|
||||
@@ -1634,7 +1641,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = FLEX;
|
||||
LastUpgradeCheck = 1110;
|
||||
LastUpgradeCheck = 1200;
|
||||
ORGANIZATIONNAME = Flipboard;
|
||||
TargetAttributes = {
|
||||
1C27A8B51F0E5A0300F0D02D = {
|
||||
@@ -1755,6 +1762,7 @@
|
||||
3A4C94CE1B5B21410088C3F2 /* FLEXGlobalsEntry.m in Sources */,
|
||||
C398625223AD6C67007E6793 /* FLEXKeyPathSearchController.m in Sources */,
|
||||
C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */,
|
||||
C32F3A19247C6B3E0063542D /* FLEXUIAppShortcuts.m in Sources */,
|
||||
71E1C2192307FBB800F5032A /* FLEXKeychainQuery.m in Sources */,
|
||||
C398627323AD7951007E6793 /* UIGestureRecognizer+Blocks.m in Sources */,
|
||||
C3F646F723A04A7500D4A011 /* FLEXShortcut.m in Sources */,
|
||||
@@ -1880,6 +1888,7 @@
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = U3LST7M92S;
|
||||
INFOPLIST_FILE = FLEXTests/Info.plist;
|
||||
@@ -1896,6 +1905,7 @@
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
DEVELOPMENT_TEAM = U3LST7M92S;
|
||||
INFOPLIST_FILE = FLEXTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
|
||||
@@ -1928,6 +1938,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -1989,6 +2000,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -2025,6 +2037,7 @@
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = NO;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
@@ -2057,6 +2070,7 @@
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = NO;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1110"
|
||||
LastUpgradeVersion = "1200"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "NO">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1C27A8B51F0E5A0300F0D02D"
|
||||
BuildableName = "FLEXTests.xctest"
|
||||
BlueprintName = "FLEXTests"
|
||||
ReferencedContainer = "container:FLEX.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "FLEX_SKIP_INIT"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1C27A8B51F0E5A0300F0D02D"
|
||||
BuildableName = "FLEXTests.xctest"
|
||||
BlueprintName = "FLEXTests"
|
||||
ReferencedContainer = "container:FLEX.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+21
-1
@@ -14,8 +14,13 @@
|
||||
#import "FLEXProperty.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXMethod.h"
|
||||
#import "FLEXIvar.h"
|
||||
|
||||
@interface Subclass : NSObject @end
|
||||
@interface Subclass : NSObject {
|
||||
@public
|
||||
NSUInteger *_indexes;
|
||||
}
|
||||
@end
|
||||
@implementation Subclass @end
|
||||
|
||||
@interface FLEXTests : XCTestCase
|
||||
@@ -101,4 +106,19 @@
|
||||
XCTAssert(props.count == 1);
|
||||
}
|
||||
|
||||
- (void)testIvarUnboxing {
|
||||
NSUInteger array[4] = { 0xaa, 0xbb, 0xcc, 0x00 };
|
||||
Subclass *obj = [Subclass new];
|
||||
obj->_indexes = array;
|
||||
|
||||
FLEXIvar *ivar = [Subclass flex_ivarNamed:@"_indexes"];
|
||||
|
||||
NSValue *arrayValue = [ivar getPotentiallyUnboxedValue:obj];
|
||||
NSUInteger *pointerValue = arrayValue.pointerValue;
|
||||
|
||||
XCTAssert(pointerValue != nil);
|
||||
XCTAssertEqual(pointerValue, (NSUInteger *)&array);
|
||||
XCTAssertEqual(pointerValue[0], 0xaa);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -43,7 +43,7 @@ Short version:
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
FLEXManager.shared().showExplorer()
|
||||
FLEXManager.shared.showExplorer()
|
||||
```
|
||||
|
||||
More complete version:
|
||||
@@ -220,7 +220,8 @@ FLEX builds on ideas and inspiration from open source tools that came before it.
|
||||
- [Gist](https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396) from [@samdmarshall](https://github.com/samdmarshall): another example of enumerating malloc blocks.
|
||||
- [Non-pointer isa](http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html): an explanation of changes to the isa field on iOS for ARM64 and mention of the useful `objc_debug_isa_class_mask` variable.
|
||||
- [GZIP](https://github.com/nicklockwood/GZIP): A library for compressing/decompressing data on iOS using libz.
|
||||
- [FMDB](https://github.com/ccgus/fmdb): This is an Objective-C wrapper around SQLite
|
||||
- [FMDB](https://github.com/ccgus/fmdb): This is an Objective-C wrapper around SQLite.
|
||||
- [InAppViewDebugger](https://github.com/indragiek/InAppViewDebugger): The inspiration and reference implementation for FLEX 4's 3D view explorer, by @indragiek.
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user