Compare commits

...

19 Commits

Author SHA1 Message Date
Tanner Bennett 700c50af5d Bump version 2020-04-06 17:37:59 -05:00
Tanner Bennett b38cca06b1 Fix ProtocolMember instance field
The ProtocolMember instance field was not being populated in runtime exports
2020-04-06 17:32:43 -05:00
Tanner Bennett 6429573918 Rename some classes with excessively long names 2020-04-06 17:32:43 -05:00
Tanner Bennett f77f5ccdc9 Push straight to single instances in heap explorer
Take the user straight to the explorer itself if there is only one instance of the selected class
2020-04-06 17:32:43 -05:00
Tanner Bennett 7aeddcdb2c Add search bar to view controllers list 2020-04-06 17:32:11 -05:00
Tanner Bennett a25ef87a51 Make new JSON viewer and System Log behavior opt-in 2020-04-06 17:32:11 -05:00
Tanner Bennett fbeb1beca0 Namespace some category filenames 2020-04-06 17:32:11 -05:00
Tanner Bennett 059bde9711 Fix crash in flex_all* methods 2020-04-06 17:32:11 -05:00
Tanner Bennett 2ca563f570 Bug fix: iPad support for FLEXAlert action sheets 2020-04-06 17:32:11 -05:00
Tanner Bennett 88c7ca9373 Add option to disable property/ivar previews 2020-03-31 12:16:57 -05:00
Tanner Bennett 83486641aa Add < iOS 13 support to example project 2020-03-30 16:28:19 -05:00
Tanner Bennett 6bd0c87881 Fix import statement to work without modules 2020-03-30 16:28:19 -05:00
Tanner Bennett 1a64da70c9 Add FLEXRuntimeExporter
Allows you to export the contents of a bundle's objc metadata as an SQLite database

fix

