Compare commits

..

90 Commits

Author SHA1 Message Date
ThePantsThief 6c9d48ac97 Fix #168 by restructuring try-catch branching 2018-11-22 17:07:29 -06:00
Tanner Bennett 5a0fbf401a Fix prettyArgumentComponentsForMethod: bug
#178 made methods with no arguments appear to take one argument by forcibly returning the selector name as the only argument. This is not desired behavior. Updating the test to reflect desired behavior reveals this.

This commit makes this method return an empty array when selectors consist of one component, and does some housekeeping on the tests added in #178.
2018-11-22 17:07:29 -06:00
Tanner Bennett 170cc5f9ad Detect and unbox pointers to objects from void *
- Also unbox C strings into NSString
- Also adds return type encoding string to method calling view controller
2018-11-22 17:07:29 -06:00
Tanner Bennett b6e0ccbcdb Add helper methods to FLEXRuntimeUtility
- Add FLEXTypeEncoding enum
- Can check whether arbitrary poiner is valid object
- Can get return type encoding for method
- Can unbox raw pointers from NSValue into actual objects, or unbox C strings into NSStrings

Code copied from the Objc runtime complies with ASPL
2018-11-22 17:06:50 -06:00
Tanner Bennett 79a704ab1a Add "Get" button to property/ivar editor screen
Previously you could only "Set" mutable ivars or properties. This commit adds a "Get" button to the same screen to allow you to view the current value instead.