fix
2020-03-30 16:28:19 -05:00
Tanner Bennett 87ea2bb147 FLEXRuntimeClient additions
- Move initializeWebKitLegacy to FLEXRuntimeClient
- Add -copySafeClassList and -copyProtocolList
2020-03-27 19:59:32 -05:00
Tanner Bennett d9e9be53d8 Various database browser related upgrades
- All database managers automatically open and close connections to the underlying database
- Allow getting last result and last rowid from FLEXSQLiteDatabaseManager
- Execute statements with arguments with FLEXSQLiteDatabaseManager
2020-03-27 19:59:32 -05:00
Tanner Bennett 142f037497 Runtime wrapper upgrades
- Make sure every object has an `imageName` property
- Expose more fine-grained metadata through FLEXProtocol
- Migrate flex_all* methods to top-level functions that the methods call into
2020-03-27 19:59:32 -05:00
Tanner Bennett 6cdb626d78 Various bug fixes 2020-03-27 19:59:32 -05:00
Tanner Bennett 6e81029b8b Show HTTP status code in commit screen error 2020-03-27 19:16:09 -05:00
Tanner Bennett 1c7048e710 Dispatch JSON viewer registration to main queue 2020-03-25 15:12:34 -05:00
95 changed files with 2046 additions and 490 deletions
@@ -8,7 +8,7 @@
#import "FLEXFilteringTableViewController.h"
#import "FLEXTableViewSection.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
@interface FLEXFilteringTableViewController ()
@@ -140,7 +140,7 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
@property (nonatomic) BOOL showsShareToolbarItem;
/// Called when the share button is pressed.
/// Default implementation does nothign. Subclasses may override.
- (void)shareButtonPressed;
- (void)shareButtonPressed:(UIBarButtonItem *)sender;
/// Subclasses may call this to opt-out of all toolbar related behavior.
/// This is necessary if you want to disable the gesture which reveals the toolbar.
@@ -222,7 +222,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.tableView.dataSource = self;
self.tableView.delegate = self;
_shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed));
_shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed:));
_bookmarksToolbarItem = [UIBarButtonItem
itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks)
];
@@ -386,7 +386,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
[self setupToolbarItems];
}
- (void)shareButtonPressed {
- (void)shareButtonPressed:(UIBarButtonItem *)sender {
}
+1 -1
View File
@@ -8,7 +8,7 @@
#import <UIKit/UIKit.h>
#import "FLEXMacros.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
@class FLEXTableView;
NS_ASSUME_NONNULL_BEGIN
@@ -68,7 +68,7 @@
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
self.toolbarItems = @[
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed)],
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
// We use a non-system done item because we change its title dynamically
[UIBarButtonItem doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
@@ -149,7 +149,7 @@
}
}
- (void)closeAllButtonPressed {
- (void)closeAllButtonPressed:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
NSInteger count = self.bookmarks.count;
NSString *title = FLEXPluralFormatString(count, @"Remove %@ bookmarks", @"Remove %@ bookmark");
@@ -158,7 +158,7 @@
[self toggleEditing];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
- (void)closeAll {
@@ -42,6 +42,7 @@
[super viewDidLoad];
self.title = @"View Controllers at Tap";
self.showsSearchBar = YES;
[self disableToolbar];
}
@@ -109,7 +109,7 @@
self.toolbarItems = @[
UIBarButtonItem.flex_fixedSpace,
UIBarButtonItem.flex_flexibleSpace,
FLEXBarButtonItemSystem(Add, self, @selector(addTabButtonPressed)),
FLEXBarButtonItemSystem(Add, self, @selector(addTabButtonPressed:)),
UIBarButtonItem.flex_flexibleSpace,
FLEXBarButtonItemSystem(Edit, self, @selector(toggleEditing)),
];
@@ -121,7 +121,7 @@
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
self.toolbarItems = @[
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed)],
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem disabledSystemItem:UIBarButtonSystemItemAdd],
UIBarButtonItem.flex_flexibleSpace,
@@ -193,7 +193,7 @@
}
}
- (void)addTabButtonPressed {
- (void)addTabButtonPressed:(UIBarButtonItem *)sender {
if (FLEXBookmarkManager.bookmarks.count) {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
make.title(@"New Tab");
@@ -208,7 +208,7 @@
] animated:YES completion:nil];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
} else {
// No bookmarks, just open the main menu
[self addTabAndDismiss:[FLEXNavigationController
@@ -224,7 +224,7 @@
}];
}
- (void)closeAllButtonPressed {
- (void)closeAllButtonPressed:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
NSInteger count = self.openTabs.count;
NSString *title = FLEXPluralFormatString(count, @"Close %@ tabs", @"Close %@ tab");
@@ -233,7 +233,7 @@
[self toggleEditing];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
- (void)closeAll {
+3 -3
View File
@@ -17,11 +17,11 @@
#import <FLEX/UIMenu+FLEX.h>
#import <FLEX/UITextField+Range.h>
#import <FLEX/NSObject+Reflection.h>
#import <FLEX/NSArray+Functional.h>
#import <FLEX/NSObject+FLEX_Reflection.h>
#import <FLEX/NSArray+FLEX.h>
#import <FLEX/NSDictionary+ObjcRuntime.h>
#import <FLEX/NSString+ObjcRuntime.h>
#import <FLEX/NSString+FLEX.h>
#import <FLEX/NSUserDefaults+FLEX.h>
#import <FLEX/NSMapTable+FLEX_Subscripting.h>
#import <FLEX/NSTimer+Blocks.h>
#import <FLEX/NSTimer+FLEX.h>
@@ -8,7 +8,7 @@
#import "FLEXDBQueryRowCell.h"
#import "FLEXMultiColumnTableView.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "UIFont+FLEX.h"
#import "FLEXColor.h"
@@ -14,14 +14,14 @@
#import <Foundation/Foundation.h>
#import "FLEXSQLResult.h"
/// Conformers should automatically open and close the database
@protocol FLEXDatabaseManager <NSObject>
@required
/// @return \c nil if the database couldn't be opened
+ (instancetype)managerForDatabase:(NSString *)path;
- (BOOL)open;
/// @return a list of all table names
- (NSArray<NSString *> *)queryAllTables;
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName;
@@ -7,7 +7,7 @@
//
#import "FLEXRealmDatabaseManager.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "FLEXSQLResult.h"
#if __has_include(<Realm/Realm.h>)
@@ -20,7 +20,7 @@
@interface FLEXRealmDatabaseManager ()
@property (nonatomic, copy) NSString *path;
@property (nonatomic) RLMRealm * realm;
@property (nonatomic) RLMRealm *realm;
@end
@@ -43,6 +43,10 @@ static Class RLMRealmClass = nil;
self = [super init];
if (self) {
_path = path;
if (![self open]) {
return nil;
}
}
return self;
@@ -14,6 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
/// Describes the result of a non-select query, or an error of any kind of query
+ (instancetype)message:(NSString *)message;
/// Describes the result of a known failed execution
+ (instancetype)error:(NSString *)message;
/// @param rowData A list of rows, where each element in the row
/// corresponds to the column given in /c columnNames
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
@@ -21,6 +24,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, nullable) NSString *message;
/// A value of YES means this is surely an error,
/// but it still might be an error even with a value of NO
@property (nonatomic, readonly) BOOL isError;
/// A list of column names
@property (nonatomic, readonly, nullable) NSArray<NSString *> *columns;
/// A list of rows, where each element in the row corresponds
@@ -7,7 +7,7 @@
//
#import "FLEXSQLResult.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
@implementation FLEXSQLResult
@synthesize keyedRows = _keyedRows;
@@ -16,6 +16,12 @@
return [[self alloc] initWithmessage:message columns:nil rows:nil];
}
+ (instancetype)error:(NSString *)message {
FLEXSQLResult *result = [self message:message];
result->_isError = YES;
return result;
}
+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
return [[self alloc] initWithmessage:nil columns:columnNames rows:rowData];
}
@@ -13,7 +13,20 @@
#import <Foundation/Foundation.h>
#import "FLEXDatabaseManager.h"
#import "FLEXSQLResult.h"
@interface FLEXSQLiteDatabaseManager : NSObject <FLEXDatabaseManager>
/// Contains the result of the last operation, which may be an error
@property (nonatomic, readonly) FLEXSQLResult *lastResult;
/// Calls into \c sqlite3_last_insert_rowid()
@property (nonatomic, readonly) NSInteger lastRowID;
/// Given a statement like 'SELECT * from @table where @col = @val' and arguments
/// like { @"table": @"Album", @"col": @"year", @"val" @1 } this method will
/// invoke the statement and properly bind the given arguments to the statement.
///
/// You may pass NSStrings, NSData, NSNumbers, or NSNulls as values.
- (FLEXSQLResult *)executeStatement:(NSString *)statement arguments:(NSDictionary<NSString *, id> *)args;
@end
@@ -8,8 +8,8 @@
#import "FLEXSQLiteDatabaseManager.h"
#import "FLEXManager.h"
#import "NSArray+Functional.h"
#import "FLEXSQLResult.h"
#import "NSArray+FLEX.h"
#import "FLEXRuntimeConstants.h"
#import <sqlite3.h>
static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
@@ -36,6 +36,10 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
return self;
}
- (void)dealloc {
[self close];
}
- (BOOL)open {
if (self.db) {
return YES;
@@ -52,8 +56,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
#endif
if (err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
return [self storeErrorForLastTask:@"Open"];
}
return YES;
@@ -81,7 +84,9 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
}
}
} else if (SQLITE_OK != rc) {
NSLog(@"error closing!: %d", rc);
[self storeErrorForLastTask:@"Close"];
self.db = nil;
return NO;
}
} while (retry);
@@ -89,6 +94,10 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
return YES;
}
- (NSInteger)lastRowID {
return (NSInteger)sqlite3_last_insert_rowid(self.db);
}
- (NSArray<NSString *> *)queryAllTables {
return [[self executeStatement:QUERY_TABLENAMES].rows flex_mapped:^id(NSArray *table, NSUInteger idx) {
return table.firstObject;
@@ -111,14 +120,24 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
}
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
return [self executeStatement:sql arguments:nil];
}
- (FLEXSQLResult *)executeStatement:(NSString *)sql arguments:(NSDictionary *)args {
[self open];
FLEXSQLResult *result = nil;
sqlite3_stmt *pstmt;
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0) == SQLITE_OK) {
int status;
if ((status = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0)) == SQLITE_OK) {
NSMutableArray<NSArray *> *rows = [NSMutableArray new];
// Bind parameters, if any
if (![self bindParameters:args toStatement:pstmt]) {
return self.lastResult;
}
// Grab columns
int columnCount = sqlite3_column_count(pstmt);
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
@@ -126,7 +145,6 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
}];
// Execute statement
int status;
while ((status = sqlite3_step(pstmt)) == SQLITE_ROW) {
// Grab rows if this is a selection query
int dataCount = sqlite3_data_count(pstmt);
@@ -140,30 +158,111 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
if (status == SQLITE_DONE) {
if (rows.count) {
// We selected some rows
result = [FLEXSQLResult columns:columns rows:rows];
result = _lastResult = [FLEXSQLResult columns:columns rows:rows];
} else {
// We executed a query like INSERT, UDPATE, or DELETE
int rowsAffected = sqlite3_changes(_db);
NSString *message = [NSString stringWithFormat:@"%d row(s) affected", rowsAffected];
result = [FLEXSQLResult message:message];
result = _lastResult = [FLEXSQLResult message:message];
}
} else {
// An error occured executing the query
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Execution: empty error)")];
result = _lastResult = [self errorResult:@"Execution"];
}
} else {
// An error occurred creating the prepared statement
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Prepared statement: empty error)")];
result = _lastResult = [self errorResult:@"Prepared statement"];
}
sqlite3_finalize(pstmt);
[self close];
return result;
}
#pragma mark - Private
/// @return YES on success, NO if an error was encountered and stored in \c lastResult
- (BOOL)bindParameters:(NSDictionary *)args toStatement:(sqlite3_stmt *)pstmt {
for (NSString *param in args.allKeys) {
int status = SQLITE_OK, idx = sqlite3_bind_parameter_index(pstmt, param.UTF8String);
id value = args[param];
if (idx == 0) {
// No parameter matching that arg
@throw NSInternalInconsistencyException;
}
// Null
if ([value isKindOfClass:[NSNull class]]) {
status = sqlite3_bind_null(pstmt, idx);
}
// String params
else if ([value isKindOfClass:[NSString class]]) {
const char *str = [value UTF8String];
status = sqlite3_bind_text(pstmt, idx, str, (int)strlen(str), SQLITE_TRANSIENT);
}
// Data params
else if ([value isKindOfClass:[NSData class]]) {
const void *blob = [value bytes];
status = sqlite3_bind_blob64(pstmt, idx, blob, [value length], SQLITE_TRANSIENT);
}
// Primitive params
else if ([value isKindOfClass:[NSNumber class]]) {
FLEXTypeEncoding type = [value objCType][0];
switch (type) {
case FLEXTypeEncodingCBool:
case FLEXTypeEncodingChar:
case FLEXTypeEncodingUnsignedChar:
case FLEXTypeEncodingShort:
case FLEXTypeEncodingUnsignedShort:
case FLEXTypeEncodingInt:
case FLEXTypeEncodingUnsignedInt:
case FLEXTypeEncodingLong:
case FLEXTypeEncodingUnsignedLong:
case FLEXTypeEncodingLongLong:
case FLEXTypeEncodingUnsignedLongLong:
status = sqlite3_bind_int64(pstmt, idx, (sqlite3_int64)[value longValue]);
break;
case FLEXTypeEncodingFloat:
case FLEXTypeEncodingDouble:
status = sqlite3_bind_double(pstmt, idx, [value doubleValue]);
break;
default:
@throw NSInternalInconsistencyException;
break;
}
}
// Unsupported type
else {
@throw NSInternalInconsistencyException;
}
if (status != SQLITE_OK) {
return [self storeErrorForLastTask:
[NSString stringWithFormat:@"Binding param named '%@'", param]
];
}
}
return YES;
}
- (BOOL)storeErrorForLastTask:(NSString *)action {
_lastResult = [self errorResult:action];
return NO;
}
- (FLEXSQLResult *)errorResult:(NSString *)description {
const char *error = sqlite3_errmsg(_db);
NSString *message = error ? @(error) : [NSString
stringWithFormat:@"(%@: empty error", description
];
return [FLEXSQLResult error:message];
}
- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
int columnType = sqlite3_column_type(stmt, columnIdx);
@@ -12,7 +12,7 @@
#import "FLEXRealmDatabaseManager.h"
#import "FLEXTableContentViewController.h"
#import "FLEXMutableListSection.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "FLEXAlert.h"
@interface FLEXTableListViewController ()
@@ -1,5 +1,5 @@
//
// FLEXCookiesTableViewController.h
// FLEXCookiesViewController.h
// FLEX
//
// Created by Rich Robinson on 19/10/2015.
@@ -9,6 +9,6 @@
#import "FLEXGlobalsEntry.h"
#import "FLEXFilteringTableViewController.h"
@interface FLEXCookiesTableViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
@interface FLEXCookiesViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
@end
@@ -1,22 +1,22 @@
//
// FLEXCookiesTableViewController.m
// FLEXCookiesViewController.m
// FLEX
//
// Created by Rich Robinson on 19/10/2015.
// Copyright © 2015 Flipboard. All rights reserved.
//
#import "FLEXCookiesTableViewController.h"
#import "FLEXCookiesViewController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXMutableListSection.h"
#import "FLEXUtility.h"
@interface FLEXCookiesTableViewController ()
@interface FLEXCookiesViewController ()
@property (nonatomic, readonly) FLEXMutableListSection<NSHTTPCookie *> *cookies;
@property (nonatomic) NSString *headerTitle;
@end
@implementation FLEXCookiesTableViewController
@implementation FLEXCookiesViewController
#pragma mark - Overrides
@@ -1,5 +1,5 @@
//
// FLEXLiveObjectsTableViewController.h
// FLEXLiveObjectsController.h
// Flipboard
//
// Created by Ryan Olson on 5/28/14.
@@ -9,6 +9,6 @@
#import "FLEXTableViewController.h"
#import "FLEXGlobalsEntry.h"
@interface FLEXLiveObjectsTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
@interface FLEXLiveObjectsController : FLEXTableViewController <FLEXGlobalsEntry>
@end
@@ -1,12 +1,12 @@
//
// FLEXLiveObjectsTableViewController.m
// FLEXLiveObjectsController.m
// Flipboard
//
// Created by Ryan Olson on 5/28/14.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXLiveObjectsTableViewController.h"
#import "FLEXLiveObjectsController.h"
#import "FLEXHeapEnumerator.h"
#import "FLEXObjectListViewController.h"
#import "FLEXUtility.h"
@@ -18,7 +18,7 @@ static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
@interface FLEXLiveObjectsTableViewController ()
@interface FLEXLiveObjectsController ()
@property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceCountsForClassNames;
@property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
@@ -28,7 +28,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
@end
@implementation FLEXLiveObjectsTableViewController
@implementation FLEXLiveObjectsController
- (void)viewDidLoad {
[super viewDidLoad];
@@ -139,7 +139,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
}
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
FLEXLiveObjectsTableViewController *liveObjectsViewController = [self new];
FLEXLiveObjectsController *liveObjectsViewController = [self new];
liveObjectsViewController.title = [self globalsEntryTitle:row];
return liveObjectsViewController;
@@ -224,8 +224,8 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *className = self.filteredClassNames[indexPath.row];
FLEXObjectListViewController *instancesViewController = [FLEXObjectListViewController instancesOfClassWithName:className];
[self.navigationController pushViewController:instancesViewController animated:YES];
UIViewController *instances = [FLEXObjectListViewController instancesOfClassWithName:className];
[self.navigationController pushViewController:instances animated:YES];
}
@end
@@ -10,7 +10,9 @@
@interface FLEXObjectListViewController : FLEXFilteringTableViewController
+ (instancetype)instancesOfClassWithName:(NSString *)className;
/// This will either return a list of the instances, or take you straight
/// to the explorer itself if there is only one instance.
+ (UIViewController *)instancesOfClassWithName:(NSString *)className;
+ (instancetype)subclassesOfClassWithName:(NSString *)className;
+ (instancetype)objectsWithReferencesToObject:(id)object;
@@ -15,7 +15,7 @@
#import "FLEXHeapEnumerator.h"
#import "FLEXObjectRef.h"
#import "NSString+FLEX.h"
#import "NSObject+Reflection.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXTableViewCell.h"
#import <malloc/malloc.h>
@@ -111,7 +111,7 @@
return self;
}
+ (instancetype)instancesOfClassWithName:(NSString *)className {
+ (UIViewController *)instancesOfClassWithName:(NSString *)className {
const char *classNameCString = className.UTF8String;
NSMutableArray *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
@@ -127,6 +127,12 @@
}];
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;
+1 -1
View File
@@ -8,7 +8,7 @@
#import "FLEXObjectRef.h"
#import "FLEXRuntimeUtility.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
@interface FLEXObjectRef ()
@property (nonatomic, readonly) BOOL wantsSummary;
@@ -1,5 +1,5 @@
//
// FLEXFileBrowserTableViewController.h
// FLEXFileBrowserController.h
// Flipboard
//
// Created by Ryan Olson on 6/9/14.
@@ -10,7 +10,7 @@
#import "FLEXGlobalsEntry.h"
#import "FLEXFileBrowserSearchOperation.h"
@interface FLEXFileBrowserTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
@interface FLEXFileBrowserController : FLEXTableViewController <FLEXGlobalsEntry>
+ (instancetype)path:(NSString *)path;
- (id)initWithPath:(NSString *)path;
@@ -1,12 +1,12 @@
//
// FLEXFileBrowserTableViewController.m
// FLEXFileBrowserController.m
// Flipboard
//
// Created by Ryan Olson on 6/9/14.
//
//
#import "FLEXFileBrowserTableViewController.h"
#import "FLEXFileBrowserController.h"
#import "FLEXUtility.h"
#import "FLEXWebViewController.h"
#import "FLEXImagePreviewViewController.h"
@@ -18,7 +18,7 @@
@interface FLEXFileBrowserTableViewCell : UITableViewCell
@end
@interface FLEXFileBrowserTableViewController () <FLEXFileBrowserSearchOperationDelegate>
@interface FLEXFileBrowserController () <FLEXFileBrowserSearchOperationDelegate>
@property (nonatomic, copy) NSString *path;
@property (nonatomic, copy) NSArray<NSString *> *childPaths;
@@ -30,7 +30,7 @@
@end
@implementation FLEXFileBrowserTableViewController
@implementation FLEXFileBrowserController
+ (instancetype)path:(NSString *)path {
return [[self alloc] initWithPath:path];
@@ -49,7 +49,7 @@
//computing path size
FLEXFileBrowserTableViewController *__weak weakSelf = self;
FLEXFileBrowserController *__weak weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileManager *fileManager = NSFileManager.defaultManager;
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
@@ -66,7 +66,7 @@
}
dispatch_async(dispatch_get_main_queue(), ^{
FLEXFileBrowserTableViewController *__strong strongSelf = weakSelf;
FLEXFileBrowserController *__strong strongSelf = weakSelf;
strongSelf.recursiveSize = @(totalSize);
[strongSelf.tableView reloadData];
});
@@ -7,7 +7,7 @@
//
#import "FLEXGlobalsSection.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "UIFont+FLEX.h"
@interface FLEXGlobalsSection ()
@@ -10,12 +10,12 @@
#import "FLEXUtility.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXObjcRuntimeViewController.h"
#import "FLEXKeychainTableViewController.h"
#import "FLEXKeychainViewController.h"
#import "FLEXObjectExplorerViewController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXLiveObjectsTableViewController.h"
#import "FLEXFileBrowserTableViewController.h"
#import "FLEXCookiesTableViewController.h"
#import "FLEXLiveObjectsController.h"
#import "FLEXFileBrowserController.h"
#import "FLEXCookiesViewController.h"
#import "FLEXGlobalsEntry.h"
#import "FLEXManager+Private.h"
#import "FLEXSystemLogViewController.h"
@@ -56,18 +56,18 @@
+ (FLEXGlobalsEntry *)globalsEntryForRow:(FLEXGlobalsRow)row {
switch (row) {
case FLEXGlobalsRowAppKeychainItems:
return [FLEXKeychainTableViewController flex_concreteGlobalsEntry:row];
return [FLEXKeychainViewController flex_concreteGlobalsEntry:row];
case FLEXGlobalsRowAddressInspector:
return [FLEXAddressExplorerCoordinator flex_concreteGlobalsEntry:row];
case FLEXGlobalsRowBrowseRuntime:
return [FLEXObjcRuntimeViewController flex_concreteGlobalsEntry:row];
case FLEXGlobalsRowLiveObjects:
return [FLEXLiveObjectsTableViewController flex_concreteGlobalsEntry:row];
return [FLEXLiveObjectsController flex_concreteGlobalsEntry:row];
case FLEXGlobalsRowCookies:
return [FLEXCookiesTableViewController flex_concreteGlobalsEntry:row];
return [FLEXCookiesViewController flex_concreteGlobalsEntry:row];
case FLEXGlobalsRowBrowseBundle:
case FLEXGlobalsRowBrowseContainer:
return [FLEXFileBrowserTableViewController flex_concreteGlobalsEntry:row];
return [FLEXFileBrowserController flex_concreteGlobalsEntry:row];
case FLEXGlobalsRowSystemLog:
return [FLEXSystemLogViewController flex_concreteGlobalsEntry:row];
case FLEXGlobalsRowNetworkHistory:
@@ -1,5 +1,5 @@
//
// FLEXKeychainTableViewController.h
// FLEXKeychainViewController.h
// FLEX
//
// Created by ray on 2019/8/17.
@@ -9,6 +9,6 @@
#import "FLEXGlobalsEntry.h"
#import "FLEXFilteringTableViewController.h"
@interface FLEXKeychainTableViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
@interface FLEXKeychainViewController : FLEXFilteringTableViewController <FLEXGlobalsEntry>
@end
@@ -1,5 +1,5 @@
//
// FLEXKeychainTableViewController.m
// FLEXKeychainViewController.m
// FLEX
//
// Created by ray on 2019/8/17.
@@ -8,17 +8,18 @@
#import "FLEXKeychain.h"
#import "FLEXKeychainQuery.h"
#import "FLEXKeychainTableViewController.h"
#import "FLEXKeychainViewController.h"
#import "FLEXTableViewCell.h"
#import "FLEXMutableListSection.h"
#import "FLEXUtility.h"
#import "UIPasteboard+FLEX.h"
#import "UIBarButtonItem+FLEX.h"
@interface FLEXKeychainTableViewController ()
@interface FLEXKeychainViewController ()
@property (nonatomic, readonly) FLEXMutableListSection<NSDictionary *> *section;
@end
@implementation FLEXKeychainTableViewController
@implementation FLEXKeychainViewController
- (id)init {
return [self initWithStyle:UITableViewStyleGrouped];
@@ -30,12 +31,8 @@
[super viewDidLoad];
self.navigationItem.rightBarButtonItems = @[
[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(trashPressed)
],
[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addPressed)
],
[UIBarButtonItem systemItem:UIBarButtonSystemItemTrash target:self action:@selector(trashPressed:)],
[UIBarButtonItem systemItem:UIBarButtonSystemItemAdd target:self action:@selector(addPressed)],
];
[self reloadData];
@@ -127,7 +124,7 @@
#pragma mark Buttons
- (void)trashPressed {
- (void)trashPressed:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
make.title(@"Clear Keychain");
make.message(@"This will remove all keychain items for this app.\n");
@@ -140,7 +137,7 @@
[self reloadData];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
- (void)addPressed {
@@ -170,7 +167,7 @@
}
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
FLEXKeychainTableViewController *viewController = [self new];
FLEXKeychainViewController *viewController = [self new];
viewController.title = [self globalsEntryTitle:row];
return viewController;
@@ -19,6 +19,17 @@
/// been loaded since this method was first called.
- (void)reloadLibrariesList;
/// You must call this method on the main thread
/// before you attempt to call \c copySafeClassList.
+ (void)initializeWebKitLegacy;
/// Do not call unless you absolutely need all classes. This will cause
/// every class in the runtime to initialize itself, which is not common.
/// Before you call this method, call \c initializeWebKitLegacy on the main thread.
- (NSArray<Class> *)copySafeClassList;
- (NSArray<Protocol *> *)copyProtocolList;
/// An array of strings representing the currently loaded libraries.
@property (nonatomic, readonly) NSArray<NSString *> *imageDisplayNames;
@@ -7,10 +7,11 @@
//
#import "FLEXRuntimeClient.h"
#import "NSObject+Reflection.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXMethod.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "FLEXRuntimeSafety.h"
#include <dlfcn.h>
#define Equals(a, b) ([a compare:b options:NSCaseInsensitiveSearch] == NSOrderedSame)
#define Contains(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location != NSNotFound)
@@ -187,6 +188,38 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
#pragma mark - Public
+ (void)initializeWebKitLegacy {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen(
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
RTLD_LAZY
);
void (*WebKitInitialize)() = dlsym(handle, "WebKitInitialize");
if (WebKitInitialize) {
NSAssert(NSThread.isMainThread,
@"WebKitInitialize can only be called on the main thread"
);
WebKitInitialize();
}
});
}
- (NSArray<Class> *)copySafeClassList {
unsigned int count = 0;
Class *classes = objc_copyClassList(&count);
return [NSArray flex_forEachUpTo:count map:^id(NSUInteger i) {
Class cls = classes[i];
return FLEXClassIsSafe(cls) ? cls : nil;
}];
}
- (NSArray<Protocol *> *)copyProtocolList {
unsigned int count = 0;
Protocol *__unsafe_unretained *protocols = objc_copyProtocolList(&count);
return [NSArray arrayWithObjects:protocols count:count];
}
- (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token {
if (self.imagePaths.count) {
TBWildcardOptions options = token.options;
@@ -8,7 +8,7 @@
#import "FLEXRuntimeKeyPath.h"
/// Wraps FLEXRuntimeClient and provides caching mechanisms
/// Wraps FLEXRuntimeClient and provides extra caching mechanisms
@interface FLEXRuntimeController : NSObject
/// @return An array of strings if the key path only evaluates
@@ -30,6 +30,7 @@
+ (NSString *)imagePathWithShortName:(NSString *)suffix;
/// Gives back short names. For example, "Foundation.framework"
+ (NSArray<NSString*> *)allBundleNames;
@end
@@ -0,0 +1,29 @@
//
// FLEXRuntimeExporter.h
// FLEX
//
// Created by Tanner Bennett on 3/26/20.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A class for exporting all runtime metadata to an SQLite database.
//API_AVAILABLE(ios(10.0))
@interface FLEXRuntimeExporter : NSObject
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
progressHandler:(void(^)(NSString *status))progress
completion:(void(^)(NSString *_Nullable error))completion;
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
forImages:(nullable NSArray<NSString *> *)images
progressHandler:(void(^)(NSString *status))progress
completion:(void(^)(NSString *_Nullable error))completion;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,875 @@
//
// FLEXRuntimeExporter.m
// FLEX
//
// Created by Tanner Bennett on 3/26/20.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXRuntimeExporter.h"
#import "FLEXSQLiteDatabaseManager.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXRuntimeController.h"
#import "FLEXRuntimeClient.h"
#import "NSArray+FLEX.h"
#import "FLEXTypeEncodingParser.h"
#import <sqlite3.h>
#import "FLEXProtocol.h"
#import "FLEXProperty.h"
#import "FLEXIvar.h"
#import "FLEXMethodBase.h"
#import "FLEXMethod.h"
#import "FLEXPropertyAttributes.h"
NSString * const kFREEnableForeignKeys = @"PRAGMA foreign_keys = ON;";
/// Loaded images
NSString * const kFRECreateTableMachOCommand = @"CREATE TABLE MachO( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"shortName TEXT, "
"imagePath TEXT, "
"bundleID TEXT "
");";
NSString * const kFREInsertImage = @"INSERT INTO MachO ( "
"shortName, imagePath, bundleID "
") VALUES ( "
"$shortName, $imagePath, $bundleID "
");";
/// Objc classes
NSString * const kFRECreateTableClassCommand = @"CREATE TABLE Class( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"className TEXT, "
"superclass INTEGER, "
"instanceSize INTEGER, "
"version INTEGER, "
"image INTEGER, "
"FOREIGN KEY(superclass) REFERENCES Class(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertClass = @"INSERT INTO Class ( "
"className, instanceSize, version, image "
") VALUES ( "
"$className, $instanceSize, $version, $image "
");";
NSString * const kFREUpdateClassSetSuper = @"UPDATE Class SET superclass = $super WHERE id = $id;";
/// Unique objc selectors
NSString * const kFRECreateTableSelectorCommand = @"CREATE TABLE Selector( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"name text NOT NULL UNIQUE "
");";
NSString * const kFREInsertSelector = @"INSERT OR IGNORE INTO Selector (name) VALUES ($name);";
/// Unique objc type encodings
NSString * const kFRECreateTableTypeEncodingCommand = @"CREATE TABLE TypeEncoding( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"string text NOT NULL UNIQUE, "
"size integer "
");";
NSString * const kFREInsertTypeEncoding = @"INSERT OR IGNORE INTO TypeEncoding "
"(string, size) VALUES ($type, $size);";
/// Unique objc type signatures
NSString * const kFRECreateTableTypeSignatureCommand = @"CREATE TABLE TypeSignature( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"string text NOT NULL UNIQUE "
");";
NSString * const kFREInsertTypeSignature = @"INSERT OR IGNORE INTO TypeSignature "
"(string) VALUES ($type);";
NSString * const kFRECreateTableMethodSignatureCommand = @"CREATE TABLE MethodSignature( "
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
"typeEncoding TEXT, "
"argc INTEGER, "
"returnType INTEGER, "
"frameLength INTEGER, "
"FOREIGN KEY(returnType) REFERENCES TypeEncoding(id) "
");";
NSString * const kFREInsertMethodSignature = @"INSERT INTO MethodSignature ( "
"typeEncoding, argc, returnType, frameLength "
") VALUES ( "
"$typeEncoding, $argc, $returnType, $frameLength "
");";
NSString * const kFRECreateTableMethodCommand = @"CREATE TABLE Method( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"sel INTEGER, "
"class INTEGER, "
"instance INTEGER, " // 0 if class method, 1 if instance method
"signature INTEGER, "
"image INTEGER, "
"FOREIGN KEY(sel) REFERENCES Selector(id), "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(signature) REFERENCES MethodSignature(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertMethod = @"INSERT INTO Method ( "
"sel, class, instance, signature, image "
") VALUES ( "
"$sel, $class, $instance, $signature, $image "
");";
NSString * const kFRECreateTablePropertyCommand = @"CREATE TABLE Property( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"name TEXT, "
"class INTEGER, "
"instance INTEGER, " // 0 if class prop, 1 if instance prop
"image INTEGER, "
"attributes TEXT, "
"customGetter INTEGER, "
"customSetter INTEGER, "
"type INTEGER, "
"ivar TEXT, "
"readonly INTEGER, "
"copy INTEGER, "
"retained INTEGER, "
"nonatomic INTEGER, "
"dynamic INTEGER, "
"weak INTEGER, "
"canGC INTEGER, "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(customGetter) REFERENCES Selector(id), "
"FOREIGN KEY(customSetter) REFERENCES Selector(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertProperty = @"INSERT INTO Property ( "
"name, class, instance, attributes, image, "
"customGetter, customSetter, type, ivar, readonly, "
"copy, retained, nonatomic, dynamic, weak, canGC "
") VALUES ( "
"$name, $class, $instance, $attributes, $image, "
"$customGetter, $customSetter, $type, $ivar, $readonly, "
"$copy, $retained, $nonatomic, $dynamic, $weak, $canGC "
");";
NSString * const kFRECreateTableIvarCommand = @"CREATE TABLE Ivar( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"name TEXT, "
"offset INTEGER, "
"type INTEGER, "
"class INTEGER, "
"image INTEGER, "
"FOREIGN KEY(type) REFERENCES TypeEncoding(id), "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertIvar = @"INSERT INTO Ivar ( "
"name, offset, type, class, image "
") VALUES ( "
"$name, $offset, $type, $class, $image "
");";
NSString * const kFRECreateTableProtocolCommand = @"CREATE TABLE Protocol( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"name TEXT, "
"image INTEGER, "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertProtocol = @"INSERT INTO Protocol "
"(name, image) VALUES ($name, $image);";
NSString * const kFRECreateTableProtocolPropertyCommand = @"CREATE TABLE ProtocolMember( "
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"protocol INTEGER, "
"required INTEGER, "
"instance INTEGER, " // 0 if class member, 1 if instance member
// Only of the two below is used
"property TEXT, "
"method TEXT, "
"image INTEGER, "
"FOREIGN KEY(protocol) REFERENCES Protocol(id), "
"FOREIGN KEY(image) REFERENCES MachO(id) "
");";
NSString * const kFREInsertProtocolMember = @"INSERT INTO ProtocolMember ( "
"protocol, required, instance, property, method, image "
") VALUES ( "
"$protocol, $required, $instance, $property, $method, $image "
");";
/// For protocols conforming to other protocols
NSString * const kFRECreateTableProtocolConformanceCommand = @"CREATE TABLE ProtocolConformance( "
"protocol INTEGER, "
"conformance INTEGER, "
"FOREIGN KEY(protocol) REFERENCES Protocol(id), "
"FOREIGN KEY(conformance) REFERENCES Protocol(id) "
");";
NSString * const kFREInsertProtocolConformance = @"INSERT INTO ProtocolConformance "
"(protocol, conformance) VALUES ($protocol, $conformance);";
/// For classes conforming to protocols
NSString * const kFRECreateTableClassConformanceCommand = @"CREATE TABLE ClassConformance( "
"class INTEGER, "
"conformance INTEGER, "
"FOREIGN KEY(class) REFERENCES Class(id), "
"FOREIGN KEY(conformance) REFERENCES Protocol(id) "
");";
NSString * const kFREInsertClassConformance = @"INSERT INTO ClassConformance "
"(class, conformance) VALUES ($class, $conformance);";
@interface FLEXRuntimeExporter ()
@property (nonatomic, readonly) FLEXSQLiteDatabaseManager *db;
@property (nonatomic, copy) NSArray<NSString *> *loadedShortBundleNames;
@property (nonatomic, copy) NSArray<NSString *> *loadedBundlePaths;
@property (nonatomic, copy) NSArray<FLEXProtocol *> *protocols;
@property (nonatomic, copy) NSArray<Class> *classes;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *bundlePathsToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *protocolsToIDs;
@property (nonatomic) NSMutableDictionary<Class, NSNumber *> *classesToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *typeEncodingsToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *methodSignaturesToIDs;
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *selectorsToIDs;
@end
@implementation FLEXRuntimeExporter
+ (NSString *)tempFilename {
NSString *temp = NSTemporaryDirectory();
NSString *uuid = [NSUUID.UUID.UUIDString substringToIndex:8];
NSString *filename = [NSString stringWithFormat:@"FLEXRuntimeDatabase-%@.db", uuid];
return [temp stringByAppendingPathComponent:filename];
}
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
progressHandler:(void(^)(NSString *status))progress
completion:(void (^)(NSString *))completion {
[self createRuntimeDatabaseAtPath:path forImages:nil progressHandler:progress completion:completion];
}
+ (void)createRuntimeDatabaseAtPath:(NSString *)path
forImages:(NSArray<NSString *> *)images
progressHandler:(void(^)(NSString *status))progress
completion:(void(^)(NSString *_Nullable error))completion {
__typeof(completion) callback = ^(NSString *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
};
// This must be called on the main thread first
if (NSThread.isMainThread) {
[FLEXRuntimeClient initializeWebKitLegacy];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[FLEXRuntimeClient initializeWebKitLegacy];
});
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSError *error = nil;
NSString *errorMessage = nil;
// Get unused temp filename, remove existing database if any
NSString *tempPath = [self tempFilename];
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
[NSFileManager.defaultManager removeItemAtPath:tempPath error:&error];
if (error) {
callback(error.localizedDescription);
return;
}
}
// Attempt to create and populate the database, abort if we fail
FLEXRuntimeExporter *exporter = [self new];
exporter.loadedBundlePaths = images;
if (![exporter createAndPopulateDatabaseAtPath:tempPath
progressHandler:progress
error:&errorMessage]) {
// Remove temp database if it was not moved
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
[NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
}
callback(errorMessage);
return;
}
// Remove old database at given path
if ([NSFileManager.defaultManager fileExistsAtPath:path]) {
[NSFileManager.defaultManager removeItemAtPath:path error:&error];
if (error) {
callback(error.localizedDescription);
return;
}
}
// Move new database to desired path
[NSFileManager.defaultManager moveItemAtPath:tempPath toPath:path error:&error];
if (error) {
callback(error.localizedDescription);
}
// Remove temp database if it was not moved
if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) {
[NSFileManager.defaultManager removeItemAtPath:tempPath error:nil];
}
callback(nil);
});
}
- (id)init {
self = [super init];
if (self) {
_bundlePathsToIDs = [NSMutableDictionary new];
_protocolsToIDs = [NSMutableDictionary new];
_classesToIDs = [NSMutableDictionary new];
_typeEncodingsToIDs = [NSMutableDictionary new];
_methodSignaturesToIDs = [NSMutableDictionary new];
_selectorsToIDs = [NSMutableDictionary new];
_bundlePathsToIDs[NSNull.null] = (id)NSNull.null;
}
return self;
}
- (BOOL)createAndPopulateDatabaseAtPath:(NSString *)path
progressHandler:(void(^)(NSString *status))step
error:(NSString **)error {
_db = [FLEXSQLiteDatabaseManager managerForDatabase:path];
[self loadMetadata:step];
if ([self createTables] && [self addImages:step] && [self addProtocols:step] &&
[self addClasses:step] && [self setSuperclasses:step] &&
[self addProtocolConformances:step] && [self addClassConformances:step] &&
[self addIvars:step] && [self addMethods:step] && [self addProperties:step]) {
_db = nil; // Close the database
return YES;
}
*error = self.db.lastResult.message;
return NO;
}
- (void)loadMetadata:(void(^)(NSString *status))progress {
progress(@"Loading metadata…");
FLEXRuntimeClient *runtime = FLEXRuntimeClient.runtime;
// Only load metadata for the existing paths if any
if (self.loadedBundlePaths) {
// Images
self.loadedShortBundleNames = [self.loadedBundlePaths flex_mapped:^id(NSString *path, NSUInteger idx) {
return [runtime shortNameForImageName:path];
}];
// Classes
self.classes = [[runtime classesForToken:FLEXSearchToken.any
inBundles:self.loadedBundlePaths.mutableCopy
] flex_mapped:^id(NSString *cls, NSUInteger idx) {
return NSClassFromString(cls);
}];
} else {
// Images
self.loadedShortBundleNames = runtime.imageDisplayNames;
self.loadedBundlePaths = [self.loadedShortBundleNames flex_mapped:^id(NSString *name, NSUInteger idx) {
return [runtime imageNameForShortName:name];
}];
// Classes
self.classes = [runtime copySafeClassList];
}
// ...except protocols, because there's not a lot of them
// and there's no way load the protocols for a given image
self.protocols = [[runtime copyProtocolList] flex_mapped:^id(Protocol *proto, NSUInteger idx) {
return [FLEXProtocol protocol:proto];
}];
}
- (BOOL)createTables {
NSArray<NSString *> *commands = @[
kFREEnableForeignKeys,
kFRECreateTableMachOCommand,
kFRECreateTableClassCommand,
kFRECreateTableSelectorCommand,
kFRECreateTableTypeEncodingCommand,
kFRECreateTableTypeSignatureCommand,
kFRECreateTableMethodSignatureCommand,
kFRECreateTableMethodCommand,
kFRECreateTablePropertyCommand,
kFRECreateTableIvarCommand,
kFRECreateTableProtocolCommand,
kFRECreateTableProtocolPropertyCommand,
kFRECreateTableProtocolConformanceCommand,
kFRECreateTableClassConformanceCommand
];
for (NSString *command in commands) {
if (![self.db executeStatement:command]) {
return NO;
}
}
return YES;
}
- (BOOL)addImages:(void(^)(NSString *status))progress {
progress(@"Adding loaded images…");
FLEXSQLiteDatabaseManager *database = self.db;
NSArray *shortNames = self.loadedShortBundleNames;
NSArray *fullPaths = self.loadedBundlePaths;
NSParameterAssert(shortNames.count == fullPaths.count);
NSInteger count = shortNames.count;
for (NSInteger i = 0; i < count; i++) {
// Grab bundle ID
NSString *bundleID = [NSBundle
bundleWithPath:fullPaths[i]
].bundleIdentifier;
[database executeStatement:kFREInsertImage arguments:@{
@"$shortName": shortNames[i],
@"$imagePath": fullPaths[i],
@"$bundleID": bundleID ?: NSNull.null
}];
if (database.lastResult.isError) {
return NO;
} else {
self.bundlePathsToIDs[fullPaths[i]] = @(database.lastRowID);
}
}
return YES;
}
NS_INLINE BOOL FREInsertProtocolMember(FLEXSQLiteDatabaseManager *db,
id proto, id required, id instance,
id prop, id methSel, id image) {
return ![db executeStatement:kFREInsertProtocolMember arguments:@{
@"$protocol": proto,
@"$required": required,
@"$instance": instance ?: NSNull.null,
@"$property": prop ?: NSNull.null,
@"$method": methSel ?: NSNull.null,
@"$image": image
}].isError;
}
- (BOOL)addProtocols:(void(^)(NSString *status))progress {
progress([NSString stringWithFormat:@"Adding %@ protocols…", @(self.protocols.count)]);
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
for (FLEXProtocol *proto in self.protocols) {
id imagePath = proto.imagePath ?: NSNull.null;
NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
NSNumber *pid = nil;
// Insert protocol
BOOL failed = [database executeStatement:kFREInsertProtocol arguments:@{
@"$name": proto.name, @"$image": image
}].isError;
// Cache rowid
if (failed) {
return NO;
} else {
self.protocolsToIDs[proto.name] = pid = @(database.lastRowID);
}
// Insert its members //
// Required methods
for (FLEXMethodDescription *method in proto.requiredMethods) {
NSString *selector = NSStringFromSelector(method.selector);
if (!FREInsertProtocolMember(database, pid, @YES, method.instance, nil, selector, image)) {
return NO;
}
}
// Optional methods
for (FLEXMethodDescription *method in proto.optionalMethods) {
NSString *selector = NSStringFromSelector(method.selector);
if (!FREInsertProtocolMember(database, pid, @NO, method.instance, nil, selector, image)) {
return NO;
}
}
if (@available(iOS 10, *)) {
// Required properties
for (FLEXProperty *property in proto.requiredProperties) {
BOOL success = FREInsertProtocolMember(
database, pid, @YES, @(property.isClassProperty), property.name, NSNull.null, image
);
if (!success) return NO;
}
// Optional properties
for (FLEXProperty *property in proto.optionalProperties) {
BOOL success = FREInsertProtocolMember(
database, pid, @NO, @(property.isClassProperty), property.name, NSNull.null, image
);
if (!success) return NO;
}
} else {
// Just... properties.
for (FLEXProperty *property in proto.properties) {
BOOL success = FREInsertProtocolMember(
database, pid, nil, @(property.isClassProperty), property.name, NSNull.null, image
);
if (!success) return NO;
}
}
}
return YES;
}
- (BOOL)addProtocolConformances:(void(^)(NSString *status))progress {
progress(@"Adding protocol-to-protocol conformances…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *protocolIDs = self.protocolsToIDs;
for (FLEXProtocol *proto in self.protocols) {
id protoID = protocolIDs[proto.name];
for (FLEXProtocol *conform in proto.protocols) {
BOOL failed = [database executeStatement:kFREInsertProtocolConformance arguments:@{
@"$protocol": protoID,
@"$conformance": protocolIDs[conform.name]
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addClasses:(void(^)(NSString *status))progress {
progress([NSString stringWithFormat:@"Adding %@ classes…", @(self.classes.count)]);
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
for (Class cls in self.classes) {
const char *imageName = class_getImageName(cls);
id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
image = image ?: NSNull.null;
BOOL failed = [database executeStatement:kFREInsertClass arguments:@{
@"$className": NSStringFromClass(cls),
@"$instanceSize": @(class_getInstanceSize(cls)),
@"$version": @(class_getVersion(cls)),
@"$image": image
}].isError;
if (failed) {
return NO;
} else {
self.classesToIDs[(id)cls] = @(database.lastRowID);
}
}
return YES;
}
- (BOOL)setSuperclasses:(void(^)(NSString *status))progress {
progress(@"Setting superclasses…");
FLEXSQLiteDatabaseManager *database = self.db;
for (Class cls in self.classes) {
// Grab superclass ID
Class superclass = class_getSuperclass(cls);
NSNumber *superclassID = _classesToIDs[class_getSuperclass(cls)];
// ... or add the superclass and cache its ID if the
// superclass does not reside in the target image(s)
if (!superclassID) {
NSDictionary *args = @{ @"$className": NSStringFromClass(superclass) };
BOOL failed = [database executeStatement:kFREInsertClass arguments:args].isError;
if (failed) { return NO; }
_classesToIDs[(id)superclass] = superclassID = @(database.lastRowID);
}
if (superclass) {
BOOL failed = [database executeStatement:kFREUpdateClassSetSuper arguments:@{
@"$super": superclassID, @"$id": _classesToIDs[cls]
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addClassConformances:(void(^)(NSString *status))progress {
progress(@"Adding class-to-protocol conformances…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *protocolIDs = self.protocolsToIDs;
NSDictionary *classIDs = self.classesToIDs;
for (Class cls in self.classes) {
id classID = classIDs[(id)cls];
for (FLEXProtocol *conform in FLEXGetConformedProtocols(cls)) {
BOOL failed = [database executeStatement:kFREInsertClassConformance arguments:@{
@"$class": classID,
@"$conformance": protocolIDs[conform.name]
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addIvars:(void(^)(NSString *status))progress {
progress(@"Adding ivars…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
for (Class cls in self.classes) {
for (FLEXIvar *ivar in FLEXGetAllIvars(cls)) {
// Insert type first
if (![self addTypeEncoding:ivar.typeEncoding size:ivar.size]) {
return NO;
}
id imagePath = ivar.imagePath ?: NSNull.null;
NSNumber *image = imageIDs[imagePath] ?: NSNull.null;
BOOL failed = [database executeStatement:kFREInsertIvar arguments:@{
@"$name": ivar.name,
@"$offset": @(ivar.offset),
@"$type": _typeEncodingsToIDs[ivar.typeEncoding],
@"$class": _classesToIDs[cls],
@"$image": image
}].isError;
if (failed) {
return NO;
}
}
}
return YES;
}
- (BOOL)addMethods:(void(^)(NSString *status))progress {
progress(@"Adding methods…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
// Loop over all classes
for (Class cls in self.classes) {
NSNumber *classID = _classesToIDs[(id)cls];
const char *imageName = class_getImageName(cls);
id image = imageName ? imageIDs[@(imageName)] : NSNull.null;
image = image ?: NSNull.null;
// Block used to process each message
BOOL (^insert)(FLEXMethod *, NSNumber *) = ^BOOL(FLEXMethod *method, NSNumber *instance) {
// Insert selector and signature first
if (![self addSelector:method.selectorString]) {
return NO;
}
if (![self addMethodSignature:method]) {
return NO;
}
return ![database executeStatement:kFREInsertMethod arguments:@{
@"$sel": self->_selectorsToIDs[method.selectorString],
@"$class": classID,
@"$instance": instance,
@"$signature": self->_methodSignaturesToIDs[method.signatureString],
@"$image": image
}].isError;
};
// Loop over all instance and class methods of that class //
for (FLEXMethod *method in FLEXGetAllMethods(cls, YES)) {
if (!insert(method, @YES)) {
return NO;
}
}
for (FLEXMethod *method in FLEXGetAllMethods(object_getClass(cls), NO)) {
if (!insert(method, @NO)) {
return NO;
}
}
}
return YES;
}
- (BOOL)addProperties:(void(^)(NSString *status))progress {
progress(@"Adding properties…");
FLEXSQLiteDatabaseManager *database = self.db;
NSDictionary *imageIDs = self.bundlePathsToIDs;
// Loop over all classes
for (Class cls in self.classes) {
NSNumber *classID = _classesToIDs[(id)cls];
// Block used to process each message
BOOL (^insert)(FLEXProperty *, NSNumber *) = ^BOOL(FLEXProperty *property, NSNumber *instance) {
FLEXPropertyAttributes *attrs = property.attributes;
NSString *customGetter = attrs.customGetterString;
NSString *customSetter = attrs.customSetterString;
// Insert selectors first
if (customGetter) {
if (![self addSelector:customGetter]) {
return NO;
}
}
if (customSetter) {
if (![self addSelector:customSetter]) {
return NO;
}
}
// Insert type encoding first
NSInteger size = [FLEXTypeEncodingParser
sizeForTypeEncoding:attrs.typeEncoding alignment:nil
];
if (![self addTypeEncoding:attrs.typeEncoding size:size]) {
return NO;
}
id imagePath = property.imagePath ?: NSNull.null;
id image = imageIDs[imagePath] ?: NSNull.null;
return ![database executeStatement:kFREInsertProperty arguments:@{
@"$name": property.name,
@"$class": classID,
@"$instance": instance,
@"$image": image,
@"$attributes": attrs.string,
@"$customGetter": self->_selectorsToIDs[customGetter] ?: NSNull.null,
@"$customSetter": self->_selectorsToIDs[customSetter] ?: NSNull.null,
@"$type": self->_typeEncodingsToIDs[attrs.typeEncoding] ?: NSNull.null,
@"$ivar": attrs.backingIvar ?: NSNull.null,
@"$readonly": @(attrs.isReadOnly),
@"$copy": @(attrs.isCopy),
@"$retained": @(attrs.isRetained),
@"$nonatomic": @(attrs.isNonatomic),
@"$dynamic": @(attrs.isDynamic),
@"$weak": @(attrs.isWeak),
@"$canGC": @(attrs.isGarbageCollectable),
}].isError;
};
// Loop over all instance and class methods of that class //
for (FLEXProperty *property in FLEXGetAllProperties(cls)) {
if (!insert(property, @YES)) {
return NO;
}
}
for (FLEXProperty *property in FLEXGetAllProperties(object_getClass(cls))) {
if (!insert(property, @NO)) {
return NO;
}
}
}
return YES;
}
- (BOOL)addSelector:(NSString *)sel {
return [self executeInsert:kFREInsertSelector args:@{
@"$name": sel
} key:sel cacheResult:_selectorsToIDs];
}
- (BOOL)addTypeEncoding:(NSString *)type size:(NSInteger)size {
return [self executeInsert:kFREInsertTypeEncoding args:@{
@"$type": type, @"$size": @(size)
} key:type cacheResult:_typeEncodingsToIDs];
}
- (BOOL)addMethodSignature:(FLEXMethod *)method {
NSString *signature = method.signatureString;
NSString *returnType = @((char *)method.returnType);
// Insert return type first
if (![self addTypeEncoding:returnType size:method.returnSize]) {
return NO;
}
return [self executeInsert:kFREInsertMethodSignature args:@{
@"$typeEncoding": signature,
@"$returnType": _typeEncodingsToIDs[returnType],
@"$argc": @(method.numberOfArguments),
@"$frameLength": @(method.signature.frameLength)
} key:signature cacheResult:_methodSignaturesToIDs];
}
- (BOOL)executeInsert:(NSString *)statement
args:(NSDictionary *)args
key:(NSString *)cacheKey
cacheResult:(NSMutableDictionary<NSString *, NSNumber *> *)rowids {
// Check if already inserted
if (rowids[cacheKey]) {
return YES;
}
// Insert
FLEXSQLiteDatabaseManager *database = _db;
[database executeStatement:statement arguments:args];
if (database.lastResult.isError) {
return NO;
}
// Cache rowid
rowids[cacheKey] = @(database.lastRowID);
return YES;
}
@end
@@ -10,9 +10,9 @@
#import "FLEXRuntimeKeyPathTokenizer.h"
#import "FLEXRuntimeController.h"
#import "NSString+FLEX.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "UITextField+Range.h"
#import "NSTimer+Blocks.h"
#import "NSTimer+FLEX.h"
#import "FLEXTableView.h"
#import "FLEXUtility.h"
#import "FLEXObjectExplorerFactory.h"
@@ -7,7 +7,7 @@
//
#import "FLEXRuntimeKeyPath.h"
#include <dlfcn.h>
#import "FLEXRuntimeClient.h"
@interface FLEXRuntimeKeyPath () {
NSString *flex_description;
@@ -49,29 +49,12 @@
keyPath->flex_description = keyPathString;
if (bundle.isAny && cls.isAny && method.isAny) {
[self initializeWebKitLegacy];
[FLEXRuntimeClient initializeWebKitLegacy];
}
return keyPath;
}
+ (void)initializeWebKitLegacy {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen(
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
RTLD_LAZY
);
void (*WebKitInitialize)() = dlsym(handle, "WebKitInitialize");
if (WebKitInitialize) {
NSAssert(NSThread.isMainThread,
@"WebKitInitialize can only be called on the main thread"
);
WebKitInitialize();
}
});
}
- (NSString *)description {
return flex_description;
}
@@ -42,6 +42,11 @@ static BOOL my_os_log_shim_enabled(void *addr) {
#pragma mark - Initialization
+ (void)load {
// User must opt-into disabling os_log
if (!NSUserDefaults.standardUserDefaults.flex_disableOSLog) {
return;
}
// Thanks to @Ram4096 on GitHub for telling me that
// os_log is conditionally enabled by the SDK version
void *addr = __builtin_return_address(0);
@@ -108,7 +113,7 @@ static BOOL my_os_log_shim_enabled(void *addr) {
}
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.title = @"Loading...";
self.title = @"Waiting for Logs...";
// Toolbar buttons //
@@ -123,11 +128,7 @@ static BOOL my_os_log_shim_enabled(void *addr) {
action:@selector(showLogSettings)
];
if (FLEXOSLogAvailable() && !FLEXNSLogHookWorks) {
[self addToolbarItems:@[scrollDown, settings]];
} else {
[self addToolbarItems:@[scrollDown]];
}
[self addToolbarItems:@[scrollDown, settings]];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -191,17 +192,35 @@ static BOOL my_os_log_shim_enabled(void *addr) {
}
- (void)showLogSettings {
FLEXOSLogController *logController = (FLEXOSLogController *)self.logController;
BOOL persistent = NSUserDefaults.standardUserDefaults.flex_cacheOSLogMessages;
NSString *toggle = persistent ? @"Disable" : @"Enable";
NSString *title = [@"Persistent logging: " stringByAppendingString:persistent ? @"ON" : @"OFF"];
NSString *body = @"In iOS 10 and up, ASL is gone. The OS Log API is much more limited. "
"To get as close to the old behavior as possible, logs must be collected manually at launch and stored.\n\n"
"Turn this feature on only when you need it.";
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
BOOL disableOSLog = defaults.flex_disableOSLog;
BOOL persistent = defaults.flex_cacheOSLogMessages;
NSString *aslToggle = disableOSLog ? @"Enable os_log (default)" : @"Disable os_log";
NSString *persistence = persistent ? @"Disable persistent logging" : @"Enable persistent logging";
NSString *title = @"System Log Settings";
NSString *body = @"In iOS 10 and up, ASL has been replaced by os_log. "
"The os_log API is much more limited. Below, you can opt-into the old behavior "
"if you want cleaner, more reliable logs within FLEX, but this will break "
"anything that expects os_log to be working, such as Console.app. "
"This setting requires the app to restart to take effect. \n\n"
"To get as close to the old behavior as possible with os_log enabled, logs must "
"be collected manually at launch and stored. This setting has no effect "
"on iOS 9 and below, or if os_log is disabled. "
"You should only enable persistent logging when you need it.";
FLEXOSLogController *logController = (FLEXOSLogController *)self.logController;
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(title).message(body).button(toggle).handler(^(NSArray<NSString *> *strings) {
NSUserDefaults.standardUserDefaults.flex_cacheOSLogMessages = !persistent;
make.title(title).message(body);
make.button(aslToggle).destructiveStyle().handler(^(NSArray<NSString *> *strings) {
[defaults toggleBoolForKey:kFLEXDefaultsDisableOSLogForceASLKey];
});
make.button(persistence).handler(^(NSArray<NSString *> *strings) {
[defaults toggleBoolForKey:kFLEXDefaultsiOSPersistentOSLogKey];
logController.persistent = !persistent;
[logController.messages addObjectsFromArray:self.logMessages.list];
});
+2 -2
View File
@@ -15,7 +15,7 @@
#import "FLEXExplorerViewController.h"
#import "FLEXNetworkMITMViewController.h"
#import "FLEXKeyboardHelpViewController.h"
#import "FLEXFileBrowserTableViewController.h"
#import "FLEXFileBrowserController.h"
#import "FLEXUtility.h"
@interface FLEXManager (ExtensibilityPrivate)
@@ -156,7 +156,7 @@
} description:@"End editing text\n\t\tDismiss top view controller"];
[self registerSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
[self toggleTopViewControllerOfClass:[FLEXFileBrowserTableViewController class]];
[self toggleTopViewControllerOfClass:[FLEXFileBrowserController class]];
} description:@"Toggle file browser menu"];
}
+15 -11
View File
@@ -11,21 +11,25 @@
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXObjectExplorerFactory.h"
#import "NSUserDefaults+FLEX.h"
@implementation FLEXManager (Networking)
+ (void)load {
// Register array/dictionary viewer for JSON responses
[self.sharedManager setCustomViewerForContentType:@"application/json"
viewControllerFutureBlock:^UIViewController *(NSData *data) {
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (jsonObject) {
return [FLEXObjectExplorerFactory explorerViewControllerForObject:jsonObject];
}
return nil;
}
];
if (NSUserDefaults.standardUserDefaults.flex_registerDictionaryJSONViewerOnLaunch) {
dispatch_async(dispatch_get_main_queue(), ^{
// Register array/dictionary viewer for JSON responses
[self.sharedManager setCustomViewerForContentType:@"application/json"
viewControllerFutureBlock:^UIViewController *(NSData *data) {
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (jsonObject) {
return [FLEXObjectExplorerFactory explorerViewControllerForObject:jsonObject];
}
return nil;
}
];
});
}
}
- (BOOL)isNetworkDebuggingEnabled {
+1 -1
View File
@@ -11,7 +11,7 @@
#import "FLEXExplorerViewController.h"
#import "FLEXWindow.h"
#import "FLEXObjectExplorerViewController.h"
#import "FLEXFileBrowserTableViewController.h"
#import "FLEXFileBrowserController.h"
@interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
+10 -10
View File
@@ -12,8 +12,8 @@
#import "FLEXNetworkTransaction.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkTransactionTableViewCell.h"
#import "FLEXNetworkTransactionDetailTableViewController.h"
#import "FLEXNetworkTransactionCell.h"
#import "FLEXNetworkTransactionDetailController.h"
#import "FLEXNetworkSettingsController.h"
#import "FLEXGlobalsViewController.h"
#import "UIBarButtonItem+FLEX.h"
@@ -61,11 +61,11 @@
]];
[self.tableView
registerClass:[FLEXNetworkTransactionTableViewCell class]
registerClass:[FLEXNetworkTransactionCell class]
forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier
];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.rowHeight = FLEXNetworkTransactionTableViewCell.preferredCellHeight;
self.tableView.rowHeight = FLEXNetworkTransactionCell.preferredCellHeight;
[self registerForNotifications];
[self updateTransactions];
@@ -109,7 +109,7 @@
#pragma mark Button Actions
- (void)settingsButtonTapped:(id)sender {
- (void)settingsButtonTapped:(UIBarButtonItem *)sender {
UIViewController *settings = [FLEXNetworkSettingsController new];
settings.navigationItem.rightBarButtonItem = FLEXBarButtonItemSystem(
Done, self, @selector(settingsViewControllerDoneTapped:)
@@ -121,7 +121,7 @@
[self presentViewController:nav animated:YES completion:nil];
}
- (void)trashButtonTapped:(id)sender {
- (void)trashButtonTapped:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
make.title(@"Clear All Recorded Requests?");
make.message(@"This cannot be undone.");
@@ -130,7 +130,7 @@
make.button(@"Clear All").destructiveStyle().handler(^(NSArray *strings) {
[FLEXNetworkRecorder.defaultRecorder clearRecordedActivity];
});
} showFrom:self];
} showFrom:self source:sender];
}
- (void)settingsViewControllerDoneTapped:(id)sender {
@@ -311,7 +311,7 @@
FLEXNetworkTransaction *transaction = notification.userInfo[kFLEXNetworkRecorderUserInfoTransactionKey];
// Update both the main table view and search table view if needed.
for (FLEXNetworkTransactionTableViewCell *cell in [self.tableView visibleCells]) {
for (FLEXNetworkTransactionCell *cell in [self.tableView visibleCells]) {
if ([cell.transaction isEqual:transaction]) {
// Using -[UITableView reloadRowsAtIndexPaths:withRowAnimation:] is overkill here and kicks off a lot of
// work that can make the table view somewhat unresponsive when lots of updates are streaming in.
@@ -352,7 +352,7 @@
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkTransactionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath];
FLEXNetworkTransactionCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath];
cell.transaction = [self transactionAtIndexPath:indexPath];
// Since we insert from the top, assign background colors bottom up to keep them consistent for each transaction.
@@ -367,7 +367,7 @@
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkTransactionDetailTableViewController *detailViewController = [FLEXNetworkTransactionDetailTableViewController new];
FLEXNetworkTransactionDetailController *detailViewController = [FLEXNetworkTransactionDetailController new];
detailViewController.transaction = [self transactionAtIndexPath:indexPath];
[self.navigationController pushViewController:detailViewController animated:YES];
}
@@ -11,6 +11,7 @@
#import "FLEXUtility.h"
#import "FLEXTableView.h"
#import "FLEXColor.h"
#import "NSUserDefaults+FLEX.h"
@interface FLEXNetworkSettingsController () <UIActionSheetDelegate>
@property (nonatomic) float cacheLimitValue;
@@ -18,6 +19,7 @@
@property (nonatomic, readonly) UISwitch *observerSwitch;
@property (nonatomic, readonly) UISwitch *cacheMediaSwitch;
@property (nonatomic, readonly) UISwitch *jsonViewerSwitch;
@property (nonatomic, readonly) UISlider *cacheLimitSlider;
@property (nonatomic) UILabel *cacheLimitLabel;
@@ -32,8 +34,11 @@
[self disableToolbar];
self.hostBlacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist.mutableCopy;
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
_observerSwitch = [UISwitch new];
_cacheMediaSwitch = [UISwitch new];
_jsonViewerSwitch = [UISwitch new];
_cacheLimitSlider = [UISlider new];
self.observerSwitch.on = FLEXNetworkObserver.enabled;
@@ -48,6 +53,12 @@
forControlEvents:UIControlEventValueChanged
];
self.jsonViewerSwitch.on = defaults.flex_registerDictionaryJSONViewerOnLaunch;
[self.jsonViewerSwitch addTarget:self
action:@selector(jsonViewerSettingToggled:)
forControlEvents:UIControlEventValueChanged
];
[self.cacheLimitSlider addTarget:self
action:@selector(cacheLimitAdjusted:)
forControlEvents:UIControlEventValueChanged
@@ -84,12 +95,16 @@
FLEXNetworkRecorder.defaultRecorder.shouldCacheMediaResponses = sender.isOn;
}
- (void)jsonViewerSettingToggled:(UISwitch *)sender {
[NSUserDefaults.standardUserDefaults toggleBoolForKey:kFLEXDefaultsRegisterJSONExplorerKey];
}
- (void)cacheLimitAdjusted:(UISlider *)sender {
self.cacheLimitValue = sender.value;
}
#pragma mark - Table View
#pragma mark - Table View Data Source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.hostBlacklist.count ? 2 : 1;
@@ -97,7 +112,7 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0: return 4;
case 0: return 5;
case 1: return self.hostBlacklist.count;
default: return 0;
}
@@ -111,6 +126,17 @@
}
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
if (section == 0) {
return @"By default, JSON is rendered in a webview. Turn on "
"\"View JSON as a dictionary/array\" to convert JSON payloads "
"to objects and view them in an object explorer. "
"This setting requires a restart of the app.";
}
return nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView
dequeueReusableCellWithIdentifier:kFLEXDefaultCell forIndexPath:indexPath
@@ -132,10 +158,14 @@
cell.accessoryView = self.cacheMediaSwitch;
break;
case 2:
cell.textLabel.text = @"View JSON as a dictionary/array";
cell.accessoryView = self.jsonViewerSwitch;
break;
case 3:
cell.textLabel.text = @"Reset Host Blacklist";
cell.textLabel.textColor = tableView.tintColor;
break;
case 3:
case 4:
cell.textLabel.text = self.cacheLimitCellTitle;
self.cacheLimitLabel = cell.textLabel;
[self.cacheLimitSlider removeFromSuperview];
@@ -179,6 +209,8 @@
return cell;
}
#pragma mark - Table View Delegate
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)ip {
// Can only select the "Reset Host Blacklist" row
return ip.section == 0 && ip.row == 2;
@@ -1,5 +1,5 @@
//
// FLEXNetworkTransactionTableViewCell.h
// FLEXNetworkTransactionCell.h
// Flipboard
//
// Created by Ryan Olson on 2/8/15.
@@ -8,11 +8,11 @@
#import <UIKit/UIKit.h>
extern NSString *const kFLEXNetworkTransactionCellIdentifier;
extern NSString * const kFLEXNetworkTransactionCellIdentifier;
@class FLEXNetworkTransaction;
@interface FLEXNetworkTransactionTableViewCell : UITableViewCell
@interface FLEXNetworkTransactionCell : UITableViewCell
@property (nonatomic) FLEXNetworkTransaction *transaction;
@@ -1,5 +1,5 @@
//
// FLEXNetworkTransactionTableViewCell.m
// FLEXNetworkTransactionCell.m
// Flipboard
//
// Created by Ryan Olson on 2/8/15.
@@ -7,14 +7,14 @@
//
#import "FLEXColor.h"
#import "FLEXNetworkTransactionTableViewCell.h"
#import "FLEXNetworkTransactionCell.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXUtility.h"
#import "FLEXResources.h"
NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactionCellIdentifier";
@interface FLEXNetworkTransactionTableViewCell ()
@interface FLEXNetworkTransactionCell ()
@property (nonatomic) UIImageView *thumbnailImageView;
@property (nonatomic) UILabel *nameLabel;
@@ -23,7 +23,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
@end
@implementation FLEXNetworkTransactionTableViewCell
@implementation FLEXNetworkTransactionCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
@@ -1,5 +1,5 @@
//
// FLEXNetworkTransactionDetailTableViewController.h
// FLEXNetworkTransactionDetailController.h
// Flipboard
//
// Created by Ryan Olson on 2/10/15.
@@ -10,7 +10,7 @@
@class FLEXNetworkTransaction;
@interface FLEXNetworkTransactionDetailTableViewController : UITableViewController
@interface FLEXNetworkTransactionDetailController : UITableViewController
@property (nonatomic) FLEXNetworkTransaction *transaction;
@@ -1,5 +1,5 @@
//
// FLEXNetworkTransactionDetailTableViewController.m
// FLEXNetworkTransactionDetailController.m
// Flipboard
//
// Created by Ryan Olson on 2/10/15.
@@ -7,7 +7,7 @@
//
#import "FLEXColor.h"
#import "FLEXNetworkTransactionDetailTableViewController.h"
#import "FLEXNetworkTransactionDetailController.h"
#import "FLEXNetworkCurlLogger.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkTransaction.h"
@@ -44,13 +44,13 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@end
@interface FLEXNetworkTransactionDetailTableViewController ()
@interface FLEXNetworkTransactionDetailController ()
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
@end
@implementation FLEXNetworkTransactionDetailTableViewController
@implementation FLEXNetworkTransactionDetailController
- (instancetype)initWithStyle:(UITableViewStyle)style {
// Force grouped style
@@ -15,7 +15,7 @@
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXUtility.h"
#import "NSObject+Reflection.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXMethod.h"
#import <objc/runtime.h>
@@ -8,10 +8,22 @@
#import "FLEXRuntime+UIKitHelpers.h"
/// Carries state about the current user defaults settings
@interface FLEXObjectExplorerDefaults : NSObject
+ (instancetype)canEdit:(BOOL)editable wantsPreviews:(BOOL)showPreviews;
/// Only \c YES for properties and ivars
@property (nonatomic, readonly) BOOL isEditable;
/// Only affects properties and ivars
@property (nonatomic, readonly) BOOL wantsDynamicPreviews;
@end
@interface FLEXObjectExplorer : NSObject
+ (instancetype)forObject:(id)objectOrClass;
+ (void)configureDefaultsForItems:(NSArray<id<FLEXObjectExplorerItem>> *)items;
@property (nonatomic, readonly) id object;
/// Subclasses can override to provide a more useful description
@property (nonatomic, readonly) NSString *objectDescription;
+31 -5
View File
@@ -9,14 +9,24 @@
#import "FLEXObjectExplorer.h"
#import "FLEXUtility.h"
#import "FLEXRuntimeUtility.h"
#import "NSObject+Reflection.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXRuntime+Compare.h"
#import "FLEXRuntime+UIKitHelpers.h"
#import "FLEXPropertyAttributes.h"
#import "NSObject+Reflection.h"
#import "FLEXMetadataSection.h"
#import "NSUserDefaults+FLEX.h"
@implementation FLEXObjectExplorerDefaults
+ (instancetype)canEdit:(BOOL)editable wantsPreviews:(BOOL)showPreviews {
FLEXObjectExplorerDefaults *defaults = [self new];
defaults->_isEditable = editable;
defaults->_wantsDynamicPreviews = showPreviews;
return defaults;
}
@end
@interface FLEXObjectExplorer () {
NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
NSMutableArray<NSArray<FLEXProperty *> *> *_allClassProperties;
@@ -55,6 +65,24 @@
#pragma mark - Public
+ (void)configureDefaultsForItems:(NSArray<id<FLEXObjectExplorerItem>> *)items {
BOOL hidePreviews = NSUserDefaults.standardUserDefaults.flex_explorerHidesVariablePreviews;
FLEXObjectExplorerDefaults *mutable = [FLEXObjectExplorerDefaults
canEdit:YES wantsPreviews:!hidePreviews
];
FLEXObjectExplorerDefaults *immutable = [FLEXObjectExplorerDefaults
canEdit:NO wantsPreviews:!hidePreviews
];
// .tag is used to cache whether the value of .isEditable;
// This could change at runtime so it is important that
// it is cached every time shortcuts are requeted and not
// just once at as shortcuts are initially registered
for (id<FLEXObjectExplorerItem> metadata in items) {
metadata.defaults = metadata.isEditable ? mutable : immutable;
}
}
- (NSString *)objectDescription {
if (!_objectDescription) {
// Hard-code UIColor description
@@ -224,9 +252,7 @@
// because no other metadata types support editing.
for (NSArray *matrix in @[_allProperties, _allIvars, /* _allMethods, _allClassMethods, _allConformedProtocols */]) {
for (NSArray *metadataByClass in matrix) {
for (id<FLEXRuntimeMetadata> metadata in metadataByClass) {
metadata.tag = metadata.isEditable ? @YES : nil;
}
[FLEXObjectExplorer configureDefaultsForItems:metadataByClass];
}
}
@@ -73,6 +73,7 @@
kFLEXDefaultsHidePropertyIvarsKey,
kFLEXDefaultsHidePropertyMethodsKey,
kFLEXDefaultsHideMethodOverridesKey,
kFLEXDefaultsHideVariablePreviewsKey,
];
}
@@ -101,7 +102,7 @@
// ... button for extra options
[self addToolbarItems:@[[UIBarButtonItem
itemWithImage:FLEXResources.moreIcon target:self action:@selector(moreButtonPressed)
itemWithImage:FLEXResources.moreIcon target:self action:@selector(moreButtonPressed:)
]]];
// Swipe gestures to swipe between classes in the hierarchy
@@ -122,12 +123,7 @@
//
// "If your app targets iOS 9.0 and later or macOS 10.11 and later,
// you don't need to unregister an observer in its dealloc method."
NSArray<NSString *> *observedNotifications = @[
kFLEXDefaultsHidePropertyIvarsKey,
kFLEXDefaultsHidePropertyMethodsKey,
kFLEXDefaultsHideMethodOverridesKey,
];
for (NSString *pref in observedNotifications) {
for (NSString *pref in self.observedNotifications) {
[NSNotificationCenter.defaultCenter
addObserver:self
selector:@selector(fullyReloadData)
@@ -221,7 +217,7 @@
[super reloadData];
}
- (void)shareButtonPressed {
- (void)shareButtonPressed:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
make.button(@"Add to Bookmarks").handler(^(NSArray<NSString *> *strings) {
[FLEXBookmarkManager.bookmarks addObject:self.object];
@@ -233,7 +229,7 @@
UIPasteboard.generalPasteboard.string = [FLEXUtility addressOfObject:self.object];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
@@ -289,13 +285,14 @@
return YES;
}
- (void)moreButtonPressed {
- (void)moreButtonPressed:(UIBarButtonItem *)sender {
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
// Maps preference keys to a description of what they affect
NSDictionary<NSString *, NSString *> *explorerToggles = @{
kFLEXDefaultsHidePropertyIvarsKey: @"Property-Backing Ivars",
kFLEXDefaultsHidePropertyMethodsKey: @"Property-Backing Methods",
kFLEXDefaultsHideMethodOverridesKey: @"Method Overrides",
kFLEXDefaultsHidePropertyIvarsKey: @"Property-Backing Ivars",
kFLEXDefaultsHidePropertyMethodsKey: @"Property-Backing Methods",
kFLEXDefaultsHideMethodOverridesKey: @"Method Overrides",
kFLEXDefaultsHideVariablePreviewsKey: @"Variable Previews"
};
// Maps the key of the action itself to a map of a description
@@ -303,9 +300,10 @@
//
// So keys that are hidden by default have NO mapped to "Show"
NSDictionary<NSString *, NSDictionary *> *nextStateDescriptions = @{
kFLEXDefaultsHidePropertyIvarsKey: @{ @NO: @"Hide ", @YES: @"Show " },
kFLEXDefaultsHidePropertyMethodsKey: @{ @NO: @"Hide ", @YES: @"Show " },
kFLEXDefaultsHideMethodOverridesKey: @{ @NO: @"Show ", @YES: @"Hide " },
kFLEXDefaultsHidePropertyIvarsKey: @{ @NO: @"Hide ", @YES: @"Show " },
kFLEXDefaultsHidePropertyMethodsKey: @{ @NO: @"Hide ", @YES: @"Show " },
kFLEXDefaultsHideMethodOverridesKey: @{ @NO: @"Show ", @YES: @"Hide " },
kFLEXDefaultsHideVariablePreviewsKey: @{ @NO: @"Hide ", @YES: @"Show " },
};
[FLEXAlert makeSheet:^(FLEXAlert *make) {
@@ -323,7 +321,7 @@
}
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
#pragma mark - Description
@@ -13,7 +13,7 @@
#import "FLEXFieldEditorViewController.h"
#import "FLEXMethodCallingViewController.h"
#import "FLEXIvar.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "FLEXRuntime+UIKitHelpers.h"
@interface FLEXMetadataSection ()
@@ -8,7 +8,10 @@
#import "FLEXBundleShortcuts.h"
#import "FLEXShortcut.h"
#import "FLEXFileBrowserTableViewController.h"
#import "FLEXAlert.h"
#import "FLEXRuntimeExporter.h"
#import "FLEXTableListViewController.h"
#import "FLEXFileBrowserController.h"
#pragma mark -
@implementation FLEXBundleShortcuts
@@ -16,15 +19,95 @@
+ (instancetype)forObject:(NSBundle *)bundle {
return [self forObject:bundle additionalRows:@[
[FLEXActionShortcut title:@"Browse Bundle Directory" subtitle:nil
viewer:^UIViewController *(id view) {
return [FLEXFileBrowserTableViewController path:bundle.bundlePath];
[FLEXActionShortcut
title:@"Browse Bundle Directory" subtitle:nil
viewer:^UIViewController *(NSBundle *bundle) {
return [FLEXFileBrowserController path:bundle.bundlePath];
}
accessoryType:^UITableViewCellAccessoryType(id view) {
accessoryType:^UITableViewCellAccessoryType(NSBundle *bundle) {
return UITableViewCellAccessoryDisclosureIndicator;
}
]
],
[FLEXActionShortcut title:@"Browse Bundle as Database…" subtitle:nil
selectionHandler:^(UIViewController *host, NSBundle *bundle) {
[self promptToExportBundleAsDatabase:bundle host:host];
}
accessoryType:^UITableViewCellAccessoryType(NSBundle *bundle) {
return UITableViewCellAccessoryDisclosureIndicator;
}
],
]];
}
+ (void)promptToExportBundleAsDatabase:(NSBundle *)bundle host:(UIViewController *)host {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Save As…").message(
@"The database be saved in the Library folder. "
"Depending on the number of classes, it may take "
"10 minutes or more to finish exporting. 20,000 "
"classes takes about 7 minutes."
);
make.configuredTextField(^(UITextField *field) {
field.placeholder = @"FLEXRuntimeExport.objc.db";
field.text = [NSString stringWithFormat:
@"%@.objc.db", bundle.executablePath.lastPathComponent
];
});
make.button(@"Start").handler(^(NSArray<NSString *> *strings) {
[self browseBundleAsDatabase:bundle host:host name:strings[0]];
});
make.button(@"Cancel").cancelStyle();
} showFrom:host];
}
+ (void)browseBundleAsDatabase:(NSBundle *)bundle host:(UIViewController *)host name:(NSString *)name {
NSParameterAssert(name.length);
UIAlertController *progress = [FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Generating Database");
// Some iOS version glitch out of there is
// no initial message and you add one later
make.message(@"");
}];
[host presentViewController:progress animated:YES completion:^{
// Generate path to store db
NSString *path = [NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, YES
)[0] stringByAppendingPathComponent:name];
progress.message = [path stringByAppendingString:@"\n\nCreating database…"];
// Generate db and show progress
[FLEXRuntimeExporter createRuntimeDatabaseAtPath:path
forImages:@[bundle.executablePath]
progressHandler:^(NSString *status) {
dispatch_async(dispatch_get_main_queue(), ^{
progress.message = [progress.message
stringByAppendingFormat:@"\n%@", status
];
[progress.view setNeedsLayout];
[progress.view layoutIfNeeded];
});
} completion:^(NSString *error) {
// Display error if any
if (error) {
progress.title = @"Error";
progress.message = error;
[progress addAction:[UIAlertAction
actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]
];
}
// Browse database
else {
[progress dismissViewControllerAnimated:YES completion:nil];
[host.navigationController pushViewController:[
[FLEXTableListViewController alloc] initWithPath:path
] animated:YES];
}
}
];
}];
}
@end
@@ -10,7 +10,7 @@
#import "FLEXShortcut.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXObjectListViewController.h"
#import "NSObject+Reflection.h"
#import "NSObject+FLEX_Reflection.h"
@interface FLEXClassShortcuts ()
@property (nonatomic, readonly) Class cls;
@@ -8,57 +8,60 @@
#import "FLEXImageShortcuts.h"
#import "FLEXImagePreviewViewController.h"
#import "FLEXShortcut.h"
#import "FLEXAlert.h"
@interface FLEXImageShortcuts ()
@property (nonatomic, readonly) UIImage *image;
@interface UIAlertController (FLEXImageShortcuts)
- (void)flex_image:(UIImage *)image disSaveWithError:(NSError *)error :(void *)context;
@end
@implementation FLEXImageShortcuts
#pragma mark - Internal
- (UIImage *)image {
return self.object;
}
#pragma mark - Overrides
+ (instancetype)forObject:(UIImage *)image {
// These additional rows will appear at the beginning of the shortcuts section.
// The methods below are written in such a way that they will not interfere
// with properties/etc being registered alongside these
return [self forObject:image additionalRows:@[@"View Image", @"Save Image"]];
}
/// View image
- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
if (row == 0) {
return [FLEXImagePreviewViewController forImage:self.image];
}
return [super viewControllerToPushForRow:row];
}
/// Save image
- (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row {
if (row == 1) {
return ^(UIViewController *host) {
UIImageWriteToSavedPhotosAlbum(self.image, nil, nil, nil);
};
}
return [super didSelectRowAction:row];
}
/// "Save Image" does not need a disclosure indicator
- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
switch (row) {
case 0: return UITableViewCellAccessoryDisclosureIndicator;
case 1: return UITableViewCellAccessoryNone;
default: return [super accessoryTypeForRow:row];
}
return [self forObject:image additionalRows:@[
[FLEXActionShortcut title:@"View Image" subtitle:nil
viewer:^UIViewController *(id image) {
return [FLEXImagePreviewViewController forImage:image];
}
accessoryType:^UITableViewCellAccessoryType(id image) {
return UITableViewCellAccessoryDisclosureIndicator;
}
],
[FLEXActionShortcut title:@"Save Image" subtitle:nil
selectionHandler:^(UIViewController *host, id image) {
// Present modal alerting user about saving
UIAlertController *alert = [FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Saving Image…");
}];
[host presentViewController:alert animated:YES completion:nil];
// Save the image
UIImageWriteToSavedPhotosAlbum(
image, alert, @selector(flex_image:disSaveWithError::), nil
);
}
accessoryType:^UITableViewCellAccessoryType(id image) {
return UITableViewCellAccessoryDisclosureIndicator;
}
]
]];
}
@end
@implementation UIAlertController (FLEXImageShortcuts)
- (void)flex_image:(UIImage *)image disSaveWithError:(NSError *)error :(void *)context {
self.title = @"Image Saved";
flex_dispatch_after(1, dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
}
@end
@@ -6,7 +6,7 @@
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXObjectExplorer.h"
NS_ASSUME_NONNULL_BEGIN
@@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
///
/// It is useful to make your own shortcuts to append/prepend
/// them to the existing list of shortcuts for a class.
@protocol FLEXShortcut <NSObject>
@protocol FLEXShortcut <FLEXObjectExplorerItem>
- (nonnull NSString *)titleWith:(id)object;
- (nullable NSString *)subtitleWith:(id)object;
@@ -33,6 +33,7 @@
@end
@implementation FLEXShortcut
@synthesize defaults = _defaults;
+ (id<FLEXShortcut>)shortcutFor:(id)item {
if ([item conformsToProtocol:@protocol(FLEXShortcut)]) {
@@ -85,7 +86,7 @@
- (NSString *)subtitleWith:(id)object {
if (self.metadataKind) {
return [self.metadata previewWithTarget:object] ?: @"nil";
return [self.metadata previewWithTarget:object];
}
// Item is probably a string; must return empty string since
@@ -124,6 +125,32 @@
return kFLEXMultilineCell;
}
#pragma mark FLEXObjectExplorerDefaults
- (void)setDefaults:(FLEXObjectExplorerDefaults *)defaults {
_defaults = defaults;
if (_metadataKind) {
self.metadata.defaults = defaults;
}
}
- (BOOL)isEditable {
if (_metadataKind) {
return self.metadata.isEditable;
}
return NO;
}
- (BOOL)isCallable {
if (_metadataKind) {
return self.metadata.isCallable;
}
return NO;
}
#pragma mark - Helpers
- (FLEXProperty *)property { return _item; }
@@ -145,6 +172,7 @@
@end
@implementation FLEXActionShortcut
@synthesize defaults = _defaults;
+ (instancetype)title:(NSString *)title
subtitle:(NSString *(^)(id))subtitle
@@ -186,7 +214,11 @@
}
- (NSString *)subtitleWith:(id)object {
return self.subtitleFuture(object);
if (self.defaults.wantsDynamicPreviews) {
return self.subtitleFuture(object);
}
return nil;
}
- (void (^)(UIViewController *))didSelectActionWith:(id)object {
@@ -216,4 +248,7 @@
return nil;
}
- (BOOL)isEditable { return NO; }
- (BOOL)isCallable { return NO; }
@end
@@ -9,7 +9,7 @@
#import "FLEXShortcutsFactory+Defaults.h"
#import "FLEXShortcut.h"
#import "FLEXRuntimeUtility.h"
#import "NSObject+Reflection.h"
#import "NSObject+FLEX_Reflection.h"
#pragma mark - Views
@@ -90,6 +90,13 @@
@"currentTitle", @"currentImage", @"enabled", @"frame",
@"superview", @"subviews"
]).forClass(UIButton.class);
// UIImageView
self.append.properties(@[
@"image", @"animationImages", @"frame", @"bounds", @"center",
@"transform", @"alpha", @"hidden", @"clipsToBounds",
@"userInteractionEnabled", @"layer", @"superview", @"subviews",
]).forClass(UIImageView.class);
}
@end
@@ -16,6 +16,7 @@
#import "FLEXIvar.h"
#import "FLEXMethod.h"
#import "FLEXRuntime+UIKitHelpers.h"
#import "FLEXObjectExplorer.h"
#pragma mark Private
@@ -86,6 +87,7 @@
return [FLEXShortcut shortcutFor:obj];
}];
_numberOfLines = 1;
// Populate titles and subtitles
[self reloadData];
}
@@ -156,6 +158,8 @@
}
- (void)reloadData {
[FLEXObjectExplorer configureDefaultsForItems:self.allShortcuts];
// Generate all (sub)titles from shortcuts
if (self.allShortcuts) {
self.allTitles = [self.allShortcuts flex_mapped:^id(FLEXShortcut *s, NSUInteger idx) {
@@ -303,15 +307,8 @@ static RegistrationBuckets *mMethods = nil;
classKey = class_getSuperclass(classKey);
}
}
// .tag is used to cache whether the value of .isEditable;
// This could change at runtime so it is important that
// it is cached every time shortcuts are requeted and not
// just once at as shortcuts are initially registered
for (id<FLEXRuntimeMetadata> metadata in shortcuts) {
metadata.tag = metadata.isEditable ? @YES : nil;
}
[FLEXObjectExplorer configureDefaultsForItems:shortcuts];
return shortcuts;
}
@@ -40,7 +40,7 @@
+ (instancetype)forObject:(UIViewController *)viewController {
BOOL (^vcIsInuse)(UIViewController *) = ^BOOL(UIViewController *controller) {
if (controller.view.window) {
if (controller.viewIfLoaded.window) {
return YES;
}
@@ -12,15 +12,25 @@
#import "FLEXMethod.h"
#import "FLEXProtocol.h"
@protocol FLEXRuntimeMetadata <NSObject>
/// Used as the main title of the row
- (NSString *)description;
/// Used to compare metadata objects for uniqueness
@property (nonatomic, readonly) NSString *name;
@class FLEXObjectExplorerDefaults;
/// Model objects of an object explorer screen adopt this
/// protocol in order respond to user defaults changes
@protocol FLEXObjectExplorerItem <NSObject>
/// Current explorer settings. Set when settings change.
@property (nonatomic) FLEXObjectExplorerDefaults *defaults;
/// YES for properties and ivars which surely support editing, NO for all methods.
@property (nonatomic, readonly) BOOL isEditable;
/// NO for ivars, YES for supported methods and properties
@property (nonatomic, readonly) BOOL isCallable;
@end
@protocol FLEXRuntimeMetadata <FLEXObjectExplorerItem>
/// Used as the main title of the row
- (NSString *)description;
/// Used to compare metadata objects for uniqueness
@property (nonatomic, readonly) NSString *name;
/// For internal use
@property (nonatomic) id tag;
@@ -76,3 +86,7 @@ typedef NS_ENUM(NSUInteger, FLEXStaticMetadataRowStyle) {
+ (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes;
@end
/// This is assigned to the \c tag property of each metadata.
@@ -15,11 +15,21 @@
#import "FLEXMethodCallingViewController.h"
#import "FLEXTableView.h"
#import "FLEXUtility.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "NSString+FLEX.h"
#define FLEXObjectExplorerDefaultsImpl \
- (FLEXObjectExplorerDefaults *)defaults { \
return self.tag; \
} \
\
- (void)setDefaults:(FLEXObjectExplorerDefaults *)defaults { \
self.tag = defaults; \
}
#pragma mark FLEXProperty
@implementation FLEXProperty (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
/// Decide whether to use potentialTarget or [potentialTarget class] to get or set property
- (id)appropriateTargetForPropertyType:(id)potentialTarget {
@@ -67,11 +77,13 @@
- (NSString *)previewWithTarget:(id)object {
if (object_isClass(object) && !self.isClassProperty) {
return self.attributes.fullDeclaration;
} else {
} else if (self.defaults.wantsDynamicPreviews) {
return [FLEXRuntimeUtility
summaryForObject:[self currentValueWithTarget:object]
];
}
return nil;
}
- (UIViewController *)viewerWithTarget:(id)object {
@@ -94,7 +106,7 @@
// We use .tag to store the cached value of .isEditable that is
// initialized by FLEXObjectExplorer in -reloadMetada
if ([self getPotentiallyUnboxedValue:targetForValueCheck]) {
if (self.tag) {
if (self.defaults.isEditable) {
// Editable non-nil value, both
return UITableViewCellAccessoryDetailDisclosureButton;
} else {
@@ -102,7 +114,7 @@
return UITableViewCellAccessoryDisclosureIndicator;
}
} else {
if (self.tag) {
if (self.defaults.isEditable) {
// Editable nil value, just (i)
return UITableViewCellAccessoryDetailButton;
} else {
@@ -177,6 +189,7 @@
#pragma mark FLEXIvar
@implementation FLEXIvar (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
- (BOOL)isEditable {
const FLEXTypeEncoding *typeEncoding = self.typeEncoding.UTF8String;
@@ -198,10 +211,13 @@
- (NSString *)previewWithTarget:(id)object {
if (object_isClass(object)) {
return self.details;
} else if (self.defaults.wantsDynamicPreviews) {
return [FLEXRuntimeUtility
summaryForObject:[self currentValueWithTarget:object]
];
}
return [FLEXRuntimeUtility
summaryForObject:[self currentValueWithTarget:object]
];
return nil;
}
- (UIViewController *)viewerWithTarget:(id)object {
@@ -222,7 +238,7 @@
// Could use .isEditable here, but we use .tag for speed since it is cached
if ([self getPotentiallyUnboxedValue:object]) {
if (self.tag) {
if (self.defaults.isEditable) {
// Editable non-nil value, both
return UITableViewCellAccessoryDetailDisclosureButton;
} else {
@@ -230,7 +246,7 @@
return UITableViewCellAccessoryDisclosureIndicator;
}
} else {
if (self.tag) {
if (self.defaults.isEditable) {
// Editable nil value, just (i)
return UITableViewCellAccessoryDetailButton;
} else {
@@ -301,6 +317,7 @@
#pragma mark FLEXMethod
@implementation FLEXMethodBase (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
- (BOOL)isEditable {
return NO;
@@ -402,6 +419,7 @@
#pragma mark FLEXProtocol
@implementation FLEXProtocol (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
- (BOOL)isEditable {
return NO;
@@ -475,6 +493,8 @@
@synthesize name = _name;
@synthesize tag = _tag;
FLEXObjectExplorerDefaultsImpl
+ (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes {
return [classes flex_mapped:^id(Class cls, NSUInteger idx) {
return [FLEXStaticMetadata_Class withClass:cls];
@@ -1,5 +1,5 @@
//
// NSArray+Functional.h
// NSArray+FLEX.h
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
@@ -1,12 +1,12 @@
//
// NSArray+Functional.m
// NSArray+FLEX.m
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#define FLEXArrayClassIsMutable(me) ([[self class] isSubclassOfClass:[NSMutableArray class]])
@@ -1,5 +1,5 @@
//
// NSObject+Reflection.h
// NSObject+FLEX_Reflection.h
// FLEX
//
// Derived from MirrorKit.
@@ -25,6 +25,17 @@ NSArray<Class> *FLEXGetAllSubclasses(_Nullable Class cls, BOOL includeSelf);
NSArray<Class> *FLEXGetClassHierarchy(_Nullable Class cls, BOOL includeSelf);
NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(_Nullable Class cls);
NSArray<FLEXIvar *> *FLEXGetAllIvars(_Nullable Class cls);
/// @param cls a class object to get instance properties,
/// or a metaclass object to get class properties
NSArray<FLEXProperty *> *FLEXGetAllProperties(_Nullable Class cls);
/// @param cls a class object to get instance methods,
/// or a metaclass object to get class methods
/// @param instance used to mark methods as instance methods or not.
/// Not used to determine whether to get instance or class methods.
NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance);
#pragma mark Reflection
@interface NSObject (Reflection)
@@ -1,5 +1,5 @@
//
// NSObject+Reflection.m
// NSObject+FLEX_Reflection.m
// FLEX
//
// Derived from MirrorKit.
@@ -7,7 +7,7 @@
// Copyright (c) 2015 Tanner Bennett. All rights reserved.
//
#import "NSObject+Reflection.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXClassBuilder.h"
#import "FLEXMirror.h"
#import "FLEXProperty.h"
@@ -15,7 +15,7 @@
#import "FLEXIvar.h"
#import "FLEXProtocol.h"
#import "FLEXPropertyAttributes.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "FLEXUtility.h"
@@ -101,6 +101,45 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
}];
}
NSArray<FLEXIvar *> *FLEXGetAllIvars(_Nullable Class cls) {
if (!cls) return nil;
unsigned int ivcount;
Ivar *objcivars = class_copyIvarList(cls, &ivcount);
NSArray *ivars = [NSArray flex_forEachUpTo:ivcount map:^id(NSUInteger i) {
return [FLEXIvar ivar:objcivars[i]];
}];
free(objcivars);
return ivars;
}
NSArray<FLEXProperty *> *FLEXGetAllProperties(_Nullable Class cls) {
if (!cls) return nil;
unsigned int pcount;
objc_property_t *objcproperties = class_copyPropertyList(cls, &pcount);
NSArray *properties = [NSArray flex_forEachUpTo:pcount map:^id(NSUInteger i) {
return [FLEXProperty property:objcproperties[i] onClass:cls];
}];
free(objcproperties);
return properties;
}
NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
if (!cls) return nil;
unsigned int mcount;
Method *objcmethods = class_copyMethodList(cls, &mcount);
NSArray *methods = [NSArray flex_forEachUpTo:mcount map:^id(NSUInteger i) {
return [FLEXMethod method:objcmethods[i] isInstanceMethod:instance];
}];
free(objcmethods);
return methods;
}
#pragma mark NSProxy
@@ -135,6 +174,11 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
FLEXClassBuilder *swiftObjectMeta = [FLEXClassBuilder builderForClass:SwiftObject_meta];
[swiftObject addMethods:instanceMethods];
[swiftObjectMeta addMethods:classMethods];
// So we can put Swift objects into dictionaries...
[swiftObjectMeta addMethods:@[
[NSObject flex_classMethodNamed:@"copyWithZone:"]]
];
}
}
@@ -192,41 +236,17 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
@implementation NSObject (Methods)
+ (NSArray<FLEXMethod *> *)flex_allMethods {
NSMutableArray *instanceMethods = (id)self.flex_allInstanceMethods;
NSMutableArray *instanceMethods = self.flex_allInstanceMethods.mutableCopy;
[instanceMethods addObjectsFromArray:self.flex_allClassMethods];
return instanceMethods;
}
+ (NSArray<FLEXMethod *> *)flex_allInstanceMethods {
unsigned int mcount;
Method *objcmethods = class_copyMethodList([self class], &mcount);
NSMutableArray *methods = [NSMutableArray new];
for (int i = 0; i < mcount; i++) {
FLEXMethod *m = [FLEXMethod method:objcmethods[i] isInstanceMethod:YES];
if (m) {
[methods addObject:m];
}
}
free(objcmethods);
return methods;
return FLEXGetAllMethods(self, YES);
}
+ (NSArray<FLEXMethod *> *)flex_allClassMethods {
unsigned int mcount;
Method *objcmethods = class_copyMethodList(self.flex_metaclass, &mcount);
NSMutableArray *methods = [NSMutableArray new];
for (int i = 0; i < mcount; i++) {
FLEXMethod *m = [FLEXMethod method:objcmethods[i] isInstanceMethod:NO];
if (m) {
[methods addObject:m];
}
}
free(objcmethods);
return methods;
return FLEXGetAllMethods(self.flex_metaclass, NO);
}
+ (FLEXMethod *)flex_methodNamed:(NSString *)name {
@@ -292,16 +312,7 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
@implementation NSObject (Ivars)
+ (NSArray<FLEXIvar *> *)flex_allIvars {
unsigned int ivcount;
Ivar *objcivars = class_copyIvarList([self class], &ivcount);
NSMutableArray *ivars = [NSMutableArray new];
for (int i = 0; i < ivcount; i++) {
[ivars addObject:[FLEXIvar ivar:objcivars[i]]];
}
free(objcivars);
return ivars;
return FLEXGetAllIvars(self);
}
+ (FLEXIvar *)flex_ivarNamed:(NSString *)name {
@@ -373,36 +384,17 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
@implementation NSObject (Properties)
+ (NSArray<FLEXProperty *> *)flex_allProperties {
NSMutableArray *instanceProperties = (id)self.flex_allInstanceProperties;
NSMutableArray *instanceProperties = self.flex_allInstanceProperties.mutableCopy;
[instanceProperties addObjectsFromArray:self.flex_allClassProperties];
return instanceProperties;
}
+ (NSArray<FLEXProperty *> *)flex_allInstanceProperties {
unsigned int pcount;
objc_property_t *objcproperties = class_copyPropertyList(self, &pcount);
NSMutableArray *properties = [NSMutableArray new];
for (int i = 0; i < pcount; i++) {
[properties addObject:[FLEXProperty property:objcproperties[i] onClass:self]];
}
free(objcproperties);
return properties;
return FLEXGetAllProperties(self);
}
+ (NSArray<FLEXProperty *> *)flex_allClassProperties {
Class metaclass = self.flex_metaclass;
unsigned int pcount;
objc_property_t *objcproperties = class_copyPropertyList(metaclass, &pcount);
NSMutableArray *properties = [NSMutableArray new];
for (int i = 0; i < pcount; i++) {
[properties addObject:[FLEXProperty property:objcproperties[i] onClass:metaclass]];
}
free(objcproperties);
return properties;
return FLEXGetAllProperties(self.flex_metaclass);
}
+ (FLEXProperty *)flex_propertyNamed:(NSString *)name {
@@ -8,6 +8,7 @@
#import <Foundation/Foundation.h>
typedef void (^VoidBlock)(void);
@interface NSTimer (Blocks)
+ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
@@ -5,7 +5,7 @@
// Created by Tanner on 3/23/17.
//
#import "NSTimer+Blocks.h"
#import "NSTimer+FLEX.h"
@interface Block : NSObject
- (void)invoke;
@@ -8,31 +8,37 @@
#import <Foundation/Foundation.h>
// Only use these if the getters and setters aren't good enough for whatever reaso
// Only use these if the getters and setters aren't good enough for whatever reason
extern NSString * const kFLEXDefaultsToolbarTopMarginKey;
extern NSString * const kFLEXDefaultsiOSPersistentOSLogKey;
extern NSString * const kFLEXDefaultsHidePropertyIvarsKey;
extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
extern NSString * const kFLEXDefaultsHideMethodOverridesKey;
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
extern NSString * const kFLEXDefaultsNetworkHostBlacklistKey;
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
/// All BOOL preferences are NO by default
@interface NSUserDefaults (FLEX)
- (void)toggleBoolForKey:(NSString *)key;
@property (nonatomic) double flex_toolbarTopMargin;
/// NO by default
@property (nonatomic) BOOL flex_cacheOSLogMessages;
/// NO by default
@property (nonatomic) BOOL flex_explorerHidesPropertyIvars;
/// NO by default
@property (nonatomic) BOOL flex_explorerHidesPropertyMethods;
/// NO by default
@property (nonatomic) BOOL flex_explorerShowsMethodOverrides;
// Not actually stored in defaults, but written to a file
@property (nonatomic) NSArray<NSString *> *flex_networkHostBlacklist;
/// Whether or not to register the object explorer as a JSON viewer on launch
@property (nonatomic) BOOL flex_registerDictionaryJSONViewerOnLaunch;
/// Disable os_log and re-enable ASL. May break Console.app output.
@property (nonatomic) BOOL flex_disableOSLog;
@property (nonatomic) BOOL flex_cacheOSLogMessages;
@property (nonatomic) BOOL flex_explorerHidesPropertyIvars;
@property (nonatomic) BOOL flex_explorerHidesPropertyMethods;
@property (nonatomic) BOOL flex_explorerShowsMethodOverrides;
@property (nonatomic) BOOL flex_explorerHidesVariablePreviews;
@end
@@ -13,17 +13,22 @@ NSString * const kFLEXDefaultsiOSPersistentOSLogKey = @"com.flipborad.flex.enabl
NSString * const kFLEXDefaultsHidePropertyIvarsKey = @"com.flipboard.FLEX.hide_property_ivars";
NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide_property_methods";
NSString * const kFLEXDefaultsHideMethodOverridesKey = @"com.flipboard.FLEX.hide_method_overrides";
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.network_host_blacklist";
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.view_json_as_object";
#define FLEXDefaultsPathForFile(name) ({ \
NSArray *paths = NSSearchPathForDirectoriesInDomains( \
NSLibraryDirectory, NSUserDomainMask, NO \
NSLibraryDirectory, NSUserDomainMask, YES \
); \
[paths[0] stringByAppendingPathComponent:@"Preferences"]; \
})
@implementation NSUserDefaults (FLEX)
#pragma mark Internal
/// @param filename the name of a plist file without any extension
- (NSString *)flex_defaultsPathForFile:(NSString *)filename {
filename = [filename stringByAppendingPathExtension:@"plist"];
@@ -35,11 +40,15 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
return [preferences stringByAppendingPathComponent:filename];
}
#pragma mark Helper
- (void)toggleBoolForKey:(NSString *)key {
[self setBool:![self boolForKey:key] forKey:key];
[NSNotificationCenter.defaultCenter postNotificationName:key object:nil];
}
#pragma mark Misc
- (double)flex_toolbarTopMargin {
if ([self objectForKey:kFLEXDefaultsToolbarTopMarginKey]) {
return [self doubleForKey:kFLEXDefaultsToolbarTopMarginKey];
@@ -52,6 +61,37 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
[self setDouble:margin forKey:kFLEXDefaultsToolbarTopMarginKey];
}
- (NSArray<NSString *> *)flex_networkHostBlacklist {
return [NSArray arrayWithContentsOfFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
]] ?: @[];
}
- (void)setFlex_networkHostBlacklist:(NSArray<NSString *> *)blacklist {
NSParameterAssert(blacklist);
[blacklist writeToFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
] atomically:YES];
}
- (BOOL)flex_registerDictionaryJSONViewerOnLaunch {
return [self boolForKey:kFLEXDefaultsRegisterJSONExplorerKey];
}
- (void)setFlex_registerDictionaryJSONViewerOnLaunch:(BOOL)enable {
[self setBool:enable forKey:kFLEXDefaultsRegisterJSONExplorerKey];
}
#pragma mark System Log
- (BOOL)flex_disableOSLog {
return [self boolForKey:kFLEXDefaultsDisableOSLogForceASLKey];
}
- (void)setFlex_disableOSLog:(BOOL)disable {
[self setBool:disable forKey:kFLEXDefaultsDisableOSLogForceASLKey];
}
- (BOOL)flex_cacheOSLogMessages {
return [self boolForKey:kFLEXDefaultsiOSPersistentOSLogKey];
}
@@ -64,6 +104,8 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
];
}
#pragma mark Object Explorer
- (BOOL)flex_explorerHidesPropertyIvars {
return [self boolForKey:kFLEXDefaultsHidePropertyIvarsKey];
}
@@ -100,17 +142,16 @@ NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.net
];
}
- (NSArray<NSString *> *)flex_networkHostBlacklist {
return [NSArray arrayWithContentsOfFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
]] ?: @[];
- (BOOL)flex_explorerHidesVariablePreviews {
return [self boolForKey:kFLEXDefaultsHideVariablePreviewsKey];
}
- (void)setFlex_networkHostBlacklist:(NSArray<NSString *> *)blacklist {
NSParameterAssert(blacklist);
[blacklist writeToFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
] atomically:YES];
- (void)setFlex_explorerHidesVariablePreviews:(BOOL)hide {
[self setBool:hide forKey:kFLEXDefaultsHideVariablePreviewsKey];
[NSNotificationCenter.defaultCenter
postNotificationName:kFLEXDefaultsHideVariablePreviewsKey
object:nil
];
}
@end
+3 -1
View File
@@ -29,7 +29,9 @@ typedef FLEXAlertAction *(^FLEXAlertActionHandler)(void(^handler)(NSArray<NSStri
/// Construct and display an alert
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController;
/// Construct and display an action sheet-style alert
+ (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController;
+ (void)makeSheet:(FLEXAlertBuilder)block
showFrom:(UIViewController *)viewController
source:(id)viewOrBarItem;
/// Construct an alert
+ (UIAlertController *)makeAlert:(FLEXAlertBuilder)block;
+30 -5
View File
@@ -62,17 +62,42 @@ NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAle
return alert._controller;
}
+ (void)make:(FLEXAlertBuilder)block withStyle:(UIAlertControllerStyle)style showFrom:(UIViewController *)viewController {
+ (void)make:(FLEXAlertBuilder)block
withStyle:(UIAlertControllerStyle)style
showFrom:(UIViewController *)viewController
source:(id)viewOrBarItem {
UIAlertController *alert = [self make:block withStyle:style];
if ([viewOrBarItem isKindOfClass:[UIBarButtonItem class]]) {
alert.popoverPresentationController.barButtonItem = viewOrBarItem;
} else if ([viewOrBarItem isKindOfClass:[UIView class]]) {
alert.popoverPresentationController.sourceView = viewOrBarItem;
alert.popoverPresentationController.sourceRect = [viewOrBarItem bounds];
} else if (viewOrBarItem) {
NSParameterAssert(
[viewOrBarItem isKindOfClass:[UIBarButtonItem class]] ||
[viewOrBarItem isKindOfClass:[UIView class]] ||
!viewOrBarItem
);
}
[viewController presentViewController:alert animated:YES completion:nil];
}
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController {
[self make:block withStyle:UIAlertControllerStyleAlert showFrom:viewController];
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
[self make:block withStyle:UIAlertControllerStyleAlert showFrom:controller source:nil];
}
+ (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController {
[self make:block withStyle:UIAlertControllerStyleActionSheet showFrom:viewController];
+ (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
[self make:block withStyle:UIAlertControllerStyleActionSheet showFrom:controller source:nil];
}
/// Construct and display an action sheet-style alert
+ (void)makeSheet:(FLEXAlertBuilder)block
showFrom:(UIViewController *)controller
source:(id)viewOrBarItem {
[self make:block
withStyle:UIAlertControllerStyleActionSheet
showFrom:controller
source:viewOrBarItem];
}
+ (UIAlertController *)makeAlert:(FLEXAlertBuilder)block {
+4
View File
@@ -71,4 +71,8 @@ NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
stringWithFormat:(count == 1 ? singularFormat : pluralFormat), @(count) \
]
#define flex_dispatch_after(nSeconds, onQueue, block) \
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, \
(int64_t)(nSeconds * NSEC_PER_SEC)), onQueue, block)
#endif /* FLEXMacros_h */
+1 -1
View File
@@ -13,7 +13,7 @@
#import <objc/runtime.h>
#import "FLEXTypeEncodingParser.h"
#import "FLEXAlert.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "UIFont+FLEX.h"
#import "NSMapTable+FLEX_Subscripting.h"
#import "FLEXMacros.h"
@@ -34,21 +34,23 @@ typedef struct FLEXTypeInfo {
/// so we need to track whenever a type contains a union
/// so that we can clean it out of pointer types.
BOOL containsUnion;
/// size can only be 0 if not void
BOOL isVoid;
} FLEXTypeInfo;
/// Type info for a completely unsupported type.
static FLEXTypeInfo FLEXTypeInfoUnsupported = (FLEXTypeInfo){ -1, 0, NO, NO, NO };
static FLEXTypeInfo FLEXTypeInfoUnsupported = (FLEXTypeInfo){ -1, 0, NO, NO, NO, NO };
/// Type info for the void return type.
static FLEXTypeInfo FLEXTypeInfoVoid = (FLEXTypeInfo){ 0, 0, YES, NO, NO };
static FLEXTypeInfo FLEXTypeInfoVoid = (FLEXTypeInfo){ 0, 0, YES, NO, NO, YES };
/// Builds type info for a fully or partially supported type.
static inline FLEXTypeInfo FLEXTypeInfoMake(ssize_t size, ssize_t align, BOOL fixed) {
return (FLEXTypeInfo){ size, align, YES, fixed, NO };
return (FLEXTypeInfo){ size, align, YES, fixed, NO, NO };
}
/// Builds type info for a fully or partially supported type.
static inline FLEXTypeInfo FLEXTypeInfoMakeU(ssize_t size, ssize_t align, BOOL fixed, BOOL hasUnion) {
return (FLEXTypeInfo){ size, align, YES, fixed, hasUnion };
return (FLEXTypeInfo){ size, align, YES, fixed, hasUnion, NO };
}
BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *alignp) {
@@ -119,7 +121,7 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
while (!parser.scan.isAtEnd) {
FLEXTypeInfo info = [parser parseNextType];
if (!info.supported || info.containsUnion || info.size == 0) {
if (!info.supported || info.containsUnion || (info.size == 0 && !info.isVoid)) {
return NO;
}
}
@@ -29,6 +29,9 @@
@property (nonatomic, readonly) NSUInteger size;
/// Describes the type encoding, size, offset, and objc_ivar
@property (nonatomic, readonly) NSString *details;
/// The full path of the image that contains this ivar definition,
/// or \c nil if this ivar was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
/// For internal use
@property (nonatomic) id tag;
@@ -11,6 +11,7 @@
#import "FLEXRuntimeUtility.h"
#import "FLEXRuntimeSafety.h"
#import "FLEXTypeEncodingParser.h"
#include <dlfcn.h>
@interface FLEXIvar () {
NSString *_flex_description;
@@ -82,6 +83,11 @@
typeForDetails = @"no type info";
sizeForDetails = @"unknown size";
}
Dl_info exeInfo;
if (dladdr(_objc_ivar, &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
}
_details = [NSString stringWithFormat:
@"%@, offset %@ — %@",
@@ -59,6 +59,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) FLEXTypeEncoding *returnType;
/// The return size of the method.
@property (nonatomic, readonly) NSUInteger returnSize;
/// The full path of the image that contains this method definition,
/// or \c nil if this ivar was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
/// Like @code - (void)foo:(int)bar @endcode
@property (nonatomic, readonly) NSString *description;
@@ -11,6 +11,7 @@
#import "FLEXMirror.h"
#import "FLEXTypeEncodingParser.h"
#import "FLEXRuntimeUtility.h"
#include <dlfcn.h>
@implementation FLEXMethod
@dynamic implementation;
@@ -153,8 +154,13 @@
_selector = method_getName(_objc_method);
_numberOfArguments = method_getNumberOfArguments(_objc_method);
_name = NSStringFromSelector(_selector);
_returnType = (FLEXTypeEncoding *)_signature.methodReturnType;
_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
@@ -59,8 +59,12 @@
@property (nonatomic, readonly) BOOL multiple;
/// @return The bundle of the image that contains this property definition,
/// or \c nil if this property was not created with \c property:onClass or
/// if this property is probably defined in the objc runtime.
/// if this property was probably defined at runtime.
@property (nonatomic, readonly) NSString *imageName;
/// The full path of the image that contains this property definition,
/// or \c nil if this property was not created with \c property:onClass or
/// if this property was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
/// For internal use
@property (nonatomic) id tag;
@@ -24,6 +24,7 @@
@implementation FLEXProperty
@synthesize multiple = _multiple;
@synthesize imageName = _imageName;
@synthesize imagePath = _imagePath;
#pragma mark Initializers
@@ -88,7 +89,9 @@
#pragma mark Private
- (void)examine {
_type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
if (self.attributes.typeEncoding.length) {
_type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
}
// Return the given selector if the class responds to it
Class cls = _cls;
@@ -153,13 +156,16 @@
}
- (void)computeSymbolInfo:(BOOL)forceBundle {
Dl_info exeInfo;
if (dladdr(_objc_property, &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
}
if ((!_multiple || !_uniqueCheckFlag) && _cls) {
_multiple = _objc_property != class_getProperty(_cls, self.name.UTF8String);
if (_multiple || forceBundle) {
Dl_info exeInfo;
dladdr(_objc_property, &exeInfo);
NSString *path = @(exeInfo.dli_fname).stringByDeletingLastPathComponent;
NSString *path = _imagePath.stringByDeletingLastPathComponent;
_imageName = [NSBundle bundleWithPath:path].executablePath.lastPathComponent;
}
}
@@ -170,6 +176,11 @@
return _multiple;
}
- (NSString *)imagePath {
[self computeSymbolInfo:YES];
return _imagePath;
}
- (NSString *)imageName {
[self computeSymbolInfo:YES];
return _imageName;
@@ -60,6 +60,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, nullable) SEL customGetter;
/// The property's custom setter, if any.
@property (nonatomic, readonly, nullable) SEL customSetter;
/// The property's custom getter as a string, if any.
@property (nonatomic, readonly, nullable) NSString *customGetterString;
/// The property's custom setter as a string, if any.
@property (nonatomic, readonly, nullable) NSString *customSetterString;
@property (nonatomic, readonly) BOOL isReadOnly;
@property (nonatomic, readonly) BOOL isCopy;
@@ -58,8 +58,10 @@
_typeEncoding = attributes[kFLEXPropertyAttributeKeyTypeEncoding];
_backingIvar = attributes[kFLEXPropertyAttributeKeyBackingIvarName];
_oldTypeEncoding = attributes[kFLEXPropertyAttributeKeyOldStyleTypeEncoding];
_customGetter = NSSelectorFromString(attributes[kFLEXPropertyAttributeKeyCustomGetter]);
_customSetter = NSSelectorFromString(attributes[kFLEXPropertyAttributeKeyCustomSetter]);
_customGetterString = attributes[kFLEXPropertyAttributeKeyCustomGetter];
_customSetterString = attributes[kFLEXPropertyAttributeKeyCustomSetter];
_customGetter = NSSelectorFromString(_customGetterString);
_customSetter = NSSelectorFromString(_customSetterString);
_isReadOnly = attributes[kFLEXPropertyAttributeKeyReadOnly] != nil;
_isCopy = attributes[kFLEXPropertyAttributeKeyCopy] != nil;
_isRetained = attributes[kFLEXPropertyAttributeKeyRetain] != nil;
@@ -363,4 +365,12 @@ PropertyWithDeltaFlag(BOOL, isGarbageCollectable, IsGarbageCollectable);
return _fullDeclaration;
}
- (NSString *)customGetterString {
return _customGetter ? NSStringFromSelector(_customGetter) : nil;
}
- (NSString *)customSetterString {
return _customSetter ? NSStringFromSelector(_customSetter) : nil;
}
@end
@@ -22,14 +22,23 @@
/// The name of the protocol.
@property (nonatomic, readonly) NSString *name;
/// The properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
/// The required methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods;
/// The optional methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods;
/// All protocols that this protocol conforms to, if any.
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
/// The full path of the image that contains this protocol definition,
/// or \c nil if this protocol was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
/// The properties in the protocol, if any. \c nil on iOS 10+
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0));
/// The required properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0));
/// The optional properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0));
/// For internal use
@property (nonatomic) id tag;
@@ -44,7 +53,8 @@
#pragma mark Method descriptions
@interface FLEXMethodDescription : NSObject
+ (instancetype)description:(struct objc_method_description)methodDescription;
+ (instancetype)description:(struct objc_method_description)description;
+ (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance;
/// The underlying method description data structure.
@property (nonatomic, readonly) struct objc_method_description objc_description;
@@ -54,4 +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
@property (nonatomic, readonly) NSNumber *instance;
@end
@@ -10,7 +10,8 @@
#import "FLEXProtocol.h"
#import "FLEXProperty.h"
#import "FLEXRuntimeUtility.h"
#import "NSArray+FLEX.h"
#include <dlfcn.h>
@implementation FLEXProtocol
@@ -66,37 +67,101 @@
- (void)examine {
_name = @(protocol_getName(self.objc_protocol));
unsigned int prcount, pccount, mdrcount, mdocount;
objc_property_t *objcproperties = protocol_copyPropertyList(self.objc_protocol, &prcount);
Protocol * __unsafe_unretained *objcprotocols = protocol_copyProtocolList(self.objc_protocol, &pccount);
struct objc_method_description *objcrmethods = protocol_copyMethodDescriptionList(self.objc_protocol, YES, YES, &mdrcount);
struct objc_method_description *objcomethods = protocol_copyMethodDescriptionList(self.objc_protocol, NO, YES, &mdocount);
// imagePath
Dl_info exeInfo;
if (dladdr((__bridge const void *)(_objc_protocol), &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
}
NSMutableArray *properties = [NSMutableArray new];
for (int i = 0; i < prcount; i++)
[properties addObject:[FLEXProperty property:objcproperties[i]]];
_properties = properties;
// Conformances and methods //
NSMutableArray *protocols = [NSMutableArray new];
for (int i = 0; i < pccount; i++)
[protocols addObject:[FLEXProtocol protocol:objcprotocols[i]]];
_protocols = protocols;
unsigned int pccount, mdrcount, mdocount;
struct objc_method_description *objcrMethods, *objcoMethods;
Protocol *protocol = _objc_protocol;
Protocol * __unsafe_unretained *protocols = protocol_copyProtocolList(protocol, &pccount);
NSMutableArray *requiredMethods = [NSMutableArray new];
for (int i = 0; i < mdrcount; i++)
[requiredMethods addObject:[FLEXMethodDescription description:objcrmethods[i]]];
_requiredMethods = requiredMethods;
// Protocols
_protocols = [NSArray flex_forEachUpTo:pccount map:^id(NSUInteger i) {
return [FLEXProtocol protocol:protocols[i]];
}];
free(protocols);
NSMutableArray *optionalMethods = [NSMutableArray new];
for (int i = 0; i < mdocount; i++)
[optionalMethods addObject:[FLEXMethodDescription description:objcomethods[i]]];
_optionalMethods = optionalMethods;
// Required instance methods
objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, YES, &mdrcount);
NSArray *rMethods = [NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcrMethods[i] instance:YES];
}];
free(objcrMethods);
free(objcproperties);
free(objcprotocols);
free(objcrmethods);
free(objcomethods);
// Required class methods
objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, NO, &mdrcount);
_requiredMethods = [[NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcrMethods[i] instance:NO];
}] arrayByAddingObjectsFromArray:rMethods];
free(objcrMethods);
// Optional instance methods
objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, YES, &mdocount);
NSArray *oMethods = [NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcoMethods[i] instance:YES];
}];
free(objcoMethods);
// Optional class methods
objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, NO, &mdocount);
_optionalMethods = [[NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcoMethods[i] instance:NO];
}] arrayByAddingObjectsFromArray:oMethods];
free(objcoMethods);
// Properties is a hassle because they didn't fix the API until iOS 10 //
if (@available(iOS 10.0, *)) {
unsigned int prrcount, procount;
Class instance = [NSObject class], meta = objc_getMetaClass("NSObject");
// Required class and instance properties //
// Instance first
objc_property_t *rProps = protocol_copyPropertyList2(protocol, &prrcount, YES, YES);
NSArray *rProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
return [FLEXProperty property:rProps[i] onClass:instance];
}];
free(rProps);
// Then class
rProps = protocol_copyPropertyList2(protocol, &prrcount, NO, YES);
_requiredProperties = [[NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
return [FLEXProperty property:rProps[i] onClass:instance];
}] arrayByAddingObjectsFromArray:rProperties];
free(rProps);
// Optional class and instance properties //
// Instance first
objc_property_t *oProps = protocol_copyPropertyList2(protocol, &procount, YES, YES);
NSArray *oProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
return [FLEXProperty property:oProps[i] onClass:meta];
}];
free(oProps);
// Then class
oProps = protocol_copyPropertyList2(protocol, &procount, NO, YES);
_optionalProperties = [[NSArray flex_forEachUpTo:procount map:^id(NSUInteger i) {
return [FLEXProperty property:oProps[i] onClass:meta];
}] arrayByAddingObjectsFromArray:oProperties];
free(oProps);
} else {
unsigned int prcount;
objc_property_t *objcproperties = protocol_copyPropertyList(protocol, &prcount);
_properties = [NSArray flex_forEachUpTo:prcount map:^id(NSUInteger i) {
return [FLEXProperty property:objcproperties[i]];
}];
free(objcproperties);
}
}
- (BOOL)conformsTo:(Protocol *)protocol {
@@ -117,11 +182,15 @@
return nil;
}
+ (instancetype)description:(struct objc_method_description)methodDescription {
return [[self alloc] initWithDescription:methodDescription];
+ (instancetype)description:(struct objc_method_description)description {
return [[self alloc] initWithDescription:description instance:nil];
}
- (id)initWithDescription:(struct objc_method_description)md {
+ (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance {
return [[self alloc] initWithDescription:description instance:@(isInstance)];
}
- (id)initWithDescription:(struct objc_method_description)md instance:(NSNumber *)instance {
NSParameterAssert(md.name != NULL);
self = [super init];
@@ -130,6 +199,7 @@
_selector = md.name;
_typeEncoding = @(md.types);
_returnType = (FLEXTypeEncoding)[self.typeEncoding characterAtIndex:0];
_instance = instance;
}
return self;
@@ -7,7 +7,7 @@
#import "FHSView.h"
#import "FLEXUtility.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
@interface FHSView (Snapshotting)
+ (UIImage *)_snapshotView:(UIView *)view;
@@ -13,7 +13,7 @@
#import "FLEXAlert.h"
#import "FLEXWindow.h"
#import "FLEXResources.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
#import "UIBarButtonItem+FLEX.h"
BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
@@ -162,7 +162,7 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
itemWithImage:FLEXResources.moreIcon
target:self action:@selector(didPressOptionsButton)
target:self action:@selector(didPressOptionsButton:)
],
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem itemWithCustomView:snapshotView.depthSlider]
@@ -205,7 +205,7 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
#pragma mark Events
- (void)didPressOptionsButton {
- (void)didPressOptionsButton:(UIBarButtonItem *)sender {
[FLEXAlert makeSheet:^(FLEXAlert *make) {
if (self.selectedView) {
make.button(@"Hide selected view").handler(^(NSArray<NSString *> *strings) {
@@ -227,7 +227,7 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
[self.snapshotView toggleShowBorders];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} showFrom:self source:sender];
}
- (void)resizeToolbarItems:(CGSize)viewSize {
@@ -7,7 +7,7 @@
//
#import "FHSViewSnapshot.h"
#import "NSArray+Functional.h"
#import "NSArray+FLEX.h"
@implementation FHSViewSnapshot
+2 -2
View File
@@ -426,7 +426,7 @@
DEVELOPMENT_TEAM = S6N2F22V2Z;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "FLEXample/Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -451,7 +451,7 @@
DEVELOPMENT_TEAM = S6N2F22V2Z;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "FLEXample/Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -40,7 +40,10 @@
self.commits.list = [Commit commitsFrom:data];
[self fadeInNewRows];
} else {
[FLEXAlert showAlert:@"Error" message:error.localizedDescription from:self];
[FLEXAlert showAlert:@"Error"
message:error.localizedDescription ?: @(statusCode).stringValue
from:self
];
}
}];
}
+13
View File
@@ -11,6 +11,7 @@ import UIKit
@UIApplicationMain @objcMembers
class AppDelegate: UIResponder, UIApplicationDelegate {
var repeatingLogExampleTimer: Timer!
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
@@ -29,9 +30,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// To show off the network logger, send several misc network requests
MiscNetworkRequests.sendExampleRequests()
// For < iOS 13, set up the window here
if ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 13 {
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = FLEXNavigationController(
rootViewController: CommitListViewController()
)
self.window = window
window.makeKeyAndVisible()
FLEXManager.shared.showExplorer()
}
return true
}
@available(iOS 13.0, *)
func application(_ application: UIApplication,
configurationForConnecting session: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+1 -1
View File
@@ -9,7 +9,7 @@
import UIKit
import SwiftUI
@objcMembers
@objcMembers @available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
@@ -25,6 +25,8 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>NSPhotoLibraryAddUsageDescription</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "FLEX"
spec.version = "4.0.0"
spec.version = "4.1.0"
spec.summary = "A set of in-app debugging and exploration tools for iOS"
spec.description = <<-DESC
- Inspect and modify views in the hierarchy.
+80 -72
View File
@@ -72,14 +72,14 @@
3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C948B1B5B21410088C3F2 /* FLEXMethodCallingViewController.m */; };
3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C949F1B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95231B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A01B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m */; };
3A4C95241B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95251B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A21B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m */; };
3A4C95241B5B21410088C3F2 /* FLEXFileBrowserController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95251B5B21410088C3F2 /* FLEXFileBrowserController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A21B5B21410088C3F2 /* FLEXFileBrowserController.m */; };
3A4C95261B5B21410088C3F2 /* FLEXGlobalsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A31B5B21410088C3F2 /* FLEXGlobalsViewController.h */; };
3A4C95271B5B21410088C3F2 /* FLEXGlobalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A41B5B21410088C3F2 /* FLEXGlobalsViewController.m */; };
3A4C95281B5B21410088C3F2 /* FLEXObjectListViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A51B5B21410088C3F2 /* FLEXObjectListViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95291B5B21410088C3F2 /* FLEXObjectListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A61B5B21410088C3F2 /* FLEXObjectListViewController.m */; };
3A4C952C1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m */; };
3A4C952C1B5B21410088C3F2 /* FLEXLiveObjectsController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsController.m */; };
3A4C952E1B5B21410088C3F2 /* FLEXWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94AB1B5B21410088C3F2 /* FLEXWebViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C952F1B5B21410088C3F2 /* FLEXWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94AC1B5B21410088C3F2 /* FLEXWebViewController.m */; };
3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94AE1B5B21410088C3F2 /* FLEXSystemLogMessage.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -95,20 +95,20 @@
3A4C953B1B5B21410088C3F2 /* FLEXNetworkSettingsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */; };
3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */; };
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m */; };
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m */; };
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */; };
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */; };
3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94C21B5B21410088C3F2 /* FLEXNetworkObserver.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95431B5B21410088C3F2 /* FLEXNetworkObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94C31B5B21410088C3F2 /* FLEXNetworkObserver.m */; };
3A4C95471B5B217D0088C3F2 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A4C95461B5B217D0088C3F2 /* libz.dylib */; };
679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 679F64841BD53B7B00A8C94C /* FLEXCookiesTableViewController.h */; };
679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */; };
679F64861BD53B7B00A8C94C /* FLEXCookiesViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 679F64841BD53B7B00A8C94C /* FLEXCookiesViewController.h */; };
679F64871BD53B7B00A8C94C /* FLEXCookiesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 679F64851BD53B7B00A8C94C /* FLEXCookiesViewController.m */; };
71E1C2132307FBB800F5032A /* FLEXKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = 71E1C20B2307FBB700F5032A /* FLEXKeychain.h */; };
71E1C2142307FBB800F5032A /* FLEXKeychainTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 71E1C20C2307FBB700F5032A /* FLEXKeychainTableViewController.h */; };
71E1C2142307FBB800F5032A /* FLEXKeychainViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 71E1C20C2307FBB700F5032A /* FLEXKeychainViewController.h */; };
71E1C2152307FBB800F5032A /* FLEXKeychainQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 71E1C20D2307FBB700F5032A /* FLEXKeychainQuery.h */; };
71E1C2172307FBB800F5032A /* FLEXKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 71E1C20F2307FBB700F5032A /* FLEXKeychain.m */; };
71E1C2182307FBB800F5032A /* FLEXKeychainTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71E1C2102307FBB700F5032A /* FLEXKeychainTableViewController.m */; };
71E1C2182307FBB800F5032A /* FLEXKeychainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71E1C2102307FBB700F5032A /* FLEXKeychainViewController.m */; };
71E1C2192307FBB800F5032A /* FLEXKeychainQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 71E1C2112307FBB700F5032A /* FLEXKeychainQuery.m */; };
7349FD6A22B93CDF00051810 /* FLEXColor.h in Headers */ = {isa = PBXBuildFile; fileRef = 7349FD6822B93CDF00051810 /* FLEXColor.h */; };
7349FD6B22B93CDF00051810 /* FLEXColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 7349FD6922B93CDF00051810 /* FLEXColor.m */; };
@@ -226,8 +226,8 @@
C383C3BA23B6A62A007A321B /* FLEXRuntimeSafety.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3B823B6A62A007A321B /* FLEXRuntimeSafety.m */; };
C383C3BD23B6B398007A321B /* UITextField+Range.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3BB23B6B398007A321B /* UITextField+Range.m */; };
C383C3BE23B6B398007A321B /* UITextField+Range.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3BC23B6B398007A321B /* UITextField+Range.h */; settings = {ATTRIBUTES = (Public, ); }; };
C383C3C123B6B429007A321B /* NSTimer+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3BF23B6B429007A321B /* NSTimer+Blocks.m */; };
C383C3C223B6B429007A321B /* NSTimer+Blocks.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3C023B6B429007A321B /* NSTimer+Blocks.h */; settings = {ATTRIBUTES = (Public, ); }; };
C383C3C123B6B429007A321B /* NSTimer+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3BF23B6B429007A321B /* NSTimer+FLEX.m */; };
C383C3C223B6B429007A321B /* NSTimer+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3C023B6B429007A321B /* NSTimer+FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
C383C3C523B6BB81007A321B /* FLEXCodeFontCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3C323B6BB81007A321B /* FLEXCodeFontCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
C383C3C623B6BB81007A321B /* FLEXCodeFontCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3C423B6BB81007A321B /* FLEXCodeFontCell.m */; };
C3854DF023F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C3854DEF23F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m */; };
@@ -304,8 +304,8 @@
C3A9424A23C78878006871A3 /* FLEXHierarchyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3A9424823C78878006871A3 /* FLEXHierarchyViewController.m */; };
C3A9424D23C78CFF006871A3 /* FHSViewSnapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = C3A9424B23C78CFF006871A3 /* FHSViewSnapshot.h */; };
C3A9424E23C78CFF006871A3 /* FHSViewSnapshot.m in Sources */ = {isa = PBXBuildFile; fileRef = C3A9424C23C78CFF006871A3 /* FHSViewSnapshot.m */; };
C3BFD070233C23ED0015FB82 /* NSArray+Functional.h in Headers */ = {isa = PBXBuildFile; fileRef = C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3BFD071233C23ED0015FB82 /* NSArray+Functional.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */; };
C3BFD070233C23ED0015FB82 /* NSArray+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C3BFD06E233C23ED0015FB82 /* NSArray+FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3BFD071233C23ED0015FB82 /* NSArray+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */; };
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
@@ -318,6 +318,8 @@
C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */; };
C3E5DA02231700EE00E655DB /* FLEXObjectInfoSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E5DA00231700EE00E655DB /* FLEXObjectInfoSection.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3EB6F8D242E9C83006EA386 /* FLEXRuntimeExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = C3EB6F8B242E9C83006EA386 /* FLEXRuntimeExporter.m */; };
C3EB6F8E242E9C83006EA386 /* FLEXRuntimeExporter.h in Headers */ = {isa = PBXBuildFile; fileRef = C3EB6F8C242E9C83006EA386 /* FLEXRuntimeExporter.h */; };
C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */ = {isa = PBXBuildFile; fileRef = C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */ = {isa = PBXBuildFile; fileRef = C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */; };
C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -342,8 +344,8 @@
C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */; };
C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */; };
C3F977872311B38F0032776D /* NSObject+Reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F977812311B38F0032776D /* NSObject+Reflection.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977822311B38F0032776D /* NSObject+Reflection.m */; };
C3F977872311B38F0032776D /* NSObject+FLEX_Reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F977812311B38F0032776D /* NSObject+FLEX_Reflection.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3F977882311B38F0032776D /* NSObject+FLEX_Reflection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977822311B38F0032776D /* NSObject+FLEX_Reflection.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -427,14 +429,14 @@
3A4C948B1B5B21410088C3F2 /* FLEXMethodCallingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMethodCallingViewController.m; sourceTree = "<group>"; };
3A4C949F1B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFileBrowserSearchOperation.h; sourceTree = "<group>"; };
3A4C94A01B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFileBrowserSearchOperation.m; sourceTree = "<group>"; };
3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFileBrowserTableViewController.h; sourceTree = "<group>"; };
3A4C94A21B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFileBrowserTableViewController.m; sourceTree = "<group>"; };
3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFileBrowserController.h; sourceTree = "<group>"; };
3A4C94A21B5B21410088C3F2 /* FLEXFileBrowserController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFileBrowserController.m; sourceTree = "<group>"; };
3A4C94A31B5B21410088C3F2 /* FLEXGlobalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXGlobalsViewController.h; sourceTree = "<group>"; };
3A4C94A41B5B21410088C3F2 /* FLEXGlobalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXGlobalsViewController.m; sourceTree = "<group>"; };
3A4C94A51B5B21410088C3F2 /* FLEXObjectListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXObjectListViewController.h; sourceTree = "<group>"; };
3A4C94A61B5B21410088C3F2 /* FLEXObjectListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectListViewController.m; sourceTree = "<group>"; };
3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXLiveObjectsTableViewController.h; sourceTree = "<group>"; };
3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXLiveObjectsTableViewController.m; sourceTree = "<group>"; };
3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXLiveObjectsController.h; sourceTree = "<group>"; };
3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXLiveObjectsController.m; sourceTree = "<group>"; };
3A4C94AB1B5B21410088C3F2 /* FLEXWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXWebViewController.h; sourceTree = "<group>"; };
3A4C94AC1B5B21410088C3F2 /* FLEXWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXWebViewController.m; sourceTree = "<group>"; };
3A4C94AE1B5B21410088C3F2 /* FLEXSystemLogMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXSystemLogMessage.h; sourceTree = "<group>"; };
@@ -451,21 +453,21 @@
3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkSettingsController.m; sourceTree = "<group>"; };
3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransaction.h; sourceTree = "<group>"; };
3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransaction.m; sourceTree = "<group>"; };
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionDetailTableViewController.h; sourceTree = "<group>"; };
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionDetailTableViewController.m; sourceTree = "<group>"; };
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionTableViewCell.h; sourceTree = "<group>"; };
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionTableViewCell.m; sourceTree = "<group>"; };
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionDetailController.h; sourceTree = "<group>"; };
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionDetailController.m; sourceTree = "<group>"; };
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionCell.h; sourceTree = "<group>"; };
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionCell.m; sourceTree = "<group>"; };
3A4C94C21B5B21410088C3F2 /* FLEXNetworkObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkObserver.h; sourceTree = "<group>"; };
3A4C94C31B5B21410088C3F2 /* FLEXNetworkObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkObserver.m; sourceTree = "<group>"; };
3A4C94C41B5B21410088C3F2 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
3A4C95461B5B217D0088C3F2 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
679F64841BD53B7B00A8C94C /* FLEXCookiesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXCookiesTableViewController.h; sourceTree = "<group>"; };
679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXCookiesTableViewController.m; sourceTree = "<group>"; };
679F64841BD53B7B00A8C94C /* FLEXCookiesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXCookiesViewController.h; sourceTree = "<group>"; };
679F64851BD53B7B00A8C94C /* FLEXCookiesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXCookiesViewController.m; sourceTree = "<group>"; };
71E1C20B2307FBB700F5032A /* FLEXKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeychain.h; sourceTree = "<group>"; };
71E1C20C2307FBB700F5032A /* FLEXKeychainTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeychainTableViewController.h; sourceTree = "<group>"; };
71E1C20C2307FBB700F5032A /* FLEXKeychainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeychainViewController.h; sourceTree = "<group>"; };
71E1C20D2307FBB700F5032A /* FLEXKeychainQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeychainQuery.h; sourceTree = "<group>"; };
71E1C20F2307FBB700F5032A /* FLEXKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeychain.m; sourceTree = "<group>"; };
71E1C2102307FBB700F5032A /* FLEXKeychainTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeychainTableViewController.m; sourceTree = "<group>"; };
71E1C2102307FBB700F5032A /* FLEXKeychainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeychainViewController.m; sourceTree = "<group>"; };
71E1C2112307FBB700F5032A /* FLEXKeychainQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeychainQuery.m; sourceTree = "<group>"; };
7349FD6822B93CDF00051810 /* FLEXColor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColor.h; sourceTree = "<group>"; };
7349FD6922B93CDF00051810 /* FLEXColor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXColor.m; sourceTree = "<group>"; };
@@ -585,8 +587,8 @@
C383C3B823B6A62A007A321B /* FLEXRuntimeSafety.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXRuntimeSafety.m; sourceTree = "<group>"; };
C383C3BB23B6B398007A321B /* UITextField+Range.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+Range.m"; sourceTree = "<group>"; };
C383C3BC23B6B398007A321B /* UITextField+Range.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+Range.h"; sourceTree = "<group>"; };
C383C3BF23B6B429007A321B /* NSTimer+Blocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+Blocks.m"; sourceTree = "<group>"; };
C383C3C023B6B429007A321B /* NSTimer+Blocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+Blocks.h"; sourceTree = "<group>"; };
C383C3BF23B6B429007A321B /* NSTimer+FLEX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+FLEX.m"; sourceTree = "<group>"; };
C383C3C023B6B429007A321B /* NSTimer+FLEX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+FLEX.h"; sourceTree = "<group>"; };
C383C3C323B6BB81007A321B /* FLEXCodeFontCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCodeFontCell.h; sourceTree = "<group>"; };
C383C3C423B6BB81007A321B /* FLEXCodeFontCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXCodeFontCell.m; sourceTree = "<group>"; };
C3854DEF23F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTypeEncodingParserTests.m; sourceTree = "<group>"; };
@@ -659,8 +661,8 @@
C3A9424823C78878006871A3 /* FLEXHierarchyViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXHierarchyViewController.m; sourceTree = "<group>"; };
C3A9424B23C78CFF006871A3 /* FHSViewSnapshot.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FHSViewSnapshot.h; sourceTree = "<group>"; };
C3A9424C23C78CFF006871A3 /* FHSViewSnapshot.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FHSViewSnapshot.m; sourceTree = "<group>"; };
C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+Functional.h"; sourceTree = "<group>"; };
C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Functional.m"; sourceTree = "<group>"; };
C3BFD06E233C23ED0015FB82 /* NSArray+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+FLEX.h"; sourceTree = "<group>"; };
C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+FLEX.m"; sourceTree = "<group>"; };
C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectRef.h; sourceTree = "<group>"; };
C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectRef.m; sourceTree = "<group>"; };
C3DFCD922416BC6500BB7084 /* FLEXFilteringTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXFilteringTableViewController.h; sourceTree = "<group>"; };
@@ -672,6 +674,8 @@
C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXRuntime+Compare.h"; sourceTree = "<group>"; };
C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXRuntime+Compare.m"; sourceTree = "<group>"; };
C3E5DA00231700EE00E655DB /* FLEXObjectInfoSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectInfoSection.h; sourceTree = "<group>"; };
C3EB6F8B242E9C83006EA386 /* FLEXRuntimeExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXRuntimeExporter.m; sourceTree = "<group>"; };
C3EB6F8C242E9C83006EA386 /* FLEXRuntimeExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXRuntimeExporter.h; sourceTree = "<group>"; };
C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXScopeCarousel.h; sourceTree = "<group>"; };
C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXScopeCarousel.m; sourceTree = "<group>"; };
C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXSubtitleTableViewCell.h; sourceTree = "<group>"; };
@@ -698,8 +702,8 @@
C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+ObjcRuntime.m"; sourceTree = "<group>"; };
C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+ObjcRuntime.h"; sourceTree = "<group>"; };
C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+ObjcRuntime.m"; sourceTree = "<group>"; };
C3F977812311B38F0032776D /* NSObject+Reflection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+Reflection.h"; sourceTree = "<group>"; };
C3F977822311B38F0032776D /* NSObject+Reflection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Reflection.m"; sourceTree = "<group>"; };
C3F977812311B38F0032776D /* NSObject+FLEX_Reflection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+FLEX_Reflection.h"; sourceTree = "<group>"; };
C3F977822311B38F0032776D /* NSObject+FLEX_Reflection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+FLEX_Reflection.m"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -908,10 +912,10 @@
3A4C94A61B5B21410088C3F2 /* FLEXObjectListViewController.m */,
C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */,
C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */,
3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h */,
3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m */,
679F64841BD53B7B00A8C94C /* FLEXCookiesTableViewController.h */,
679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */,
3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsController.h */,
3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsController.m */,
679F64841BD53B7B00A8C94C /* FLEXCookiesViewController.h */,
679F64851BD53B7B00A8C94C /* FLEXCookiesViewController.m */,
3A4C94AB1B5B21410088C3F2 /* FLEXWebViewController.h */,
3A4C94AC1B5B21410088C3F2 /* FLEXWebViewController.m */,
C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */,
@@ -950,10 +954,10 @@
3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */,
3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */,
3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */,
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h */,
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m */,
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h */,
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m */,
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */,
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */,
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */,
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */,
2EF6B04C1D494BE50006BDA5 /* FLEXNetworkCurlLogger.h */,
2EF6B04B1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m */,
3A4C94C11B5B21410088C3F2 /* PonyDebugger */,
@@ -987,8 +991,8 @@
71E1C20F2307FBB700F5032A /* FLEXKeychain.m */,
71E1C20D2307FBB700F5032A /* FLEXKeychainQuery.h */,
71E1C2112307FBB700F5032A /* FLEXKeychainQuery.m */,
71E1C20C2307FBB700F5032A /* FLEXKeychainTableViewController.h */,
71E1C2102307FBB700F5032A /* FLEXKeychainTableViewController.m */,
71E1C20C2307FBB700F5032A /* FLEXKeychainViewController.h */,
71E1C2102307FBB700F5032A /* FLEXKeychainViewController.m */,
);
path = Keychain;
sourceTree = "<group>";
@@ -1084,8 +1088,8 @@
children = (
3A4C949F1B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h */,
3A4C94A01B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m */,
3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h */,
3A4C94A21B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m */,
3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserController.h */,
3A4C94A21B5B21410088C3F2 /* FLEXFileBrowserController.m */,
);
path = FileBrowser;
sourceTree = "<group>";
@@ -1220,12 +1224,12 @@
C398625C23AD6E90007E6793 /* UIFont+FLEX.m */,
C383C3BC23B6B398007A321B /* UITextField+Range.h */,
C383C3BB23B6B398007A321B /* UITextField+Range.m */,
C383C3C023B6B429007A321B /* NSTimer+Blocks.h */,
C383C3BF23B6B429007A321B /* NSTimer+Blocks.m */,
C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */,
C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */,
C3F977812311B38F0032776D /* NSObject+Reflection.h */,
C3F977822311B38F0032776D /* NSObject+Reflection.m */,
C383C3C023B6B429007A321B /* NSTimer+FLEX.h */,
C383C3BF23B6B429007A321B /* NSTimer+FLEX.m */,
C3BFD06E233C23ED0015FB82 /* NSArray+FLEX.h */,
C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */,
C3F977812311B38F0032776D /* NSObject+FLEX_Reflection.h */,
C3F977822311B38F0032776D /* NSObject+FLEX_Reflection.m */,
C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */,
C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */,
C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */,
@@ -1288,6 +1292,8 @@
C398626F23AD71C6007E6793 /* DataSources */ = {
isa = PBXGroup;
children = (
C3EB6F8C242E9C83006EA386 /* FLEXRuntimeExporter.h */,
C3EB6F8B242E9C83006EA386 /* FLEXRuntimeExporter.m */,
C398626723AD71C1007E6793 /* FLEXRuntimeClient.h */,
C398626A23AD71C1007E6793 /* FLEXRuntimeClient.m */,
C398626823AD71C1007E6793 /* FLEXRuntimeController.h */,
@@ -1430,7 +1436,7 @@
94A515141C4CA1C00063292F /* FLEXManager.h in Headers */,
C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
C3A9422C23C3DA39006871A3 /* FHSView.h in Headers */,
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */,
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h in Headers */,
3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */,
C398625523AD6C67007E6793 /* FLEXObjcRuntimeViewController.h in Headers */,
C3E5DA02231700EE00E655DB /* FLEXObjectInfoSection.h in Headers */,
@@ -1480,7 +1486,7 @@
C3A9424923C78878006871A3 /* FLEXHierarchyViewController.h in Headers */,
C3DFCD942416BC6500BB7084 /* FLEXFilteringTableViewController.h in Headers */,
3A4C94F11B5B21410088C3F2 /* FLEXArgumentInputFontsPickerView.h in Headers */,
C3BFD070233C23ED0015FB82 /* NSArray+Functional.h in Headers */,
C3BFD070233C23ED0015FB82 /* NSArray+FLEX.h in Headers */,
C398682923AC370100E9E391 /* FLEXViewShortcuts.h in Headers */,
C3474C4023DA496400466532 /* FLEXKeyValueTableViewCell.h in Headers */,
779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */,
@@ -1516,11 +1522,11 @@
C36FBFDB230F3B98008D95D5 /* FLEXClassBuilder.h in Headers */,
779B1ED41C0C4D7C001F5E49 /* FLEXDBQueryRowCell.h in Headers */,
C301994A2409B38A00759E8E /* CALayer+FLEX.h in Headers */,
3A4C952C1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h in Headers */,
3A4C952C1B5B21410088C3F2 /* FLEXLiveObjectsController.h in Headers */,
3A4C94EF1B5B21410088C3F2 /* FLEXArgumentInputDateView.h in Headers */,
C36FBFCE230F3B98008D95D5 /* FLEXProperty.h in Headers */,
C36FBFD2230F3B98008D95D5 /* FLEXMethodBase.h in Headers */,
71E1C2142307FBB800F5032A /* FLEXKeychainTableViewController.h in Headers */,
71E1C2142307FBB800F5032A /* FLEXKeychainViewController.h in Headers */,
3A4C94F71B5B21410088C3F2 /* FLEXArgumentInputNotSupportedView.h in Headers */,
C3F31D3E2267D883003C991A /* FLEXTableViewCell.h in Headers */,
3A4C94E51B5B21410088C3F2 /* FLEXUtility.h in Headers */,
@@ -1530,7 +1536,7 @@
94A515251C4CA2080063292F /* FLEXExplorerToolbar.h in Headers */,
C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */,
3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */,
C383C3C223B6B429007A321B /* NSTimer+Blocks.h in Headers */,
C383C3C223B6B429007A321B /* NSTimer+FLEX.h in Headers */,
3A4C952E1B5B21410088C3F2 /* FLEXWebViewController.h in Headers */,
C3A9424D23C78CFF006871A3 /* FHSViewSnapshot.h in Headers */,
C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */,
@@ -1555,12 +1561,12 @@
C38EF26323A2FCD20047A7EC /* FLEXViewControllerShortcuts.h in Headers */,
3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */,
C386D6EB24199E9600699085 /* FLEX-ObjectExploring.h in Headers */,
679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */,
C3F977872311B38F0032776D /* NSObject+Reflection.h in Headers */,
679F64861BD53B7B00A8C94C /* FLEXCookiesViewController.h in Headers */,
C3F977872311B38F0032776D /* NSObject+FLEX_Reflection.h in Headers */,
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */,
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h in Headers */,
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionCell.h in Headers */,
C398626523AD70F5007E6793 /* FLEXKBToolbarButton.h in Headers */,
3A4C95241B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h in Headers */,
3A4C95241B5B21410088C3F2 /* FLEXFileBrowserController.h in Headers */,
C31D93E423E38CBE005517BF /* FLEXBlockShortcuts.h in Headers */,
94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */,
71E1C2152307FBB800F5032A /* FLEXKeychainQuery.h in Headers */,
@@ -1577,6 +1583,7 @@
C3A9424023C5443A006871A3 /* FHSSnapshotNodes.h in Headers */,
C32A195E231732E800EB02AC /* FLEXCollectionContentSection.h in Headers */,
3A4C94E11B5B21410088C3F2 /* FLEXResources.h in Headers */,
C3EB6F8E242E9C83006EA386 /* FLEXRuntimeExporter.h in Headers */,
779B1ED81C0C4D7C001F5E49 /* FLEXTableLeftCell.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1713,11 +1720,12 @@
3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */,
C362AE8223C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m in Sources */,
C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */,
C3EB6F8D242E9C83006EA386 /* FLEXRuntimeExporter.m in Sources */,
3A4C94F61B5B21410088C3F2 /* FLEXArgumentInputObjectView.m in Sources */,
3A4C94EC1B5B21410088C3F2 /* FLEXImagePreviewViewController.m in Sources */,
C383C3BA23B6A62A007A321B /* FLEXRuntimeSafety.m in Sources */,
3A4C94F21B5B21410088C3F2 /* FLEXArgumentInputFontsPickerView.m in Sources */,
C3BFD071233C23ED0015FB82 /* NSArray+Functional.m in Sources */,
C3BFD071233C23ED0015FB82 /* NSArray+FLEX.m in Sources */,
7349FD6B22B93CDF00051810 /* FLEXColor.m in Sources */,
C3878DBA23A749960038FDBE /* FLEXVariableEditorViewController.m in Sources */,
94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */,
@@ -1742,7 +1750,7 @@
C36FBFDC230F3B98008D95D5 /* FLEXIvar.m in Sources */,
C313854023F5C1A10046E63C /* FLEXViewControllersViewController.m in Sources */,
C3F527C22318670F009CBA07 /* FLEXImageShortcuts.m in Sources */,
679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */,
679F64871BD53B7B00A8C94C /* FLEXCookiesViewController.m in Sources */,
C3A9422D23C3DA39006871A3 /* FHSView.m in Sources */,
3A4C94CE1B5B21410088C3F2 /* FLEXGlobalsEntry.m in Sources */,
C398625223AD6C67007E6793 /* FLEXKeyPathSearchController.m in Sources */,
@@ -1765,13 +1773,13 @@
3A4C95041B5B21410088C3F2 /* FLEXArgumentInputView.m in Sources */,
C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */,
C312A13523ECBE5800E38049 /* FLEXBookmarksViewController.m in Sources */,
3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m in Sources */,
3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionCell.m in Sources */,
C3DFCD952416BC6500BB7084 /* FLEXFilteringTableViewController.m in Sources */,
C3F527BE2318603F009CBA07 /* FLEXShortcutsSection.m in Sources */,
3A4C94D61B5B21410088C3F2 /* FLEXObjectExplorerViewController.m in Sources */,
C383C3BD23B6B398007A321B /* UITextField+Range.m in Sources */,
3A4C94DE1B5B21410088C3F2 /* FLEXHeapEnumerator.m in Sources */,
3A4C95251B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m in Sources */,
3A4C95251B5B21410088C3F2 /* FLEXFileBrowserController.m in Sources */,
C36FBFD1230F3B98008D95D5 /* FLEXClassBuilder.m in Sources */,
779B1ED51C0C4D7C001F5E49 /* FLEXDBQueryRowCell.m in Sources */,
3A4C94E21B5B21410088C3F2 /* FLEXResources.m in Sources */,
@@ -1810,11 +1818,11 @@
3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
C3531B9E23E69BB200A184AD /* FLEXManager+Networking.m in Sources */,
C39EADC923F37B89005618BE /* FLEXTypeEncodingParser.m in Sources */,
C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
C3F977882311B38F0032776D /* NSObject+FLEX_Reflection.m in Sources */,
C36B096623E0D4A1008F5D47 /* UIMenu+FLEX.m in Sources */,
94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,
C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */,
C383C3C123B6B429007A321B /* NSTimer+Blocks.m in Sources */,
C383C3C123B6B429007A321B /* NSTimer+FLEX.m in Sources */,
3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
C3474C4123DA496400466532 /* FLEXKeyValueTableViewCell.m in Sources */,
779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
@@ -1823,7 +1831,7 @@
3A4C95001B5B21410088C3F2 /* FLEXArgumentInputSwitchView.m in Sources */,
C386D6F02419A33F00699085 /* FLEXRuntimeConstants.m in Sources */,
C3F31D442267D883003C991A /* FLEXTableView.m in Sources */,
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m in Sources */,
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m in Sources */,
224D49AB1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.m in Sources */,
C312A13C23ECE79000E38049 /* FLEXWindowManagerController.m in Sources */,
C36FBFDA230F3B98008D95D5 /* FLEXPropertyAttributes.m in Sources */,
@@ -1834,7 +1842,7 @@
C34D4EB923A2B17900C1F903 /* FLEXBundleShortcuts.m in Sources */,
C3531BAB23E88FAC00A184AD /* FLEXTabList.m in Sources */,
C3DFCD992416E7DD00BB7084 /* FLEXMutableListSection.m in Sources */,
3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */,
3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsController.m in Sources */,
C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */,
C398625023AD6C67007E6793 /* FLEXObjcRuntimeViewController.m in Sources */,
C398626E23AD71C1007E6793 /* FLEXRuntimeClient.m in Sources */,
@@ -1845,7 +1853,7 @@
3A4C95291B5B21410088C3F2 /* FLEXObjectListViewController.m in Sources */,
C397E319240EC98F0091E4EC /* FLEXSQLResult.m in Sources */,
C31D93E923E38E97005517BF /* FLEXBlockDescription.m in Sources */,
71E1C2182307FBB800F5032A /* FLEXKeychainTableViewController.m in Sources */,
71E1C2182307FBB800F5032A /* FLEXKeychainViewController.m in Sources */,
94A5151E1C4CA1F10063292F /* FLEXExplorerViewController.m in Sources */,
C398625A23AD6C88007E6793 /* FLEXSearchToken.m in Sources */,
3A4C95371B5B21410088C3F2 /* FLEXNetworkMITMViewController.m in Sources */,
+2 -2
View File
@@ -8,8 +8,8 @@
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "NSObject+Reflection.h"
#import "NSArray+Functional.h"
#import "NSObject+FLEX_Reflection.h"
#import "NSArray+FLEX.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXProperty.h"
#import "FLEXUtility.h"