It may be worth considering other approaches to this entirely, such as an alert that asks you if you want to get or set the ivar/property before a new screen is even pushed, or maybe a "Get" button as an accessory view on the rows of mutable ivars/properties.
2018-11-22 17:02:52 -06:00
Tanner Bennett 44e9d55fb8 Fix crash surrounding SwiftObject subclasses
SwiftObject subclasses cannot be safely inspected with the Objc runtime, attempts to do so sometimes lead to crashes
2018-11-22 16:09:21 -06:00
Geor Kasapidi ab9515caaf Allow registration of content type viewers (#241) 2018-11-09 06:07:09 -06:00
Tanner Bennett 0dd0fc9418 Update to recommended project settings
Silence warning for "implementing deprecated method" in Apple's own sample code.
2018-11-04 02:34:19 -06:00
Tanner Bennett 24d5f3e9b2 Add FLEXColorExplorerViewController
Provides a visual of the color for all UIColor objects.
2018-11-04 02:34:19 -06:00
Terry Lewis 693f57eef7 Added view-color indicator to cells within view-hierarchy list. (#239) 2018-11-04 02:30:40 -06:00
Tanner Bennett 7c17ce0787 Fix Instances table view bug
Accidentally returned a FLEXObjectRef and not the object itself
2018-10-17 16:52:40 -05:00
Tanner Bennett 400a3ccd1c Add +[NSBundle mainBundle] to global list 2018-10-17 16:52:40 -05:00
Colin Humber a8cdac1872 Fixed strict prototype warnings (#231) 2018-08-23 14:25:31 -05:00
zhaogyrain dedac1f98d Fix #225
Fixes the SQL editor clipping under the navigation bar on iPhone X
2018-08-08 23:35:40 -05:00
Tanner Bennett efa317f0d1 Merge pull request #227 from NSExceptional/organize-refs-pr
Group similar "objects with ivars referencing this object"
2018-07-30 18:08:52 -05:00
Tanner Bennett 9b55bb10de Group similar "objects referencing this object" 2018-07-24 20:20:30 -05:00
Tanner Bennett 122fb41fa8 Merge pull request #228 from NSExceptional/inheritance-pr
Additional object explorer scopes
2018-07-19 16:29:48 -05:00
Tanner Bennett d6b5e8c77d Additional object explorer scopes
Scopes now include:
- No inheritance (base class)
- Base class with parent attributes
- Base class with all inherited attributes, except NSObject
- NSObject attributes only

It is unusual that you need to see anything but the parent's attributes alongside those of the base class, and you especially rarely need to see NSObject attributes.
2018-07-19 16:29:15 -05:00
Tanner Bennett a6ad98dd53 Catch exceptions thrown by [value description] 2018-07-12 22:33:07 -05:00
Tanner Bennett cc35f2086a Recommended project settings (9.2) 2018-07-12 22:32:05 -05:00
Tanner Bennett 7038aae6db ExplorerToolbar → Toolbar 2018-07-12 22:31:23 -05:00
Tanner Bennett f5433153d0 Only scroll log view if new messages have arrived
This fixes a bug where being scrolled to the bottom of the system log screen would make the table view stutter repeatedly because it was trying to scroll to the bottom while already being at the bottom.
2018-07-12 22:22:12 -05:00
Tanner Bennett 8b7c59d949 Fix #117: limit network response size
Limit cached network response size to 50 MB
2018-07-12 22:22:12 -05:00
Tanner Bennett faef524b6c Merge pull request #220 from bharat/master
Resolve Xcode 9.3 warnings and deprecations
2018-07-11 14:19:59 -05:00
Tanner Bennett a2bdc03684 Merge pull request #188 from NSExceptional/master
Simplify view controller presentation code
2018-07-10 20:19:36 -05:00
Tanner Bennett bd5f9740b7 Simplify view controller presentation code
Removes duplicate code related to presenting a view controller from the
FLEX window (usually triggered by a toolbar button).

This adds `-[FLEXExplorerViewController
presentOrDismissViewControllerFromToolbar:shouldDismiss:completion:]`
which also makes it easier for others to add their own toolbar buttons
to present some other kind of screen, or for FLEX itself to utilize
again in the future.
2018-07-10 17:57:04 -05:00
Ryan Olson 505bb2ca41 Merge pull request #198 from NSExceptional/exception-pr
Reveal exception reason in dialog
2018-07-10 10:07:37 -07:00
Ryan Olson 009711ab3f Merge pull request #199 from NSExceptional/log-pr
System log will cache messages
2018-07-10 10:06:10 -07:00
Ryan Olson 7ad7653cdf Merge pull request #226 from pujiaxin33/feature_AddShareToFileBrowser
add share  action to FLEXFileBrowserTableViewController
2018-07-10 10:02:35 -07:00
Ryan Olson e5f51e4dfa Merge pull request #222 from NSExceptional/heap-enum-fix
Fix heap enumeration crash
2018-07-10 09:49:08 -07:00
pjx 92029d2b43 add share action to FLEXFileBrowserTableViewController 2018-07-06 12:01:50 +08:00
Bharat Mediratta 7da059791e Further amendment to macro definition 2018-06-04 14:00:18 -07:00
Bharat Mediratta af57527961 Minor cleanups in macros and indentation 2018-06-04 13:57:41 -07:00
Bharat Mediratta 9a8f45663e Revert update to Xcode 9.3; Stay at Xcode 8 for now 2018-06-04 13:56:54 -07:00
Tanner Bennett 8528c8a1f6 System log will cache messages
fix paren
2018-05-21 06:51:17 -05:00
Tanner Bennett d682fd0ace Fix heap enumeration crash
We only need to enumerate the `DefaultMallocZone` zone to find objects we care about.
2018-05-20 02:16:22 -05:00
Tanner Bennett 31af87a81e Reveal exception reason in dialog
When a user-invoked method call fails, FLEX presents a dialog informing you that it failed.

In practice, it is more useful to see the exception name and reason than the (potentially crazy long) object description.
2018-05-07 20:15:55 -05:00
Bharat Mediratta 386d6ae06a UITouch.maximumForcePossible is only available in iOS 9.0+ 2018-05-03 12:33:00 -07:00
Bharat Mediratta df79ae7971 Fix deprecation warnings
Replace:
 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

With:
 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
2018-05-03 12:18:55 -07:00
Bharat Mediratta d30c642707 Adjust FLEX_AT_LEAST_IOS11_SDK to work with -Wexpansion-to-defined 2018-05-03 12:10:22 -07:00
Bharat Mediratta f463e2b43e Xcode 9.3 automated fixes 2018-05-03 12:07:46 -07:00
Shoaib Meenai ed49a4fc89 Use FOUNDATION_EXTERN for global variable
Global variables that can be accessed from both C (or Objective-C) and
C++ (or Objective-C++) source files should be marked `extern "C"` when
in C++ mode (which is what `FOUNDATION_EXTERN` does), to ensure
consistent access across languages.
2018-04-03 21:40:04 -07:00
Chaoshuai Lu b897250fde Fix generics 2018-03-22 16:41:55 -07:00
Greg Heo 06709a5afe Reorder to match order in Installation section 2018-01-23 23:07:47 -08:00
Greg Heo 6eee9e6080 Refactor Installation section 2018-01-23 23:07:47 -08:00
Greg Heo c50e6e51c5 Add Swift example 2018-01-23 23:07:47 -08:00
Marcus Ficner d1e9248695 Fix typo 2018-01-23 23:05:10 -08:00
Chaoshuai Lu a535f10d0c Add generics to foundation collection classes 2018-01-23 23:04:33 -08:00
Chaoshuai Lu 009aa5e3f9 Add iPhone X safe area handling to layout the toolbar correctly 2017-12-17 23:12:41 -08:00
Chaoshuai Lu 675f03fc71 Make Travis use xcodebuild (and xcpretty) instead of (deprecated) xctool 2017-12-17 22:48:15 -08:00
Ryan Olson 99eccdf4c3 Merge pull request #178 from tikoyesayan/master
Fix crash in FLEXRuntimeUtility
2017-08-27 17:22:00 -07:00
Ryan Olson 29afa5e80f Merge pull request #177 from CodeLife2012/networkbug
Should keep the same completion logic
2017-08-27 16:53:20 -07:00
Ryan Olson bf26bc6539 Fix formatting from 62ef95f 2017-08-27 15:56:08 -07:00
Ryan Olson f7b40646e2 Fix network search bar showing over pushed request detail pages 2017-08-27 15:52:22 -07:00
Ryan Olson 84c1fb159b Merge pull request #194 from adysart/xcode9
Xcode9
2017-08-27 15:29:13 -07:00
Aidan Dysart 62ef95ff93 satisfy the -Wstrict-prototypes clang warning 2017-08-08 11:01:10 -07:00
Aidan Dysart 731b729db7 Upgrade project and scheme for Xcode 9, enable default warnings 2017-08-08 11:00:48 -07:00
Tigran Yesayan a1c464d1a7 Add missing free function in testMethodListForClass: method in unit test 2017-07-11 12:48:05 +04:00
Tigran Yesayan eb2ecbf9b3 Added unit test to check the new way of getting method components 2017-07-09 14:56:56 +04:00
Tigran Yesayan 16fab66f7b Fix crash in FLEXRuntimeUtility 2017-07-09 14:52:41 +04:00
Ryan Olson b3e70ac491 Merge pull request #189 from NSExceptional/refactor-toolbar
Make FLEXExplorerToolbar more extensible
2017-07-05 08:58:24 -07:00
Tanner Bennett d5177bb049 Make FLEXExplorerToolbar more extensible
Exposes `FLEXExplorerToolbar.toolbarItems` as a public API so that others can more easily, safely, and reliably add their own items to the FLEX toolbar.
2017-07-03 23:50:46 -05:00
Ryan Olson bb0faeb3cf Merge pull request #184 from revolter/hotfix/code-style
Fix code style
2017-07-03 18:10:17 -07:00
Ryan Olson 761feef3c0 Merge pull request #190 from showbie/request-error-in-red
Highlight request name in red if status code is a 4xx or 5xx
2017-07-03 17:47:00 -07:00
Colin Humber 352bae03ea Highlight request name in red if status code is a 4xx or 5xx 2017-06-29 12:58:01 -06:00
Ryan Olson b0085cae7d Merge pull request #187 from nicked/patch-1
Fixed keyboard shortcuts in Xcode 9 simulator
2017-06-19 17:41:44 -07:00
nicked b2f93f1752 Fixed keyboard shortcuts in Xcode 9 simulator
Pressing Cmd+S in the Simulator normally just takes a screenshot, but in Xcode 9, it also triggers the default "s" keyboard shortcut which enables the FLEX Select tool. This is because the keyboard flags is ANDed with a logical NOT of UIKeyModifierShift instead of the bitwise inverse
2017-06-14 18:03:26 +02:00
Ryan Olson 354510f2c4 Merge pull request #185 from showbie/host-blacklist
Add ability to filter out noisy network requests from being recorded
2017-05-31 22:23:26 -07:00
Colin Humber b8c6175193 Added configuration to filter out network requests for particular hosts from being recorded 2017-05-31 14:18:46 -06:00
Iulian Onofrei d409b110f5 Fix code style 2017-05-23 10:31:02 +03:00
Ryan Olson 5c73220158 Merge pull request #182 from NSExceptional/fix-heap
Fix #119
2017-05-21 22:33:26 -07:00
Tanner Bennett 397721e7ea Fix #119 2017-05-21 18:20:42 -05:00
Ryan Olson 5714275bcd Merge pull request #176 from NSIRLConnection/master
Fix #175
2017-05-21 15:27:56 -07:00
Ryan Olson 833c584e41 Merge pull request #180 from revolter/feature/database-password
Add support for SQLCipher protected databases
2017-05-21 15:22:31 -07:00
Ryan Olson 49b24487c5 Merge pull request #181 from revolter/feature/toolbar-position
Add toolbar position persistence
2017-05-21 15:17:49 -07:00
Iulian Onofrei 6d4eb01a07 Add toolbar position persistence 2017-05-10 17:33:42 +03:00
Iulian Onofrei a752203ff9 Add support for SQLCipher protected databases 2017-05-10 17:12:24 +03:00
Karl Peng c69427613d should keep the same completion logic 2017-03-24 17:29:08 +08:00
Michael 5d75a83568 Fix #175 by checking if the objects respond to compare: 2017-03-09 14:06:47 -05:00
Ryan Olson 7642a0632d Merge pull request #160 from defagos/carthage-instructions-image
Add image for Carthage FLEX exclusion setup
2017-01-15 12:07:48 -08:00
Ryan Olson 841054a713 Merge pull request #161 from unixzii/master
Add a convenient action button in FLEXImagePreviewViewController
2017-01-15 12:05:37 -08:00
杨弘宇 49f368fd63 replace Copy button with Action button in FLEXImagePreviewViewController's navigation item 2016-11-08 20:58:57 +08:00
Samuel Défago 1dc99250c8 Add image for Carthage FLEX exclusion setup 2016-11-07 09:09:19 +01:00
Ryan Olson 00edccf326 Merge pull request #159 from defagos/carthage-instructions
Add Carthage instructions for FLEX exclusion from release builds
2016-11-04 16:54:32 -07:00
Samuel Défago 7f3af90645 Add Carthage instructions for FLEX exclusion from release builds 2016-11-04 07:43:09 +01:00
Ryan Olson 1a030f06cd Merge pull request #157 from froody/size
[FLEX] Add size to heap objects view
2016-10-20 19:48:28 -07:00
Tom Birch 0477858bed [FLEX] Add size to heap objects view
Change-Id: I757714b2570d400886959caa72e731ef65a925b5
2016-10-19 19:55:19 -07:00
Ryan Olson 000e061d00 Merge pull request #152 from rtyu128/rtyu128-FLEXNetworkObserver-Fixed
Fix #150.
2016-10-10 08:16:59 -07:00
Anchor 224978b31b Fixed #150.
Use @selector(URLSession:dataTask:didBecomeDownloadTask:) instead of @selector(URLSession:dataTask:didBecomeDownloadTask:delegate:) in method search.
2016-10-08 10:58:56 +08:00
Ryan Olson 7fd133f13b Bump version in podspec 2016-10-01 15:46:38 -07:00
94 changed files with 2705 additions and 629 deletions
+6 -2
View File
@@ -1,8 +1,12 @@
language: objective-c
xcode_workspace: FLEX.xcworkspace
xcode_sdk: iphonesimulator
before_install:
- gem install xcpretty
matrix:
include:
- xcode_scheme: UICatalog
xcode_sdk: iphonesimulator
- xcode_scheme: FLEX
xcode_sdk: iphonesimulator
script:
- set -o pipefail
- xcodebuild -workspace $TRAVIS_XCODE_WORKSPACE -scheme $TRAVIS_XCODE_SCHEME -sdk $TRAVIS_XCODE_SDK build | xcpretty
@@ -228,7 +228,7 @@
CGFloat hexLabelOriginY = CGRectGetMaxY(self.colorPreviewBox.frame) - self.colorPreviewBox.layer.borderWidth - self.hexLabel.frame.size.height;
self.hexLabel.frame = CGRectMake(hexLabelOriginX, hexLabelOriginY, self.hexLabel.frame.size.width, self.hexLabel.frame.size.height);
NSArray *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput];
NSArray<FLEXColorComponentInputView *> *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput];
for (FLEXColorComponentInputView *inputView in colorComponentInputViews) {
CGSize fitSize = [inputView sizeThatFits:constrainSize];
inputView.frame = CGRectMake(0, runningOriginY, fitSize.width, fitSize.height);
@@ -11,7 +11,7 @@
@interface FLEXArgumentInputFontsPickerView ()
@property (nonatomic, strong) NSMutableArray *availableFonts;
@property (nonatomic, strong) NSMutableArray<NSString *> *availableFonts;
@end
@@ -35,7 +35,7 @@
if ([self.availableFonts indexOfObject:inputValue] == NSNotFound) {
[self.availableFonts insertObject:inputValue atIndex:0];
}
[(UIPickerView*)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO];
[(UIPickerView *)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO];
}
- (id)inputValue
@@ -56,7 +56,7 @@
- (void)createAvailableFonts
{
NSMutableArray *unsortedFontsArray = [NSMutableArray array];
NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray array];
for (NSString *eachFontFamily in [UIFont familyNames]) {
for (NSString *eachFontName in [UIFont fontNamesForFamilyName:eachFontFamily]) {
[unsortedFontsArray addObject:eachFontName];
@@ -90,7 +90,7 @@
fontLabel = (UILabel*)view;
}
UIFont *font = [UIFont fontWithName:self.availableFonts[row] size:15.0];
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
NSDictionary<NSString *, id> *attributesDictionary = [NSDictionary<NSString *, id> dictionaryWithObject:font forKey:NSFontAttributeName];
NSAttributedString *attributesString = [[NSAttributedString alloc] initWithString:self.availableFonts[row] attributes:attributesDictionary];
fontLabel.attributedText = attributesString;
[fontLabel sizeToFit];
@@ -35,7 +35,7 @@
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
{
static NSArray *primitiveTypes = nil;
static NSArray<NSString *> *primitiveTypes = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
primitiveTypes = @[@(@encode(char)),
@@ -12,7 +12,7 @@
@interface FLEXArgumentInputStructView ()
@property (nonatomic, strong) NSArray *argumentInputViews;
@property (nonatomic, strong) NSArray<FLEXArgumentInputView *> *argumentInputViews;
@end
@@ -22,8 +22,8 @@
{
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
NSMutableArray *inputViews = [NSMutableArray array];
NSArray *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray array];
NSArray<NSString *> *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
[FLEXRuntimeUtility enumerateTypesInStructEncoding:typeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
@@ -179,9 +179,9 @@
return type && type[0] == '{';
}
+ (NSArray *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
+ (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
{
NSArray *customTitles = nil;
NSArray<NSString *> *customTitles = nil;
if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
customTitles = @[@"CGPoint origin", @"CGSize size"];
} else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
@@ -22,7 +22,7 @@
{
self = [super initWithFrame:CGRectZero];
if (self) {
self.typeEncoding = @(typeEncoding);
self.typeEncoding = typeEncoding != NULL ? @(typeEncoding) : nil;
}
return self;
}
+3 -1
View File
@@ -8,11 +8,13 @@
#import <UIKit/UIKit.h>
@class FLEXArgumentInputView;
@interface FLEXFieldEditorView : UIView
@property (nonatomic, copy) NSString *targetDescription;
@property (nonatomic, copy) NSString *fieldDescription;
@property (nonatomic, strong) NSArray *argumentInputViews;
@property (nonatomic, strong) NSArray<FLEXArgumentInputView *> *argumentInputViews;
@end
+1 -1
View File
@@ -103,7 +103,7 @@
}
}
- (void)setArgumentInputViews:(NSArray *)argumentInputViews
- (void)setArgumentInputViews:(NSArray<FLEXArgumentInputView *> *)argumentInputViews
{
if (![_argumentInputViews isEqual:argumentInputViews]) {
@@ -22,7 +22,11 @@
@property (nonatomic, strong, readonly) id target;
@property (nonatomic, strong, readonly) FLEXFieldEditorView *fieldEditorView;
@property (nonatomic, strong, readonly) UIBarButtonItem *setterButton;
- (void)actionButtonPressed:(id)sender;
- (NSString *)titleForActionButton;
/// Pushes an explorer view controller for the given object
/// or pops the current view controller.
- (void)exploreObjectOrPopViewController:(id)objectOrNil;
@end
@@ -10,8 +10,10 @@
#import "FLEXFieldEditorView.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXUtility.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXArgumentInputView.h"
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXObjectExplorerViewController.h"
@interface FLEXFieldEditorViewController () <UIScrollViewDelegate>
@@ -114,4 +116,16 @@
return @"Set";
}
- (void)exploreObjectOrPopViewController:(id)objectOrNil {
if (objectOrNil) {
// For non-nil (or void) return types, push an explorer view controller to display the object
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:objectOrNil];
[self.navigationController pushViewController:explorerViewController animated:YES];
} else {
// If we didn't get a returned object but the method call succeeded,
// pop this view controller off the stack to indicate that the call went through.
[self.navigationController popViewControllerAnimated:YES];
}
}
@end
@@ -6,10 +6,10 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
#import <objc/runtime.h>
@interface FLEXIvarEditorViewController : FLEXFieldEditorViewController
@interface FLEXIvarEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithTarget:(id)target ivar:(Ivar)ivar;
@@ -55,6 +55,17 @@
[FLEXRuntimeUtility setValue:self.firstInputView.inputValue forIvar:self.ivar onObject:self.target];
self.firstInputView.inputValue = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
// Pop view controller for consistency;
// property setters and method calls also pop on success.
[self.navigationController popViewControllerAnimated:YES];
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
[self exploreObjectOrPopViewController:returnedObject];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
@@ -9,14 +9,13 @@
#import "FLEXMethodCallingViewController.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXFieldEditorView.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXObjectExplorerViewController.h"
#import "FLEXArgumentInputView.h"
#import "FLEXArgumentInputViewFactory.h"
@interface FLEXMethodCallingViewController ()
@property (nonatomic, assign) Method method;
@property (nonatomic, assign) FLEXTypeEncoding *returnType;
@end
@@ -27,7 +26,8 @@
self = [super initWithTarget:target];
if (self) {
self.method = method;
self.title = [self isClassMethod] ? @"Class Method" : @"Method";
self.returnType = [FLEXRuntimeUtility returnTypeForMethod:method];
self.title = [self isClassMethod] ? @"Class Method" : @"Method";;
}
return self;
}
@@ -36,10 +36,14 @@
{
[super viewDidLoad];
self.fieldEditorView.fieldDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
NSString *returnType = @((const char *)self.returnType);
NSString *methodDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
NSString *format = @"Signature:\n%@\n\nReturn Type:\n%@";
NSString *info = [NSString stringWithFormat:format, methodDescription, returnType];
self.fieldEditorView.fieldDescription = info;
NSArray *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
NSMutableArray *argumentInputViews = [NSMutableArray array];
NSArray<NSString *> *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray array];
unsigned int argumentIndex = kFLEXNumberOfImplicitArgs;
for (NSString *methodComponent in methodComponents) {
char *argumentTypeEncoding = method_copyArgumentType(self.method, argumentIndex);
@@ -54,6 +58,12 @@
self.fieldEditorView.argumentInputViews = argumentInputViews;
}
- (void)dealloc
{
free(self.returnType);
self.returnType = NULL;
}
- (BOOL)isClassMethod
{
return self.target && self.target == [self.target class];
@@ -86,14 +96,8 @@
NSString *message = [error localizedDescription];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
} else if (returnedObject) {
// For non-nil (or void) return types, push an explorer view controller to display the returned object
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:returnedObject];
[self.navigationController pushViewController:explorerViewController animated:YES];
} else {
// If we didn't get a returned object but the method call succeeded,
// pop this view controller off the stack to indicate that the call went through.
[self.navigationController popViewControllerAnimated:YES];
[self exploreObjectOrPopViewController:returnedObject];
}
}
@@ -0,0 +1,18 @@
//
// FLEXMutableFieldEditorViewController.h
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
@interface FLEXMutableFieldEditorViewController : FLEXFieldEditorViewController
@property (nonatomic, strong, readonly) UIBarButtonItem *getterButton;
- (void)getterButtonPressed:(id)sender;
- (NSString *)titleForGetterButton;
@end
@@ -0,0 +1,36 @@
//
// FLEXMutableFieldEditorViewController.m
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXMutableFieldEditorViewController.h"
#import "FLEXFieldEditorView.h"
@interface FLEXMutableFieldEditorViewController ()
@property (nonatomic, strong, readwrite) UIBarButtonItem *getterButton;
@end
@implementation FLEXMutableFieldEditorViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.getterButton = [[UIBarButtonItem alloc] initWithTitle:[self titleForGetterButton] style:UIBarButtonItemStyleDone target:self action:@selector(getterButtonPressed:)];
self.navigationItem.rightBarButtonItems = @[self.setterButton, self.getterButton];
}
- (void)getterButtonPressed:(id)sender {
// Subclasses can override
[self.fieldEditorView endEditing:YES];
}
- (NSString *)titleForGetterButton {
return @"Get";
}
@end
@@ -6,10 +6,10 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
#import <objc/runtime.h>
@interface FLEXPropertyEditorViewController : FLEXFieldEditorViewController
@interface FLEXPropertyEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithTarget:(id)target property:(objc_property_t)property;
@@ -76,6 +76,13 @@
}
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
[self exploreObjectOrPopViewController:returnedObject];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
{
if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
@@ -17,6 +17,12 @@
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
- (BOOL)wantsWindowToBecomeKey;
/// @brief Used to present (or dismiss) a modal view controller ("tool"), typically triggered by pressing a button in the toolbar.
///
/// If a tool is already presented, this method simply dismisses it and calls the completion block.
/// If no tool is presented, @code future() @endcode is presented and the completion block is called.
- (void)toggleToolWithViewControllerProvider:(UIViewController *(^)(void))future completion:(void(^)(void))completion;
// Keyboard shortcut helpers
- (void)toggleSelectTool;
@@ -16,6 +16,8 @@
#import "FLEXObjectExplorerFactory.h"
#import "FLEXNetworkHistoryTableViewController.h"
static NSString *const kFLEXToolbarTopMarginDefaultsKey = @"com.flex.FLEXToolbar.topMargin";
typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
FLEXExplorerModeDefault,
FLEXExplorerModeSelect,
@@ -43,10 +45,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
/// Borders of all the visible views in the hierarchy at the selection point.
/// The keys are NSValues with the correponding view (nonretained).
@property (nonatomic, strong) NSDictionary *outlineViewsForVisibleViews;
@property (nonatomic, strong) NSDictionary<NSValue *, UIView *> *outlineViewsForVisibleViews;
/// The actual views at the selection point with the deepest view last.
@property (nonatomic, strong) NSArray *viewsAtTapPoint;
@property (nonatomic, strong) NSArray<UIView *> *viewsAtTapPoint;
/// The view that we're currently highlighting with an overlay and displaying details for.
@property (nonatomic, strong) UIView *selectedView;
@@ -66,7 +68,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
@property (nonatomic, assign) UIStatusBarStyle previousStatusBarStyle;
/// All views that we're KVOing. Used to help us clean up properly.
@property (nonatomic, strong) NSMutableSet *observedViews;
@property (nonatomic, strong) NSMutableSet<UIView *> *observedViews;
@end
@@ -94,10 +96,14 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// Toolbar
self.explorerToolbar = [[FLEXExplorerToolbar alloc] init];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:self.view.bounds.size];
// Start the toolbar off below any bars that may be at the top of the view.
CGFloat toolbarOriginY = 100.0;
self.explorerToolbar.frame = CGRectMake(0.0, toolbarOriginY, toolbarSize.width, toolbarSize.height);
id toolbarOriginYDefault = [[NSUserDefaults standardUserDefaults] objectForKey:kFLEXToolbarTopMarginDefaultsKey];
CGFloat toolbarOriginY = toolbarOriginYDefault ? [toolbarOriginYDefault doubleValue] : 100;
CGRect safeArea = [self viewSafeArea];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea))];
[self updateToolbarPositionWithUnconstrainedFrame:CGRectMake(CGRectGetMinX(safeArea), toolbarOriginY, toolbarSize.width, toolbarSize.height)];
self.explorerToolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
[self.view addSubview:self.explorerToolbar];
[self setupToolbarActions];
@@ -165,32 +171,30 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return shouldAutorotate;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
for (UIView *outlineView in [self.outlineViewsForVisibleViews allValues]) {
outlineView.hidden = YES;
}
self.selectedViewOverlay.hidden = YES;
}
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
for (UIView *outlineView in [self.outlineViewsForVisibleViews allValues]) {
outlineView.hidden = YES;
}
self.selectedViewOverlay.hidden = YES;
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
for (UIView *view in self.viewsAtTapPoint) {
NSValue *key = [NSValue valueWithNonretainedObject:view];
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.frame = [self frameInLocalCoordinatesForView:view];
if (self.currentMode == FLEXExplorerModeSelect) {
outlineView.hidden = NO;
}
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
for (UIView *view in self.viewsAtTapPoint) {
NSValue *key = [NSValue valueWithNonretainedObject:view];
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.frame = [self frameInLocalCoordinatesForView:view];
if (self.currentMode == FLEXExplorerModeSelect) {
outlineView.hidden = NO;
}
}
if (self.selectedView) {
self.selectedViewOverlay.frame = [self frameInLocalCoordinatesForView:self.selectedView];
self.selectedViewOverlay.hidden = NO;
}
if (self.selectedView) {
self.selectedViewOverlay.frame = [self frameInLocalCoordinatesForView:self.selectedView];
self.selectedViewOverlay.hidden = NO;
}
}];
}
#pragma mark - Setter Overrides
- (void)setSelectedView:(UIView *)selectedView
@@ -206,7 +210,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// Update the toolbar and selected overlay
self.explorerToolbar.selectedViewDescription = [FLEXUtility descriptionForView:selectedView includingFrame:YES];
self.explorerToolbar.selectedViewOverlayColor = [FLEXUtility consistentRandomColorForObject:selectedView];;
self.explorerToolbar.selectedViewOverlayColor = [FLEXUtility consistentRandomColorForObject:selectedView];
if (selectedView) {
if (!self.selectedViewOverlay) {
@@ -232,7 +236,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
}
- (void)setViewsAtTapPoint:(NSArray *)viewsAtTapPoint
- (void)setViewsAtTapPoint:(NSArray<UIView *> *)viewsAtTapPoint
{
if (![_viewsAtTapPoint isEqual:viewsAtTapPoint]) {
for (UIView *view in _viewsAtTapPoint) {
@@ -262,7 +266,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
case FLEXExplorerModeSelect:
// Make sure the outline views are unhidden in case we came from the move mode.
for (id key in self.outlineViewsForVisibleViews) {
for (NSValue *key in self.outlineViewsForVisibleViews) {
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.hidden = NO;
}
@@ -270,7 +274,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
case FLEXExplorerModeMove:
// Hide all the outline views to focus on the selected view, which is the only one that will move.
for (id key in self.outlineViewsForVisibleViews) {
for (NSValue *key in self.outlineViewsForVisibleViews) {
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.hidden = YES;
}
@@ -311,9 +315,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
[self.observedViews removeObject:view];
}
+ (NSArray *)viewKeyPathsToTrack
+ (NSArray<NSString *> *)viewKeyPathsToTrack
{
static NSArray *trackedViewKeyPaths = nil;
static NSArray<NSString *> *trackedViewKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *frameKeyPath = NSStringFromSelector(@selector(frame));
@@ -322,7 +326,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return trackedViewKeyPaths;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context
{
[self updateOverlayAndDescriptionForObjectIfNeeded:object];
}
@@ -376,10 +380,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
[self toggleViewsTool];
}
- (NSArray *)allViewsInHierarchy
- (NSArray<UIView *> *)allViewsInHierarchy
{
NSMutableArray *allViews = [NSMutableArray array];
NSArray *windows = [FLEXUtility allWindows];
NSMutableArray<UIView *> *allViews = [NSMutableArray array];
NSArray<UIWindow *> *windows = [FLEXUtility allWindows];
for (UIWindow *window in windows) {
if (window != self.view.window) {
[allViews addObject:window];
@@ -462,14 +466,24 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
CGRect newToolbarFrame = self.toolbarFrameBeforeDragging;
newToolbarFrame.origin.y += translation.y;
CGFloat maxY = CGRectGetMaxY(self.view.bounds) - newToolbarFrame.size.height;
if (newToolbarFrame.origin.y < 0.0) {
newToolbarFrame.origin.y = 0.0;
} else if (newToolbarFrame.origin.y > maxY) {
newToolbarFrame.origin.y = maxY;
[self updateToolbarPositionWithUnconstrainedFrame:newToolbarFrame];
}
- (void)updateToolbarPositionWithUnconstrainedFrame:(CGRect)unconstrainedFrame
{
CGRect safeArea = [self viewSafeArea];
// We only constrain the Y-axis because We want the toolbar to handle the X-axis safeArea layout by itself
CGFloat minY = CGRectGetMinY(safeArea);
CGFloat maxY = CGRectGetMaxY(safeArea) - unconstrainedFrame.size.height;
if (unconstrainedFrame.origin.y < minY) {
unconstrainedFrame.origin.y = minY;
} else if (unconstrainedFrame.origin.y > maxY) {
unconstrainedFrame.origin.y = maxY;
}
self.explorerToolbar.frame = newToolbarFrame;
self.explorerToolbar.frame = unconstrainedFrame;
[[NSUserDefaults standardUserDefaults] setDouble:unconstrainedFrame.origin.y forKey:kFLEXToolbarTopMarginDefaultsKey];
}
- (void)handleToolbarHintTapGesture:(UITapGestureRecognizer *)tapGR
@@ -526,8 +540,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// For outlined views and the selected view, only use visible views.
// Outlining hidden views adds clutter and makes the selection behavior confusing.
NSArray *visibleViewsAtTapPoint = [self viewsAtPoint:selectionPointInWindow skipHiddenViews:YES];
NSMutableDictionary *newOutlineViewsForVisibleViews = [NSMutableDictionary dictionary];
NSArray<UIView *> *visibleViewsAtTapPoint = [self viewsAtPoint:selectionPointInWindow skipHiddenViews:YES];
NSMutableDictionary<NSValue *, UIView *> *newOutlineViewsForVisibleViews = [NSMutableDictionary dictionary];
for (UIView *view in visibleViewsAtTapPoint) {
UIView *outlineView = [self outlineViewForView:view];
[self.view addSubview:outlineView];
@@ -555,16 +569,16 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)removeAndClearOutlineViews
{
for (id key in self.outlineViewsForVisibleViews) {
for (NSValue *key in self.outlineViewsForVisibleViews) {
UIView *outlineView = self.outlineViewsForVisibleViews[key];
[outlineView removeFromSuperview];
}
self.outlineViewsForVisibleViews = nil;
}
- (NSArray *)viewsAtPoint:(CGPoint)tapPointInWindow skipHiddenViews:(BOOL)skipHidden
- (NSArray<UIView *> *)viewsAtPoint:(CGPoint)tapPointInWindow skipHiddenViews:(BOOL)skipHidden
{
NSMutableArray *views = [NSMutableArray array];
NSMutableArray<UIView *> *views = [NSMutableArray array];
for (UIWindow *window in [FLEXUtility allWindows]) {
// Don't include the explorer's own window or subviews.
if (window != self.view.window && [window pointInside:tapPointInWindow withEvent:nil]) {
@@ -594,9 +608,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return [[self recursiveSubviewsAtPoint:tapPointInWindow inView:windowForSelection skipHiddenViews:YES] lastObject];
}
- (NSArray *)recursiveSubviewsAtPoint:(CGPoint)pointInView inView:(UIView *)view skipHiddenViews:(BOOL)skipHidden
- (NSArray<UIView *> *)recursiveSubviewsAtPoint:(CGPoint)pointInView inView:(UIView *)view skipHiddenViews:(BOOL)skipHidden
{
NSMutableArray *subviewsAtPoint = [NSMutableArray array];
NSMutableArray<UIView *> *subviewsAtPoint = [NSMutableArray array];
for (UIView *subview in view.subviews) {
BOOL isHidden = subview.hidden || subview.alpha < 0.01;
if (skipHidden && isHidden) {
@@ -618,9 +632,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return subviewsAtPoint;
}
- (NSArray *)allRecursiveSubviewsInView:(UIView *)view
- (NSArray<UIView *> *)allRecursiveSubviewsInView:(UIView *)view
{
NSMutableArray *subviews = [NSMutableArray array];
NSMutableArray<UIView *> *subviews = [NSMutableArray array];
for (UIView *subview in view.subviews) {
[subviews addObject:subview];
[subviews addObjectsFromArray:[self allRecursiveSubviewsInView:subview]];
@@ -628,9 +642,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return subviews;
}
- (NSDictionary *)hierarchyDepthsForViews:(NSArray *)views
- (NSDictionary<NSValue *, NSNumber *> *)hierarchyDepthsForViews:(NSArray<UIView *> *)views
{
NSMutableDictionary *hierarchyDepths = [NSMutableDictionary dictionary];
NSMutableDictionary<NSValue *, NSNumber *> *hierarchyDepths = [NSMutableDictionary dictionary];
for (UIView *view in views) {
NSInteger depth = 0;
UIView *tryView = view;
@@ -674,6 +688,33 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
#pragma mark - Safe Area Handling
- (CGRect)viewSafeArea
{
CGRect safeArea = self.view.bounds;
#if FLEX_AT_LEAST_IOS11_SDK
if (@available(iOS 11, *)) {
safeArea = UIEdgeInsetsInsetRect(self.view.bounds, self.view.safeAreaInsets);
}
#endif
return safeArea;
}
#if FLEX_AT_LEAST_IOS11_SDK
- (void)viewSafeAreaInsetsDidChange
{
if (@available(iOS 11, *)) {
[super viewSafeAreaInsetsDidChange];
}
CGRect safeArea = [self viewSafeArea];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea))];
[self updateToolbarPositionWithUnconstrainedFrame:CGRectMake(CGRectGetMinX(self.explorerToolbar.frame), CGRectGetMinY(self.explorerToolbar.frame), toolbarSize.width, toolbarSize.height)];
}
#endif
#pragma mark - Touch Handling
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates
@@ -712,7 +753,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
{
// Note that we need to wait until the view controller is dismissed to calculated the frame of the outline view.
// Otherwise the coordinate conversion doesn't give the correct result.
[self resignKeyAndDismissViewControllerAnimated:YES completion:^{
[self toggleViewsToolWithCompletion:^{
// If the selected view is outside of the tap point array (selected from "Full Hierarchy"),
// then clear out the tap point array and remove all the outline views.
if (![self.viewsAtTapPoint containsObject:selectedView]) {
@@ -792,6 +833,15 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return self.previousKeyWindow != nil;
}
- (void)toggleToolWithViewControllerProvider:(UIViewController *(^)(void))future completion:(void(^)(void))completion
{
if (self.presentedViewController) {
[self resignKeyAndDismissViewControllerAnimated:YES completion:completion];
} else {
[self makeKeyAndPresentViewController:future() animated:YES completion:completion];
}
}
#pragma mark - Keyboard Shortcut Helpers
- (void)toggleSelectTool
@@ -814,49 +864,32 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)toggleViewsTool
{
BOOL viewsModalShown = [[self presentedViewController] isKindOfClass:[UINavigationController class]];
viewsModalShown = viewsModalShown && [[[(UINavigationController *)[self presentedViewController] viewControllers] firstObject] isKindOfClass:[FLEXHierarchyTableViewController class]];
if (viewsModalShown) {
[self resignKeyAndDismissViewControllerAnimated:YES completion:nil];
} else {
void (^presentBlock)() = ^{
NSArray *allViews = [self allViewsInHierarchy];
NSDictionary *depthsForViews = [self hierarchyDepthsForViews:allViews];
FLEXHierarchyTableViewController *hierarchyTVC = [[FLEXHierarchyTableViewController alloc] initWithViews:allViews viewsAtTap:self.viewsAtTapPoint selectedView:self.selectedView depths:depthsForViews];
hierarchyTVC.delegate = self;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:hierarchyTVC];
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
};
if (self.presentedViewController) {
[self resignKeyAndDismissViewControllerAnimated:NO completion:presentBlock];
} else {
presentBlock();
[self toggleViewsToolWithCompletion:nil];
}
- (void)toggleViewsToolWithCompletion:(void(^)(void))completion
{
[self toggleToolWithViewControllerProvider:^UIViewController *{
NSArray<UIView *> *allViews = [self allViewsInHierarchy];
NSDictionary *depthsForViews = [self hierarchyDepthsForViews:allViews];
FLEXHierarchyTableViewController *hierarchyTVC = [[FLEXHierarchyTableViewController alloc] initWithViews:allViews viewsAtTap:self.viewsAtTapPoint selectedView:self.selectedView depths:depthsForViews];
hierarchyTVC.delegate = self;
return [[UINavigationController alloc] initWithRootViewController:hierarchyTVC];
} completion:^{
if (completion) {
completion();
}
}
}];
}
- (void)toggleMenuTool
{
BOOL menuModalShown = [[self presentedViewController] isKindOfClass:[UINavigationController class]];
menuModalShown = menuModalShown && [[[(UINavigationController *)[self presentedViewController] viewControllers] firstObject] isKindOfClass:[FLEXGlobalsTableViewController class]];
if (menuModalShown) {
[self resignKeyAndDismissViewControllerAnimated:YES completion:nil];
} else {
void (^presentBlock)() = ^{
FLEXGlobalsTableViewController *globalsViewController = [[FLEXGlobalsTableViewController alloc] init];
globalsViewController.delegate = self;
[FLEXGlobalsTableViewController setApplicationWindow:[[UIApplication sharedApplication] keyWindow]];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:globalsViewController];
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
};
if (self.presentedViewController) {
[self resignKeyAndDismissViewControllerAnimated:NO completion:presentBlock];
} else {
presentBlock();
}
}
[self toggleToolWithViewControllerProvider:^UIViewController *{
FLEXGlobalsTableViewController *globalsViewController = [[FLEXGlobalsTableViewController alloc] init];
globalsViewController.delegate = self;
[FLEXGlobalsTableViewController setApplicationWindow:[[UIApplication sharedApplication] keyWindow]];
return [[UINavigationController alloc] initWithRootViewController:globalsViewController];
} completion:nil];
}
- (void)handleDownArrowKeyPressed
+21
View File
@@ -9,6 +9,8 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef UIViewController *(^FLEXCustomContentViewerFuture)(NSData *data);
@interface FLEXManager : NSObject
+ (instancetype)sharedManager;
@@ -30,6 +32,12 @@
/// The response cache uses an NSCache, so it may purge prior to hitting the limit when the app is under memory pressure.
@property (nonatomic, assign) NSUInteger networkResponseCacheByteLimit;
/// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
/// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
/// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
@property (nonatomic, copy) NSArray<NSString *> *networkRequestHostBlacklist;
#pragma mark - Keyboard Shortcuts
/// Simulator keyboard shortcuts are enabled by default.
@@ -49,6 +57,10 @@
#pragma mark - Extensions
/// Default database password is @c nil by default.
/// Set this to the password you want the databases to open with.
@property (copy, nonatomic) NSString *defaultSqliteDatabasePassword;
/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
/// @param entryName The string to be displayed in the cell.
/// @param objectFutureBlock When you tap on the row, information about the object returned by this block will be displayed.
@@ -67,4 +79,13 @@
- (void)registerGlobalEntryWithName:(NSString *)entryName
viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock;
/// Sets custom viewer for specific content type.
/// @param contentType Mime type like application/json
/// @param viewControllerFutureBlock Viewer (view controller) creation block
/// @note This method must be called from the main thread.
/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
- (void)setCustomViewerForContentType:(NSString *)contentType
viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock;
@end
@@ -19,8 +19,8 @@
- (instancetype)initWithPath:(NSString*)path;
- (BOOL)open;
- (NSArray *)queryAllTables;
- (NSArray *)queryAllColumnsWithTableName:(NSString *)tableName;
- (NSArray *)queryAllDataWithTableName:(NSString *)tableName;
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables;
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName;
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName;
@end
@@ -19,7 +19,7 @@
@property (nonatomic, strong) UITableView *contentTableView;
@property (nonatomic, strong) UIView *leftHeader;
@property (nonatomic, strong) NSDictionary *sortStatusDict;
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *sortStatusDict;
@property (nonatomic, strong) NSArray *rowData;
@end
@@ -135,7 +135,7 @@ static const CGFloat kColumnMargin = 1;
- (void)loadHeaderData
{
NSArray *subviews = self.headerScrollView.subviews;
NSArray<UIView *> *subviews = self.headerScrollView.subviews;
for (UIView *subview in subviews) {
[subview removeFromSuperview];
@@ -18,7 +18,7 @@
@interface FLEXRealmDatabaseManager ()
@property (nonatomic, copy) NSString *path;
@property (nonatomic, strong) id realm;
@property (nonatomic, strong) RLMRealm * realm;
@end
@@ -57,9 +57,9 @@
return (error == nil);
}
- (NSArray *)queryAllTables
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables
{
NSMutableArray *allTables = [NSMutableArray array];
NSMutableArray<NSDictionary<NSString *, id> *> *allTables = [NSMutableArray array];
RLMSchema *schema = [self.realm schema];
for (RLMObjectSchema *objectSchema in schema.objectSchema) {
@@ -67,21 +67,21 @@
continue;
}
NSDictionary *dictionary = @{@"name":objectSchema.className};
NSDictionary<NSString *, id> *dictionary = @{@"name":objectSchema.className};
[allTables addObject:dictionary];
}
return allTables;
}
- (NSArray *)queryAllColumnsWithTableName:(NSString *)tableName
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName
{
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
if (objectSchema == nil) {
return nil;
}
NSMutableArray *columnNames = [NSMutableArray array];
NSMutableArray<NSString *> *columnNames = [NSMutableArray array];
for (RLMProperty *property in objectSchema.properties) {
[columnNames addObject:property.name];
}
@@ -89,7 +89,7 @@
return columnNames;
}
- (NSArray *)queryAllDataWithTableName:(NSString *)tableName
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName
{
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
RLMResults *results = [self.realm allObjects:tableName];
@@ -97,9 +97,9 @@
return nil;
}
NSMutableArray *allDataEntries = [NSMutableArray array];
NSMutableArray<NSDictionary<NSString *, id> *> *allDataEntries = [NSMutableArray array];
for (RLMObject *result in results) {
NSMutableDictionary *entry = [NSMutableDictionary dictionary];
NSMutableDictionary<NSString *, id> *entry = [NSMutableDictionary dictionary];
for (RLMProperty *property in objectSchema.properties) {
id value = [result valueForKey:property.name];
entry[property.name] = (value) ? (value) : [NSNull null];
@@ -22,13 +22,13 @@
@end
@interface RLMSchema : NSObject
@property (nonatomic, readonly) NSArray *objectSchema;
@property (nonatomic, readonly) NSArray<RLMObjectSchema *> *objectSchema;
- (RLMObjectSchema *)schemaForClassName:(NSString *)className;
@end
@interface RLMObjectSchema : NSObject
@property (nonatomic, readonly) NSString *className;
@property (nonatomic, readonly) NSArray *properties;
@property (nonatomic, readonly) NSArray<RLMProperty *> *properties;
@end
@interface RLMProperty : NSString
@@ -43,4 +43,4 @@
@end
#endif
#endif
@@ -7,6 +7,7 @@
//
#import "FLEXSQLiteDatabaseManager.h"
#import "FLEXManager.h"
#import <sqlite3.h>
@@ -33,6 +34,17 @@ static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master W
return YES;
}
int err = sqlite3_open([_databasePath UTF8String], &_db);
#if SQLITE_HAS_CODEC
NSString *defaultSqliteDatabasePassword = [FLEXManager sharedManager].defaultSqliteDatabasePassword;
if (defaultSqliteDatabasePassword) {
const char *key = defaultSqliteDatabasePassword.UTF8String;
sqlite3_key(_db, key, (int)strlen(key));
}
#endif
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
@@ -74,23 +86,24 @@ static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master W
}
- (NSArray *)queryAllTables
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables
{
return [self executeQuery:QUERY_TABLENAMES_SQL];
}
- (NSArray *)queryAllColumnsWithTableName:(NSString *)tableName
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName
{
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
NSArray *resultArray = [self executeQuery:sql];
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in resultArray) {
[array addObject:dict[@"name"]];
NSArray<NSDictionary<NSString *, id> *> *resultArray = [self executeQuery:sql];
NSMutableArray<NSString *> *array = [NSMutableArray array];
for (NSDictionary<NSString *, id> *dict in resultArray) {
NSString *columnName = (NSString *)dict[@"name"] ?: @"";
[array addObject:columnName];
}
return array;
}
- (NSArray *)queryAllDataWithTableName:(NSString *)tableName
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName
{
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@",tableName];
return [self executeQuery:sql];
@@ -99,16 +112,16 @@ static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master W
#pragma mark -
#pragma mark - Private
- (NSArray *)executeQuery:(NSString *)sql
- (NSArray<NSDictionary<NSString *, id> *> *)executeQuery:(NSString *)sql
{
[self open];
NSMutableArray *resultArray = [NSMutableArray array];
NSMutableArray<NSDictionary<NSString *, id> *> *resultArray = [NSMutableArray array];
sqlite3_stmt *pstmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pstmt, 0) == SQLITE_OK) {
while (sqlite3_step(pstmt) == SQLITE_ROW) {
NSUInteger num_cols = (NSUInteger)sqlite3_data_count(pstmt);
if (num_cols > 0) {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
NSMutableDictionary<NSString *, id> *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
int columnCount = sqlite3_column_count(pstmt);
@@ -18,9 +18,9 @@
@interface FLEXTableContentCell : UITableViewCell
@property (nonatomic, strong)NSArray *labels;
@property (nonatomic, strong) NSArray<UILabel *> *labels;
@property (nonatomic, weak) id<FLEXTableContentCellDelegate>delegate;
@property (nonatomic, weak) id<FLEXTableContentCellDelegate> delegate;
+ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number;
@@ -21,7 +21,7 @@
FLEXTableContentCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[FLEXTableContentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
NSMutableArray *labels = [NSMutableArray array];
NSMutableArray<UILabel *> *labels = [NSMutableArray array];
for (int i = 0; i < number ; i++) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = [UIColor whiteColor];
@@ -10,7 +10,7 @@
@interface FLEXTableContentViewController : UIViewController
@property (nonatomic, strong) NSArray *columnsArray;
@property (nonatomic, strong) NSArray *contentsArray;
@property (nonatomic, strong) NSArray<NSString *> *columnsArray;
@property (nonatomic, strong) NSArray<NSDictionary<NSString *, id> *> *contentsArray;
@end
+31 -32
View File
@@ -13,45 +13,39 @@
@interface FLEXTableContentViewController ()<FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate>
@property (nonatomic, strong)FLEXMultiColumnTableView *multiColumView;
@property (nonatomic, strong) FLEXMultiColumnTableView *multiColumView;
@end
@implementation FLEXTableContentViewController
- (instancetype)init
{
self = [super init];
if (self) {
CGRect rectStatus = [UIApplication sharedApplication].statusBarFrame;
CGFloat y = 64;
if (rectStatus.size.height == 0) {
y = 32;
}
_multiColumView = [[FLEXMultiColumnTableView alloc] initWithFrame:
CGRectMake(0, y, self.view.frame.size.width, self.view.frame.size.height - y)];
_multiColumView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_multiColumView.backgroundColor = [UIColor whiteColor];
_multiColumView.dataSource = self;
_multiColumView.delegate = self;
self.automaticallyAdjustsScrollViewInsets = NO;
[self.view addSubview:_multiColumView];
}
return self;
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
[self.view addSubview:self.multiColumView];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.multiColumView reloadData];
}
#pragma mark -
#pragma mark init SubView
- (FLEXMultiColumnTableView *)multiColumView {
if (!_multiColumView) {
_multiColumView = [[FLEXMultiColumnTableView alloc] initWithFrame:
CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
_multiColumView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
_multiColumView.backgroundColor = [UIColor whiteColor];
_multiColumView.dataSource = self;
_multiColumView.delegate = self;
}
return _multiColumView;
}
#pragma mark MultiColumnTableView DataSource
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView
@@ -78,7 +72,7 @@
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row
{
if (self.contentsArray.count > row) {
NSDictionary *dic = self.contentsArray[row];
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
if (self.contentsArray.count > column) {
return [NSString stringWithFormat:@"%@",[dic objectForKey:self.columnsArray[column]]];
}
@@ -90,11 +84,11 @@
{
NSMutableArray *result = [NSMutableArray array];
if (self.contentsArray.count > row) {
NSDictionary *dic = self.contentsArray[row];
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
for (int i = 0; i < self.columnsArray.count; i ++) {
[result addObject:dic[self.columnsArray[i]]];
}
return result;
return result;
}
return nil;
}
@@ -119,7 +113,7 @@
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView
{
NSString *str = [NSString stringWithFormat:@"%lu",(unsigned long)self.contentsArray.count];
NSDictionary *attrs = @{@"NSFontAttributeName":[UIFont systemFontOfSize:17.0]};
NSDictionary<NSString *, id> *attrs = @{@"NSFontAttributeName":[UIFont systemFontOfSize:17.0]};
CGSize size = [str boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attrs context:nil].size;
@@ -139,7 +133,7 @@
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType
{
NSArray *sortContentData = [self.contentsArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSArray<NSDictionary<NSString *, id> *> *sortContentData = [self.contentsArray sortedArrayUsingComparator:^NSComparisonResult(NSDictionary<NSString *, id> * obj1, NSDictionary<NSString *, id> * obj2) {
if ([obj1 objectForKey:text] == [NSNull null]) {
return NSOrderedAscending;
@@ -147,6 +141,11 @@
if ([obj2 objectForKey:text] == [NSNull null]) {
return NSOrderedDescending;
}
if (![[obj1 objectForKey:text] respondsToSelector:@selector(compare:)] && ![[obj2 objectForKey:text] respondsToSelector:@selector(compare:)]) {
return NSOrderedSame;
}
NSComparisonResult result = [[obj1 objectForKey:text] compare:[obj2 objectForKey:text]];
return result;
@@ -171,10 +170,10 @@
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
_multiColumView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
self->_multiColumView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
}
else {
_multiColumView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
self->_multiColumView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
}
[self.view setNeedsLayout];
} completion:nil];
@@ -20,10 +20,10 @@
NSString *_databasePath;
}
@property (nonatomic, strong) NSArray *tables;
@property (nonatomic, strong) NSArray<NSString *> *tables;
+ (NSArray *)supportedSQLiteExtensions;
+ (NSArray *)supportedRealmExtensions;
+ (NSArray<NSString *> *)supportedSQLiteExtensions;
+ (NSArray<NSString *> *)supportedRealmExtensions;
@end
@@ -45,12 +45,12 @@
{
NSString *pathExtension = path.pathExtension.lowercaseString;
NSArray *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
NSArray<NSString *> *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
if ([sqliteExtensions indexOfObject:pathExtension] != NSNotFound) {
return [[FLEXSQLiteDatabaseManager alloc] initWithPath:path];
}
NSArray *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
NSArray<NSString *> *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
if (realmExtensions != nil && [realmExtensions indexOfObject:pathExtension] != NSNotFound) {
return [[FLEXRealmDatabaseManager alloc] initWithPath:path];
}
@@ -60,10 +60,11 @@
- (void)getAllTables
{
NSArray *resultArray = [_dbm queryAllTables];
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in resultArray) {
[array addObject:dict[@"name"]];
NSArray<NSDictionary<NSString *, id> *> *resultArray = [_dbm queryAllTables];
NSMutableArray<NSString *> *array = [NSMutableArray array];
for (NSDictionary<NSString *, id> *dict in resultArray) {
NSString *columnName = (NSString *)dict[@"name"] ?: @"";
[array addObject:columnName];
}
self.tables = array;
}
@@ -106,12 +107,12 @@
{
extension = extension.lowercaseString;
NSArray *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
NSArray<NSString *> *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
if (sqliteExtensions.count > 0 && [sqliteExtensions indexOfObject:extension] != NSNotFound) {
return YES;
}
NSArray *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
NSArray<NSString *> *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
if (realmExtensions.count > 0 && [realmExtensions indexOfObject:extension] != NSNotFound) {
return YES;
}
@@ -119,12 +120,12 @@
return NO;
}
+ (NSArray *)supportedSQLiteExtensions
+ (NSArray<NSString *> *)supportedSQLiteExtensions
{
return @[@"db", @"sqlite", @"sqlite3"];
}
+ (NSArray *)supportedRealmExtensions
+ (NSArray<NSString *> *)supportedRealmExtensions
{
if (NSClassFromString(@"RLMRealm") == nil) {
return nil;
@@ -14,8 +14,8 @@
@interface FLEXClassesTableViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSArray *classNames;
@property (nonatomic, strong) NSArray *filteredClassNames;
@property (nonatomic, strong) NSArray<NSString *> *classNames;
@property (nonatomic, strong) NSArray<NSString *> *filteredClassNames;
@property (nonatomic, strong) UISearchBar *searchBar;
@end
@@ -42,7 +42,7 @@
}
}
- (void)setClassNames:(NSArray *)classNames
- (void)setClassNames:(NSArray<NSString *> *)classNames
{
_classNames = classNames;
self.filteredClassNames = classNames;
@@ -53,7 +53,7 @@
unsigned int classNamesCount = 0;
const char **classNames = objc_copyClassNamesForImage([self.binaryImageName UTF8String], &classNamesCount);
if (classNames) {
NSMutableArray *classNameStrings = [NSMutableArray array];
NSMutableArray<NSString *> *classNameStrings = [NSMutableArray array];
for (unsigned int i = 0; i < classNamesCount; i++) {
const char *className = classNames[i];
NSString *classNameString = [NSString stringWithUTF8String:className];
@@ -12,7 +12,7 @@
@interface FLEXCookiesTableViewController ()
@property (nonatomic, strong) NSArray *cookies;
@property (nonatomic, strong) NSArray<NSHTTPCookie *> *cookies;
@end
@@ -25,7 +25,7 @@
self.title = @"Cookies";
NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
_cookies =[[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies sortedArrayUsingDescriptors:@[nameSortDescriptor]];
_cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies sortedArrayUsingDescriptors:@[nameSortDescriptor]];
}
return self;
@@ -20,6 +20,6 @@
@protocol FLEXFileBrowserSearchOperationDelegate <NSObject>
- (void)fileBrowserSearchOperationResult:(NSArray *)searchResult size:(uint64_t)size;
- (void)fileBrowserSearchOperationResult:(NSArray<NSString *> *)searchResult size:(uint64_t)size;
@end
@@ -38,7 +38,7 @@
- (uint64_t)totalSizeAtPath:(NSString *)path
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
uint64_t totalSize = [attributes fileSize];
for (NSString *fileName in [fileManager enumeratorAtPath:path]) {
@@ -65,16 +65,16 @@
- (void)main
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSMutableArray *searchPaths = [NSMutableArray array];
NSMutableDictionary *sizeMapping = [NSMutableDictionary dictionary];
NSMutableArray<NSString *> *searchPaths = [NSMutableArray array];
NSMutableDictionary<NSString *, NSNumber *> *sizeMapping = [NSMutableDictionary dictionary];
uint64_t totalSize = 0;
NSMutableArray *stack = [NSMutableArray array];
NSMutableArray<NSString *> *stack = [NSMutableArray array];
[stack flex_push:self.path];
//recursive found all match searchString paths, and precomputing there size
while ([stack count]) {
NSString *currentPath = [stack flex_pop];
NSArray *directoryPath = [fileManager contentsOfDirectoryAtPath:currentPath error:nil];
NSArray<NSString *> *directoryPath = [fileManager contentsOfDirectoryAtPath:currentPath error:nil];
for (NSString *subPath in directoryPath) {
NSString *fullPath = [currentPath stringByAppendingPathComponent:subPath];
@@ -99,7 +99,7 @@
}
//sort
NSArray *sortedArray = [searchPaths sortedArrayUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
NSArray<NSString *> *sortedArray = [searchPaths sortedArrayUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
uint64_t pathSize1 = [sizeMapping[path1] unsignedLongLongValue];
uint64_t pathSize2 = [sizeMapping[path2] unsignedLongLongValue];
if (pathSize1 < pathSize2) {
@@ -19,8 +19,8 @@
@interface FLEXFileBrowserTableViewController () <FLEXFileBrowserFileOperationControllerDelegate, FLEXFileBrowserSearchOperationDelegate, UISearchResultsUpdating, UISearchControllerDelegate>
@property (nonatomic, copy) NSString *path;
@property (nonatomic, copy) NSArray *childPaths;
@property (nonatomic, strong) NSArray *searchPaths;
@property (nonatomic, copy) NSArray<NSString *> *childPaths;
@property (nonatomic, strong) NSArray<NSString *> *searchPaths;
@property (nonatomic, strong) NSNumber *recursiveSize;
@property (nonatomic, strong) NSNumber *searchPathsSize;
@property (nonatomic, strong) UISearchController *searchController;
@@ -44,30 +44,30 @@
self.path = path;
self.title = [path lastPathComponent];
self.operationQueue = [NSOperationQueue new];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.delegate = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.tableView.tableHeaderView = self.searchController.searchBar;
//computing path size
FLEXFileBrowserTableViewController *__weak weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
uint64_t totalSize = [attributes fileSize];
for (NSString *fileName in [fileManager enumeratorAtPath:path]) {
attributes = [fileManager attributesOfItemAtPath:[path stringByAppendingPathComponent:fileName] error:NULL];
totalSize += [attributes fileSize];
// Bail if the interested view controller has gone away.
if (!weakSelf) {
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
FLEXFileBrowserTableViewController *__strong strongSelf = weakSelf;
strongSelf.recursiveSize = @(totalSize);
@@ -86,14 +86,11 @@
{
[super viewDidLoad];
UIMenuItem *renameMenuItem = [[UIMenuItem alloc] initWithTitle:@"Rename" action:@selector(fileBrowserRename:)];
UIMenuItem *deleteMenuItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(fileBrowserDelete:)];
[UIMenuController sharedMenuController].menuItems = @[renameMenuItem, deleteMenuItem];
}
#pragma mark - FLEXFileBrowserSearchOperationDelegate
- (void)fileBrowserSearchOperationResult:(NSArray *)searchResult size:(uint64_t)size
- (void)fileBrowserSearchOperationResult:(NSArray<NSString *> *)searchResult size:(uint64_t)size
{
self.searchPaths = searchResult;
self.searchPathsSize = @(size);
@@ -133,22 +130,22 @@
{
BOOL isSearchActive = self.searchController.isActive;
NSNumber *currentSize = isSearchActive ? self.searchPathsSize : self.recursiveSize;
NSArray *currentPaths = isSearchActive ? self.searchPaths : self.childPaths;
NSArray<NSString *> *currentPaths = isSearchActive ? self.searchPaths : self.childPaths;
NSString *sizeString = nil;
if (!currentSize) {
sizeString = @"Computing size…";
} else {
sizeString = [NSByteCountFormatter stringFromByteCount:[currentSize longLongValue] countStyle:NSByteCountFormatterCountStyleFile];
}
return [NSString stringWithFormat:@"%lu files (%@)", (unsigned long)[currentPaths count], sizeString];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *fullPath = [self filePathAtIndexPath:indexPath];
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
NSDictionary<NSString *, id> *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
BOOL isDirectory = [[attributes fileType] isEqual:NSFileTypeDirectory];
NSString *subtitle = nil;
if (isDirectory) {
@@ -158,15 +155,15 @@
NSString *sizeString = [NSByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleFile];
subtitle = [NSString stringWithFormat:@"%@ - %@", sizeString, [attributes fileModificationDate]];
}
static NSString *textCellIdentifier = @"textCell";
static NSString *imageCellIdentifier = @"imageCell";
UITableViewCell *cell = nil;
// Separate image and text only cells because otherwise the separator lines get out-of-whack on image cells reused with text only.
BOOL showImagePreview = [FLEXUtility isImagePathExtension:[fullPath pathExtension]];
NSString *cellIdentifier = showImagePreview ? imageCellIdentifier : textCellIdentifier;
if (!cell) {
cell = [[FLEXFileBrowserTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
@@ -177,12 +174,12 @@
NSString *cellTitle = [fullPath lastPathComponent];
cell.textLabel.text = cellTitle;
cell.detailTextLabel.text = subtitle;
if (showImagePreview) {
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.image = [UIImage imageWithContentsOfFile:fullPath];
}
return cell;
}
@@ -191,7 +188,7 @@
NSString *fullPath = [self filePathAtIndexPath:indexPath];
NSString *subpath = [fullPath lastPathComponent];
NSString *pathExtension = [subpath pathExtension];
BOOL isDirectory = NO;
BOOL stillExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDirectory];
if (stillExists) {
@@ -212,7 +209,7 @@
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
prettyString = [[NSPropertyListSerialization propertyListWithData:fileData options:0 format:NULL error:NULL] description];
}
if ([prettyString length] > 0) {
drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
} else if ([FLEXWebViewController supportsPathExtension:pathExtension]) {
@@ -227,7 +224,7 @@
}
}
}
if (drillInViewController) {
drillInViewController.title = [subpath lastPathComponent];
[self.navigationController pushViewController:drillInViewController animated:YES];
@@ -243,12 +240,25 @@
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIMenuItem *renameMenuItem = [[UIMenuItem alloc] initWithTitle:@"Rename" action:@selector(fileBrowserRename:)];
UIMenuItem *deleteMenuItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(fileBrowserDelete:)];
NSMutableArray *menus = [NSMutableArray arrayWithObjects:renameMenuItem, deleteMenuItem, nil];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
NSError *error = nil;
NSDictionary *attributes = [NSFileManager.defaultManager attributesOfItemAtPath:fullPath error:&error];
if (error == nil && [attributes fileType] != NSFileTypeDirectory) {
UIMenuItem *shareMenuItem = [[UIMenuItem alloc] initWithTitle:@"Share" action:@selector(fileBrowserShare:)];
[menus addObject:shareMenuItem];
}
[UIMenuController sharedMenuController].menuItems = menus;
return YES;
}
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
return action == @selector(fileBrowserDelete:) || action == @selector(fileBrowserRename:);
return action == @selector(fileBrowserDelete:) || action == @selector(fileBrowserRename:) || action == @selector(fileBrowserShare:);
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
@@ -288,12 +298,21 @@
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
self.fileOperationController = [[FLEXFileBrowserFileDeleteOperationController alloc] initWithPath:fullPath];
self.fileOperationController.delegate = self;
[self.fileOperationController show];
}
- (void)fileBrowserShare:(UITableViewCell *)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[fullPath] applicationActivities:nil];
[self presentViewController:activityViewController animated:true completion:nil];
}
- (void)reloadDisplayedPaths
{
if (self.searchController.isActive) {
@@ -306,8 +325,8 @@
- (void)reloadChildPaths
{
NSMutableArray *childPaths = [NSMutableArray array];
NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:NULL];
NSMutableArray<NSString *> *childPaths = [NSMutableArray array];
NSArray<NSString *> *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:NULL];
for (NSString *subpath in subpaths) {
[childPaths addObject:[self.path stringByAppendingPathComponent:subpath]];
}
@@ -348,4 +367,10 @@
[[UIApplication sharedApplication] sendAction:_cmd to:target from:self forEvent:nil];
}
- (void)fileBrowserShare:(UIMenuController *)sender
{
id target = [self.nextResponder targetForAction:_cmd withSender:sender];
[[UIApplication sharedApplication] sendAction:_cmd to:target from:self forEvent:nil];
}
@end
@@ -33,6 +33,7 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
FLEXGlobalsRowAppDelegate,
FLEXGlobalsRowRootViewController,
FLEXGlobalsRowUserDefaults,
FLEXGlobalsRowMainBundle,
FLEXGlobalsRowApplication,
FLEXGlobalsRowKeyWindow,
FLEXGlobalsRowMainScreen,
@@ -42,17 +43,15 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
@interface FLEXGlobalsTableViewController ()
/// [FLEXGlobalsTableViewControllerEntry *]
@property (nonatomic, readonly, copy) NSArray *entries;
@property (nonatomic, readonly, copy) NSArray<FLEXGlobalsTableViewControllerEntry *> *entries;
@end
@implementation FLEXGlobalsTableViewController
/// [FLEXGlobalsTableViewControllerEntry *]
+ (NSArray *)defaultGlobalEntries
+ (NSArray<FLEXGlobalsTableViewControllerEntry *> *)defaultGlobalEntries
{
NSMutableArray *defaultGlobalEntries = [NSMutableArray array];
NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *defaultGlobalEntries = [NSMutableArray array];
for (FLEXGlobalsRow defaultRowIndex = 0; defaultRowIndex < FLEXGlobalsRowCount; defaultRowIndex++) {
FLEXGlobalsTableViewControllerEntryNameFuture titleFuture = nil;
@@ -125,6 +124,16 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
};
break;
case FLEXGlobalsRowMainBundle:
titleFuture = ^NSString *{
return @"📦 +[NSBundle mainBundle]";
};
viewControllerFuture = ^UIViewController *{
NSBundle *mainBundle = [NSBundle mainBundle];
return [FLEXObjectExplorerFactory explorerViewControllerForObject:mainBundle];
};
break;
case FLEXGlobalsRowApplication:
titleFuture = ^NSString *{
return @"💾 +[UIApplication sharedApplication]";
@@ -12,18 +12,49 @@
#import "FLEXRuntimeUtility.h"
#import "FLEXUtility.h"
#import "FLEXHeapEnumerator.h"
#import "FLEXObjectRef.h"
#import <malloc/malloc.h>
@interface FLEXInstancesTableViewController ()
@property (nonatomic, strong) NSArray *instances;
@property (nonatomic, strong) NSArray *fieldNames;
/// Array of [[section], [section], ...]
/// where [section] is [["row title", instance], ["row title", instance], ...]
@property (nonatomic) NSArray<FLEXObjectRef *> *instances;
@property (nonatomic) NSArray<NSArray<FLEXObjectRef*>*> *sections;
@property (nonatomic) NSArray<NSString *> *sectionTitles;
@property (nonatomic) NSArray<NSPredicate *> *predicates;
@property (nonatomic, readonly) NSInteger maxSections;
@end
@implementation FLEXInstancesTableViewController
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references {
return [self initWithReferences:references predicates:nil sectionTitles:nil];
}
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references
predicates:(NSArray<NSPredicate *> *)predicates
sectionTitles:(NSArray<NSString *> *)sectionTitles {
NSParameterAssert(predicates.count == sectionTitles.count);
self = [super init];
if (self) {
self.instances = references;
self.predicates = predicates;
self.sectionTitles = sectionTitles;
if (predicates.count) {
[self buildSections];
} else {
self.sections = @[references];
}
}
return self;
}
+ (instancetype)instancesTableViewControllerForClassName:(NSString *)className
{
const char *classNameCString = [className UTF8String];
@@ -38,17 +69,21 @@
}
}
}];
FLEXInstancesTableViewController *instancesViewController = [[self alloc] init];
instancesViewController.instances = instances;
instancesViewController.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)[instances count]];
return instancesViewController;
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:references];
viewController.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)[instances count]];
return viewController;
}
+ (instancetype)instancesTableViewControllerForInstancesReferencingObject:(id)object
{
NSMutableArray *instances = [NSMutableArray array];
NSMutableArray *fieldNames = [NSMutableArray array];
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray array];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Skip Swift objects
if ([actualClass isKindOfClass:NSClassFromString(@"SwiftObject")]) {
return;
}
// Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
// Once we find a match, record it and move on to the next object. There's no reason to find multiple matches within the same object.
Class tryClass = actualClass;
@@ -62,8 +97,7 @@
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
[instances addObject:tryObject];
[fieldNames addObject:@(ivar_getName(ivar))];
[instances addObject:[FLEXObjectRef referencing:tryObject ivar:@(ivar_getName(ivar))]];
return;
}
}
@@ -71,11 +105,85 @@
tryClass = class_getSuperclass(tryClass);
}
}];
FLEXInstancesTableViewController *instancesViewController = [[self alloc] init];
instancesViewController.instances = instances;
instancesViewController.fieldNames = fieldNames;
instancesViewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
return instancesViewController;
NSArray<NSPredicate *> *predicates = [self defaultPredicates];
NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:instances
predicates:predicates
sectionTitles:sectionTitles];
viewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
return viewController;
}
+ (NSPredicate *)defaultPredicateForSection:(NSInteger)section
{
// These are the types of references that we typically don't care about.
// We want this list of "object-ivar pairs" split into two sections.
BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
NSString *row = ref.reference;
return [row isEqualToString:@"__NSObserver object"] ||
[row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
};
/// These are common AutoLayout related references we also rarely care about.
BOOL(^isConstraintRelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
static NSSet *ignored = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ignored = [NSSet setWithArray:@[
@"NSLayoutConstraint _container",
@"NSContentSizeLayoutConstraint _container",
@"NSAutoresizingMaskLayoutConstraint _container",
@"MASViewConstraint _installedView",
@"MASLayoutConstraint _container",
@"MASViewAttribute _view"
]];
});
NSString *row = ref.reference;
return ([row hasPrefix:@"NSLayout"] && [row hasSuffix:@" _referenceItem"]) ||
([row hasPrefix:@"NSIS"] && [row hasSuffix:@" _delegate"]) ||
([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
[ignored containsObject:row];
};
BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
};
switch (section) {
case 0: return [NSPredicate predicateWithBlock:isEssential];
case 1: return [NSPredicate predicateWithBlock:isConstraintRelated];
case 2: return [NSPredicate predicateWithBlock:isObserver];
default: return nil;
}
}
+ (NSArray<NSPredicate *> *)defaultPredicates {
return @[[self defaultPredicateForSection:0],
[self defaultPredicateForSection:1],
[self defaultPredicateForSection:2]];
}
+ (NSArray<NSString *> *)defaultSectionTitles {
return @[@"", @"AutoLayout", @"Trivial"];
}
- (void)buildSections
{
NSInteger maxSections = self.maxSections;
NSMutableArray *sections = [NSMutableArray array];
for (NSInteger i = 0; i < maxSections; i++) {
NSPredicate *predicate = self.predicates[i];
[sections addObject:[self.instances filteredArrayUsingPredicate:predicate]];
}
self.sections = sections;
}
- (NSInteger)maxSections {
return self.predicates.count ?: 1;
}
@@ -83,12 +191,12 @@
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
return self.maxSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.instances count];
return self.sections[section].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
@@ -103,26 +211,33 @@
cell.detailTextLabel.font = cellFont;
cell.detailTextLabel.textColor = [UIColor grayColor];
}
id instance = self.instances[indexPath.row];
NSString *title = nil;
if ((NSInteger)[self.fieldNames count] > indexPath.row) {
title = [NSString stringWithFormat:@"%@ %@", NSStringFromClass(object_getClass(instance)), self.fieldNames[indexPath.row]];
} else {
title = [NSString stringWithFormat:@"%@ %p", NSStringFromClass(object_getClass(instance)), instance];
}
cell.textLabel.text = title;
cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:instance];
FLEXObjectRef *row = self.sections[indexPath.section][indexPath.row];
cell.textLabel.text = row.reference;
cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:row.object];
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (self.sectionTitles.count) {
// Return nil instead of empty strings
NSString *title = self.sectionTitles[section];
if (title.length) {
return title;
}
}
return nil;
}
#pragma mark - Table View Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
id instance = self.instances[indexPath.row];
id instance = self.instances[indexPath.row].object;
FLEXObjectExplorerViewController *drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:instance];
[self.navigationController pushViewController:drillInViewController animated:YES];
}
@@ -14,8 +14,8 @@
@interface FLEXLibrariesTableViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSArray *imageNames;
@property (nonatomic, strong) NSArray *filteredImageNames;
@property (nonatomic, strong) NSArray<NSString *> *imageNames;
@property (nonatomic, strong) NSArray<NSString *> *filteredImageNames;
@property (nonatomic, strong) UISearchBar *searchBar;
@property (nonatomic, strong) Class foundClass;
@@ -52,7 +52,7 @@
unsigned int imageNamesCount = 0;
const char **imageNames = objc_copyImageNames(&imageNamesCount);
if (imageNames) {
NSMutableArray *imageNameStrings = [NSMutableArray array];
NSMutableArray<NSString *> *imageNameStrings = [NSMutableArray array];
NSString *appImageName = [FLEXUtility applicationImageName];
for (unsigned int i = 0; i < imageNamesCount; i++) {
const char *imageName = imageNames[i];
@@ -76,14 +76,14 @@
- (NSString *)shortNameForImageName:(NSString *)imageName
{
NSArray *components = [imageName componentsSeparatedByString:@"/"];
NSArray<NSString *> *components = [imageName componentsSeparatedByString:@"/"];
if (components.count >= 2) {
return [NSString stringWithFormat:@"%@/%@", components[components.count - 2], components[components.count - 1]];
}
return imageName.lastPathComponent;
}
- (void)setImageNames:(NSArray *)imageNames
- (void)setImageNames:(NSArray<NSString *> *)imageNames
{
if (![_imageNames isEqual:imageNames]) {
_imageNames = imageNames;
@@ -97,7 +97,7 @@
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if ([searchText length] > 0) {
NSPredicate *searchPreidcate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
NSPredicate *searchPreidcate = [NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary<NSString *, id> *bindings) {
BOOL matches = NO;
NSString *shortName = [self shortNameForImageName:evaluatedObject];
if ([shortName rangeOfString:searchText options:NSCaseInsensitiveSearch].length > 0) {
@@ -14,12 +14,14 @@
static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
@interface FLEXLiveObjectsTableViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSDictionary *instanceCountsForClassNames;
@property (nonatomic, readonly) NSArray *allClassNames;
@property (nonatomic, strong) NSArray *filteredClassNames;
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *instanceCountsForClassNames;
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
@property (nonatomic, readonly) NSArray<NSString *> *allClassNames;
@property (nonatomic, strong) NSArray<NSString *> *filteredClassNames;
@property (nonatomic, strong) UISearchBar *searchBar;
@end
@@ -34,7 +36,7 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
self.searchBar.delegate = self;
self.searchBar.showsScopeBar = YES;
self.searchBar.scopeButtonTitles = @[@"Sort Alphabetically", @"Sort by Count"];
self.searchBar.scopeButtonTitles = @[@"Sort Alphabetically", @"Sort by Count", @"Sort by Size"];
[self.searchBar sizeToFit];
self.tableView.tableHeaderView = self.searchBar;
@@ -44,7 +46,7 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
[self reloadTableData];
}
- (NSArray *)allClassNames
- (NSArray<NSString *> *)allClassNames
{
return [self.instanceCountsForClassNames allKeys];
}
@@ -72,18 +74,21 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
}];
// Convert our CF primitive dictionary into a nicer mapping of class name strings to counts that we will use as the table's model.
NSMutableDictionary *mutableCountsForClassNames = [NSMutableDictionary dictionary];
NSMutableDictionary<NSString *, NSNumber *> *mutableCountsForClassNames = [NSMutableDictionary dictionary];
NSMutableDictionary<NSString *, NSNumber *> *mutableSizesForClassNames = [NSMutableDictionary dictionary];
for (unsigned int i = 0; i < classCount; i++) {
Class class = classes[i];
NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)(class));
NSString *className = @(class_getName(class));
if (instanceCount > 0) {
NSString *className = @(class_getName(class));
[mutableCountsForClassNames setObject:@(instanceCount) forKey:className];
}
[mutableSizesForClassNames setObject:@(class_getInstanceSize(class)) forKey:className];
}
free(classes);
self.instanceCountsForClassNames = mutableCountsForClassNames;
self.instanceSizesForClassNames = mutableSizesForClassNames;
[self updateTableDataForSearchFilter];
}
@@ -99,19 +104,27 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
NSString *title = @"Live Objects";
NSUInteger totalCount = 0;
NSUInteger totalSize = 0;
for (NSString *className in self.allClassNames) {
totalCount += [self.instanceCountsForClassNames[className] unsignedIntegerValue];
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
totalCount += count;
totalSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
}
NSUInteger filteredCount = 0;
NSUInteger filteredSize = 0;
for (NSString *className in self.filteredClassNames) {
filteredCount += [self.instanceCountsForClassNames[className] unsignedIntegerValue];
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
filteredCount += count;
filteredSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
}
if (filteredCount == totalCount) {
// Unfiltered
title = [title stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
title = [title stringByAppendingFormat:@" (%lu, %@)", (unsigned long)totalCount,
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
} else {
title = [title stringByAppendingFormat:@" (filtered, %lu)", (unsigned long)filteredCount];
title = [title stringByAppendingFormat:@" (filtered, %lu, %@)", (unsigned long)filteredCount,
[NSByteCountFormatter stringFromByteCount:filteredSize countStyle:NSByteCountFormatterCountStyleFile]];
}
self.title = title;
@@ -159,6 +172,15 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
// Reversed for descending counts.
return [count2 compare:count1];
}];
} else if (self.searchBar.selectedScopeButtonIndex == kFLEXLiveObjectsSortBySizeIndex) {
self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
NSNumber *count1 = self.instanceCountsForClassNames[className1];
NSNumber *count2 = self.instanceCountsForClassNames[className2];
NSNumber *size1 = self.instanceSizesForClassNames[className1];
NSNumber *size2 = self.instanceSizesForClassNames[className2];
// Reversed for descending sizes.
return [@(count2.integerValue * size2.integerValue) compare:@(count1.integerValue * size1.integerValue)];
}];
}
[self updateTitle];
@@ -190,7 +212,10 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
NSString *className = self.filteredClassNames[indexPath.row];
NSNumber *count = self.instanceCountsForClassNames[className];
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld)", className, (long)[count integerValue]];
NSNumber *size = self.instanceSizesForClassNames[className];
unsigned long totalSize = count.unsignedIntegerValue * size.unsignedIntegerValue;
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld, %@)", className, (long)[count integerValue],
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
return cell;
}
@@ -0,0 +1,22 @@
//
// FLEXObjectRef.h
// FLEX
//
// Created by Tanner Bennett on 7/24/18.
// Copyright (c) 2018 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface FLEXObjectRef : NSObject
+ (instancetype)referencing:(id)object;
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects;
/// For example, "NSString 0x1d4085d0" or "NSLayoutConstraint _object"
@property (nonatomic, readonly) NSString *reference;
@property (nonatomic, readonly) id object;
@end
@@ -0,0 +1,47 @@
//
// FLEXObjectRef.m
// FLEX
//
// Created by Tanner Bennett on 7/24/18.
// Copyright (c) 2018 Flipboard. All rights reserved.
//
#import "FLEXObjectRef.h"
#import <objc/runtime.h>
@implementation FLEXObjectRef
+ (instancetype)referencing:(id)object {
return [[self alloc] initWithObject:object ivarName:nil];
}
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects {
NSMutableArray<FLEXObjectRef *> *refs = [NSMutableArray array];
for (id obj in objects) {
[refs addObject:[self referencing:obj]];
}
return refs;
}
- (id)initWithObject:(id)object ivarName:(NSString *)ivar {
self = [super init];
if (self) {
_object = object;
NSString *class = NSStringFromClass(object_getClass(object));
if (ivar) {
_reference = [NSString stringWithFormat:@"%@ %@", class, ivar];
} else {
_reference = [NSString stringWithFormat:@"%@ %p", class, object];
}
}
return self;
}
@end
@@ -102,23 +102,23 @@
+ (BOOL)supportsPathExtension:(NSString *)extension
{
BOOL supported = NO;
NSSet *supportedExtensions = [self webViewSupportedPathExtensions];
NSSet<NSString *> *supportedExtensions = [self webViewSupportedPathExtensions];
if ([supportedExtensions containsObject:[extension lowercaseString]]) {
supported = YES;
}
return supported;
}
+ (NSSet *)webViewSupportedPathExtensions
+ (NSSet<NSString *> *)webViewSupportedPathExtensions
{
static NSSet *pathExtenstions = nil;
static NSSet<NSString *> *pathExtenstions = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Note that this is not exhaustive, but all these extensions should work well in the web view.
// See https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/CreatingContentforSafarioniPhone/CreatingContentforSafarioniPhone.html#//apple_ref/doc/uid/TP40006482-SW7
pathExtenstions = [NSSet setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
pathExtenstions = [NSSet<NSString *> setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
});
return pathExtenstions;
@@ -10,7 +10,7 @@
@implementation FLEXSystemLogMessage
+(instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
{
FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
@@ -74,12 +74,12 @@ static const UIEdgeInsets kFLEXLogMessageCellInsets = {10.0, 10.0, 10.0, 10.0};
+ (NSAttributedString *)attributedTextForLogMessage:(FLEXSystemLogMessage *)logMessage highlightedText:(NSString *)highlightedText
{
NSString *text = [self displayedTextForLogMessage:logMessage];
NSDictionary *attributes = @{ NSFontAttributeName : [UIFont fontWithName:@"CourierNewPSMT" size:12.0] };
NSDictionary<NSString *, id> *attributes = @{ NSFontAttributeName : [UIFont fontWithName:@"CourierNewPSMT" size:12.0] };
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:attributes];
if ([highlightedText length] > 0) {
NSMutableAttributedString *mutableAttributedText = [attributedText mutableCopy];
NSMutableDictionary *highlightAttributes = [@{ NSBackgroundColorAttributeName : [UIColor yellowColor] } mutableCopy];
NSMutableDictionary<NSString *, id> *highlightAttributes = [@{ NSBackgroundColorAttributeName : [UIColor yellowColor] } mutableCopy];
[highlightAttributes addEntriesFromDictionary:attributes];
NSRange remainingSearchRange = NSMakeRange(0, text.length);
@@ -15,9 +15,10 @@
@interface FLEXSystemLogTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
@property (nonatomic, strong) UISearchController *searchController;
@property (nonatomic, copy) NSArray *logMessages;
@property (nonatomic, copy) NSArray *filteredLogMessages;
@property (nonatomic, readonly) NSMutableArray<FLEXSystemLogMessage *> *logMessages;
@property (nonatomic, copy) NSArray<FLEXSystemLogMessage *> *filteredLogMessages;
@property (nonatomic, strong) NSTimer *logUpdateTimer;
@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
@end
@@ -27,6 +28,9 @@
{
[super viewDidLoad];
_logMessages = [NSMutableArray array];
_logMessageIdentifiers = [NSMutableIndexSet indexSet];
[self.tableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.title = @"Loading...";
@@ -65,10 +69,18 @@
- (void)updateLogMessages
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *logMessages = [[self class] allLogMessagesForCurrentProcess];
NSArray<FLEXSystemLogMessage *> *newMessages = [self newLogMessagesForCurrentProcess];
if (!newMessages.count) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.title = @"System Log";
self.logMessages = logMessages;
[self.logMessages addObjectsFromArray:newMessages];
for (FLEXSystemLogMessage *message in newMessages) {
[self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
}
// "Follow" the log as new messages stream in if we were previously near the bottom.
BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
@@ -154,7 +166,7 @@
{
NSString *searchString = searchController.searchBar.text;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary *bindings) {
NSArray<FLEXSystemLogMessage *> *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary<NSString *, id> *bindings) {
NSString *displayedText = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage];
return [displayedText rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
}]];
@@ -169,18 +181,51 @@
#pragma mark - Log Message Fetching
+ (NSArray *)allLogMessagesForCurrentProcess
- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
{
asl_object_t query = asl_new(ASL_TYPE_QUERY);
if (!self.logMessages.count) {
return [[self class] allLogMessagesForCurrentProcess];
}
// Filter for messages from the current process. Note that this appears to happen by default on device, but is required in the simulator.
NSString *pidString = [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]];
asl_set_query(query, ASL_KEY_PID, [pidString UTF8String], ASL_QUERY_OP_EQUAL);
aslresponse response = asl_search(NULL, query);
aslresponse response = [FLEXSystemLogTableViewController ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray *logMessages = [NSMutableArray array];
NSMutableArray<FLEXSystemLogMessage *> *newMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
NSUInteger messageID = (NSUInteger)atoll(asl_get(aslMessage, ASL_KEY_MSG_ID));
if (![self.logMessageIdentifiers containsIndex:messageID]) {
[newMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
}
asl_release(response);
return newMessages;
}
+ (aslresponse)ASLMessageListForCurrentProcess
{
static NSString *pidString = nil;
if (!pidString) {
pidString = @([[NSProcessInfo processInfo] processIdentifier]).stringValue;
}
// Create system log query object.
asl_object_t query = asl_new(ASL_TYPE_QUERY);
// Filter for messages from the current process.
// Note that this appears to happen by default on device, but is required in the simulator.
asl_set_query(query, ASL_KEY_PID, pidString.UTF8String, ASL_QUERY_OP_EQUAL);
return asl_search(NULL, query);
}
+ (NSArray<FLEXSystemLogMessage *> *)allLogMessagesForCurrentProcess
{
aslresponse response = [self ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray<FLEXSystemLogMessage *> *logMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
+5 -1
View File
@@ -8,9 +8,13 @@
#import "FLEXManager.h"
@class FLEXGlobalsTableViewControllerEntry;
@interface FLEXManager ()
/// An array of FLEXGlobalsTableViewControllerEntry objects that have been registered by the user.
@property (nonatomic, readonly, strong) NSArray *userGlobalEntries;
@property (nonatomic, readonly, strong) NSArray<FLEXGlobalsTableViewControllerEntry *> *userGlobalEntries;
@property (nonatomic, readonly, strong) NSDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
@end
+24 -3
View File
@@ -24,7 +24,8 @@
@property (nonatomic, strong) FLEXWindow *explorerWindow;
@property (nonatomic, strong) FLEXExplorerViewController *explorerViewController;
@property (nonatomic, readonly, strong) NSMutableArray *userGlobalEntries;
@property (nonatomic, readonly, strong) NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *userGlobalEntries;
@property (nonatomic, readonly, strong) NSMutableDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
@end
@@ -44,7 +45,8 @@
{
self = [super init];
if (self) {
_userGlobalEntries = [[NSMutableArray alloc] init];
_userGlobalEntries = [NSMutableArray array];
_customContentTypeViewers = [NSMutableDictionary dictionary];
}
return self;
}
@@ -115,6 +117,16 @@
[[FLEXNetworkRecorder defaultRecorder] setResponseCacheByteLimit:networkResponseCacheByteLimit];
}
- (void)setNetworkRequestHostBlacklist:(NSArray<NSString *> *)networkRequestHostBlacklist
{
[FLEXNetworkRecorder defaultRecorder].hostBlacklist = networkRequestHostBlacklist;
}
- (NSArray<NSString *> *)hostBlacklist
{
return [FLEXNetworkRecorder defaultRecorder].hostBlacklist;
}
#pragma mark - FLEXWindowEventDelegate
- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow
@@ -278,6 +290,15 @@
[self.userGlobalEntries addObject:entry];
}
- (void)setCustomViewerForContentType:(NSString *)contentType viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock
{
NSParameterAssert(contentType.length);
NSParameterAssert(viewControllerFutureBlock);
NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
self.customContentTypeViewers[contentType.lowercaseString] = viewControllerFutureBlock;
}
- (void)tryScrollDown
{
UIScrollView *firstScrollView = [self firstScrollView];
@@ -302,7 +323,7 @@
- (UIScrollView *)firstScrollView
{
NSMutableArray *views = [[[[UIApplication sharedApplication] keyWindow] subviews] mutableCopy];
NSMutableArray<UIView *> *views = [[[[UIApplication sharedApplication] keyWindow] subviews] mutableCopy];
UIScrollView *scrollView = nil;
while ([views count] > 0) {
UIView *view = [views firstObject];
+1 -1
View File
@@ -18,7 +18,7 @@
[curlCommandString appendFormat:@"-H \'%@: %@\' ", key, val];
}];
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
if (cookies) {
[curlCommandString appendFormat:@"-H \'Cookie:"];
for (NSHTTPCookie *cookie in cookies) {
@@ -17,9 +17,9 @@
@interface FLEXNetworkHistoryTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
/// Backing model
@property (nonatomic, copy) NSArray *networkTransactions;
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *networkTransactions;
@property (nonatomic, assign) long long bytesReceived;
@property (nonatomic, copy) NSArray *filteredNetworkTransactions;
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions;
@property (nonatomic, assign) long long filteredBytesReceived;
@property (nonatomic, assign) BOOL rowInsertInProgress;
@@ -41,6 +41,10 @@
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNetworkObserverEnabledStateChangedNotification:) name:kFLEXNetworkObserverEnabledStateChangedNotification object:nil];
self.title = @"📡 Network";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Settings" style:UIBarButtonItemStylePlain target:self action:@selector(settingsButtonTapped:)];
// Needed to avoid search bar showing over detail pages pushed on the nav stack
// see http://asciiwwdc.com/2014/sessions/228
self.definesPresentationContext = YES;
}
return self;
}
@@ -86,7 +90,7 @@
self.networkTransactions = [[FLEXNetworkRecorder defaultRecorder] networkTransactions];
}
- (void)setNetworkTransactions:(NSArray *)networkTransactions
- (void)setNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions
{
if (![_networkTransactions isEqual:networkTransactions]) {
_networkTransactions = networkTransactions;
@@ -105,7 +109,7 @@
[self updateFirstSectionHeader];
}
- (void)setFilteredNetworkTransactions:(NSArray *)filteredNetworkTransactions
- (void)setFilteredNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)filteredNetworkTransactions
{
if (![_filteredNetworkTransactions isEqual:filteredNetworkTransactions]) {
_filteredNetworkTransactions = filteredNetworkTransactions;
@@ -192,7 +196,7 @@
[self tryUpdateTransactions];
}];
NSMutableArray *indexPathsToReload = [NSMutableArray array];
NSMutableArray<NSIndexPath *> *indexPathsToReload = [NSMutableArray array];
for (NSInteger row = 0; row < addedRowCount; row++) {
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]];
}
@@ -328,7 +332,7 @@
{
NSString *searchString = self.searchController.searchBar.text;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredNetworkTransactions = [self.networkTransactions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXNetworkTransaction *transaction, NSDictionary *bindings) {
NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions = [self.networkTransactions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXNetworkTransaction *transaction, NSDictionary<NSString *, id> *bindings) {
return [[transaction.request.URL absoluteString] rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
}]];
dispatch_async(dispatch_get_main_queue(), ^{
+4 -1
View File
@@ -27,10 +27,13 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
/// If NO, the recorder not cache will not cache response for content types with an "image", "video", or "audio" prefix.
@property (nonatomic, assign) BOOL shouldCacheMediaResponses;
@property (nonatomic, copy) NSArray<NSString *> *hostBlacklist;
// Accessing recorded network activity
/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first.
- (NSArray *)networkTransactions;
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions;
/// The full response data IFF it hasn't been purged due to memory pressure.
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction;
+13 -7
View File
@@ -22,8 +22,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
@interface FLEXNetworkRecorder ()
@property (nonatomic, strong) NSCache *responseCache;
@property (nonatomic, strong) NSMutableArray *orderedTransactions;
@property (nonatomic, strong) NSMutableDictionary *networkTransactionsForRequestIdentifiers;
@property (nonatomic, strong) NSMutableArray<FLEXNetworkTransaction *> *orderedTransactions;
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *networkTransactionsForRequestIdentifiers;
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@@ -74,9 +74,9 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
[[NSUserDefaults standardUserDefaults] setObject:@(responseCacheByteLimit) forKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey];
}
- (NSArray *)networkTransactions
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions
{
__block NSArray *transactions = nil;
__block NSArray<FLEXNetworkTransaction *> *transactions = nil;
dispatch_sync(self.queue, ^{
transactions = [self.orderedTransactions copy];
});
@@ -104,6 +104,12 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
for (NSString *host in self.hostBlacklist) {
if ([request.URL.host hasSuffix:host]) {
return;
}
}
NSDate *startDate = [NSDate date];
if (redirectResponse) {
@@ -169,7 +175,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
BOOL shouldCache = [responseBody length] > 0;
if (!self.shouldCacheMediaResponses) {
NSArray *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ];
NSArray<NSString *> *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ];
for (NSString *ignoredPrefix in ignoredMIMETypePrefixes) {
shouldCache = shouldCache && ![transaction.response.MIMEType hasPrefix:ignoredPrefix];
}
@@ -246,7 +252,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)postNewTransactionNotificationWithTransaction:(FLEXNetworkTransaction *)transaction
{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
NSDictionary<NSString *, id> *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkRecorderNewTransactionNotification object:self userInfo:userInfo];
});
}
@@ -254,7 +260,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)postUpdateNotificationForTransaction:(FLEXNetworkTransaction *)transaction
{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
NSDictionary<NSString *, id> *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkRecorderTransactionUpdatedNotification object:self userInfo:userInfo];
});
}
@@ -13,7 +13,7 @@
@interface FLEXNetworkSettingsTableViewController () <UIActionSheetDelegate>
@property (nonatomic, copy) NSArray *cells;
@property (nonatomic, copy) NSArray<UITableViewCell *> *cells;
@property (nonatomic, strong) UITableViewCell *cacheLimitCell;
@@ -34,7 +34,7 @@
{
[super viewDidLoad];
NSMutableArray *mutableCells = [NSMutableArray array];
NSMutableArray<UITableViewCell *> *mutableCells = [NSMutableArray array];
UITableViewCell *networkDebuggingCell = [self switchCellWithTitle:@"Network Debugging" toggleAction:@selector(networkDebuggingToggled:) isOn:[FLEXNetworkObserver isEnabled]];
[mutableCells addObject:networkDebuggingCell];
@@ -136,7 +136,7 @@
if (isDestructive) {
actionButton.tintColor = [UIColor redColor];
}
actionButton.titleLabel.font = [[self class] cellTitleFont];;
actionButton.titleLabel.font = [[self class] cellTitleFont];
[actionButton addTarget:self action:@selector(clearRequestsTapped:) forControlEvents:UIControlEventTouchUpInside];
[buttonCell.contentView addSubview:actionButton];
@@ -14,17 +14,7 @@
#import "FLEXImagePreviewViewController.h"
#import "FLEXMultilineTableViewCell.h"
#import "FLEXUtility.h"
@interface FLEXNetworkDetailSection : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray *rows;
@end
@implementation FLEXNetworkDetailSection
@end
#import "FLEXManager+Private.h"
typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@@ -40,9 +30,20 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@end
@interface FLEXNetworkDetailSection : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
@end
@implementation FLEXNetworkDetailSection
@end
@interface FLEXNetworkTransactionDetailTableViewController ()
@property (nonatomic, copy) NSArray *sections;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
@end
@@ -75,7 +76,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
}
}
- (void)setSections:(NSArray *)sections
- (void)setSections:(NSArray<FLEXNetworkDetailSection *> *)sections
{
if (![_sections isEqual:sections]) {
_sections = [sections copy];
@@ -85,7 +86,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
- (void)rebuildTableSections
{
NSMutableArray *sections = [NSMutableArray array];
NSMutableArray<FLEXNetworkDetailSection *> *sections = [NSMutableArray array];
FLEXNetworkDetailSection *generalSection = [[self class] generalSectionForTransaction:self.transaction];
if ([generalSection.rows count] > 0) {
@@ -210,10 +211,10 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row
{
NSDictionary *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
NSDictionary *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
NSForegroundColorAttributeName : [UIColor blackColor] };
NSDictionary<NSString *, id> *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
NSDictionary<NSString *, id> *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
NSForegroundColorAttributeName : [UIColor blackColor] };
NSString *title = [NSString stringWithFormat:@"%@: ", row.title];
NSString *detailText = row.detailText ?: @"";
@@ -228,7 +229,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction
{
NSMutableArray *rows = [NSMutableArray array];
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray array];
FLEXNetworkDetailRow *requestURLRow = [[FLEXNetworkDetailRow alloc] init];
requestURLRow.title = @"Request URL";
@@ -390,7 +391,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction
{
NSDictionary *queryDictionary = [FLEXUtility dictionaryFromQuery:transaction.request.URL.query];
NSDictionary<NSString *, id> *queryDictionary = [FLEXUtility dictionaryFromQuery:transaction.request.URL.query];
FLEXNetworkDetailSection *querySection = [[FLEXNetworkDetailSection alloc] init];
querySection.title = @"Query Parameters";
querySection.rows = [self networkDetailRowsFromDictionary:queryDictionary];
@@ -409,12 +410,12 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return responseHeadersSection;
}
+ (NSArray *)networkDetailRowsFromDictionary:(NSDictionary *)dictionary
+ (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromDictionary:(NSDictionary<NSString *, id> *)dictionary
{
NSMutableArray *rows = [NSMutableArray arrayWithCapacity:[dictionary count]];
NSArray *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray arrayWithCapacity:[dictionary count]];
NSArray<NSString *> *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
for (NSString *key in sortedKeys) {
NSString *value = dictionary[key];
id value = dictionary[key];
FLEXNetworkDetailRow *row = [[FLEXNetworkDetailRow alloc] init];
row.title = key;
row.detailText = [value description];
@@ -425,6 +426,16 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (UIViewController *)detailViewControllerForMIMEType:(NSString *)mimeType data:(NSData *)data
{
FLEXCustomContentViewerFuture makeCustomViewer = [FLEXManager sharedManager].customContentTypeViewers[mimeType.lowercaseString];
if (makeCustomViewer) {
UIViewController *viewer = makeCustomViewer(data);
if (viewer) {
return viewer;
}
}
// FIXME (RKO): Don't rely on UTF8 string encoding
UIViewController *detailViewController = nil;
if ([FLEXUtility isValidJSONData:data]) {
@@ -79,7 +79,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
self.nameLabel.text = [self nameLabelText];
CGSize nameLabelPreferredSize = [self.nameLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
self.nameLabel.frame = CGRectMake(textOriginX, kVerticalPadding, availableTextWidth, nameLabelPreferredSize.height);
self.nameLabel.textColor = self.transaction.error ? [UIColor redColor] : [UIColor blackColor];
self.nameLabel.textColor = (self.transaction.error || [FLEXUtility isErrorStatusCodeFromURLResponse:self.transaction.response]) ? [UIColor redColor] : [UIColor blackColor];
self.pathLabel.text = [self pathLabelText];
CGSize pathLabelPreferredSize = [self.pathLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
@@ -111,7 +111,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
- (NSString *)pathLabelText
{
NSURL *url = self.transaction.request.URL;
NSMutableArray *mutablePathComponents = [[url pathComponents] mutableCopy];
NSMutableArray<NSString *> *mutablePathComponents = [[url pathComponents] mutableCopy];
if ([mutablePathComponents count] > 0) {
[mutablePathComponents removeLastObject];
}
@@ -124,7 +124,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
- (NSString *)transactionDetailsLabelText
{
NSMutableArray *detailComponents = [NSMutableArray array];
NSMutableArray<NSString *> *detailComponents = [NSMutableArray array];
NSString *timestamp = [[self class] timestampStringFromRequestDate:self.transaction.startTime];
if ([timestamp length] > 0) {
@@ -14,7 +14,7 @@
#import <Foundation/Foundation.h>
extern NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
FOUNDATION_EXTERN NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
/// This class swizzles NSURLConnection and NSURLSession delegate methods to observe events in the URL loading system.
/// High level network events are sent to the default FLEXNetworkRecorder instance which maintains the request history and caches response bodies.
@@ -68,7 +68,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
@interface FLEXNetworkObserver ()
@property (nonatomic, strong) NSMutableDictionary *requestStatesForRequestIDs;
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLEXInternalRequestState *> *requestStatesForRequestIDs;
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@@ -172,7 +172,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
@selector(URLSession:dataTask:didReceiveData:),
@selector(URLSession:dataTask:didReceiveResponse:completionHandler:),
@selector(URLSession:task:didCompleteWithError:),
@selector(URLSession:dataTask:didBecomeDownloadTask:delegate:),
@selector(URLSession:dataTask:didBecomeDownloadTask:),
@selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:),
@selector(URLSession:downloadTask:didFinishDownloadingToURL:)
};
@@ -318,7 +318,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
if ([FLEXNetworkObserver isEnabled]) {
NSString *requestID = [self nextRequestID];
[[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
[[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
NSURLConnectionAsyncCompletion completionWrapper = ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
[[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
@@ -357,7 +357,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
if ([FLEXNetworkObserver isEnabled]) {
NSString *requestID = [self nextRequestID];
[[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
[[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
NSError *temporaryError = nil;
NSURLResponse *temporaryResponse = nil;
@@ -420,7 +420,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
// with nil completion block.
if ([FLEXNetworkObserver isEnabled] && completion) {
NSString *requestID = [self nextRequestID];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completionWrapper);
[self setRequestID:requestID forConnectionOrTask:task];
@@ -462,9 +462,9 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
NSURLSessionUploadTask *(^asyncUploadTaskSwizzleBlock)(Class, NSURLRequest *, id, NSURLSessionAsyncCompletion) = ^NSURLSessionUploadTask *(Class slf, NSURLRequest *request, id argument, NSURLSessionAsyncCompletion completion) {
NSURLSessionUploadTask *task = nil;
if ([FLEXNetworkObserver isEnabled]) {
if ([FLEXNetworkObserver isEnabled] && completion) {
NSString *requestID = [self nextRequestID];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completionWrapper);
[self setRequestID:requestID forConnectionOrTask:task];
@@ -479,7 +479,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
});
}
+ (NSString *)mechansimFromClassMethod:(SEL)selector onClass:(Class)class
+ (NSString *)mechanismFromClassMethod:(SEL)selector onClass:(Class)class
{
return [NSString stringWithFormat:@"+[%@ %@]", NSStringFromClass(class), NSStringFromSelector(selector)];
}
@@ -674,7 +674,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
[[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf];
} originalImplementationBlock:^{
((id(*)(id, SEL, id, id, id, id, void(^)()))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler);
((id(*)(id, SEL, id, id, id, id, void(^)(NSURLRequest *)))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler);
}];
};
@@ -755,7 +755,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
[[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf];
} originalImplementationBlock:^{
((void(*)(id, SEL, id, id, id, void(^)()))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler);
((void(*)(id, SEL, id, id, id, void(^)(NSURLSessionResponseDisposition)))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler);
}];
};
@@ -944,7 +944,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
NSMutableData *dataAccumulator = nil;
if (response.expectedContentLength < 0) {
dataAccumulator = [[NSMutableData alloc] init];
} else {
} else if (response.expectedContentLength < 52428800) {
dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength];
}
requestState.dataAccumulator = dataAccumulator;
@@ -995,7 +995,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
{
[self performBlock:^{
// Mimic the behavior of NSURLSession which is to create an error on cancellation.
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" };
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" };
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
[self connection:connection didFailWithError:error delegate:nil];
}];
@@ -35,11 +35,11 @@ typedef NS_ENUM(NSUInteger, FLEXClassExplorerRow) {
#pragma mark - Superclass Overrides
- (NSArray *)possibleExplorerSections
- (NSArray<NSNumber *> *)possibleExplorerSections
{
// Move class methods to between our custom section and the properties section since
// we are more interested in the class sections than in the instance level sections.
NSMutableArray *mutableSections = [[super possibleExplorerSections] mutableCopy];
NSMutableArray<NSNumber *> *mutableSections = [[super possibleExplorerSections] mutableCopy];
[mutableSections removeObject:@(FLEXObjectExplorerSectionClassMethods)];
[mutableSections insertObject:@(FLEXObjectExplorerSectionClassMethods) atIndex:[mutableSections indexOfObject:@(FLEXObjectExplorerSectionProperties)]];
return mutableSections;
@@ -0,0 +1,13 @@
//
// FLEXColorExplorerViewController.h
// Flipboard
//
// Created by Tanner on 10/18/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXObjectExplorerViewController.h"
@interface FLEXColorExplorerViewController : FLEXObjectExplorerViewController
@end
@@ -0,0 +1,49 @@
//
// FLEXColorExplorerViewController.m
// Flipboard
//
// Created by Tanner on 10/18/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXColorExplorerViewController.h"
@interface FLEXColorExplorerViewController ()
@end
@implementation FLEXColorExplorerViewController
- (BOOL)shouldShowDescription
{
return NO;
}
- (NSString *)customSectionTitle
{
return @"Color";
}
- (NSArray *)customSectionRowCookies
{
return @[@0];
}
- (UIView *)customViewForRowCookie:(id)rowCookie
{
CGFloat width = [UIScreen mainScreen].bounds.size.width;
UIView *square = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 44)];
square.backgroundColor = (UIColor *)self.object;
return square;
}
//- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
// if (indexPath.section == 0 && indexPath.row == 0) {
// cell.contentView.backgroundColor = (UIColor *)self.object;
// }
//
// return cell;
//}
@end
@@ -17,6 +17,7 @@
#import "FLEXImageExplorerViewController.h"
#import "FLEXClassExplorerViewController.h"
#import "FLEXLayerExplorerViewController.h"
#import "FLEXColorExplorerViewController.h"
#import <objc/runtime.h>
@implementation FLEXObjectExplorerFactory
@@ -28,7 +29,7 @@
return nil;
}
static NSDictionary *explorerSubclassesForObjectTypeStrings = nil;
static NSDictionary<NSString *, Class> *explorerSubclassesForObjectTypeStrings = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
explorerSubclassesForObjectTypeStrings = @{NSStringFromClass([NSArray class]) : [FLEXArrayExplorerViewController class],
@@ -38,7 +39,9 @@
NSStringFromClass([UIViewController class]) : [FLEXViewControllerExplorerViewController class],
NSStringFromClass([UIView class]) : [FLEXViewExplorerViewController class],
NSStringFromClass([UIImage class]) : [FLEXImageExplorerViewController class],
NSStringFromClass([CALayer class]) : [FLEXLayerExplorerViewController class]};
NSStringFromClass([CALayer class]) : [FLEXLayerExplorerViewController class],
NSStringFromClass([UIColor class]) : [FLEXColorExplorerViewController class]
};
});
Class explorerClass = nil;
@@ -33,6 +33,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectExplorerSection) {
- (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie;
- (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie;
- (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie;
- (UIView *)customViewForRowCookie:(id)rowCookie;
// More subclass configuration hooks.
@@ -17,6 +17,20 @@
#import "FLEXInstancesTableViewController.h"
#import <objc/runtime.h>
typedef NS_ENUM(NSUInteger, FLEXObjectExplorerScope) {
FLEXObjectExplorerScopeNoInheritance,
FLEXObjectExplorerScopeWithParent,
FLEXObjectExplorerScopeAllButNSObject,
FLEXObjectExplorerScopeNSObjectOnly
};
typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
FLEXMetadataKindProperties,
FLEXMetadataKindIvars,
FLEXMetadataKindMethods,
FLEXMetadataKindClassMethods
};
// Convenience boxes to keep runtime properties, ivars, and methods in foundation collections.
@interface FLEXPropertyBox : NSObject
@property (nonatomic, assign) objc_property_t property;
@@ -36,36 +50,41 @@
@implementation FLEXMethodBox
@end
static const NSInteger kFLEXObjectExplorerScopeNoInheritanceIndex = 0;
static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
@interface FLEXObjectExplorerViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSArray *properties;
@property (nonatomic, strong) NSArray *inheritedProperties;
@property (nonatomic, strong) NSArray *filteredProperties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *properties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *propertiesWithParent;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *inheritedProperties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *NSObjectProperties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *filteredProperties;
@property (nonatomic, strong) NSArray *ivars;
@property (nonatomic, strong) NSArray *inheritedIvars;
@property (nonatomic, strong) NSArray *filteredIvars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivarsWithParent;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *inheritedIvars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *NSObjectIvars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *filteredIvars;
@property (nonatomic, strong) NSArray *methods;
@property (nonatomic, strong) NSArray *inheritedMethods;
@property (nonatomic, strong) NSArray *filteredMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methodsWithParent;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredMethods;
@property (nonatomic, strong) NSArray *classMethods;
@property (nonatomic, strong) NSArray *inheritedClassMethods;
@property (nonatomic, strong) NSArray *filteredClassMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethodsWithParent;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedClassMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectClassMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredClassMethods;
@property (nonatomic, strong) NSArray *superclasses;
@property (nonatomic, strong) NSArray *filteredSuperclasses;
@property (nonatomic, strong) NSArray<Class> *superclasses;
@property (nonatomic, strong) NSArray<Class> *filteredSuperclasses;
@property (nonatomic, strong) NSArray *cachedCustomSectionRowCookies;
@property (nonatomic, strong) NSIndexSet *customSectionVisibleIndexes;
@property (nonatomic, strong) UISearchBar *searchBar;
@property (nonatomic, strong) NSString *filterText;
@property (nonatomic, assign) BOOL includeInheritance;
@property (nonatomic, assign) FLEXObjectExplorerScope scope;
@end
@@ -85,8 +104,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
self.searchBar.delegate = self;
self.searchBar.showsScopeBar = YES;
self.searchBar.scopeButtonTitles = @[@"No Inheritance", @"Include Inheritance"];
[self.searchBar sizeToFit];
[self refreshScopeTitles];
self.tableView.tableHeaderView = self.searchBar;
self.refreshControl = [[UIRefreshControl alloc] init];
@@ -116,6 +134,28 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
#pragma mark - Search
- (void)refreshScopeTitles {
if (!self.searchBar) return;
Class parent = [self.object superclass];
Class parentSuper = [parent superclass];
NSMutableArray *scopes = [NSMutableArray arrayWithObject:@"Base"];
if (parent) {
[scopes addObject:@"+ Parent"];
}
if (parentSuper && parentSuper != [NSObject class]) {
[scopes addObject:@"+ Inherited"];
}
if ([self.object isKindOfClass:[NSObject class]]) {
[scopes addObject:@"NSObject"];
}
self.searchBar.scopeButtonTitles = scopes;
[self.searchBar sizeToFit];
[self updateTableData];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
self.filterText = searchText;
@@ -128,13 +168,62 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
{
if (selectedScope == kFLEXObjectExplorerScopeIncludeInheritanceIndex) {
self.includeInheritance = YES;
} else if (selectedScope == kFLEXObjectExplorerScopeNoInheritanceIndex) {
self.includeInheritance = NO;
self.scope = selectedScope;
[self updateDisplayedData];
}
- (NSArray *)metadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
switch (metadataKind) {
case FLEXMetadataKindProperties:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.properties;
case FLEXObjectExplorerScopeWithParent:
return self.propertiesWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedProperties;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectProperties;
}
case FLEXMetadataKindIvars:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.ivars;
case FLEXObjectExplorerScopeWithParent:
return self.ivarsWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedIvars;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectIvars;
}
case FLEXMetadataKindMethods:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.methods;
case FLEXObjectExplorerScopeWithParent:
return self.methodsWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedMethods;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectMethods;
}
case FLEXMetadataKindClassMethods:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.classMethods;
case FLEXObjectExplorerScopeWithParent:
return self.classMethodsWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedClassMethods;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectClassMethods;
}
}
}
- (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
return [self metadata:metadataKind forScope:scope].count;
}
#pragma mark - Setter overrides
@@ -143,15 +232,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
_object = object;
// Use [object class] here rather than object_getClass because we don't want to show the KVO prefix for observed objects.
self.title = [[object class] description];
[self updateTableData];
}
- (void)setIncludeInheritance:(BOOL)includeInheritance
{
if (_includeInheritance != includeInheritance) {
_includeInheritance = includeInheritance;
[self updateDisplayedData];
}
[self refreshScopeTitles];
}
- (void)setFilterText:(NSString *)filterText
@@ -215,12 +296,18 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
{
Class class = [self.object class];
self.properties = [[self class] propertiesForClass:class];
self.inheritedProperties = [[self class] inheritedPropertiesForClass:class];
self.propertiesWithParent = [self.properties arrayByAddingObjectsFromArray:[[self class] propertiesForClass:[class superclass]]];
self.inheritedProperties = [self.properties arrayByAddingObjectsFromArray:[[self class] inheritedPropertiesForClass:class]];
self.NSObjectProperties = [[self class] propertiesForClass:[NSObject class]];
}
+ (NSArray *)propertiesForClass:(Class)class
+ (NSArray<FLEXPropertyBox *> *)propertiesForClass:(Class)class
{
NSMutableArray *boxedProperties = [NSMutableArray array];
if (!class) {
return @[];
}
NSMutableArray<FLEXPropertyBox *> *boxedProperties = [NSMutableArray array];
unsigned int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList(class, &propertyCount);
if (propertyList) {
@@ -234,10 +321,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return boxedProperties;
}
+ (NSArray *)inheritedPropertiesForClass:(Class)class
/// Skips NSObject
+ (NSArray<FLEXPropertyBox *> *)inheritedPropertiesForClass:(Class)class
{
NSMutableArray *inheritedProperties = [NSMutableArray array];
while ((class = [class superclass])) {
NSMutableArray<FLEXPropertyBox *> *inheritedProperties = [NSMutableArray array];
while ((class = [class superclass]) && class != [NSObject class]) {
[inheritedProperties addObjectsFromArray:[self propertiesForClass:class]];
}
return inheritedProperties;
@@ -245,14 +333,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)updateFilteredProperties
{
NSArray *candidateProperties = self.properties;
if (self.includeInheritance) {
candidateProperties = [candidateProperties arrayByAddingObjectsFromArray:self.inheritedProperties];
}
NSArray<FLEXPropertyBox *> *candidateProperties = [self metadata:FLEXMetadataKindProperties forScope:self.scope];
NSArray *unsortedFilteredProperties = nil;
NSArray<FLEXPropertyBox *> *unsortedFilteredProperties = nil;
if ([self.filterText length] > 0) {
NSMutableArray *mutableUnsortedFilteredProperties = [NSMutableArray array];
NSMutableArray<FLEXPropertyBox *> *mutableUnsortedFilteredProperties = [NSMutableArray array];
for (FLEXPropertyBox *propertyBox in candidateProperties) {
NSString *prettyName = [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
@@ -282,7 +367,10 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
id value = nil;
if ([self canHaveInstanceState]) {
FLEXPropertyBox *propertyBox = self.filteredProperties[index];
NSString *typeString = [FLEXRuntimeUtility typeEncodingForProperty:propertyBox.property];
const FLEXTypeEncoding *encoding = [typeString cStringUsingEncoding:NSUTF8StringEncoding];
value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
}
return value;
}
@@ -294,12 +382,17 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
{
Class class = [self.object class];
self.ivars = [[self class] ivarsForClass:class];
self.inheritedIvars = [[self class] inheritedIvarsForClass:class];
self.ivarsWithParent = [self.ivars arrayByAddingObjectsFromArray:[[self class] ivarsForClass:[class superclass]]];
self.inheritedIvars = [self.ivars arrayByAddingObjectsFromArray:[[self class] inheritedIvarsForClass:class]];
self.NSObjectIvars = [[self class] ivarsForClass:[NSObject class]];
}
+ (NSArray *)ivarsForClass:(Class)class
+ (NSArray<FLEXIvarBox *> *)ivarsForClass:(Class)class
{
NSMutableArray *boxedIvars = [NSMutableArray array];
if (!class) {
return @[];
}
NSMutableArray<FLEXIvarBox *> *boxedIvars = [NSMutableArray array];
unsigned int ivarCount = 0;
Ivar *ivarList = class_copyIvarList(class, &ivarCount);
if (ivarList) {
@@ -313,10 +406,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return boxedIvars;
}
+ (NSArray *)inheritedIvarsForClass:(Class)class
/// Skips NSObject
+ (NSArray<FLEXIvarBox *> *)inheritedIvarsForClass:(Class)class
{
NSMutableArray *inheritedIvars = [NSMutableArray array];
while ((class = [class superclass])) {
NSMutableArray<FLEXIvarBox *> *inheritedIvars = [NSMutableArray array];
while ((class = [class superclass]) && class != [NSObject class]) {
[inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
}
return inheritedIvars;
@@ -324,14 +418,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)updateFilteredIvars
{
NSArray *candidateIvars = self.ivars;
if (self.includeInheritance) {
candidateIvars = [candidateIvars arrayByAddingObjectsFromArray:self.inheritedIvars];
}
NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forScope:self.scope];
NSArray *unsortedFilteredIvars = nil;
NSArray<FLEXIvarBox *> *unsortedFilteredIvars = nil;
if ([self.filterText length] > 0) {
NSMutableArray *mutableUnsortedFilteredIvars = [NSMutableArray array];
NSMutableArray<FLEXIvarBox *> *mutableUnsortedFilteredIvars = [NSMutableArray array];
for (FLEXIvarBox *ivarBox in candidateIvars) {
NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
@@ -361,7 +452,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
id value = nil;
if ([self canHaveInstanceState]) {
FLEXIvarBox *ivarBox = self.filteredIvars[index];
const FLEXTypeEncoding *encoding = ivar_getTypeEncoding(ivarBox.ivar);
value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
}
return value;
}
@@ -373,12 +466,15 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
{
Class class = [self.object class];
self.methods = [[self class] methodsForClass:class];
self.inheritedMethods = [[self class] inheritedMethodsForClass:class];
self.methodsWithParent = [self.methods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[class superclass]]];
self.inheritedMethods = [self.methods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:class]];
self.NSObjectMethods = [[self class] methodsForClass:[NSObject class]];
}
- (void)updateFilteredMethods
{
self.filteredMethods = [self filteredMethodsFromMethods:self.methods inheritedMethods:self.inheritedMethods areClassMethods:NO];
NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forScope:self.scope];
self.filteredMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:NO];
}
- (void)updateClassMethods
@@ -386,17 +482,24 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
const char *className = [NSStringFromClass([self.object class]) UTF8String];
Class metaClass = objc_getMetaClass(className);
self.classMethods = [[self class] methodsForClass:metaClass];
self.inheritedClassMethods = [[self class] inheritedMethodsForClass:metaClass];
self.classMethodsWithParent = [self.classMethods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[metaClass superclass]]];
self.inheritedClassMethods = [self.classMethods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:metaClass]];
self.NSObjectClassMethods = [[self class] methodsForClass:[NSObject class]];
}
- (void)updateFilteredClassMethods
{
self.filteredClassMethods = [self filteredMethodsFromMethods:self.classMethods inheritedMethods:self.inheritedClassMethods areClassMethods:YES];
NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forScope:self.scope];
self.filteredClassMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:YES];
}
+ (NSArray *)methodsForClass:(Class)class
+ (NSArray<FLEXMethodBox *> *)methodsForClass:(Class)class
{
NSMutableArray *boxedMethods = [NSMutableArray array];
if (!class) {
return @[];
}
NSMutableArray<FLEXMethodBox *> *boxedMethods = [NSMutableArray array];
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(class, &methodCount);
if (methodList) {
@@ -410,25 +513,22 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return boxedMethods;
}
+ (NSArray *)inheritedMethodsForClass:(Class)class
/// Skips NSObject
+ (NSArray<FLEXMethodBox *> *)inheritedMethodsForClass:(Class)class
{
NSMutableArray *inheritedMethods = [NSMutableArray array];
while ((class = [class superclass])) {
NSMutableArray<FLEXMethodBox *> *inheritedMethods = [NSMutableArray array];
while ((class = [class superclass]) && class != [NSObject class]) {
[inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
}
return inheritedMethods;
}
- (NSArray *)filteredMethodsFromMethods:(NSArray *)methods inheritedMethods:(NSArray *)inheritedMethods areClassMethods:(BOOL)areClassMethods
- (NSArray<FLEXMethodBox *> *)filteredMethodsFromMethods:(NSArray<FLEXMethodBox *> *)methods areClassMethods:(BOOL)areClassMethods
{
NSArray *candidateMethods = methods;
if (self.includeInheritance) {
candidateMethods = [candidateMethods arrayByAddingObjectsFromArray:inheritedMethods];
}
NSArray *unsortedFilteredMethods = nil;
NSArray<FLEXMethodBox *> *candidateMethods = methods;
NSArray<FLEXMethodBox *> *unsortedFilteredMethods = nil;
if ([self.filterText length] > 0) {
NSMutableArray *mutableUnsortedFilteredMethods = [NSMutableArray array];
NSMutableArray<FLEXMethodBox *> *mutableUnsortedFilteredMethods = [NSMutableArray array];
for (FLEXMethodBox *methodBox in candidateMethods) {
NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
@@ -440,7 +540,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
unsortedFilteredMethods = candidateMethods;
}
NSArray *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
NSArray<FLEXMethodBox *> *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
return [name1 caseInsensitiveCompare:name2];
@@ -464,9 +564,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
#pragma mark - Superclasses
+ (NSArray *)superclassesForClass:(Class)class
+ (NSArray<Class> *)superclassesForClass:(Class)class
{
NSMutableArray *superClasses = [NSMutableArray array];
NSMutableArray<Class> *superClasses = [NSMutableArray array];
while ((class = [class superclass])) {
[superClasses addObject:class];
}
@@ -481,7 +581,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)updateFilteredSuperclasses
{
if ([self.filterText length] > 0) {
NSMutableArray *filteredSuperclasses = [NSMutableArray array];
NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
for (Class superclass in self.superclasses) {
if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
[filteredSuperclasses addObject:superclass];
@@ -496,9 +596,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
#pragma mark - Table View Data Helpers
- (NSArray *)possibleExplorerSections
- (NSArray<NSNumber *> *)possibleExplorerSections
{
static NSArray *possibleSections = nil;
static NSArray<NSNumber *> *possibleSections = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
possibleSections = @[@(FLEXObjectExplorerSectionDescription),
@@ -513,9 +613,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return possibleSections;
}
- (NSArray *)visibleExplorerSections
- (NSArray<NSNumber *> *)visibleExplorerSections
{
NSMutableArray *visibleSections = [NSMutableArray array];
NSMutableArray<NSNumber *> *visibleSections = [NSMutableArray array];
for (NSNumber *possibleSection in [self possibleExplorerSections]) {
FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
@@ -736,34 +836,22 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
} break;
case FLEXObjectExplorerSectionProperties: {
NSUInteger totalCount = [self.properties count];
if (self.includeInheritance) {
totalCount += [self.inheritedProperties count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
} break;
case FLEXObjectExplorerSectionIvars: {
NSUInteger totalCount = [self.ivars count];
if (self.includeInheritance) {
totalCount += [self.inheritedIvars count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
} break;
case FLEXObjectExplorerSectionMethods: {
NSUInteger totalCount = [self.methods count];
if (self.includeInheritance) {
totalCount += [self.inheritedMethods count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
} break;
case FLEXObjectExplorerSectionClassMethods: {
NSUInteger totalCount = [self.classMethods count];
if (self.includeInheritance) {
totalCount += [self.inheritedClassMethods count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
} break;
@@ -858,7 +946,8 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
BOOL isCustomSection = explorerSection == FLEXObjectExplorerSectionCustom;
BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineTableViewCellIdentifier : @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
@@ -874,7 +963,16 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
cell.detailTextLabel.textColor = [UIColor grayColor];
}
}
UIView *customView;
if (isCustomSection) {
customView = [self customViewForRowCookie:[self customSectionRowCookieForVisibleRow:indexPath.row]];
if (customView) {
[cell.contentView addSubview:customView];
}
}
cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
@@ -891,7 +989,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont] }];
CGFloat preferredHeight = [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.frame.size.width style:tableView.style showsAccessory:NO];
height = MAX(height, preferredHeight);
} else if (explorerSection == FLEXObjectExplorerSectionCustom) {
id cookie = [self customSectionRowCookieForVisibleRow:indexPath.row];
height = [self heightForCustomViewRowForRowCookie:cookie];
}
return height;
}
@@ -1020,6 +1122,16 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return nil;
}
- (UIView *)customViewForRowCookie:(id)rowCookie
{
return nil;
}
- (CGFloat)heightForCustomViewRowForRowCookie:(id)rowCookie
{
return self.tableView.rowHeight;
}
- (BOOL)canHaveInstanceState
{
return YES;
@@ -57,9 +57,9 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
return rowCookies;
}
- (NSArray *)shortcutPropertyNames
- (NSArray<NSString *> *)shortcutPropertyNames
{
NSArray *propertyNames = @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled", @"layer"];
NSArray<NSString *> *propertyNames = @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled", @"layer"];
if ([self.viewToExplore isKindOfClass:[UILabel class]]) {
propertyNames = [@[@"text", @"font", @"textColor"] arrayByAddingObjectsFromArray:propertyNames];
@@ -195,22 +195,22 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
// We add these properties to the class at runtime if they haven't been added yet.
// This way, we can use our property editor to access and change them.
// The property attributes match the declared attributes in UIView.h
NSDictionary *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
NSDictionary<NSString *, NSString *> *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"frame" attributes:frameAttributes toClass:[UIView class]];
NSDictionary *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
NSDictionary<NSString *, NSString *> *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"alpha" attributes:alphaAttributes toClass:[UIView class]];
NSDictionary *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
NSDictionary<NSString *, NSString *> *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"clipsToBounds" attributes:clipsAttributes toClass:[UIView class]];
NSDictionary *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
NSDictionary<NSString *, NSString *> *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
[FLEXRuntimeUtility tryAddPropertyWithName:"opaque" attributes:opaqueAttributes toClass:[UIView class]];
NSDictionary *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
NSDictionary<NSString *, NSString *> *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
[FLEXRuntimeUtility tryAddPropertyWithName:"hidden" attributes:hiddenAttributes toClass:[UIView class]];
NSDictionary *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(FLEXEncodeClass(UIColor)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
NSDictionary<NSString *, NSString *> *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(FLEXEncodeClass(UIColor)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"backgroundColor" attributes:backgroundColorAttributes toClass:[UIView class]];
}
+4
View File
@@ -12,6 +12,10 @@
@interface FLEXExplorerToolbar : UIView
/// The items to be displayed in the toolbar. Defaults to:
/// globalsItem, hierarchyItem, selectItem, moveItem, closeItem
@property (nonatomic, copy) NSArray<FLEXToolbarItem *> *toolbarItems;
/// Toolbar item for selecting views.
/// Users of the toolbar can configure the enabled/selected state and event targets/actions.
@property (nonatomic, strong, readonly) FLEXToolbarItem *selectItem;
+76 -30
View File
@@ -22,12 +22,13 @@
@property (nonatomic, strong) UIImageView *dragHandleImageView;
@property (nonatomic, strong) NSArray *toolbarItems;
@property (nonatomic, strong) UIView *selectedViewDescriptionContainer;
@property (nonatomic, strong) UIView *selectedViewDescriptionSafeAreaContainer;
@property (nonatomic, strong) UIView *selectedViewColorIndicator;
@property (nonatomic, strong) UILabel *selectedViewDescriptionLabel;
@property (nonatomic, strong,readwrite) UIView *backgroundView;
@end
@implementation FLEXExplorerToolbar
@@ -36,10 +37,12 @@
{
self = [super initWithFrame:frame];
if (self) {
NSMutableArray *toolbarItems = [NSMutableArray array];
self.backgroundView = [[UIView alloc] init];
self.backgroundView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.95];
[self addSubview:self.backgroundView];
self.dragHandle = [[UIView alloc] init];
self.dragHandle.backgroundColor = [FLEXToolbarItem defaultBackgroundColor];
self.dragHandle.backgroundColor = [UIColor clearColor];
[self addSubview:self.dragHandle];
UIImage *dragHandle = [FLEXResources dragHandle];
@@ -48,56 +51,52 @@
UIImage *globalsIcon = [FLEXResources globeIcon];
self.globalsItem = [FLEXToolbarItem toolbarItemWithTitle:@"menu" image:globalsIcon];
[self addSubview:self.globalsItem];
[toolbarItems addObject:self.globalsItem];
UIImage *listIcon = [FLEXResources listIcon];
self.hierarchyItem = [FLEXToolbarItem toolbarItemWithTitle:@"views" image:listIcon];
[self addSubview:self.hierarchyItem];
[toolbarItems addObject:self.hierarchyItem];
UIImage *selectIcon = [FLEXResources selectIcon];
self.selectItem = [FLEXToolbarItem toolbarItemWithTitle:@"select" image:selectIcon];
[self addSubview:self.selectItem];
[toolbarItems addObject:self.selectItem];
UIImage *moveIcon = [FLEXResources moveIcon];
self.moveItem = [FLEXToolbarItem toolbarItemWithTitle:@"move" image:moveIcon];
[self addSubview:self.moveItem];
[toolbarItems addObject:self.moveItem];
UIImage *closeIcon = [FLEXResources closeIcon];
self.closeItem = [FLEXToolbarItem toolbarItemWithTitle:@"close" image:closeIcon];
[self addSubview:self.closeItem];
[toolbarItems addObject:self.closeItem];
self.toolbarItems = toolbarItems;
self.backgroundColor = [UIColor clearColor];
self.selectedViewDescriptionContainer = [[UIView alloc] init];
self.selectedViewDescriptionContainer.backgroundColor = [UIColor colorWithWhite:0.9 alpha:0.95];
self.selectedViewDescriptionContainer.hidden = YES;
[self addSubview:self.selectedViewDescriptionContainer];
self.selectedViewDescriptionSafeAreaContainer = [[UIView alloc] init];
self.selectedViewDescriptionSafeAreaContainer.backgroundColor = [UIColor clearColor];
[self.selectedViewDescriptionContainer addSubview:self.selectedViewDescriptionSafeAreaContainer];
self.selectedViewColorIndicator = [[UIView alloc] init];
self.selectedViewColorIndicator.backgroundColor = [UIColor redColor];
[self.selectedViewDescriptionContainer addSubview:self.selectedViewColorIndicator];
[self.selectedViewDescriptionSafeAreaContainer addSubview:self.selectedViewColorIndicator];
self.selectedViewDescriptionLabel = [[UILabel alloc] init];
self.selectedViewDescriptionLabel.backgroundColor = [UIColor clearColor];
self.selectedViewDescriptionLabel.font = [[self class] descriptionLabelFont];
[self.selectedViewDescriptionContainer addSubview:self.selectedViewDescriptionLabel];
[self.selectedViewDescriptionSafeAreaContainer addSubview:self.selectedViewDescriptionLabel];
self.toolbarItems = @[_globalsItem, _hierarchyItem, _selectItem, _moveItem, _closeItem];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect safeArea = [self safeArea];
// Drag Handle
const CGFloat kToolbarItemHeight = [[self class] toolbarItemHeight];
self.dragHandle.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, [[self class] dragHandleWidth], kToolbarItemHeight);
self.dragHandle.frame = CGRectMake(CGRectGetMinX(safeArea), CGRectGetMinY(safeArea), [[self class] dragHandleWidth], kToolbarItemHeight);
CGRect dragHandleImageFrame = self.dragHandleImageView.frame;
dragHandleImageFrame.origin.x = FLEXFloor((self.dragHandle.frame.size.width - dragHandleImageFrame.size.width) / 2.0);
dragHandleImageFrame.origin.y = FLEXFloor((self.dragHandle.frame.size.height - dragHandleImageFrame.size.height) / 2.0);
@@ -106,9 +105,9 @@
// Toolbar Items
CGFloat originX = CGRectGetMaxX(self.dragHandle.frame);
CGFloat originY = self.bounds.origin.y;
CGFloat originY = CGRectGetMinY(safeArea);
CGFloat height = kToolbarItemHeight;
CGFloat width = FLEXFloor((CGRectGetMaxX(self.bounds) - originX) / [self.toolbarItems count]);
CGFloat width = FLEXFloor((CGRectGetWidth(safeArea) - CGRectGetWidth(self.dragHandle.frame)) / [self.toolbarItems count]);
for (UIView *toolbarItem in self.toolbarItems) {
toolbarItem.frame = CGRectMake(originX, originY, width, height);
originX = CGRectGetMaxX(toolbarItem.frame);
@@ -117,8 +116,10 @@
// Make sure the last toolbar item goes to the edge to account for any accumulated rounding effects.
UIView *lastToolbarItem = [self.toolbarItems lastObject];
CGRect lastToolbarItemFrame = lastToolbarItem.frame;
lastToolbarItemFrame.size.width = CGRectGetMaxX(self.bounds) - lastToolbarItemFrame.origin.x;
lastToolbarItemFrame.size.width = CGRectGetMaxX(safeArea) - lastToolbarItemFrame.origin.x;
lastToolbarItem.frame = lastToolbarItemFrame;
self.backgroundView.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), kToolbarItemHeight);
const CGFloat kSelectedViewColorDiameter = [[self class] selectedViewColorIndicatorDiameter];
const CGFloat kDescriptionLabelHeight = [[self class] descriptionLabelHeight];
@@ -127,11 +128,19 @@
const CGFloat kDescriptionContainerHeight = [[self class] descriptionContainerHeight];
CGRect descriptionContainerFrame = CGRectZero;
descriptionContainerFrame.size.width = CGRectGetWidth(self.bounds);
descriptionContainerFrame.size.height = kDescriptionContainerHeight;
descriptionContainerFrame.origin.x = CGRectGetMinX(self.bounds);
descriptionContainerFrame.origin.y = CGRectGetMaxY(self.bounds) - kDescriptionContainerHeight;
descriptionContainerFrame.size.width = self.bounds.size.width;
self.selectedViewDescriptionContainer.frame = descriptionContainerFrame;
CGRect descriptionSafeAreaContainerFrame = CGRectZero;
descriptionSafeAreaContainerFrame.size.width = CGRectGetWidth(safeArea);
descriptionSafeAreaContainerFrame.size.height = kDescriptionContainerHeight;
descriptionSafeAreaContainerFrame.origin.x = CGRectGetMinX(safeArea);
descriptionSafeAreaContainerFrame.origin.y = CGRectGetMinY(safeArea);
self.selectedViewDescriptionSafeAreaContainer.frame = descriptionSafeAreaContainerFrame;
// Selected View Color
CGRect selectedViewColorFrame = CGRectZero;
selectedViewColorFrame.size.width = kSelectedViewColorDiameter;
@@ -150,10 +159,36 @@
descriptionLabelFrame.size.width = CGRectGetMaxX(self.selectedViewDescriptionContainer.bounds) - kHorizontalPadding - descriptionOriginX;
self.selectedViewDescriptionLabel.frame = descriptionLabelFrame;
}
#pragma mark - Setter Overrides
- (void)setToolbarItems:(NSArray<FLEXToolbarItem *> *)toolbarItems {
if (_toolbarItems == toolbarItems) {
return;
}
// Remove old toolbar items, if any
for (FLEXToolbarItem *item in _toolbarItems) {
[item removeFromSuperview];
}
// Trim to 5 items if necessary
if (toolbarItems.count > 5) {
toolbarItems = [toolbarItems subarrayWithRange:NSMakeRange(0, 5)];
}
for (FLEXToolbarItem *item in toolbarItems) {
[self addSubview:item];
}
_toolbarItems = toolbarItems.copy;
// Lay out new items
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (void)setSelectedViewOverlayColor:(UIColor *)selectedViewOverlayColor
{
if (![_selectedViewOverlayColor isEqual:selectedViewOverlayColor]) {
@@ -223,4 +258,15 @@
return CGSizeMake(size.width, height);
}
- (CGRect)safeArea
{
CGRect safeArea = self.bounds;
#if FLEX_AT_LEAST_IOS11_SDK
if (@available(iOS 11, *)) {
safeArea = UIEdgeInsetsInsetRect(self.bounds, self.safeAreaInsets);
}
#endif
return safeArea;
}
@end
-2
View File
@@ -12,6 +12,4 @@
+ (instancetype)toolbarItemWithTitle:(NSString *)title image:(UIImage *)image;
+ (UIColor *)defaultBackgroundColor;
@end
+2 -2
View File
@@ -43,7 +43,7 @@
#pragma mark - Display Defaults
+ (NSDictionary *)titleAttributes
+ (NSDictionary<NSString *, id> *)titleAttributes
{
return @{NSFontAttributeName : [FLEXUtility defaultFontOfSize:12.0]};
}
@@ -70,7 +70,7 @@
+ (UIColor *)defaultBackgroundColor
{
return [UIColor colorWithWhite:1.0 alpha:0.95];
return [UIColor clearColor];
}
+ (CGFloat)topMargin
+367
View File
@@ -0,0 +1,367 @@
APPLE PUBLIC SOURCE LICENSE
Version 2.0 - August 6, 2003
Please read this License carefully before downloading this software.
By downloading or using this software, you are agreeing to be bound by
the terms of this License. If you do not or cannot agree to the terms
of this License, please do not download or use the software.
1. General; Definitions. This License applies to any program or other
work which Apple Computer, Inc. ("Apple") makes publicly available and
which contains a notice placed by Apple identifying such program or
work as "Original Code" and stating that it is subject to the terms of
this Apple Public Source License version 2.0 ("License"). As used in
this License:
1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is
the grantor of rights, (i) claims of patents that are now or hereafter
acquired, owned by or assigned to Apple and (ii) that cover subject
matter contained in the Original Code, but only to the extent
necessary to use, reproduce and/or distribute the Original Code
without infringement; and (b) in the case where You are the grantor of
rights, (i) claims of patents that are now or hereafter acquired,
owned by or assigned to You and (ii) that cover subject matter in Your
Modifications, taken alone or in combination with Original Code.
1.2 "Contributor" means any person or entity that creates or
contributes to the creation of Modifications.
1.3 "Covered Code" means the Original Code, Modifications, the
combination of Original Code and any Modifications, and/or any
respective portions thereof.
1.4 "Externally Deploy" means: (a) to sublicense, distribute or
otherwise make Covered Code available, directly or indirectly, to
anyone other than You; and/or (b) to use Covered Code, alone or as
part of a Larger Work, in any way to provide a service, including but
not limited to delivery of content, through electronic communication
with a client other than You.
1.5 "Larger Work" means a work which combines Covered Code or portions
thereof with code not governed by the terms of this License.
1.6 "Modifications" mean any addition to, deletion from, and/or change
to, the substance and/or structure of the Original Code, any previous
Modifications, the combination of Original Code and any previous
Modifications, and/or any respective portions thereof. When code is
released as a series of files, a Modification is: (a) any addition to
or deletion from the contents of a file containing Covered Code;
and/or (b) any new file or other representation of computer program
statements that contains any part of Covered Code.
1.7 "Original Code" means (a) the Source Code of a program or other
work as originally made available by Apple under this License,
including the Source Code of any updates or upgrades to such programs
or works made available by Apple under this License, and that has been
expressly identified by Apple as such in the header file(s) of such
work; and (b) the object code compiled from such Source Code and
originally made available by Apple under this License.
1.8 "Source Code" means the human readable form of a program or other
work that is suitable for making modifications to it, including all
modules it contains, plus any associated interface definition files,
scripts used to control compilation and installation of an executable
(object code).
1.9 "You" or "Your" means an individual or a legal entity exercising
rights under this License. For legal entities, "You" or "Your"
includes any entity which controls, is controlled by, or is under
common control with, You, where "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of fifty percent
(50%) or more of the outstanding shares or beneficial ownership of
such entity.
2. Permitted Uses; Conditions & Restrictions. Subject to the terms
and conditions of this License, Apple hereby grants You, effective on
the date You accept this License and download the Original Code, a
world-wide, royalty-free, non-exclusive license, to the extent of
Apple's Applicable Patent Rights and copyrights covering the Original
Code, to do the following:
2.1 Unmodified Code. You may use, reproduce, display, perform,
internally distribute within Your organization, and Externally Deploy
verbatim, unmodified copies of the Original Code, for commercial or
non-commercial purposes, provided that in each instance:
(a) You must retain and reproduce in all copies of Original Code the
copyright and other proprietary notices and disclaimers of Apple as
they appear in the Original Code, and keep intact all notices in the
Original Code that refer to this License; and
(b) You must include a copy of this License with every copy of Source
Code of Covered Code and documentation You distribute or Externally
Deploy, and You may not offer or impose any terms on such Source Code
that alter or restrict this License or the recipients' rights
hereunder, except as permitted under Section 6.
2.2 Modified Code. You may modify Covered Code and use, reproduce,
display, perform, internally distribute within Your organization, and
Externally Deploy Your Modifications and Covered Code, for commercial
or non-commercial purposes, provided that in each instance You also
meet all of these conditions:
(a) You must satisfy all the conditions of Section 2.1 with respect to
the Source Code of the Covered Code;
(b) You must duplicate, to the extent it does not already exist, the
notice in Exhibit A in each file of the Source Code of all Your
Modifications, and cause the modified files to carry prominent notices
stating that You changed the files and the date of any change; and
(c) If You Externally Deploy Your Modifications, You must make
Source Code of all Your Externally Deployed Modifications either
available to those to whom You have Externally Deployed Your
Modifications, or publicly available. Source Code of Your Externally
Deployed Modifications must be released under the terms set forth in
this License, including the license grants set forth in Section 3
below, for as long as you Externally Deploy the Covered Code or twelve
(12) months from the date of initial External Deployment, whichever is
longer. You should preferably distribute the Source Code of Your
Externally Deployed Modifications electronically (e.g. download from a
web site).
2.3 Distribution of Executable Versions. In addition, if You
Externally Deploy Covered Code (Original Code and/or Modifications) in
object code, executable form only, You must include a prominent
notice, in the code itself as well as in related documentation,
stating that Source Code of the Covered Code is available under the
terms of this License with information on how and where to obtain such
Source Code.
2.4 Third Party Rights. You expressly acknowledge and agree that
although Apple and each Contributor grants the licenses to their
respective portions of the Covered Code set forth herein, no
assurances are provided by Apple or any Contributor that the Covered
Code does not infringe the patent or other intellectual property
rights of any other entity. Apple and each Contributor disclaim any
liability to You for claims brought by any other entity based on
infringement of intellectual property rights or otherwise. As a
condition to exercising the rights and licenses granted hereunder, You
hereby assume sole responsibility to secure any other intellectual
property rights needed, if any. For example, if a third party patent
license is required to allow You to distribute the Covered Code, it is
Your responsibility to acquire that license before distributing the
Covered Code.
3. Your Grants. In consideration of, and as a condition to, the
licenses granted to You under this License, You hereby grant to any
person or entity receiving or distributing Covered Code under this
License a non-exclusive, royalty-free, perpetual, irrevocable license,
under Your Applicable Patent Rights and other intellectual property
rights (other than patent) owned or controlled by You, to use,
reproduce, display, perform, modify, sublicense, distribute and
Externally Deploy Your Modifications of the same scope and extent as
Apple's licenses under Sections 2.1 and 2.2 above.
4. Larger Works. You may create a Larger Work by combining Covered
Code with other code not governed by the terms of this License and
distribute the Larger Work as a single product. In each such instance,
You must make sure the requirements of this License are fulfilled for
the Covered Code or any portion thereof.
5. Limitations on Patent License. Except as expressly stated in
Section 2, no other patent rights, express or implied, are granted by
Apple herein. Modifications and/or Larger Works may require additional
patent licenses from Apple which Apple may grant in its sole
discretion.
6. Additional Terms. You may choose to offer, and to charge a fee for,
warranty, support, indemnity or liability obligations and/or other
rights consistent with the scope of the license granted herein
("Additional Terms") to one or more recipients of Covered Code.
However, You may do so only on Your own behalf and as Your sole
responsibility, and not on behalf of Apple or any Contributor. You
must obtain the recipient's agreement that any such Additional Terms
are offered by You alone, and You hereby agree to indemnify, defend
and hold Apple and every Contributor harmless for any liability
incurred by or claims asserted against Apple or such Contributor by
reason of any such Additional Terms.
7. Versions of the License. Apple may publish revised and/or new
versions of this License from time to time. Each version will be given
a distinguishing version number. Once Original Code has been published
under a particular version of this License, You may continue to use it
under the terms of that version. You may also choose to use such
Original Code under the terms of any subsequent version of this
License published by Apple. No one other than Apple has the right to
modify the terms applicable to Covered Code created under this
License.
8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in
part pre-release, untested, or not fully tested works. The Covered
Code may contain errors that could cause failures or loss of data, and
may be incomplete or contain inaccuracies. You expressly acknowledge
and agree that use of the Covered Code, or any portion thereof, is at
Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND
WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND
APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE
PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM
ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF
MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR
PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD
PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST
INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE
FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS,
THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR
ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO
ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE
AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY.
You acknowledge that the Covered Code is not intended for use in the
operation of nuclear facilities, aircraft navigation, communication
systems, or air traffic control machines in which case the failure of
the Covered Code could lead to death, personal injury, or severe
physical or environmental damage.
9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO
EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL,
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING
TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR
ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY,
TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF
APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY
REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF
INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY
TO YOU. In no event shall Apple's total liability to You for all
damages (other than as may be required by applicable law) under this
License exceed the amount of fifty dollars ($50.00).
10. Trademarks. This License does not grant any rights to use the
trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS",
"QuickTime", "QuickTime Streaming Server" or any other trademarks,
service marks, logos or trade names belonging to Apple (collectively
"Apple Marks") or to any trademark, service mark, logo or trade name
belonging to any Contributor. You agree not to use any Apple Marks in
or as part of the name of products derived from the Original Code or
to endorse or promote products derived from the Original Code other
than as expressly permitted by and in strict compliance at all times
with Apple's third party trademark usage guidelines which are posted
at http://www.apple.com/legal/guidelinesfor3rdparties.html.
11. Ownership. Subject to the licenses granted under this License,
each Contributor retains all rights, title and interest in and to any
Modifications made by such Contributor. Apple retains all rights,
title and interest in and to the Original Code and any Modifications
made by or on behalf of Apple ("Apple Modifications"), and such Apple
Modifications will not be automatically subject to this License. Apple
may, at its sole discretion, choose to license such Apple
Modifications under this License, or on different terms from those
contained in this License or may choose not to license them at all.
12. Termination.
12.1 Termination. This License and the rights granted hereunder will
terminate:
(a) automatically without notice from Apple if You fail to comply with
any term(s) of this License and fail to cure such breach within 30
days of becoming aware of such breach;
(b) immediately in the event of the circumstances described in Section
13.5(b); or
(c) automatically without notice from Apple if You, at any time during
the term of this License, commence an action for patent infringement
against Apple; provided that Apple did not first commence
an action for patent infringement against You in that instance.
12.2 Effect of Termination. Upon termination, You agree to immediately
stop any further use, reproduction, modification, sublicensing and
distribution of the Covered Code. All sublicenses to the Covered Code
which have been properly granted prior to termination shall survive
any termination of this License. Provisions which, by their nature,
should remain in effect beyond the termination of this License shall
survive, including but not limited to Sections 3, 5, 8, 9, 10, 11,
12.2 and 13. No party will be liable to any other for compensation,
indemnity or damages of any sort solely as a result of terminating
this License in accordance with its terms, and termination of this
License will be without prejudice to any other right or remedy of
any party.
13. Miscellaneous.
13.1 Government End Users. The Covered Code is a "commercial item" as
defined in FAR 2.101. Government software and technical data rights in
the Covered Code include only those rights customarily provided to the
public as defined in this License. This customary commercial license
in technical data and software is provided in accordance with FAR
12.211 (Technical Data) and 12.212 (Computer Software) and, for
Department of Defense purchases, DFAR 252.227-7015 (Technical Data --
Commercial Items) and 227.7202-3 (Rights in Commercial Computer
Software or Computer Software Documentation). Accordingly, all U.S.
Government End Users acquire Covered Code with only those rights set
forth herein.
13.2 Relationship of Parties. This License will not be construed as
creating an agency, partnership, joint venture or any other form of
legal association between or among You, Apple or any Contributor, and
You will not represent to the contrary, whether expressly, by
implication, appearance or otherwise.
13.3 Independent Development. Nothing in this License will impair
Apple's right to acquire, license, develop, have others develop for
it, market and/or distribute technology or products that perform the
same or similar functions as, or otherwise compete with,
Modifications, Larger Works, technology or products that You may
develop, produce, market or distribute.
13.4 Waiver; Construction. Failure by Apple or any Contributor to
enforce any provision of this License will not be deemed a waiver of
future enforcement of that or any other provision. Any law or
regulation which provides that the language of a contract shall be
construed against the drafter will not apply to this License.
13.5 Severability. (a) If for any reason a court of competent
jurisdiction finds any provision of this License, or portion thereof,
to be unenforceable, that provision of the License will be enforced to
the maximum extent permissible so as to effect the economic benefits
and intent of the parties, and the remainder of this License will
continue in full force and effect. (b) Notwithstanding the foregoing,
if applicable law prohibits or restricts You from fully and/or
specifically complying with Sections 2 and/or 3 or prevents the
enforceability of either of those Sections, this License will
immediately terminate and You must immediately discontinue any use of
the Covered Code and destroy all copies of it that are in your
possession or control.
13.6 Dispute Resolution. Any litigation or other dispute resolution
between You and Apple relating to this License shall take place in the
Northern District of California, and You and Apple hereby consent to
the personal jurisdiction of, and venue in, the state and federal
courts within that District with respect to this License. The
application of the United Nations Convention on Contracts for the
International Sale of Goods is expressly excluded.
13.7 Entire Agreement; Governing Law. This License constitutes the
entire agreement between the parties with respect to the subject
matter hereof. This License shall be governed by the laws of the
United States and the State of California, except that body of
California law concerning conflicts of law.
Where You are located in the province of Quebec, Canada, the following
clause applies: The parties hereby confirm that they have requested
that this License and all related documents be drafted in English. Les
parties ont exige que le present contrat et tous les documents
connexes soient rediges en anglais.
EXHIBIT A.
"Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
Reserved.
This file contains Original Code and/or Modifications of Original Code
as defined in and that are subject to the Apple Public Source License
Version 2.0 (the 'License'). You may not use this file except in
compliance with the License. Please obtain a copy of the License at
http://www.opensource.apple.com/apsl/ and read it before using this
file.
The Original Code and all software distributed under the License are
distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
Please see the License for the specific language governing rights and
limitations under the License."
+42 -5
View File
@@ -22,8 +22,7 @@ typedef struct {
static void range_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount)
{
flex_object_enumeration_block_t block = (__bridge flex_object_enumeration_block_t)context;
if (!block) {
if (!context) {
return;
}
@@ -40,7 +39,7 @@ static void range_callback(task_t task, void *context, unsigned type, vm_range_t
#endif
// If the class pointer matches one in our set of class pointers from the runtime, then we should have an object.
if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
block((__bridge id)tryObject, tryClass);
(*(flex_object_enumeration_block_t __unsafe_unretained *)context)((__bridge id)tryObject, tryClass);
}
}
}
@@ -71,9 +70,47 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
if (result == KERN_SUCCESS) {
for (unsigned int i = 0; i < zoneCount; i++) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
if (zone->introspect && zone->introspect->enumerator) {
zone->introspect->enumerator(TASK_NULL, (__bridge void *)block, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
malloc_introspection_t *introspection = zone->introspect;
NSString *zoneName = @(zone->zone_name);
if (![zoneName isEqualToString:@"DefaultMallocZone"] || !introspection) {
continue;
}
void (*lock_zone)(malloc_zone_t *zone) = introspection->force_lock;
void (*unlock_zone)(malloc_zone_t *zone) = introspection->force_unlock;
// Callback has to unlock the zone so we freely allocate memory inside the given block
flex_object_enumeration_block_t callback = ^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
unlock_zone(zone);
block(object, actualClass);
lock_zone(zone);
};
// The largest realistic memory address varies by platform.
// Only 48 bits are used by 64 bit machines while
// 32 bit machines use all bits.
#if __arm64__
static uintptr_t MAX_REALISTIC_ADDRESS = 0xFFFFFFFFFFFF;
#else
static uintptr_t MAX_REALISTIC_ADDRESS = INT_MAX;
#endif
// There is little documentation on when and why
// any of these function pointers might be NULL
// or garbage, so we resort to checking for NULL
// and impossible memory addresses at least
if (lock_zone && unlock_zone &&
introspection->enumerator &&
(uintptr_t)lock_zone < MAX_REALISTIC_ADDRESS &&
(uintptr_t)unlock_zone < MAX_REALISTIC_ADDRESS) {
lock_zone(zone);
introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
unlock_zone(zone);
}
// Only one zone to enumerate
break;
}
}
}
+16 -12
View File
@@ -57,12 +57,12 @@
- (NSString *)description
{
NSDictionary *keyMappings = @{ UIKeyInputUpArrow : @"",
UIKeyInputDownArrow : @"",
UIKeyInputLeftArrow : @"",
UIKeyInputRightArrow : @"",
UIKeyInputEscape : @"",
@" " : @""};
NSDictionary<NSString *, NSString *> *keyMappings = @{ UIKeyInputUpArrow : @"",
UIKeyInputDownArrow : @"",
UIKeyInputLeftArrow : @"",
UIKeyInputRightArrow : @"",
UIKeyInputEscape : @"",
@" " : @""};
NSString *prettyKey = nil;
if (self.key && keyMappings[self.key]) {
@@ -113,7 +113,7 @@
@interface FLEXKeyboardShortcutManager ()
@property (nonatomic, strong) NSMutableDictionary *actionsForKeyInputs;
@property (nonatomic, strong) NSMutableDictionary<FLEXKeyInput *, dispatch_block_t> *actionsForKeyInputs;
@property (nonatomic, assign, getter=isPressingShift) BOOL pressingShift;
@property (nonatomic, assign, getter=isPressingCommand) BOOL pressingCommand;
@@ -165,10 +165,14 @@
pressureLevel++;
}
if (pressureLevel > 0) {
for (UITouch *touch in [event allTouches]) {
double adjustedPressureLevel = pressureLevel * 20 * touch.maximumPossibleForce;
[touch setValue:@(adjustedPressureLevel) forKey:@"_pressure"];
#if FLEX_AT_LEAST_IOS11_SDK
if (@available(iOS 9.0, *)) {
for (UITouch *touch in [event allTouches]) {
double adjustedPressureLevel = pressureLevel * 20 * touch.maximumPossibleForce;
[touch setValue:@(adjustedPressureLevel) forKey:@"_pressure"];
}
}
#endif
}
}
@@ -260,7 +264,7 @@ static const long kFLEXCommandKeyCode = 0xe3;
dispatch_block_t actionBlock = self.actionsForKeyInputs[exactMatch];
if (!actionBlock) {
FLEXKeyInput *shiftMatch = [FLEXKeyInput keyInputForKey:modifiedInput flags:flags&(!UIKeyModifierShift)];
FLEXKeyInput *shiftMatch = [FLEXKeyInput keyInputForKey:modifiedInput flags:flags&(~UIKeyModifierShift)];
actionBlock = self.actionsForKeyInputs[shiftMatch];
}
@@ -292,7 +296,7 @@ static const long kFLEXCommandKeyCode = 0xe3;
- (NSString *)keyboardShortcutsDescription
{
NSMutableString *description = [NSMutableString string];
NSArray *keyInputs = [[self.actionsForKeyInputs allKeys] sortedArrayUsingComparator:^NSComparisonResult(FLEXKeyInput *_Nonnull input1, FLEXKeyInput *_Nonnull input2) {
NSArray<FLEXKeyInput *> *keyInputs = [[self.actionsForKeyInputs allKeys] sortedArrayUsingComparator:^NSComparisonResult(FLEXKeyInput *_Nonnull input1, FLEXKeyInput *_Nonnull input2) {
return [input1.key caseInsensitiveCompare:input2.key];
}];
for (FLEXKeyInput *keyInput in keyInputs) {
+23
View File
@@ -0,0 +1,23 @@
//
// FLEXObjcInternal.h
// FLEX
//
// Created by Tanner Bennett on 11/1/18.
//
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
/// @brief Assumes memory is valid and readable.
/// @discussion objc-internal.h, objc-private.h, and objc-config.h
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
/// http://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
BOOL FLEXPointerIsValidObjcObject(const void * ptr);
#ifdef __cplusplus
}
#endif
+309
View File
@@ -0,0 +1,309 @@
//
// FLEXObjcInternal.mm
// FLEX
//
// Created by Tanner Bennett on 11/1/18.
//
/*
* Copyright (c) 2005-2007 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#import "FLEXObjcInternal.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#define ALWAYS_INLINE inline __attribute__((always_inline))
#define NEVER_INLINE inline __attribute__((noinline))
// The macros below are copied straight from
// objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with
// as few modifications as possible. Changes are noted in boxed comments.
// https://opensource.apple.com/source/objc4/objc4-723/
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-private.h.auto.html
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-config.h.auto.html
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html
/////////////////////
// objc-internal.h //
/////////////////////
#if TARGET_OS_OSX && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#define _OBJC_TAG_INDEX_MASK 0x7
// array slot includes the tag bit itself
#define _OBJC_TAG_SLOT_COUNT 16
#define _OBJC_TAG_SLOT_MASK 0xf
#define _OBJC_TAG_EXT_INDEX_MASK 0xff
// array slot has no extra bits
#define _OBJC_TAG_EXT_SLOT_COUNT 256
#define _OBJC_TAG_EXT_SLOT_MASK 0xff
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
# define _OBJC_TAG_INDEX_SHIFT 60
# define _OBJC_TAG_SLOT_SHIFT 60
# define _OBJC_TAG_PAYLOAD_LSHIFT 4
# define _OBJC_TAG_PAYLOAD_RSHIFT 4
# define _OBJC_TAG_EXT_MASK (0xfUL<<60)
# define _OBJC_TAG_EXT_INDEX_SHIFT 52
# define _OBJC_TAG_EXT_SLOT_SHIFT 52
# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else
# define _OBJC_TAG_MASK 1UL
# define _OBJC_TAG_INDEX_SHIFT 1
# define _OBJC_TAG_SLOT_SHIFT 0
# define _OBJC_TAG_PAYLOAD_LSHIFT 0
# define _OBJC_TAG_PAYLOAD_RSHIFT 4
# define _OBJC_TAG_EXT_MASK 0xfUL
# define _OBJC_TAG_EXT_INDEX_SHIFT 4
# define _OBJC_TAG_EXT_SLOT_SHIFT 4
# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#endif
//////////////////////////////////////
// originally _objc_isTaggedPointer //
//////////////////////////////////////
static BOOL flex_isTaggedPointer(const void *ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
///////////////////
// objc-config.h //
///////////////////
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
#if __ARM_ARCH_7K__ >= 2
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa
// field as a maskable pointer with other data around it.
#if (!__LP64__ || TARGET_OS_WIN32 || TARGET_OS_SIMULATOR)
# define SUPPORT_PACKED_ISA 0
#else
# define SUPPORT_PACKED_ISA 1
#endif
// Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something
// in the isa field that is not a raw pointer.
#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA
# define SUPPORT_NONPOINTER_ISA 0
#else
# define SUPPORT_NONPOINTER_ISA 1
#endif
////////////////////
// objc-private.h //
////////////////////
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2
# define ISA_INDEX_IS_NPI 1
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15;
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;
# define RC_ONE (1ULL<<25)
# define RC_HALF (1ULL<<6)
};
# else
# error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif
};
///////////////////
// objc-object.h //
///////////////////
////////////////////////////////////////////////
// originally objc_object::isExtTaggedPointer //
////////////////////////////////////////////////
static BOOL flex_isExtTaggedPointer(const void *ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK;
}
struct flex_objc_object {
isa_t isa;
};
//////////////////////////////////////////////////////////
// Returns nil on platforms without nonpointer isa. //
// Supporting those platforms would be too complicated //
// for such a niche feature anyway. - @NSExceptional //
// //
// Code modified from objc_object::ISA() on 11/04/18 //
//////////////////////////////////////////////////////////
static id flex_getIsa(const flex_objc_object *object) {
#if SUPPORT_NONPOINTER_ISA
if (object->isa.nonpointer) {
return object_getClass((__bridge id)object);
}
return (__bridge Class)(void *)object->isa.bits;
#else
return nil;
#endif
}
/////////////////////////////////////
// FLEXObjectInternal //
// No Apple code beyond this point //
/////////////////////////////////////
extern "C" {
/// Assumes memory is valid and readable.
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
BOOL FLEXPointerIsValidObjcObject(const void * ptr)
{
uintptr_t pointer = (uintptr_t)ptr;
if (!ptr) {
return NO;
}
// Tagged pointers have 0x1 set, no other valid pointers do
// objc-internal.h -> _objc_isTaggedPointer()
if (flex_isTaggedPointer(ptr) || flex_isExtTaggedPointer(ptr)) {
return YES;
}
// Check pointer alignment
if ((pointer % sizeof(uintptr_t)) != 0) {
return NO;
}
// From LLDB:
// Pointers in a class_t will only have bits 0 through 46 set,
// so if any pointer has bits 47 through 63 high, we know that this is not a valid isa
// http://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
if ((pointer & 0xFFFF800000000000) != 0) {
return NO;
}
// http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html :
if (flex_getIsa((const flex_objc_object *)ptr)) {
return YES;
}
return NO;
}
}
+1
View File
@@ -18,6 +18,7 @@
+ (UIImage *)listIcon;
+ (UIImage *)moveIcon;
+ (UIImage *)selectIcon;
+ (UIImage *)checkerPattern;
+ (UIImage *)jsonIcon;
+ (UIImage *)textPlainIcon;
File diff suppressed because one or more lines are too long
+36 -1
View File
@@ -25,10 +25,44 @@ extern NSString *const kFLEXUtilityAttributeWeak;
extern NSString *const kFLEXUtilityAttributeGarbageCollectable;
extern NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding;
typedef NS_ENUM(char, FLEXTypeEncoding)
{
FLEXTypeEncodingUnknown = '?',
FLEXTypeEncodingChar = 'c',
FLEXTypeEncodingInt = 'i',
FLEXTypeEncodingShort = 's',
FLEXTypeEncodingLong = 'l',
FLEXTypeEncodingLongLong = 'q',
FLEXTypeEncodingUnsignedChar = 'C',
FLEXTypeEncodingUnsignedInt = 'I',
FLEXTypeEncodingUnsignedShort = 'S',
FLEXTypeEncodingUnsignedLong = 'L',
FLEXTypeEncodingUnsignedLongLong = 'Q',
FLEXTypeEncodingFloat = 'f',
FLEXTypeEncodingDouble = 'd',
FLEXTypeEncodingCBool = 'B',
FLEXTypeEncodingVoid = 'v',
FLEXTypeEncodingCString = '*',
FLEXTypeEncodingObjcObject = '@',
FLEXTypeEncodingObjcClass = '#',
FLEXTypeEncodingSelector = ':',
FLEXTypeEncodingArray = '[',
FLEXTypeEncodingStruct = '{',
FLEXTypeEncodingUnion = '(',
FLEXTypeEncodingBitField = 'b',
FLEXTypeEncodingPointer = '^',
FLEXTypeEncodingConst = 'r'
};
#define FLEXEncodeClass(class) ("@\"" #class "\"")
@interface FLEXRuntimeUtility : NSObject
// General Helpers
+ (BOOL)pointerIsValidObjcObject:(const void *)pointer;
/// Unwraps raw pointers to objects stored in NSValue, and re-boxes C strings into NSStrings.
+ (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType;
// Property Helpers
+ (NSString *)prettyNameForProperty:(objc_property_t)property;
+ (NSString *)typeEncodingForProperty:(objc_property_t)property;
@@ -37,7 +71,7 @@ extern NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding;
+ (NSString *)fullDescriptionForProperty:(objc_property_t)property;
+ (id)valueForProperty:(objc_property_t)property onObject:(id)object;
+ (NSString *)descriptionForIvarOrPropertyValue:(id)value;
+ (void)tryAddPropertyWithName:(const char *)name attributes:(NSDictionary *)attributePairs toClass:(__unsafe_unretained Class)theClass;
+ (void)tryAddPropertyWithName:(const char *)name attributes:(NSDictionary<NSString *, NSString *> *)attributePairs toClass:(__unsafe_unretained Class)theClass;
// Ivar Helpers
+ (NSString *)prettyNameForIvar:(Ivar)ivar;
@@ -47,6 +81,7 @@ extern NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding;
// Method Helpers
+ (NSString *)prettyNameForMethod:(Method)method isClassMethod:(BOOL)isClassMethod;
+ (NSArray *)prettyArgumentComponentsForMethod:(Method)method;
+ (FLEXTypeEncoding *)returnTypeForMethod:(Method)method;
// Method Calling/Field Editing
+ (id)performSelector:(SEL)selector onObject:(id)object withArguments:(NSArray *)arguments error:(NSError * __autoreleasing *)error;
+124 -43
View File
@@ -8,6 +8,7 @@
#import <UIKit/UIKit.h>
#import "FLEXRuntimeUtility.h"
#import "FLEXObjcInternal.h"
// See https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
NSString *const kFLEXUtilityAttributeTypeEncoding = @"T";
@@ -36,6 +37,57 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
@implementation FLEXRuntimeUtility
#pragma mark - General Helpers (Public)
+ (BOOL)pointerIsValidObjcObject:(const void *)pointer
{
return FLEXPointerIsValidObjcObject(pointer);
}
+ (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType
{
if (!returnedObjectOrNil) {
return nil;
}
NSInteger i = 0;
if (returnType[i] == FLEXTypeEncodingConst) {
i++;
}
BOOL returnsObjectOrClass = returnType[i] == FLEXTypeEncodingObjcObject ||
returnType[i] == FLEXTypeEncodingObjcClass;
BOOL returnsVoidPointer = returnType[i] == FLEXTypeEncodingPointer &&
returnType[i+1] == FLEXTypeEncodingVoid;
BOOL returnsCString = returnType[i] == FLEXTypeEncodingCString;
// If we got back an NSValue and the return type is not an object,
// we check to see if the pointer is of a valid object. If not,
// we just display the NSValue.
if (!returnsObjectOrClass) {
// Can only be NSValue since return type is not an object,
// so we bail if this doesn't add up
if (![returnedObjectOrNil isKindOfClass:[NSValue class]]) {
return returnedObjectOrNil;
}
NSValue *value = (NSValue *)returnedObjectOrNil;
if (returnsCString) {
// Wrap char * in NSString
const char *string = (const char *)value.pointerValue;
returnedObjectOrNil = [NSString stringWithCString:string encoding:NSUTF8StringEncoding];
} else if (returnsVoidPointer) {
// Cast valid objects disguised as void * to id
if ([FLEXRuntimeUtility pointerIsValidObjcObject:value.pointerValue]) {
returnedObjectOrNil = (__bridge id)value.pointerValue;
}
}
}
return returnedObjectOrNil;
}
#pragma mark - Property Helpers (Public)
+ (NSString *)prettyNameForProperty:(objc_property_t)property
@@ -48,7 +100,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
+ (NSString *)typeEncodingForProperty:(objc_property_t)property
{
NSDictionary *attributesDictionary = [self attributesDictionaryForProperty:property];
NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
return attributesDictionary[kFLEXUtilityAttributeTypeEncoding];
}
@@ -73,8 +125,8 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
+ (NSString *)fullDescriptionForProperty:(objc_property_t)property
{
NSDictionary *attributesDictionary = [self attributesDictionaryForProperty:property];
NSMutableArray *attributesStrings = [NSMutableArray array];
NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
NSMutableArray<NSString *> *attributesStrings = [NSMutableArray array];
// Atomicity
if (attributesDictionary[kFLEXUtilityAttributeNonAtomic]) {
@@ -155,10 +207,14 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
}
}
if (!description) {
// Single line display - replace newlines and tabs with spaces.
description = [[value description] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
@try {
if (!description) {
// Single line display - replace newlines and tabs with spaces.
description = [[value description] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
}
} @catch (NSException *e) {
description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"];
}
if (!description) {
@@ -168,7 +224,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
return description;
}
+ (void)tryAddPropertyWithName:(const char *)name attributes:(NSDictionary *)attributePairs toClass:(__unsafe_unretained Class)theClass
+ (void)tryAddPropertyWithName:(const char *)name attributes:(NSDictionary<NSString *, NSString *> *)attributePairs toClass:(__unsafe_unretained Class)theClass
{
objc_property_t property = class_getProperty(theClass, name);
if (!property) {
@@ -262,7 +318,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
NSString *readableReturnType = [self readableTypeForEncoding:@(returnType)];
free(returnType);
NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType];
NSArray *components = [self prettyArgumentComponentsForMethod:method];
NSArray<NSString *> *components = [self prettyArgumentComponentsForMethod:method];
if ([components count] > 0) {
prettyName = [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]];
} else {
@@ -272,25 +328,38 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
return prettyName;
}
+ (NSArray *)prettyArgumentComponentsForMethod:(Method)method
+ (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method
{
NSMutableArray *components = [NSMutableArray array];
NSMutableArray<NSString *> *components = [NSMutableArray array];
NSString *selectorName = NSStringFromSelector(method_getName(method));
NSArray *selectorComponents = [selectorName componentsSeparatedByString:@":"];
unsigned int numberOfArguments = method_getNumberOfArguments(method);
NSMutableArray<NSString *> *selectorComponents = [[selectorName componentsSeparatedByString:@":"] mutableCopy];
for (unsigned int argIndex = kFLEXNumberOfImplicitArgs; argIndex < numberOfArguments; argIndex++) {
char *argType = method_copyArgumentType(method, argIndex);
NSString *readableArgType = [self readableTypeForEncoding:@(argType)];
// this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods
if (selectorComponents.count == 1) {
return @[];
}
if ([selectorComponents.lastObject isEqualToString:@""]) {
[selectorComponents removeLastObject];
}
for (unsigned int argIndex = 0; argIndex < selectorComponents.count; argIndex++) {
char *argType = method_copyArgumentType(method, argIndex + kFLEXNumberOfImplicitArgs);
NSString *readableArgType = (argType != NULL) ? [self readableTypeForEncoding:@(argType)] : nil;
free(argType);
NSString *prettyComponent = [NSString stringWithFormat:@"%@:(%@) ", [selectorComponents objectAtIndex:argIndex - kFLEXNumberOfImplicitArgs], readableArgType];
NSString *prettyComponent = [NSString stringWithFormat:@"%@:(%@) ", [selectorComponents objectAtIndex:argIndex], readableArgType];
[components addObject:prettyComponent];
}
return components;
}
+ (FLEXTypeEncoding *)returnTypeForMethod:(Method)method
{
return (FLEXTypeEncoding *)method_copyReturnType(method);
}
#pragma mark - Method Calling/Field Editing (Public)
@@ -299,7 +368,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
// Bail if the object won't respond to this selector.
if (![object respondsToSelector:selector]) {
if (error) {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@ does not respond to the selector %@", object, NSStringFromSelector(selector)]};
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@ does not respond to the selector %@", object, NSStringFromSelector(selector)]};
*error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector userInfo:userInfo];
}
return nil;
@@ -335,59 +404,71 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
// Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature
if (strcmp([argumentValue objCType], typeEncodingCString) != 0) {
if (error) {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Type encoding mismatch for agrument at index %lu. Value type: %s; Method argument type: %s.", (unsigned long)argumentsArrayIndex, [argumentValue objCType], typeEncodingCString]};
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Type encoding mismatch for agrument at index %lu. Value type: %s; Method argument type: %s.", (unsigned long)argumentsArrayIndex, [argumentValue objCType], typeEncodingCString]};
*error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch userInfo:userInfo];
}
return nil;
}
NSUInteger bufferSize = 0;
@try {
NSUInteger bufferSize = 0;
// NSGetSizeAndAlignment barfs on type encoding for bitfields.
NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
if (bufferSize > 0) {
void *buffer = calloc(bufferSize, 1);
[argumentValue getValue:buffer];
[invocation setArgument:buffer atIndex:argumentIndex];
free(buffer);
}
} @catch (NSException *exception) { }
if (bufferSize > 0) {
void *buffer = calloc(bufferSize, 1);
[argumentValue getValue:buffer];
[invocation setArgument:buffer atIndex:argumentIndex];
free(buffer);
}
}
}
}
// Try to invoke the invocation but guard against an exception being thrown.
BOOL successfullyInvoked = NO;
id returnObject = nil;
@try {
// Some methods are not fit to be called...
// Looking at you -[UIResponder(UITextInputAdditions) _caretRect]
[invocation invoke];
successfullyInvoked = YES;
} @catch (NSException *exception) {
// Bummer...
if (error) {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Exception thrown while performing selector %@ on object %@", NSStringFromSelector(selector), object]};
*error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeInvocationFailed userInfo:userInfo];
}
}
// Retreive the return value and box if necessary.
id returnObject = nil;
if (successfullyInvoked) {
// Retreive the return value and box if necessary.
const char *returnType = [methodSignature methodReturnType];
if (returnType[0] == @encode(id)[0] || returnType[0] == @encode(Class)[0]) {
// Return value is an object.
__unsafe_unretained id objectReturnedFromMethod = nil;
[invocation getReturnValue:&objectReturnedFromMethod];
returnObject = objectReturnedFromMethod;
} else if (returnType[0] != @encode(void)[0]) {
// Will use arbitrary buffer for return value and box it.
void *returnValue = malloc([methodSignature methodReturnLength]);
if (returnValue) {
[invocation getReturnValue:returnValue];
returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
free(returnValue);
}
}
} @catch (NSException *exception) {
// Bummer...
if (error) {
// "… on <class>" / "… on instance of <class>"
NSString *class = NSStringFromClass([object class]);
NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class];
NSString *message = [NSString stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@",
exception.name,
NSStringFromSelector(selector),
calledOn,
exception.reason];
*error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain
code:FLEXRuntimeUtilityErrorCodeInvocationFailed
userInfo:@{ NSLocalizedDescriptionKey : message }];
}
}
return returnObject;
@@ -550,12 +631,12 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
#pragma mark - Internal Helpers
+ (NSDictionary *)attributesDictionaryForProperty:(objc_property_t)property
+ (NSDictionary<NSString *, NSString *> *)attributesDictionaryForProperty:(objc_property_t)property
{
NSString *attributes = @(property_getAttributes(property));
// Thanks to MAObjcRuntime for inspiration here.
NSArray *attributePairs = [attributes componentsSeparatedByString:@","];
NSMutableDictionary *attributesDictionary = [NSMutableDictionary dictionaryWithCapacity:[attributePairs count]];
NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","];
NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary dictionaryWithCapacity:[attributePairs count]];
for (NSString *attributePair in attributePairs) {
[attributesDictionary setObject:[attributePair substringFromIndex:1] forKey:[attributePair substringToIndex:1]];
}
+11 -2
View File
@@ -6,12 +6,20 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import <Availability.h>
#import <AvailabilityInternal.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#define FLEXFloor(x) (floor([[UIScreen mainScreen] scale] * (x)) / [[UIScreen mainScreen] scale])
#if defined(__IPHONE_11_0)
#define FLEX_AT_LEAST_IOS11_SDK (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
#else
#define FLEX_AT_LEAST_IOS11_SDK NO
#endif
@interface FLEXUtility : NSObject
+ (UIColor *)consistentRandomColorForObject:(id)object;
@@ -35,12 +43,13 @@
+ (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data;
+ (NSString *)stringFromRequestDuration:(NSTimeInterval)duration;
+ (NSString *)statusCodeStringFromURLResponse:(NSURLResponse *)response;
+ (NSDictionary *)dictionaryFromQuery:(NSString *)query;
+ (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response;
+ (NSDictionary<NSString *, id> *)dictionaryFromQuery:(NSString *)query;
+ (NSString *)prettyJSONStringFromData:(NSData *)data;
+ (BOOL)isValidJSONData:(NSData *)data;
+ (NSData *)inflatedDataFromCompressedData:(NSData *)compressedData;
+ (NSArray *)allWindows;
+ (NSArray<UIWindow *> *)allWindows;
// Swizzling utilities
+25 -13
View File
@@ -138,7 +138,7 @@
+ (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString
{
static NSDictionary *escapingDictionary = nil;
static NSDictionary<NSString *, NSString *> *escapingDictionary = nil;
static NSRegularExpression *regex = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -156,7 +156,7 @@
NSMutableString *mutableString = [originalString mutableCopy];
NSArray *matches = [regex matchesInString:mutableString options:0 range:NSMakeRange(0, [mutableString length])];
NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:mutableString options:0 range:NSMakeRange(0, [mutableString length])];
for (NSTextCheckingResult *result in [matches reverseObjectEnumerator]) {
NSString *foundString = [mutableString substringWithRange:result.range];
NSString *replacementString = escapingDictionary[foundString];
@@ -170,7 +170,7 @@
+ (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask
{
NSArray *supportedOrientations = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"];
NSArray<NSString *> *supportedOrientations = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"];
UIInterfaceOrientationMask supportedOrientationsMask = 0;
if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortrait"]) {
supportedOrientationsMask |= UIInterfaceOrientationMaskPortrait;
@@ -203,9 +203,9 @@
UIImage *thumbnail = nil;
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, 0);
if (imageSource) {
NSDictionary *options = @{ (__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(dimension) };
NSDictionary<NSString *, id> *options = @{ (__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(dimension) };
CGImageRef scaledImageRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
if (scaledImageRef) {
@@ -249,15 +249,27 @@
return httpResponseString;
}
+ (NSDictionary *)dictionaryFromQuery:(NSString *)query
+ (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response {
NSIndexSet *errorStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(400, 200)];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return [errorStatusCodes containsIndex:httpResponse.statusCode];
}
return NO;
}
+ (NSDictionary<NSString *, id> *)dictionaryFromQuery:(NSString *)query
{
NSMutableDictionary *queryDictionary = [NSMutableDictionary dictionary];
NSMutableDictionary<NSString *, id> *queryDictionary = [NSMutableDictionary dictionary];
// [a=1, b=2, c=3]
NSArray *queryComponents = [query componentsSeparatedByString:@"&"];
NSArray<NSString *> *queryComponents = [query componentsSeparatedByString:@"&"];
for (NSString *keyValueString in queryComponents) {
// [a, 1]
NSArray *components = [keyValueString componentsSeparatedByString:@"="];
NSArray<NSString *> *components = [keyValueString componentsSeparatedByString:@"="];
if ([components count] == 2) {
NSString *key = [[components firstObject] stringByRemovingPercentEncoding];
id value = [[components lastObject] stringByRemovingPercentEncoding];
@@ -338,12 +350,12 @@
return inflatedData;
}
+ (NSArray *)allWindows
+ (NSArray<UIWindow *> *)allWindows
{
BOOL includeInternalWindows = YES;
BOOL onlyVisibleWindows = NO;
NSArray *allWindowsComponents = @[@"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:"];
NSArray<NSString *> *allWindowsComponents = @[@"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:"];
SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
@@ -355,7 +367,7 @@
[invocation setArgument:&onlyVisibleWindows atIndex:3];
[invocation invoke];
__unsafe_unretained NSArray *windows = nil;
__unsafe_unretained NSArray<UIWindow *> *windows = nil;
[invocation getReturnValue:&windows];
return windows;
}
@@ -14,5 +14,6 @@
@property (nonatomic, assign) NSInteger viewDepth;
@property (nonatomic, strong) UIColor *viewColor;
@property (nonatomic, strong) UIView *viewBackgroundColorView;
@end
@@ -38,25 +38,37 @@
self.textLabel.font = [UIFont fontWithName:@"HelveticaNeue-Medium" size:14.0];
self.detailTextLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
self.accessoryType = UITableViewCellAccessoryDetailButton;
self.viewBackgroundColorView = [[UIView alloc] init];
self.viewBackgroundColorView.clipsToBounds = YES;
self.viewBackgroundColorView.layer.borderColor = [UIColor blackColor].CGColor;
self.viewBackgroundColorView.layer.borderWidth = 1.0f;
[self.contentView addSubview:self.viewBackgroundColorView];
}
return self;
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
UIColor *originalColour = self.viewBackgroundColorView.backgroundColor;
[super setHighlighted:highlighted animated:animated];
// UITableViewCell changes all subviews in the contentView to backgroundColor = clearColor.
// We want to preserve the hierarchy background color when highlighted.
self.depthIndicatorView.backgroundColor = [FLEXUtility hierarchyIndentPatternColor];
self.viewBackgroundColorView.backgroundColor = originalColour;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
UIColor *originalColour = self.viewBackgroundColorView.backgroundColor;
[super setSelected:selected animated:animated];
// See setHighlighted above.
self.depthIndicatorView.backgroundColor = [FLEXUtility hierarchyIndentPatternColor];
self.viewBackgroundColorView.backgroundColor = originalColour;
}
- (void)layoutSubviews
@@ -65,6 +77,7 @@
const CGFloat kContentPadding = 10.0;
const CGFloat kDepthIndicatorWidthMultiplier = 4.0;
const CGFloat kViewBackgroundColourDimension = 20;
CGRect depthIndicatorFrame = CGRectMake(kContentPadding, 0, self.viewDepth * kDepthIndicatorWidthMultiplier, self.contentView.bounds.size.height);
self.depthIndicatorView.frame = depthIndicatorFrame;
@@ -77,7 +90,7 @@
CGRect textLabelFrame = self.textLabel.frame;
CGFloat textOriginX = CGRectGetMaxX(circleFrame) + 4.0;
textLabelFrame.origin.x = textOriginX;
textLabelFrame.size.width = CGRectGetMaxX(self.contentView.bounds) - kContentPadding - textOriginX;
textLabelFrame.size.width = CGRectGetMaxX(self.contentView.frame) - kContentPadding - textOriginX - kViewBackgroundColourDimension;
self.textLabel.frame = textLabelFrame;
CGRect detailTextLabelFrame = self.detailTextLabel.frame;
@@ -85,6 +98,15 @@
detailTextLabelFrame.origin.x = detailOriginX;
detailTextLabelFrame.size.width = CGRectGetMaxX(self.contentView.bounds) - kContentPadding - detailOriginX;
self.detailTextLabel.frame = detailTextLabelFrame;
CGRect viewBackgroundColourViewFrame = self.textLabel.frame;
viewBackgroundColourViewFrame.size.width = kViewBackgroundColourDimension;
viewBackgroundColourViewFrame.size.height = kViewBackgroundColourDimension;
viewBackgroundColourViewFrame.origin.x = CGRectGetMaxX(self.textLabel.frame) + kContentPadding;
viewBackgroundColourViewFrame.origin.y = ABS(CGRectGetHeight(self.contentView.frame) - CGRectGetHeight(viewBackgroundColourViewFrame)) / 2;
self.viewBackgroundColorView.frame = viewBackgroundColourViewFrame;
self.viewBackgroundColorView.layer.cornerRadius = kViewBackgroundColourDimension / 2;
}
- (void)setViewColor:(UIColor *)viewColor
@@ -12,7 +12,7 @@
@interface FLEXHierarchyTableViewController : UITableViewController
- (id)initWithViews:(NSArray *)allViews viewsAtTap:(NSArray *)viewsAtTap selectedView:(UIView *)selectedView depths:(NSDictionary *)depthsForViews;
- (instancetype)initWithViews:(NSArray<UIView *> *)allViews viewsAtTap:(NSArray<UIView *> *)viewsAtTap selectedView:(UIView *)selectedView depths:(NSDictionary<NSValue *, NSNumber *> *)depthsForViews;
@property (nonatomic, weak) id <FLEXHierarchyTableViewControllerDelegate> delegate;
@@ -11,17 +11,18 @@
#import "FLEXHierarchyTableViewCell.h"
#import "FLEXObjectExplorerViewController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXResources.h"
static const NSInteger kFLEXHierarchyScopeViewsAtTapIndex = 0;
static const NSInteger kFLEXHierarchyScopeFullHierarchyIndex = 1;
@interface FLEXHierarchyTableViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSArray *allViews;
@property (nonatomic, strong) NSDictionary *depthsForViews;
@property (nonatomic, strong) NSArray *viewsAtTap;
@property (nonatomic, strong) NSArray<UIView *> *allViews;
@property (nonatomic, strong) NSDictionary<NSValue *, NSNumber *> *depthsForViews;
@property (nonatomic, strong) NSArray<UIView *> *viewsAtTap;
@property (nonatomic, strong) UIView *selectedView;
@property (nonatomic, strong) NSArray *displayedViews;
@property (nonatomic, strong) NSArray<UIView *> *displayedViews;
@property (nonatomic, strong) UISearchBar *searchBar;
@@ -29,7 +30,7 @@ static const NSInteger kFLEXHierarchyScopeFullHierarchyIndex = 1;
@implementation FLEXHierarchyTableViewController
- (id)initWithViews:(NSArray *)allViews viewsAtTap:(NSArray *)viewsAtTap selectedView:(UIView *)selectedView depths:(NSDictionary *)depthsForViews
- (instancetype)initWithViews:(NSArray<UIView *> *)allViews viewsAtTap:(NSArray<UIView *> *)viewsAtTap selectedView:(UIView *)selectedView depths:(NSDictionary<NSValue *, NSNumber *> *)depthsForViews
{
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
@@ -92,7 +93,7 @@ static const NSInteger kFLEXHierarchyScopeFullHierarchyIndex = 1;
- (void)updateDisplayedViews
{
NSArray *candidateViews = nil;
NSArray<UIView *> *candidateViews = nil;
if ([self showScopeBar]) {
if (self.searchBar.selectedScopeButtonIndex == kFLEXHierarchyScopeViewsAtTapIndex) {
candidateViews = self.viewsAtTap;
@@ -104,7 +105,7 @@ static const NSInteger kFLEXHierarchyScopeFullHierarchyIndex = 1;
}
if ([self.searchBar.text length] > 0) {
self.displayedViews = [candidateViews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *candidateView, NSDictionary *bindings) {
self.displayedViews = [candidateViews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *candidateView, NSDictionary<NSString *, id> *bindings) {
NSString *title = [FLEXUtility descriptionForView:candidateView includingFrame:NO];
NSString *candidateViewPointerAddress = [NSString stringWithFormat:@"%p", candidateView];
BOOL matchedViewPointerAddress = [candidateViewPointerAddress rangeOfString:self.searchBar.text options:NSCaseInsensitiveSearch].location != NSNotFound;
@@ -180,6 +181,20 @@ static const NSInteger kFLEXHierarchyScopeFullHierarchyIndex = 1;
cell.detailTextLabel.textColor = [UIColor blackColor];
}
// Use a pattern-based colour to simplify application of the checker pattern.
static UIColor *checkerPatternColour = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
checkerPatternColour = [UIColor colorWithPatternImage:[FLEXResources checkerPattern]];
});
UIColor *viewColour = view.backgroundColor;
if (!viewColour || [viewColour isEqual:[UIColor clearColor]]) {
cell.viewBackgroundColorView.backgroundColor = checkerPatternColour;
} else {
cell.viewBackgroundColorView.backgroundColor = viewColour;
}
return cell;
}
@@ -47,7 +47,7 @@
self.scrollView.maximumZoomScale = 2.0;
[self.view addSubview:self.scrollView];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actionButtonPressed:)];
}
- (void)viewDidLayoutSubviews
@@ -78,9 +78,31 @@
self.scrollView.contentInset = UIEdgeInsetsMake(verticalInset, horizontalInset, verticalInset, horizontalInset);
}
- (void)copyButtonPressed:(id)sender
- (void)actionButtonPressed:(id)sender
{
[[UIPasteboard generalPasteboard] setImage:self.image];
static BOOL CanSaveToCameraRoll = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([UIDevice currentDevice].systemVersion.floatValue < 10) {
CanSaveToCameraRoll = YES;
return;
}
NSBundle *mainBundle = [NSBundle mainBundle];
if ([mainBundle.infoDictionary.allKeys containsObject:@"NSPhotoLibraryUsageDescription"]) {
CanSaveToCameraRoll = YES;
} else {
NSLog(@"Add NSPhotoLibraryUsageDescription in app's Info.plist for saving captured image into camera roll.");
}
});
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.image] applicationActivities:@[]];
if (!CanSaveToCameraRoll) {
activityVC.excludedActivityTypes = @[UIActivityTypeSaveToCameraRoll];
}
[self presentViewController:activityVC animated:YES completion:nil];
}
@end
+27 -1
View File
@@ -332,7 +332,7 @@
5356823218F3656900BAAD62 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0700;
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = f;
};
buildConfigurationList = 5356823518F3656900BAAD62 /* Build configuration list for PBXProject "UICatalog" */;
@@ -444,19 +444,32 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -484,18 +497,31 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -49,6 +49,9 @@
@implementation AAPLSplitViewControllerDelegate
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
#pragma mark - UISplitViewControllerDelegate
// Implementing this delegate method allows us to present the detail view controller with the "More" bar button item.
@@ -75,4 +78,6 @@
[detailRootViewController.navigationItem setLeftBarButtonItem:nil animated:YES];
}
#pragma clang diagnostic pop
@end
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "FLEX"
spec.version = "2.3.0"
spec.version = "2.4.0"
spec.summary = "A set of in-app debugging and exploration tools for iOS"
spec.description = <<-DESC
- Inspect and modify views in the hierarchy.
+169 -3
View File
@@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
04F1CA191C137CF1000A52B0 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 04F1CA181C137CF1000A52B0 /* LICENSE */; };
1C27A8B91F0E5A0400F0D02D /* FLEXTestsMethodsList.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C27A8B81F0E5A0400F0D02D /* FLEXTestsMethodsList.m */; };
1C27A8BB1F0E5A0400F0D02D /* FLEX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A4C941F1B5B20570088C3F2 /* FLEX.framework */; };
222C88221C7339DC007CA15F /* FLEXRealmDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 222C88211C7339DC007CA15F /* FLEXRealmDefines.h */; };
224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 224D49A41C673AB5000EAB86 /* FLEXRealmDatabaseManager.h */; };
224D49A91C673AB5000EAB86 /* FLEXRealmDatabaseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 224D49A51C673AB5000EAB86 /* FLEXRealmDatabaseManager.m */; };
@@ -164,10 +166,31 @@
94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */; };
94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */; };
C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */; };
C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */; };
C395D6DA21789BD800BEAD4D /* FLEXColorExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */; };
C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */; };
C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */; };
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
1C27A8BC1F0E5A0400F0D02D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 3A4C94161B5B20570088C3F2 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 3A4C941E1B5B20570088C3F2;
remoteInfo = FLEX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
04F1CA181C137CF1000A52B0 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
1C27A8B61F0E5A0300F0D02D /* FLEXTestsMethodsList.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FLEXTestsMethodsList.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1C27A8B81F0E5A0400F0D02D /* FLEXTestsMethodsList.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTestsMethodsList.m; sourceTree = "<group>"; };
1C27A8BA1F0E5A0400F0D02D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
222C88211C7339DC007CA15F /* FLEXRealmDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXRealmDefines.h; sourceTree = "<group>"; };
224D49A41C673AB5000EAB86 /* FLEXRealmDatabaseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXRealmDatabaseManager.h; sourceTree = "<group>"; };
224D49A51C673AB5000EAB86 /* FLEXRealmDatabaseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXRealmDatabaseManager.m; sourceTree = "<group>"; };
@@ -327,9 +350,25 @@
94A515241C4CA2080063292F /* FLEXToolbarItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLEXToolbarItem.m; path = Classes/Toolbar/FLEXToolbarItem.m; sourceTree = SOURCE_ROOT; };
94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardHelpViewController.h; sourceTree = "<group>"; };
94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeyboardHelpViewController.m; sourceTree = "<group>"; };
C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjcInternal.h; sourceTree = "<group>"; };
C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEXObjcInternal.mm; sourceTree = "<group>"; };
C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorExplorerViewController.h; sourceTree = "<group>"; };
C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXColorExplorerViewController.m; sourceTree = "<group>"; };
C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMutableFieldEditorViewController.h; sourceTree = "<group>"; };
C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXMutableFieldEditorViewController.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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1C27A8B31F0E5A0300F0D02D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1C27A8BB1F0E5A0400F0D02D /* FLEX.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
3A4C941B1B5B20570088C3F2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -342,10 +381,20 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1C27A8B71F0E5A0400F0D02D /* FLEXTestsMethodsList */ = {
isa = PBXGroup;
children = (
1C27A8B81F0E5A0400F0D02D /* FLEXTestsMethodsList.m */,
1C27A8BA1F0E5A0400F0D02D /* Info.plist */,
);
path = FLEXTestsMethodsList;
sourceTree = "<group>";
};
3A4C94151B5B20570088C3F2 = {
isa = PBXGroup;
children = (
3A4C94211B5B20570088C3F2 /* FLEX */,
1C27A8B71F0E5A0400F0D02D /* FLEXTestsMethodsList */,
3A4C95451B5B216C0088C3F2 /* Frameworks */,
3A4C94201B5B20570088C3F2 /* Products */,
);
@@ -355,6 +404,7 @@
isa = PBXGroup;
children = (
3A4C941F1B5B20570088C3F2 /* FLEX.framework */,
1C27A8B61F0E5A0300F0D02D /* FLEXTestsMethodsList.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -414,6 +464,8 @@
3A4C94511B5B21410088C3F2 /* FLEXViewControllerExplorerViewController.m */,
3A4C94521B5B21410088C3F2 /* FLEXViewExplorerViewController.h */,
3A4C94531B5B21410088C3F2 /* FLEXViewExplorerViewController.m */,
C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */,
C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */,
);
path = ObjectExplorers;
sourceTree = "<group>";
@@ -427,6 +479,8 @@
3A4C94581B5B21410088C3F2 /* FLEXMultilineTableViewCell.m */,
3A4C94591B5B21410088C3F2 /* FLEXResources.h */,
3A4C945A1B5B21410088C3F2 /* FLEXResources.m */,
C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */,
C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */,
3A4C945B1B5B21410088C3F2 /* FLEXRuntimeUtility.h */,
3A4C945C1B5B21410088C3F2 /* FLEXRuntimeUtility.m */,
3A4C945D1B5B21410088C3F2 /* FLEXUtility.h */,
@@ -462,6 +516,8 @@
3A4C94851B5B21410088C3F2 /* FLEXFieldEditorView.m */,
3A4C94861B5B21410088C3F2 /* FLEXFieldEditorViewController.h */,
3A4C94871B5B21410088C3F2 /* FLEXFieldEditorViewController.m */,
C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */,
C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */,
3A4C94881B5B21410088C3F2 /* FLEXIvarEditorViewController.h */,
3A4C94891B5B21410088C3F2 /* FLEXIvarEditorViewController.m */,
3A4C948A1B5B21410088C3F2 /* FLEXMethodCallingViewController.h */,
@@ -513,8 +569,7 @@
94A515231C4CA2080063292F /* FLEXToolbarItem.h */,
94A515241C4CA2080063292F /* FLEXToolbarItem.m */,
);
name = Toolbar;
path = ExplorerToolbar;
path = Toolbar;
sourceTree = "<group>";
};
3A4C949A1B5B21410088C3F2 /* GlobalStateExplorers */ = {
@@ -533,6 +588,8 @@
3A4C94A41B5B21410088C3F2 /* FLEXGlobalsTableViewController.m */,
3A4C94A51B5B21410088C3F2 /* FLEXInstancesTableViewController.h */,
3A4C94A61B5B21410088C3F2 /* FLEXInstancesTableViewController.m */,
C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */,
C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */,
3A4C94A71B5B21410088C3F2 /* FLEXLibrariesTableViewController.h */,
3A4C94A81B5B21410088C3F2 /* FLEXLibrariesTableViewController.m */,
3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h */,
@@ -656,6 +713,7 @@
779B1EDA1C0C4D7C001F5E49 /* FLEXTableListViewController.h in Headers */,
3A4C94ED1B5B21410088C3F2 /* FLEXArgumentInputColorView.h in Headers */,
3A4C94EB1B5B21410088C3F2 /* FLEXImagePreviewViewController.h in Headers */,
C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */,
3A4C95381B5B21410088C3F2 /* FLEXNetworkRecorder.h in Headers */,
3A4C94251B5B20570088C3F2 /* FLEX.h in Headers */,
3A4C95051B5B21410088C3F2 /* FLEXArgumentInputViewFactory.h in Headers */,
@@ -665,6 +723,7 @@
3A4C94FD1B5B21410088C3F2 /* FLEXArgumentInputStructView.h in Headers */,
94A515141C4CA1C00063292F /* FLEXManager.h in Headers */,
3A4C95201B5B21410088C3F2 /* FLEXFileBrowserFileOperationController.h in Headers */,
C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */,
3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */,
3A4C95361B5B21410088C3F2 /* FLEXNetworkHistoryTableViewController.h in Headers */,
@@ -725,10 +784,12 @@
3A4C94FB1B5B21410088C3F2 /* FLEXArgumentInputStringView.h in Headers */,
3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */,
679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */,
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */,
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h in Headers */,
3A4C95241B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h in Headers */,
94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */,
94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */,
C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */,
94A515181C4CA1D70063292F /* FLEXManager+Private.h in Headers */,
3A4C94E11B5B21410088C3F2 /* FLEXResources.h in Headers */,
779B1ED81C0C4D7C001F5E49 /* FLEXTableLeftCell.h in Headers */,
@@ -738,6 +799,24 @@
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
1C27A8B51F0E5A0300F0D02D /* FLEXTestsMethodsList */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1C27A8C01F0E5A0400F0D02D /* Build configuration list for PBXNativeTarget "FLEXTestsMethodsList" */;
buildPhases = (
1C27A8B21F0E5A0300F0D02D /* Sources */,
1C27A8B31F0E5A0300F0D02D /* Frameworks */,
1C27A8B41F0E5A0300F0D02D /* Resources */,
);
buildRules = (
);
dependencies = (
1C27A8BD1F0E5A0400F0D02D /* PBXTargetDependency */,
);
name = FLEXTestsMethodsList;
productName = FLEXTestsMethodsList;
productReference = 1C27A8B61F0E5A0300F0D02D /* FLEXTestsMethodsList.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
3A4C941E1B5B20570088C3F2 /* FLEX */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3A4C94351B5B20570088C3F2 /* Build configuration list for PBXNativeTarget "FLEX" */;
@@ -763,9 +842,12 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = FLEX;
LastUpgradeCheck = 0800;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = Flipboard;
TargetAttributes = {
1C27A8B51F0E5A0300F0D02D = {
DevelopmentTeam = U3LST7M92S;
};
3A4C941E1B5B20570088C3F2 = {
CreatedOnToolsVersion = 6.4;
};
@@ -784,11 +866,19 @@
projectRoot = "";
targets = (
3A4C941E1B5B20570088C3F2 /* FLEX */,
1C27A8B51F0E5A0300F0D02D /* FLEXTestsMethodsList */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
1C27A8B41F0E5A0300F0D02D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
3A4C941D1B5B20570088C3F2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -800,6 +890,14 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1C27A8B21F0E5A0300F0D02D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1C27A8B91F0E5A0400F0D02D /* FLEXTestsMethodsList.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
3A4C941A1B5B20570088C3F2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -818,6 +916,7 @@
94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */,
3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */,
3A4C94C61B5B21410088C3F2 /* FLEXArrayExplorerViewController.m in Sources */,
C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */,
3A4C95081B5B21410088C3F2 /* FLEXDefaultEditorViewController.m in Sources */,
779B1ED11C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m in Sources */,
3A4C94EE1B5B21410088C3F2 /* FLEXArgumentInputColorView.m in Sources */,
@@ -855,12 +954,15 @@
3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
3A4C95331B5B21410088C3F2 /* FLEXSystemLogTableViewCell.m in Sources */,
C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */,
3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,
3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
C395D6DA21789BD800BEAD4D /* FLEXColorExplorerViewController.m in Sources */,
3A4C95001B5B21410088C3F2 /* FLEXArgumentInputSwitchView.m in Sources */,
3A4C94CC1B5B21410088C3F2 /* FLEXDictionaryExplorerViewController.m in Sources */,
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m in Sources */,
@@ -884,7 +986,46 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
1C27A8BD1F0E5A0400F0D02D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 3A4C941E1B5B20570088C3F2 /* FLEX */;
targetProxy = 1C27A8BC1F0E5A0400F0D02D /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
1C27A8BE1F0E5A0400F0D02D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = U3LST7M92S;
INFOPLIST_FILE = FLEXTestsMethodsList/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEXTestsMethodsList;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
1C27A8BF1F0E5A0400F0D02D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
DEVELOPMENT_TEAM = U3LST7M92S;
INFOPLIST_FILE = FLEXTestsMethodsList/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEXTestsMethodsList;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
3A4C94331B5B20570088C3F2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -893,14 +1034,22 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@@ -944,14 +1093,22 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@@ -1025,6 +1182,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1C27A8C01F0E5A0400F0D02D /* Build configuration list for PBXNativeTarget "FLEXTestsMethodsList" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1C27A8BE1F0E5A0400F0D02D /* Debug */,
1C27A8BF1F0E5A0400F0D02D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
3A4C94191B5B20570088C3F2 /* Build configuration list for PBXProject "FLEX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
+101
View File
@@ -0,0 +1,101 @@
//
// FLEXTestMethodList.m
// FLEXTestMethodList
//
// Created by Tigran Yesayan on 7/6/17.
// Copyright © 2017 Flipboard. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "FLEXRuntimeUtility.h"
#import "FLEXManager.h"
#import "FLEXWindow.h"
#import "FLEXMultiColumnTableView.h"
@interface FLEXRuntimeUtility (Testing)
+ (NSString *)readableTypeForEncoding:(NSString *)encodingString;
@end
@interface FLEXTestMethodList : XCTestCase
@end
@implementation FLEXTestMethodList
- (void)testExample {
NSArray<Class> *classesToTest = @[
[NSObject class],
[NSArray class],
[UIApplication class],
[UIView class],
[NSThread class],
[CALayer class],
[NSDictionary class],
[NSProxy class],
[NSData class],
[FLEXManager class],
[FLEXWindow class],
[FLEXMultiColumnTableView class],
[NSString class],
[NSSet class],
[NSUndoManager class],
[NSMutableArray class],
[NSMutableDictionary class],
[NSException class],
[UIImage class],
[UIViewController class],
[UIScreen class],
[UIResponder class],
[NSNumber class],
[NSValue class],
[NSError class],
[NSNotificationCenter class],
[NSUserActivity class],
[NSUserDefaults class],
[NSExpression class],
[NSBundle class]
];
for (Class cls in classesToTest) {
[self testMethodListForClass:cls];
}
}
- (void)testMethodListForClass:(Class)class {
NSLog(@"class: %@", NSStringFromClass(class));
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(class, &methodCount);
for (unsigned int i = 0; i < methodCount; ++i) {
Method method = methods[i];
NSArray *prevWay = [self prettyArgumentComponentsForMethod:method];
NSArray *newWay = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:method];
XCTAssertEqualObjects(prevWay, newWay);
}
free(methods);
}
#pragma mark - Method to test with
- (NSArray *)prettyArgumentComponentsForMethod:(Method)method {
NSMutableArray *components = [NSMutableArray array];
NSString *selectorName = NSStringFromSelector(method_getName(method));
NSArray *selectorComponents = [selectorName componentsSeparatedByString:@":"];
unsigned int numberOfArguments = method_getNumberOfArguments(method);
for (unsigned int argIndex = kFLEXNumberOfImplicitArgs; argIndex < numberOfArguments; argIndex++) {
char *argType = method_copyArgumentType(method, argIndex);
NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)];
free(argType);
NSString *prettyComponent = [NSString stringWithFormat:@"%@:(%@) ", [selectorComponents objectAtIndex:argIndex - kFLEXNumberOfImplicitArgs], readableArgType];
[components addObject:prettyComponent];
}
return components;
}
@end
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

+54 -7
View File
@@ -37,9 +37,15 @@ In the iOS simulator, you can use keyboard shortcuts to activate FLEX. `f` will
Short version:
```objc
// Objective-C
[[FLEXManager sharedManager] showExplorer];
```
```swift
// Swift
FLEXManager.shared().showExplorer()
```
More complete version:
```objc
@@ -114,18 +120,61 @@ The code injection is left as an exercise for the reader. :innocent:
## Installation
FLEX is available on [CocoaPods](http://cocoapods.org/?q=FLEX). Simply add the following line to your podfile:
FLEX requires an app that targets iOS 7 or higher.
### CocoaPods
FLEX is available on [CocoaPods](https://cocoapods.org/pods/FLEX). Simply add the following line to your podfile:
```ruby
pod 'FLEX', '~> 2.0', :configurations => ['Debug']
```
Alternatively, you can manually add the files in `Classes/` to your Xcode project. FLEX requires iOS 7 or higher.
### Carthage
Add the following to your Cartfile:
```
github "flipboard/FLEX" ~> 2.0
```
### Manual
Manually add the files in `Classes/` to your Xcode project.
## Excluding FLEX from Release (App Store) Builds
*Note: CocoaPods handles this automatically if you only specify the Debug configuration for FLEX in your Podfile.*
FLEX makes it easy to explore the internals of your app, so it is not something you should expose to your users. Fortunately, it is easy to exclude FLEX files from Release builds. In Xcode, navigate to the "Build Settings" tab of your project. Click the plus and select `Add User-Defined Setting`.
FLEX makes it easy to explore the internals of your app, so it is not something you should expose to your users. Fortunately, it is easy to exclude FLEX files from Release builds. The strategies differ depending on how you integrated FLEX in your project, and are described below.
At the places in your code where you integrate FLEX, do a `#if DEBUG` check to ensure the tool is only accessible in your `Debug` builds and to avoid errors in your `Release` builds. For more help with integrating FLEX, see the example project.
### FLEX added with CocoaPods
CocoaPods automatically excludes FLEX from release builds if you only specify the Debug configuration for FLEX in your Podfile.
### FLEX added with Carthage
If you are using Carthage, only including the `FLEX.framework` in debug builds is easy:
1. Do NOT add `FLEX.framework` to the embedded binaries of your target, as it would otherwise be included in all builds (therefore also in release ones).
1. Instead, add `$(PROJECT_DIR)/Carthage/Build/iOS` to your target _Framework Search Paths_ (this setting might already be present if you already included other frameworks with Carthage). This makes it possible to import the FLEX framework from your source files. It does not harm if this setting is added for all configurations, but it should at least be added for the debug one.
1. Add a _Run Script Phase_ to your target (inserting it after the existing `Link Binary with Libraries` phase, for example), and which will embed `FLEX.framework` in debug builds only:
```shell
if [ "$CONFIGURATION" == "Debug" ]; then
/usr/local/bin/carthage copy-frameworks
fi
```
Finally, add `$(SRCROOT)/Carthage/Build/iOS/FLEX.framework` as input file of this script phase.
<p align="center"><img src="README-images/flex-exclusion-carthage.jpg"/></p>
### FLEX files added manually to a project
In Xcode, navigate to the "Build Settings" tab of your project. Click the plus and select `Add User-Defined Setting`.
![Add User-Defined Setting](http://engineering.flipboard.com/assets/flex/flex-readme-exclude-1.png)
@@ -133,10 +182,8 @@ Name the setting `EXCLUDED_SOURCE_FILE_NAMES`. For your `Release` configuration,
![EXCLUDED_SOURCE_FILE_NAMES](http://engineering.flipboard.com/assets/flex/flex-readme-exclude-2.png)
At the places in your code where you integrate FLEX, do a `#if DEBUG` check to ensure the tool is only accessible in your `Debug` builds and to avoid errors in your `Release` builds. For more help with integrating FLEX, see the example project.
## Additional Notes
- When setting fields of type `id` or values in `NSUserDefaults`, FLEX attempts to parse the input string as `JSON`. This allows you to use a combination of strings, numbers, arrays, and dictionaries. If you want to set a string value, it must be wrapped in quotes. For ivars or properties that are explicitly typed as `NSStrings`, quotes are not required.
- You may want to disable the exception breakpoint while using FLEX. Certain functions that FLEX uses throw exceptions when they get input they can't handle (i.e. `NSGetSizeAndAlignment()`). FLEX catches these to avoid crashing, but your breakpoint will get hit if it is active.