Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcdb33fce2 | |||
| 1eb8e4f430 | |||
| 1d937777c0 | |||
| 308afda5c2 | |||
| f7b00e02ee | |||
| 0ff29e1f90 | |||
| 3fe31e8628 | |||
| 33263bfcfa | |||
| d6caab29dc | |||
| 5d181adcb8 | |||
| 4ba2fdc289 | |||
| 140dc32775 | |||
| 78568cd5be | |||
| b010cdb072 | |||
| 37e299733b | |||
| f3a1587cf1 | |||
| 821ca1683b | |||
| 7e13ca2757 | |||
| 129c91c876 | |||
| d2f6ff0b40 | |||
| 69414e4174 | |||
| 0654fb4b5f | |||
| d7d40e6d27 | |||
| bec7e0c229 | |||
| 82a19e41e7 | |||
| 867ae614e5 | |||
| 22b7c6ccc7 | |||
| 9e9704580a | |||
| 1ef608cf8a | |||
| b64cd37ec6 | |||
| 44e9d55fb8 | |||
| ab9515caaf | |||
| 0dd0fc9418 | |||
| 24d5f3e9b2 | |||
| 693f57eef7 | |||
| 7c17ce0787 | |||
| 400a3ccd1c | |||
| a8cdac1872 | |||
| dedac1f98d | |||
| efa317f0d1 | |||
| 9b55bb10de | |||
| 122fb41fa8 | |||
| d6b5e8c77d | |||
| a6ad98dd53 | |||
| cc35f2086a | |||
| 7038aae6db | |||
| f5433153d0 | |||
| 8b7c59d949 | |||
| faef524b6c | |||
| a2bdc03684 | |||
| bd5f9740b7 | |||
| 505bb2ca41 | |||
| 009711ab3f | |||
| 7ad7653cdf | |||
| e5f51e4dfa | |||
| 92029d2b43 | |||
| 7da059791e | |||
| af57527961 | |||
| 9a8f45663e | |||
| 8528c8a1f6 | |||
| d682fd0ace | |||
| 31af87a81e | |||
| 386d6ae06a | |||
| df79ae7971 | |||
| d30c642707 | |||
| f463e2b43e | |||
| ed49a4fc89 | |||
| b897250fde | |||
| 06709a5afe | |||
| 6eee9e6080 | |||
| c50e6e51c5 | |||
| d1e9248695 | |||
| a535f10d0c | |||
| 009aa5e3f9 | |||
| 675f03fc71 | |||
| 99eccdf4c3 | |||
| 29afa5e80f | |||
| bf26bc6539 | |||
| f7b40646e2 | |||
| 84c1fb159b | |||
| 62ef95ff93 | |||
| 731b729db7 | |||
| a1c464d1a7 | |||
| eb2ecbf9b3 | |||
| 16fab66f7b | |||
| b3e70ac491 | |||
| d5177bb049 | |||
| bb0faeb3cf | |||
| 761feef3c0 | |||
| 352bae03ea | |||
| b0085cae7d | |||
| b2f93f1752 | |||
| 354510f2c4 | |||
| b8c6175193 | |||
| d409b110f5 | |||
| 5c73220158 | |||
| 397721e7ea | |||
| 5714275bcd | |||
| 833c584e41 | |||
| 49b24487c5 | |||
| 6d4eb01a07 | |||
| a752203ff9 | |||
| c69427613d | |||
| 5d75a83568 | |||
| 7642a0632d | |||
| 841054a713 | |||
| 49f368fd63 | |||
| 1dc99250c8 | |||
| 00edccf326 | |||
| 7f3af90645 | |||
| 1a030f06cd | |||
| 0477858bed | |||
| 000e061d00 | |||
| 224978b31b | |||
| 7fd133f13b |
+6
-2
@@ -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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXArgumentInputFontsPickerView.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by 啟倫 陳 on 2014/7/27.
|
||||
// Copyright (c) 2014年 f. All rights reserved.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXArgumentInputFontsPickerView.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by 啟倫 陳 on 2014/7/27.
|
||||
// Copyright (c) 2014年 f. All rights reserved.
|
||||
@@ -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];
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "FLEXArgumentInputTextView.h"
|
||||
|
||||
// #warning TODO This is never supported
|
||||
@interface FLEXArgumentInputJSONObjectView : FLEXArgumentInputTextView
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -39,26 +39,23 @@
|
||||
+ (Class)argumentInputViewSubclassForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue
|
||||
{
|
||||
Class argumentInputViewSubclass = nil;
|
||||
NSArray<Class> *inputViewClasses = @[[FLEXArgumentInputColorView class],
|
||||
[FLEXArgumentInputFontView class],
|
||||
[FLEXArgumentInputStringView class],
|
||||
[FLEXArgumentInputStructView class],
|
||||
[FLEXArgumentInputSwitchView class],
|
||||
[FLEXArgumentInputDateView class],
|
||||
[FLEXArgumentInputNumberView class],
|
||||
[FLEXArgumentInputJSONObjectView class]];
|
||||
|
||||
// Note that order is important here since multiple subclasses may support the same type.
|
||||
// An example is the number subclass and the bool subclass for the type @encode(BOOL).
|
||||
// Both work, but we'd prefer to use the bool subclass.
|
||||
if ([FLEXArgumentInputColorView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputColorView class];
|
||||
} else if ([FLEXArgumentInputFontView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputFontView class];
|
||||
} else if ([FLEXArgumentInputStringView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputStringView class];
|
||||
} else if ([FLEXArgumentInputStructView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputStructView class];
|
||||
} else if ([FLEXArgumentInputSwitchView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputSwitchView class];
|
||||
} else if ([FLEXArgumentInputDateView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputDateView class];
|
||||
} else if ([FLEXArgumentInputNumberView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputNumberView class];
|
||||
} else if ([FLEXArgumentInputJSONObjectView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = [FLEXArgumentInputJSONObjectView class];
|
||||
for (Class inputView in inputViewClasses) {
|
||||
if ([inputView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
|
||||
argumentInputViewSubclass = inputView;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return argumentInputViewSubclass;
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXFieldEditorViewController.h"
|
||||
#import "FLEXMutableFieldEditorViewController.h"
|
||||
|
||||
@interface FLEXDefaultEditorViewController : FLEXFieldEditorViewController
|
||||
@interface FLEXDefaultEditorViewController : FLEXMutableFieldEditorViewController
|
||||
|
||||
- (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
|
||||
|
||||
|
||||
@@ -64,6 +64,13 @@
|
||||
self.firstInputView.inputValue = [self.defaults objectForKey:self.key];
|
||||
}
|
||||
|
||||
- (void)getterButtonPressed:(id)sender
|
||||
{
|
||||
[super getterButtonPressed:sender];
|
||||
id returnedObject = [self.defaults objectForKey:self.key];
|
||||
[self exploreObjectOrPopViewController:returnedObject];
|
||||
}
|
||||
|
||||
+ (BOOL)canEditDefaultWithValue:(id)currentValue
|
||||
{
|
||||
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:@encode(id) currentValue:currentValue];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
@interface FLEXMethodCallingViewController ()
|
||||
|
||||
@property (nonatomic, assign) Method method;
|
||||
@property (nonatomic, assign) FLEXTypeEncoding *returnType;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,7 +28,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 +38,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 +60,12 @@
|
||||
self.fieldEditorView.argumentInputViews = argumentInputViews;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
free(self.returnType);
|
||||
self.returnType = NULL;
|
||||
}
|
||||
|
||||
- (BOOL)isClassMethod
|
||||
{
|
||||
return self.target && self.target == [self.target class];
|
||||
@@ -88,12 +100,11 @@
|
||||
[alert show];
|
||||
} else if (returnedObject) {
|
||||
// For non-nil (or void) return types, push an explorer view controller to display the returned object
|
||||
returnedObject = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:returnedObject type:self.returnType];
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -51,6 +51,13 @@ static const CGFloat kColumnMargin = 1;
|
||||
CGFloat height = self.frame.size.height;
|
||||
CGFloat topheaderHeight = [self topHeaderHeight];
|
||||
CGFloat leftHeaderWidth = [self leftHeaderWidth];
|
||||
CGFloat topInsets = 0.f;
|
||||
|
||||
#if FLEX_AT_LEAST_IOS11_SDK
|
||||
if (@available (iOS 11.0, *)) {
|
||||
topInsets = self.safeAreaInsets.top;
|
||||
}
|
||||
#endif
|
||||
|
||||
CGFloat contentWidth = 0.0;
|
||||
NSInteger rowsCount = [self numberOfColumns];
|
||||
@@ -58,13 +65,13 @@ static const CGFloat kColumnMargin = 1;
|
||||
contentWidth += [self contentWidthForColumn:i];
|
||||
}
|
||||
|
||||
self.leftTableView.frame = CGRectMake(0, topheaderHeight, leftHeaderWidth, height - topheaderHeight);
|
||||
self.headerScrollView.frame = CGRectMake(leftHeaderWidth, 0, width - leftHeaderWidth, topheaderHeight);
|
||||
self.leftTableView.frame = CGRectMake(0, topheaderHeight + topInsets, leftHeaderWidth, height - topheaderHeight - topInsets);
|
||||
self.headerScrollView.frame = CGRectMake(leftHeaderWidth, topInsets, width - leftHeaderWidth, topheaderHeight);
|
||||
self.headerScrollView.contentSize = CGSizeMake( self.contentTableView.frame.size.width, self.headerScrollView.frame.size.height);
|
||||
self.contentTableView.frame = CGRectMake(0, 0, contentWidth + [self numberOfColumns] * [self columnMargin] , height - topheaderHeight);
|
||||
self.contentScrollView.frame = CGRectMake(leftHeaderWidth, topheaderHeight, width - leftHeaderWidth, height - topheaderHeight);
|
||||
self.contentTableView.frame = CGRectMake(0, 0, contentWidth + [self numberOfColumns] * [self columnMargin] , height - topheaderHeight - topInsets);
|
||||
self.contentScrollView.frame = CGRectMake(leftHeaderWidth, topheaderHeight + topInsets, width - leftHeaderWidth, height - topheaderHeight - topInsets);
|
||||
self.contentScrollView.contentSize = self.contentTableView.frame.size;
|
||||
self.leftHeader.frame = CGRectMake(0, 0, [self leftHeaderWidth], [self topHeaderHeight]);
|
||||
self.leftHeader.frame = CGRectMake(0, topInsets, [self leftHeaderWidth], [self topHeaderHeight]);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +142,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);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXTableContentHeaderCell.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/26.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXTableContentHeaderCell.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/26.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXTableContentCell.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXTableContentCell.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
@@ -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
|
||||
|
||||
Regular → Executable
+31
-32
@@ -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];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXTableLeftCell.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXTableLeftCell.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXFileBrowserSearchOperation.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by 啟倫 陳 on 2014/8/4.
|
||||
// Copyright (c) 2014年 f. All rights reserved.
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXFileBrowserSearchOperation.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by 啟倫 陳 on 2014/8/4.
|
||||
// Copyright (c) 2014年 f. All rights reserved.
|
||||
@@ -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) {
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#import "FLEXWebViewController.h"
|
||||
#import "FLEXImagePreviewViewController.h"
|
||||
#import "FLEXTableListViewController.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
|
||||
@interface FLEXFileBrowserTableViewCell : UITableViewCell
|
||||
@end
|
||||
@@ -19,8 +21,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 +46,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 +88,17 @@
|
||||
{
|
||||
[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 - Misc
|
||||
|
||||
- (void)alert:(NSString *)title message:(NSString *)message {
|
||||
[[[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
|
||||
}
|
||||
|
||||
#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 +138,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 +163,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,78 +182,105 @@
|
||||
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;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
||||
NSString *fullPath = [self filePathAtIndexPath:indexPath];
|
||||
NSString *subpath = [fullPath lastPathComponent];
|
||||
NSString *pathExtension = [subpath pathExtension];
|
||||
|
||||
NSString *subpath = fullPath.lastPathComponent;
|
||||
NSString *pathExtension = subpath.pathExtension;
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
BOOL stillExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDirectory];
|
||||
if (stillExists) {
|
||||
UIViewController *drillInViewController = nil;
|
||||
if (isDirectory) {
|
||||
drillInViewController = [[[self class] alloc] initWithPath:fullPath];
|
||||
} else if ([FLEXUtility isImagePathExtension:pathExtension]) {
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
|
||||
drillInViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
|
||||
|
||||
if (!stillExists) {
|
||||
[self alert:@"File Not Found" message:@"The file at the specified path no longer exists."];
|
||||
[self reloadDisplayedPaths];
|
||||
return;
|
||||
}
|
||||
|
||||
UIViewController *drillInViewController = nil;
|
||||
if (isDirectory) {
|
||||
drillInViewController = [[[self class] alloc] initWithPath:fullPath];
|
||||
} else if ([FLEXUtility isImagePathExtension:pathExtension]) {
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
|
||||
drillInViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
|
||||
} else {
|
||||
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
|
||||
if (!fileData.length) {
|
||||
[self alert:@"Empty File" message:@"No data returned from the file."];
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case keyed archives, json, and plists to get more readable data.
|
||||
NSString *prettyString = nil;
|
||||
if ([pathExtension isEqualToString:@"json"]) {
|
||||
prettyString = [FLEXUtility prettyJSONStringFromData:fileData];
|
||||
} else {
|
||||
// Special case keyed archives, json, and plists to get more readable data.
|
||||
NSString *prettyString = nil;
|
||||
if ([pathExtension isEqual:@"archive"] || [pathExtension isEqual:@"coded"]) {
|
||||
prettyString = [[NSKeyedUnarchiver unarchiveObjectWithFile:fullPath] description];
|
||||
} else if ([pathExtension isEqualToString:@"json"]) {
|
||||
prettyString = [FLEXUtility prettyJSONStringFromData:[NSData dataWithContentsOfFile:fullPath]];
|
||||
} else if ([pathExtension isEqualToString:@"plist"]) {
|
||||
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
|
||||
@try {
|
||||
// Try to decode an archived object regardless of file extension
|
||||
id object = [NSKeyedUnarchiver unarchiveObjectWithData:fileData];
|
||||
drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:object];
|
||||
} @catch (NSException *e) {
|
||||
// Try to decode a property list instead, also regardless of file extension
|
||||
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]) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
|
||||
} else if ([FLEXTableListViewController supportsExtension:subpath.pathExtension]) {
|
||||
drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
|
||||
}
|
||||
else {
|
||||
NSString *fileString = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:NULL];
|
||||
if ([fileString length] > 0) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithText:fileString];
|
||||
}
|
||||
}
|
||||
|
||||
if (prettyString.length) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
|
||||
} else if ([FLEXWebViewController supportsPathExtension:pathExtension]) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
|
||||
} else if ([FLEXTableListViewController supportsExtension:pathExtension]) {
|
||||
drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
|
||||
}
|
||||
else if (!drillInViewController) {
|
||||
NSString *fileString = [NSString stringWithUTF8String:fileData.bytes];
|
||||
if (fileString.length) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithText:fileString];
|
||||
}
|
||||
}
|
||||
|
||||
if (drillInViewController) {
|
||||
drillInViewController.title = [subpath lastPathComponent];
|
||||
[self.navigationController pushViewController:drillInViewController animated:YES];
|
||||
} else {
|
||||
[self openFileController:fullPath];
|
||||
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
if (drillInViewController) {
|
||||
drillInViewController.title = subpath.lastPathComponent;
|
||||
[self.navigationController pushViewController:drillInViewController animated:YES];
|
||||
} else {
|
||||
[[[UIAlertView alloc] initWithTitle:@"File Removed" message:@"The file at the specified path no longer exists." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
|
||||
[self reloadDisplayedPaths];
|
||||
// Share the file otherwise
|
||||
[self openFileController:fullPath];
|
||||
}
|
||||
}
|
||||
|
||||
- (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 +320,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 +347,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 +389,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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "FLEXGlobalsTableViewController.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXLibrariesTableViewController.h"
|
||||
#import "FLEXClassesTableViewController.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
@@ -26,6 +27,7 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
FLEXGlobalsRowNetworkHistory,
|
||||
FLEXGlobalsRowSystemLog,
|
||||
FLEXGlobalsRowLiveObjects,
|
||||
FLEXGlobalsRowAddressInspector,
|
||||
FLEXGlobalsRowFileBrowser,
|
||||
FLEXGlobalsCookies,
|
||||
FLEXGlobalsRowSystemLibraries,
|
||||
@@ -33,6 +35,7 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
FLEXGlobalsRowAppDelegate,
|
||||
FLEXGlobalsRowRootViewController,
|
||||
FLEXGlobalsRowUserDefaults,
|
||||
FLEXGlobalsRowMainBundle,
|
||||
FLEXGlobalsRowApplication,
|
||||
FLEXGlobalsRowKeyWindow,
|
||||
FLEXGlobalsRowMainScreen,
|
||||
@@ -42,21 +45,20 @@ 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;
|
||||
FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture = nil;
|
||||
FLEXGlobalsTableViewControllerRowAction rowAction = nil;
|
||||
|
||||
switch (defaultRowIndex) {
|
||||
case FLEXGlobalsRowAppClasses:
|
||||
@@ -70,6 +72,49 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
return classesViewController;
|
||||
};
|
||||
break;
|
||||
|
||||
case FLEXGlobalsRowAddressInspector:
|
||||
titleFuture = ^NSString *{
|
||||
return @"🔎 Address Explorer";
|
||||
};
|
||||
|
||||
rowAction = ^(FLEXGlobalsTableViewController *host) {
|
||||
NSString *title = @"Explore Object at Address";
|
||||
NSString *message = @"Paste a hexadecimal address below, starting with '0x'. "
|
||||
"Use the unsafe option if you need to bypass pointer validation, "
|
||||
"but know that it may crash the app if the address is invalid.";
|
||||
|
||||
UIAlertController *addressInput = [UIAlertController alertControllerWithTitle:title
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
void (^handler)(UIAlertAction *) = ^(UIAlertAction *action) {
|
||||
if (action.style == UIAlertActionStyleCancel) {
|
||||
[host deselectSelectedRow]; return;
|
||||
}
|
||||
NSString *address = addressInput.textFields.firstObject.text;
|
||||
[host tryExploreAddress:address safely:action.style == UIAlertActionStyleDefault];
|
||||
};
|
||||
[addressInput addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
NSString *copied = [UIPasteboard generalPasteboard].string;
|
||||
textField.placeholder = @"0x00000070deadbeef";
|
||||
// Go ahead and paste our clipboard if we have an address copied
|
||||
if ([copied hasPrefix:@"0x"]) {
|
||||
textField.text = copied;
|
||||
[textField selectAll:nil];
|
||||
}
|
||||
}];
|
||||
[addressInput addAction:[UIAlertAction actionWithTitle:@"Explore"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:handler]];
|
||||
[addressInput addAction:[UIAlertAction actionWithTitle:@"Unsafe Explore"
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:handler]];
|
||||
[addressInput addAction:[UIAlertAction actionWithTitle:@"Cancel"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:handler]];
|
||||
[host presentViewController:addressInput animated:YES completion:nil];
|
||||
};
|
||||
break;
|
||||
|
||||
case FLEXGlobalsRowSystemLibraries: {
|
||||
NSString *titleString = @"📚 System Libraries";
|
||||
@@ -125,6 +170,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]";
|
||||
@@ -203,10 +258,18 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
break;
|
||||
}
|
||||
|
||||
NSParameterAssert(titleFuture);
|
||||
NSParameterAssert(viewControllerFuture);
|
||||
NSAssert(viewControllerFuture || rowAction, @"The switch-case above must assign one of these");
|
||||
|
||||
if (viewControllerFuture) {
|
||||
[defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry
|
||||
entryWithNameFuture:titleFuture
|
||||
viewControllerFuture:viewControllerFuture]];
|
||||
} else {
|
||||
[defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry
|
||||
entryWithNameFuture:titleFuture
|
||||
action:rowAction]];
|
||||
}
|
||||
|
||||
[defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry entryWithNameFuture:titleFuture viewControllerFuture:viewControllerFuture]];
|
||||
}
|
||||
|
||||
return defaultGlobalEntries;
|
||||
@@ -222,6 +285,11 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)deselectSelectedRow {
|
||||
NSIndexPath *selected = self.tableView.indexPathForSelectedRow;
|
||||
[self.tableView deselectRowAtIndexPath:selected animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
+ (void)setApplicationWindow:(UIWindow *)applicationWindow
|
||||
@@ -238,13 +306,39 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(donePressed:)];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - Misc
|
||||
|
||||
- (void)donePressed:(id)sender
|
||||
{
|
||||
[self.delegate globalsViewControllerDidFinish:self];
|
||||
}
|
||||
|
||||
- (void)tryExploreAddress:(NSString *)addressString safely:(BOOL)safely {
|
||||
NSScanner *scanner = [NSScanner scannerWithString:addressString];
|
||||
unsigned long long hexValue = 0;
|
||||
BOOL didParseAddress = [scanner scanHexLongLong:&hexValue];
|
||||
const void *pointerValue = (void *)hexValue;
|
||||
|
||||
NSString *error = nil;
|
||||
|
||||
if (didParseAddress) {
|
||||
if (safely && ![FLEXRuntimeUtility pointerIsValidObjcObject:pointerValue]) {
|
||||
error = @"The given address is unlikely to be a valid object.";
|
||||
}
|
||||
} else {
|
||||
error = @"Malformed address. Make sure it's not too long and starts with '0x'.";
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
id object = (__bridge id)pointerValue;
|
||||
FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:object];
|
||||
[self.navigationController pushViewController:explorer animated:YES];
|
||||
} else {
|
||||
[FLEXUtility alert:@"Uh-oh" message:error from:self];
|
||||
[self deselectSelectedRow];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Table Data Helpers
|
||||
|
||||
- (FLEXGlobalsTableViewControllerEntry *)globalEntryAtIndexPath:(NSIndexPath *)indexPath
|
||||
@@ -259,13 +353,6 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
return entry.entryNameFuture();
|
||||
}
|
||||
|
||||
- (UIViewController *)viewControllerToPushForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [self globalEntryAtIndexPath:indexPath];
|
||||
|
||||
return entry.viewControllerFuture();
|
||||
}
|
||||
|
||||
#pragma mark - Table View Data Source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
@@ -293,14 +380,16 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
return cell;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Table View Delegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UIViewController *viewControllerToPush = [self viewControllerToPushForRowAtIndexPath:indexPath];
|
||||
|
||||
[self.navigationController pushViewController:viewControllerToPush animated:YES];
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [self globalEntryAtIndexPath:indexPath];
|
||||
if (entry.viewControllerFuture) {
|
||||
[self.navigationController pushViewController:entry.viewControllerFuture() animated:YES];
|
||||
} else {
|
||||
entry.rowAction(self);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// Taken from https://github.com/llvm-mirror/lldb/blob/master/tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h
|
||||
// by Tanner Bennett on 03/03/2019 with minimal modifications.
|
||||
//
|
||||
|
||||
//===-- ActivityStreamAPI.h -------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef ActivityStreamSPI_h
|
||||
#define ActivityStreamSPI_h
|
||||
|
||||
#include <sys/time.h>
|
||||
// #include <xpc/xpc.h>
|
||||
|
||||
/* By default, XPC objects are declared as Objective-C types when building with
|
||||
* an Objective-C compiler. This allows them to participate in ARC, in RR
|
||||
* management by the Blocks runtime and in leaks checking by the static
|
||||
* analyzer, and enables them to be added to Cocoa collections.
|
||||
*
|
||||
* See <os/object.h> for details.
|
||||
*/
|
||||
#if OS_OBJECT_USE_OBJC
|
||||
OS_OBJECT_DECL(xpc_object);
|
||||
#else
|
||||
typedef void * xpc_object_t;
|
||||
#endif
|
||||
|
||||
#define OS_ACTIVITY_MAX_CALLSTACK 32
|
||||
|
||||
// Enums
|
||||
|
||||
typedef NS_ENUM(uint32_t, os_activity_stream_flag_t) {
|
||||
OS_ACTIVITY_STREAM_PROCESS_ONLY = 0x00000001,
|
||||
OS_ACTIVITY_STREAM_SKIP_DECODE = 0x00000002,
|
||||
OS_ACTIVITY_STREAM_PAYLOAD = 0x00000004,
|
||||
OS_ACTIVITY_STREAM_HISTORICAL = 0x00000008,
|
||||
OS_ACTIVITY_STREAM_CALLSTACK = 0x00000010,
|
||||
OS_ACTIVITY_STREAM_DEBUG = 0x00000020,
|
||||
OS_ACTIVITY_STREAM_BUFFERED = 0x00000040,
|
||||
OS_ACTIVITY_STREAM_NO_SENSITIVE = 0x00000080,
|
||||
OS_ACTIVITY_STREAM_INFO = 0x00000100,
|
||||
OS_ACTIVITY_STREAM_PROMISCUOUS = 0x00000200,
|
||||
OS_ACTIVITY_STREAM_PRECISE_TIMESTAMPS = 0x00000200
|
||||
};
|
||||
|
||||
typedef NS_ENUM(uint32_t, os_activity_stream_type_t) {
|
||||
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE = 0x0201,
|
||||
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION = 0x0202,
|
||||
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_USERACTION = 0x0203,
|
||||
|
||||
OS_ACTIVITY_STREAM_TYPE_TRACE_MESSAGE = 0x0300,
|
||||
|
||||
OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE = 0x0400,
|
||||
OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE = 0x0480,
|
||||
|
||||
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_BEGIN = 0x0601,
|
||||
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_END = 0x0602,
|
||||
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_EVENT = 0x0603,
|
||||
|
||||
OS_ACTIVITY_STREAM_TYPE_STATEDUMP_EVENT = 0x0A00,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(uint32_t, os_activity_stream_event_t) {
|
||||
OS_ACTIVITY_STREAM_EVENT_STARTED = 1,
|
||||
OS_ACTIVITY_STREAM_EVENT_STOPPED = 2,
|
||||
OS_ACTIVITY_STREAM_EVENT_FAILED = 3,
|
||||
OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED = 4,
|
||||
OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED = 5,
|
||||
};
|
||||
|
||||
// Types
|
||||
|
||||
typedef uint64_t os_activity_id_t;
|
||||
typedef struct os_activity_stream_s *os_activity_stream_t;
|
||||
typedef struct os_activity_stream_entry_s *os_activity_stream_entry_t;
|
||||
|
||||
#define OS_ACTIVITY_STREAM_COMMON() \
|
||||
uint64_t trace_id; \
|
||||
uint64_t timestamp; \
|
||||
uint64_t thread; \
|
||||
const uint8_t *image_uuid; \
|
||||
const char *image_path; \
|
||||
struct timeval tv_gmt; \
|
||||
struct timezone tz; \
|
||||
uint32_t offset
|
||||
|
||||
typedef struct os_activity_stream_common_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
} * os_activity_stream_common_t;
|
||||
|
||||
struct os_activity_create_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
const char *name;
|
||||
os_activity_id_t creator_aid;
|
||||
uint64_t unique_pid;
|
||||
};
|
||||
|
||||
struct os_activity_transition_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
os_activity_id_t transition_id;
|
||||
};
|
||||
|
||||
typedef struct os_log_message_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
const char *format;
|
||||
const uint8_t *buffer;
|
||||
size_t buffer_sz;
|
||||
const uint8_t *privdata;
|
||||
size_t privdata_sz;
|
||||
const char *subsystem;
|
||||
const char *category;
|
||||
uint32_t oversize_id;
|
||||
uint8_t ttl;
|
||||
bool persisted;
|
||||
} * os_log_message_t;
|
||||
|
||||
typedef struct os_trace_message_v2_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
const char *format;
|
||||
const void *buffer;
|
||||
size_t bufferLen;
|
||||
xpc_object_t __unsafe_unretained payload;
|
||||
} * os_trace_message_v2_t;
|
||||
|
||||
typedef struct os_activity_useraction_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
const char *action;
|
||||
bool persisted;
|
||||
} * os_activity_useraction_t;
|
||||
|
||||
typedef struct os_signpost_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
const char *format;
|
||||
const uint8_t *buffer;
|
||||
size_t buffer_sz;
|
||||
const uint8_t *privdata;
|
||||
size_t privdata_sz;
|
||||
const char *subsystem;
|
||||
const char *category;
|
||||
uint64_t duration_nsec;
|
||||
uint32_t callstack_depth;
|
||||
uint64_t callstack[OS_ACTIVITY_MAX_CALLSTACK];
|
||||
} * os_signpost_t;
|
||||
|
||||
typedef struct os_activity_statedump_s {
|
||||
OS_ACTIVITY_STREAM_COMMON();
|
||||
char *message;
|
||||
size_t message_size;
|
||||
char image_path_buffer[PATH_MAX];
|
||||
} * os_activity_statedump_t;
|
||||
|
||||
struct os_activity_stream_entry_s {
|
||||
os_activity_stream_type_t type;
|
||||
|
||||
// information about the process streaming the data
|
||||
pid_t pid;
|
||||
uint64_t proc_id;
|
||||
const uint8_t *proc_imageuuid;
|
||||
const char *proc_imagepath;
|
||||
|
||||
// the activity associated with this streamed event
|
||||
os_activity_id_t activity_id;
|
||||
os_activity_id_t parent_id;
|
||||
|
||||
union {
|
||||
struct os_activity_stream_common_s common;
|
||||
struct os_activity_create_s activity_create;
|
||||
struct os_activity_transition_s activity_transition;
|
||||
struct os_log_message_s log_message;
|
||||
struct os_trace_message_v2_s trace_message;
|
||||
struct os_activity_useraction_s useraction;
|
||||
struct os_signpost_s signpost;
|
||||
struct os_activity_statedump_s statedump;
|
||||
};
|
||||
};
|
||||
|
||||
// Blocks
|
||||
|
||||
typedef bool (^os_activity_stream_block_t)(os_activity_stream_entry_t entry,
|
||||
int error);
|
||||
|
||||
typedef void (^os_activity_stream_event_block_t)(
|
||||
os_activity_stream_t stream, os_activity_stream_event_t event);
|
||||
|
||||
// SPI entry point prototypes
|
||||
|
||||
typedef os_activity_stream_t (*os_activity_stream_for_pid_t)(
|
||||
pid_t pid, os_activity_stream_flag_t flags,
|
||||
os_activity_stream_block_t stream_block);
|
||||
|
||||
typedef void (*os_activity_stream_resume_t)(os_activity_stream_t stream);
|
||||
|
||||
typedef void (*os_activity_stream_cancel_t)(os_activity_stream_t stream);
|
||||
|
||||
typedef char *(*os_log_copy_formatted_message_t)(os_log_message_t log_message);
|
||||
|
||||
typedef void (*os_activity_stream_set_event_handler_t)(
|
||||
os_activity_stream_t stream, os_activity_stream_event_block_t block);
|
||||
|
||||
#endif /* ActivityStreamSPI_h */
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// FLEXASLLogController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/14/19.
|
||||
// Copyright © 2019 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXLogController.h"
|
||||
|
||||
@interface FLEXASLLogController : NSObject <FLEXLogController>
|
||||
|
||||
/// Guaranteed to call back on the main thread.
|
||||
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
|
||||
|
||||
- (BOOL)startMonitoring;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// FLEXASLLogController.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/14/19.
|
||||
// Copyright © 2019 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXASLLogController.h"
|
||||
#import <asl.h>
|
||||
|
||||
// Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
#define updateInterval 5.0
|
||||
#else
|
||||
#define updateInterval 1.0
|
||||
#endif
|
||||
|
||||
@interface FLEXASLLogController ()
|
||||
|
||||
@property (nonatomic, readonly) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
|
||||
|
||||
@property (nonatomic, strong) NSTimer *logUpdateTimer;
|
||||
@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
|
||||
|
||||
// ASL stuff
|
||||
|
||||
@property (nonatomic) NSUInteger heapSize;
|
||||
@property (nonatomic) dispatch_queue_t logQueue;
|
||||
@property (nonatomic) dispatch_io_t io;
|
||||
@property (nonatomic) NSString *remaining;
|
||||
@property (nonatomic) int stderror;
|
||||
@property (nonatomic) NSString *lastTimestamp;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXASLLogController
|
||||
|
||||
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
|
||||
{
|
||||
return [[self alloc] initWithUpdateHandler:newMessagesHandler];
|
||||
}
|
||||
|
||||
- (id)initWithUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
|
||||
{
|
||||
NSParameterAssert(newMessagesHandler);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_updateHandler = newMessagesHandler;
|
||||
_logMessageIdentifiers = [NSMutableIndexSet indexSet];
|
||||
self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval
|
||||
target:self
|
||||
selector:@selector(updateLogMessages)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self.logUpdateTimer invalidate];
|
||||
}
|
||||
|
||||
- (BOOL)startMonitoring {
|
||||
[self.logUpdateTimer fire];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateLogMessages
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray<FLEXSystemLogMessage *> *newMessages;
|
||||
@synchronized (self) {
|
||||
newMessages = [self newLogMessagesForCurrentProcess];
|
||||
if (!newMessages.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (FLEXSystemLogMessage *message in newMessages) {
|
||||
[self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
|
||||
}
|
||||
|
||||
self.lastTimestamp = @(asl_get(newMessages.lastObject.aslMessage, ASL_KEY_TIME) ?: "null");
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.updateHandler(newMessages);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Log Message Fetching
|
||||
|
||||
- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
|
||||
{
|
||||
if (!self.logMessageIdentifiers.count) {
|
||||
return [self allLogMessagesForCurrentProcess];
|
||||
}
|
||||
|
||||
aslresponse response = [self ASLMessageListForCurrentProcess];
|
||||
aslmsg aslMessage = NULL;
|
||||
|
||||
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);
|
||||
// Filter for messages after the last retreived message.
|
||||
if (self.lastTimestamp) {
|
||||
asl_set_query(query, ASL_KEY_TIME, self.lastTimestamp.UTF8String, ASL_QUERY_OP_GREATER);
|
||||
}
|
||||
|
||||
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]];
|
||||
}
|
||||
asl_release(response);
|
||||
|
||||
return logMessages;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// FLEXLogController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/17/19.
|
||||
// Copyright © 2019 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FLEXSystemLogMessage.h"
|
||||
|
||||
@protocol FLEXLogController <NSObject>
|
||||
|
||||
/// Guaranteed to call back on the main thread.
|
||||
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
|
||||
|
||||
- (BOOL)startMonitoring;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// FLEXOSLogController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 12/19/18.
|
||||
// Copyright © 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXLogController.h"
|
||||
|
||||
#define FLEXOSLogAvailable() ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion >= 10)
|
||||
|
||||
extern NSString * const kFLEXiOSPersistentOSLogKey;
|
||||
|
||||
/// The log controller used for iOS 10 and up.
|
||||
@interface FLEXOSLogController : NSObject <FLEXLogController>
|
||||
|
||||
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
|
||||
|
||||
- (BOOL)startMonitoring;
|
||||
|
||||
/// Whether log messages are to be recorded and kept in-memory in the background.
|
||||
/// You do not need to initialize this value, only change it.
|
||||
@property (nonatomic) BOOL persistent;
|
||||
/// Used mostly internally, but also used by the log VC to persist messages
|
||||
/// that were created prior to enabling persistence.
|
||||
@property (nonatomic) NSMutableArray<FLEXSystemLogMessage *> *messages;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// FLEXOSLogController.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 12/19/18.
|
||||
// Copyright © 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXOSLogController.h"
|
||||
#include <dlfcn.h>
|
||||
#include "ActivityStreamAPI.h"
|
||||
|
||||
NSString * const kFLEXiOSPersistentOSLogKey = @"com.flex.enablePersistentOSLogLogging";
|
||||
|
||||
static os_activity_stream_for_pid_t OSActivityStreamForPID;
|
||||
static os_activity_stream_resume_t OSActivityStreamResume;
|
||||
static os_activity_stream_cancel_t OSActivityStreamCancel;
|
||||
static os_log_copy_formatted_message_t OSLogCopyFormattedMessage;
|
||||
static os_activity_stream_set_event_handler_t OSActivityStreamSetEventHandler;
|
||||
static int (*proc_name)(int, char *, unsigned int);
|
||||
static int (*proc_listpids)(uint32_t, uint32_t, void*, int);
|
||||
static uint8_t (*OSLogGetType)(void *);
|
||||
|
||||
@interface FLEXOSLogController ()
|
||||
|
||||
+ (FLEXOSLogController *)sharedLogController;
|
||||
|
||||
@property (nonatomic) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
|
||||
|
||||
@property (nonatomic) BOOL canPrint;
|
||||
@property (nonatomic) int filterPid;
|
||||
@property (nonatomic) BOOL levelInfo;
|
||||
@property (nonatomic) BOOL subsystemInfo;
|
||||
|
||||
@property (nonatomic) os_activity_stream_t stream;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXOSLogController
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
// Persist logs when the app launches on iOS 10 if we have persitent logs turned on
|
||||
if (FLEXOSLogAvailable()) {
|
||||
BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
|
||||
if (persistent) {
|
||||
[self sharedLogController].persistent = YES;
|
||||
[[self sharedLogController] startMonitoring];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (instancetype)sharedLogController {
|
||||
static FLEXOSLogController *shared = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
shared = [self new];
|
||||
});
|
||||
|
||||
return shared;
|
||||
}
|
||||
|
||||
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
|
||||
{
|
||||
FLEXOSLogController *shared = [self sharedLogController];
|
||||
shared.updateHandler = newMessagesHandler;
|
||||
return shared;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
NSAssert(FLEXOSLogAvailable(), @"os_log is only available on iOS 10 and up");
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filterPid = [NSProcessInfo processInfo].processIdentifier;
|
||||
_levelInfo = NO;
|
||||
_subsystemInfo = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
OSActivityStreamCancel(self.stream);
|
||||
_stream = nil;
|
||||
}
|
||||
|
||||
- (void)setPersistent:(BOOL)persistent {
|
||||
if (_persistent == persistent) return;
|
||||
|
||||
_persistent = persistent;
|
||||
self.messages = persistent ? [NSMutableArray array] : nil;
|
||||
}
|
||||
|
||||
- (BOOL)startMonitoring {
|
||||
if (![self lookupSPICalls]) {
|
||||
// >= iOS 10 is required
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Are we already monitoring?
|
||||
if (self.stream) {
|
||||
// Should we send out the "persisted" messages?
|
||||
if (self.updateHandler && self.messages.count) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.updateHandler(self.messages);
|
||||
});
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Stream entry handler
|
||||
os_activity_stream_block_t block = ^bool(os_activity_stream_entry_t entry, int error) {
|
||||
return [self handleStreamEntry:entry error:error];
|
||||
};
|
||||
|
||||
// Controls which types of messages we see
|
||||
// 'Historical' appears to just show NSLog stuff
|
||||
uint32_t activity_stream_flags = OS_ACTIVITY_STREAM_HISTORICAL;
|
||||
activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
|
||||
// activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
|
||||
|
||||
self.stream = OSActivityStreamForPID(self.filterPid, activity_stream_flags, block);
|
||||
|
||||
// Specify the stream-related event handler
|
||||
OSActivityStreamSetEventHandler(self.stream, [self streamEventHandlerBlock]);
|
||||
// Start the stream
|
||||
OSActivityStreamResume(self.stream);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)lookupSPICalls {
|
||||
static BOOL hasSPI = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
void *handle = dlopen("/System/Library/PrivateFrameworks/LoggingSupport.framework/LoggingSupport", RTLD_NOW);
|
||||
|
||||
OSActivityStreamForPID = (os_activity_stream_for_pid_t)dlsym(handle, "os_activity_stream_for_pid");
|
||||
OSActivityStreamResume = (os_activity_stream_resume_t)dlsym(handle, "os_activity_stream_resume");
|
||||
OSActivityStreamCancel = (os_activity_stream_cancel_t)dlsym(handle, "os_activity_stream_cancel");
|
||||
OSLogCopyFormattedMessage = (os_log_copy_formatted_message_t)dlsym(handle, "os_log_copy_formatted_message");
|
||||
OSActivityStreamSetEventHandler = (os_activity_stream_set_event_handler_t)dlsym(handle, "os_activity_stream_set_event_handler");
|
||||
proc_name = (int(*)(int, char *, unsigned int))dlsym(handle, "proc_name");
|
||||
proc_listpids = (int(*)(uint32_t, uint32_t, void*, int))dlsym(handle, "proc_listpids");
|
||||
OSLogGetType = (uint8_t(*)(void *))dlsym(handle, "os_log_get_type");
|
||||
|
||||
hasSPI = (OSActivityStreamForPID != NULL) &&
|
||||
(OSActivityStreamResume != NULL) &&
|
||||
(OSActivityStreamCancel != NULL) &&
|
||||
(OSLogCopyFormattedMessage != NULL) &&
|
||||
(OSActivityStreamSetEventHandler != NULL) &&
|
||||
(OSLogGetType != NULL) &&
|
||||
(proc_name != NULL);
|
||||
});
|
||||
|
||||
return hasSPI;
|
||||
}
|
||||
|
||||
- (BOOL)handleStreamEntry:(os_activity_stream_entry_t)entry error:(int)error {
|
||||
if (!self.canPrint || (self.filterPid != -1 && entry->pid != self.filterPid)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!error && entry) {
|
||||
if (entry->type == OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE ||
|
||||
entry->type == OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE) {
|
||||
os_log_message_t log_message = &entry->log_message;
|
||||
|
||||
// Get date
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSince1970:log_message->tv_gmt.tv_sec];
|
||||
|
||||
// Get log message text
|
||||
const char *messageText = OSLogCopyFormattedMessage(log_message);
|
||||
// https://github.com/limneos/oslog/issues/1
|
||||
if (entry->log_message.format && !(strcmp(entry->log_message.format, messageText))) {
|
||||
messageText = (char *)entry->log_message.format;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
FLEXSystemLogMessage *message = [FLEXSystemLogMessage logMessageFromDate:date text:@(messageText)];
|
||||
if (self.persistent) {
|
||||
[self.messages addObject:message];
|
||||
}
|
||||
if (self.updateHandler) {
|
||||
self.updateHandler(@[message]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (os_activity_stream_event_block_t)streamEventHandlerBlock {
|
||||
return [^void(os_activity_stream_t stream, os_activity_stream_event_t event) {
|
||||
switch (event) {
|
||||
case OS_ACTIVITY_STREAM_EVENT_STARTED:
|
||||
self.canPrint = YES;
|
||||
break;
|
||||
case OS_ACTIVITY_STREAM_EVENT_STOPPED:
|
||||
break;
|
||||
case OS_ACTIVITY_STREAM_EVENT_FAILED:
|
||||
break;
|
||||
case OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED:
|
||||
break;
|
||||
case OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED:
|
||||
break;
|
||||
default:
|
||||
printf("=== Unhandled case ===\n");
|
||||
break;
|
||||
}
|
||||
} copy];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXSystemLogMessage.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 1/25/15.
|
||||
// Copyright (c) 2015 f. All rights reserved.
|
||||
@@ -8,14 +8,24 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <asl.h>
|
||||
#import "ActivityStreamAPI.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXSystemLogMessage : NSObject
|
||||
|
||||
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage;
|
||||
//+ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage;
|
||||
+ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text;
|
||||
|
||||
@property (nonatomic, strong) NSDate *date;
|
||||
@property (nonatomic, copy) NSString *sender;
|
||||
@property (nonatomic, copy) NSString *messageText;
|
||||
@property (nonatomic, assign) long long messageID;
|
||||
// ASL specific properties
|
||||
@property (nonatomic, readonly, nullable) NSString *sender;
|
||||
@property (nonatomic, readonly, nullable) aslmsg aslMessage;
|
||||
|
||||
@property (nonatomic, readonly) NSDate *date;
|
||||
@property (nonatomic, readonly) NSString *messageText;
|
||||
@property (nonatomic, readonly) long long messageID;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXSystemLogMessage.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 1/25/15.
|
||||
// Copyright (c) 2015 f. All rights reserved.
|
||||
@@ -10,9 +10,11 @@
|
||||
|
||||
@implementation FLEXSystemLogMessage
|
||||
|
||||
+(instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
|
||||
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
|
||||
{
|
||||
FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
|
||||
NSDate *date = nil;
|
||||
NSString *sender = nil, *text = nil;
|
||||
long long identifier = 0;
|
||||
|
||||
const char *timestamp = asl_get(aslMessage, ASL_KEY_TIME);
|
||||
if (timestamp) {
|
||||
@@ -21,30 +23,67 @@
|
||||
if (nanoseconds) {
|
||||
timeInterval += [@(nanoseconds) doubleValue] / NSEC_PER_SEC;
|
||||
}
|
||||
logMessage.date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
|
||||
date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
|
||||
}
|
||||
|
||||
const char *sender = asl_get(aslMessage, ASL_KEY_SENDER);
|
||||
if (sender) {
|
||||
logMessage.sender = @(sender);
|
||||
const char *s = asl_get(aslMessage, ASL_KEY_SENDER);
|
||||
if (s) {
|
||||
sender = @(s);
|
||||
}
|
||||
|
||||
const char *messageText = asl_get(aslMessage, ASL_KEY_MSG);
|
||||
if (messageText) {
|
||||
logMessage.messageText = @(messageText);
|
||||
text = @(messageText);
|
||||
}
|
||||
|
||||
const char *messageID = asl_get(aslMessage, ASL_KEY_MSG_ID);
|
||||
if (messageID) {
|
||||
logMessage.messageID = [@(messageID) longLongValue];
|
||||
identifier = [@(messageID) longLongValue];
|
||||
}
|
||||
|
||||
return logMessage;
|
||||
FLEXSystemLogMessage *message = [[self alloc] initWithDate:date sender:sender text:text messageID:identifier];
|
||||
message->_aslMessage = aslMessage;
|
||||
return message;
|
||||
}
|
||||
|
||||
+ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage
|
||||
{
|
||||
abort();
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text
|
||||
{
|
||||
return [[self alloc] initWithDate:date sender:nil text:text messageID:0];
|
||||
}
|
||||
|
||||
- (id)initWithDate:(NSDate *)date sender:(NSString *)sender text:(NSString *)text messageID:(long long)identifier
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_date = date;
|
||||
_sender = sender;
|
||||
_messageText = text;
|
||||
_messageID = identifier;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
return [object isKindOfClass:[FLEXSystemLogMessage class]] && self.messageID == [object messageID];
|
||||
if ([object isKindOfClass:[self class]]) {
|
||||
if (self.messageID) {
|
||||
// Only ASL uses messageID, otherwise it is 0
|
||||
return self.messageID == [object messageID];
|
||||
} else {
|
||||
// Test message texts and dates for OS Log
|
||||
return [self.messageText isEqual:[object messageText]] &&
|
||||
[self.date isEqualToDate:[object date]];
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
@@ -52,4 +91,10 @@
|
||||
return (NSUInteger)self.messageID;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *escaped = [self.messageText stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
|
||||
return [NSString stringWithFormat:@"(%@) %@", @(self.messageText.length), escaped];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXSystemLogTableViewCell.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 1/25/15.
|
||||
// Copyright (c) 2015 f. All rights reserved.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXSystemLogTableViewCell.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 1/25/15.
|
||||
// Copyright (c) 2015 f. All rights reserved.
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXSystemLogTableViewController.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 1/19/15.
|
||||
// Copyright (c) 2015 f. All rights reserved.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXSystemLogTableViewController.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 1/19/15.
|
||||
// Copyright (c) 2015 f. All rights reserved.
|
||||
@@ -8,16 +8,16 @@
|
||||
|
||||
#import "FLEXSystemLogTableViewController.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXSystemLogMessage.h"
|
||||
#import "FLEXASLLogController.h"
|
||||
#import "FLEXOSLogController.h"
|
||||
#import "FLEXSystemLogTableViewCell.h"
|
||||
#import <asl.h>
|
||||
|
||||
@interface FLEXSystemLogTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
|
||||
|
||||
@property (nonatomic, strong) UISearchController *searchController;
|
||||
@property (nonatomic, copy) NSArray *logMessages;
|
||||
@property (nonatomic, copy) NSArray *filteredLogMessages;
|
||||
@property (nonatomic, strong) NSTimer *logUpdateTimer;
|
||||
@property (nonatomic, readonly) id<FLEXLogController> logController;
|
||||
@property (nonatomic, readonly) NSMutableArray<FLEXSystemLogMessage *> *logMessages;
|
||||
@property (nonatomic, copy) NSArray<FLEXSystemLogMessage *> *filteredLogMessages;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,57 +27,56 @@
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) {
|
||||
self.title = @"System Log";
|
||||
|
||||
[self.logMessages addObjectsFromArray:newMessages];
|
||||
|
||||
// "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;
|
||||
[self.tableView reloadData];
|
||||
if (wasNearBottom) {
|
||||
[self scrollToLastRow];
|
||||
}
|
||||
};
|
||||
|
||||
_logMessages = [NSMutableArray array];
|
||||
if ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion <= 9) {
|
||||
_logController = [FLEXASLLogController withUpdateHandler:logHandler];
|
||||
} else {
|
||||
_logController = [FLEXOSLogController withUpdateHandler:logHandler];
|
||||
}
|
||||
|
||||
[self.tableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.title = @"Loading...";
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ " style:UIBarButtonItemStylePlain target:self action:@selector(scrollToLastRow)];
|
||||
|
||||
|
||||
UIBarButtonItem *scrollDown = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ "
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(scrollToLastRow)];
|
||||
UIBarButtonItem *settings = [[UIBarButtonItem alloc] initWithTitle:@"Settings"
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(showLogSettings)];
|
||||
if (FLEXOSLogAvailable()) {
|
||||
self.navigationItem.rightBarButtonItems = @[scrollDown, settings];
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = scrollDown;
|
||||
}
|
||||
|
||||
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
|
||||
self.searchController.delegate = self;
|
||||
self.searchController.searchResultsUpdater = self;
|
||||
self.searchController.dimsBackgroundDuringPresentation = NO;
|
||||
self.tableView.tableHeaderView = self.searchController.searchBar;
|
||||
|
||||
[self updateLogMessages];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
NSTimeInterval updateInterval = 1.0;
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
// Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
|
||||
updateInterval = 5.0;
|
||||
#endif
|
||||
|
||||
self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval target:self selector:@selector(updateLogMessages) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self.logUpdateTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)updateLogMessages
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray *logMessages = [[self class] allLogMessagesForCurrentProcess];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.title = @"System Log";
|
||||
self.logMessages = logMessages;
|
||||
|
||||
// "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;
|
||||
[self.tableView reloadData];
|
||||
if (wasNearBottom) {
|
||||
[self scrollToLastRow];
|
||||
}
|
||||
});
|
||||
});
|
||||
[self.logController startMonitoring];
|
||||
}
|
||||
|
||||
- (void)scrollToLastRow
|
||||
@@ -89,6 +88,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showLogSettings
|
||||
{
|
||||
FLEXOSLogController *logController = (FLEXOSLogController *)self.logController;
|
||||
BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
|
||||
NSString *toggle = persistent ? @"Disable" : @"Enable";
|
||||
NSString *title = [@"Persistent logging: " stringByAppendingString:persistent ? @"ON" : @"OFF"];
|
||||
NSString *body = @"In iOS 10 and up, ASL is gone. The OS Log API is much more limited. "
|
||||
"To get as close to the old behavior as possible, logs must be collected manually at launch and stored.\n\n"
|
||||
"Turn this feature on only when you need it.";
|
||||
|
||||
UIAlertController *settings = [UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert];
|
||||
[settings addAction:[UIAlertAction actionWithTitle:toggle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[[NSUserDefaults standardUserDefaults] setBool:!persistent forKey:kFLEXiOSPersistentOSLogKey];
|
||||
logController.persistent = !persistent;
|
||||
[logController.messages addObjectsFromArray:self.logMessages];
|
||||
}]];
|
||||
[settings addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]];
|
||||
|
||||
[self presentViewController:settings animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
@@ -98,7 +118,7 @@
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return self.searchController.isActive ? [self.filteredLogMessages count] : [self.logMessages count];
|
||||
return self.searchController.isActive ? self.filteredLogMessages.count : self.logMessages.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
@@ -137,9 +157,8 @@
|
||||
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
if (action == @selector(copy:)) {
|
||||
FLEXSystemLogMessage *logMessage = [self logMessageAtIndexPath:indexPath];
|
||||
NSString *stringToCopy = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage] ?: @"";
|
||||
[[UIPasteboard generalPasteboard] setString:stringToCopy];
|
||||
// We usually only want to copy the log message itself, not any metadata associated with it.
|
||||
[UIPasteboard generalPasteboard].string = [self logMessageAtIndexPath:indexPath].messageText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +173,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;
|
||||
}]];
|
||||
@@ -167,26 +186,4 @@
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Log Message Fetching
|
||||
|
||||
+ (NSArray *)allLogMessagesForCurrentProcess
|
||||
{
|
||||
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.
|
||||
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);
|
||||
aslmsg aslMessage = NULL;
|
||||
|
||||
NSMutableArray *logMessages = [NSMutableArray array];
|
||||
while ((aslMessage = asl_next(response))) {
|
||||
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
|
||||
}
|
||||
asl_release(response);
|
||||
|
||||
return logMessages;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
==============================================================================
|
||||
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
|
||||
==============================================================================
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
---- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
==============================================================================
|
||||
Software from third parties included in the LLVM Project:
|
||||
==============================================================================
|
||||
The LLVM Project contains third party software which is under different license
|
||||
terms. All such code will be identified clearly using at least one of two
|
||||
mechanisms:
|
||||
1) It will be in a separate directory tree with its own `LICENSE.txt` or
|
||||
`LICENSE` file at the top containing the specific license and restrictions
|
||||
which apply to that software, or
|
||||
2) It will contain specific license and restriction terms at the top of every
|
||||
file.
|
||||
|
||||
==============================================================================
|
||||
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
|
||||
==============================================================================
|
||||
University of Illinois/NCSA
|
||||
Open Source License
|
||||
|
||||
Copyright (c) 2010 Apple Inc.
|
||||
All rights reserved.
|
||||
|
||||
Developed by:
|
||||
|
||||
LLDB Team
|
||||
|
||||
http://lldb.llvm.org/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal with
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimers.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimers in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of the LLDB Team, copyright holders, nor the names of
|
||||
its contributors may be used to endorse or promote products derived from
|
||||
this Software without specific prior written permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -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,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];
|
||||
|
||||
@@ -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) {
|
||||
@@ -28,12 +28,7 @@
|
||||
}
|
||||
|
||||
if (request.HTTPBody) {
|
||||
if ([request.allHTTPHeaderFields[@"Content-Length"] intValue] < 1024) {
|
||||
[curlCommandString appendFormat:@"-d \'%@\'",
|
||||
[[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]];
|
||||
} else {
|
||||
[curlCommandString appendFormat:@"[TOO MUCH DATA TO INCLUDE]"];
|
||||
}
|
||||
[curlCommandString appendFormat:@"-d \'%@\'", [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
|
||||
return curlCommandString;
|
||||
|
||||
@@ -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(), ^{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}];
|
||||
|
||||
+2
-2
@@ -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
|
||||
+8
-2
@@ -1,22 +1,28 @@
|
||||
//
|
||||
// FLEXGlobalsTableViewControllerEntry.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Javier Soto on 7/26/14.
|
||||
// Copyright (c) 2014 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
@class FLEXGlobalsTableViewController;
|
||||
|
||||
typedef NSString *(^FLEXGlobalsTableViewControllerEntryNameFuture)(void);
|
||||
/// Simply return a view controller to be pushed on the navigation stack
|
||||
typedef UIViewController *(^FLEXGlobalsTableViewControllerViewControllerFuture)(void);
|
||||
/// Do something like present an alert, then use the host
|
||||
/// view controller to present or push another view controller.
|
||||
typedef void (^FLEXGlobalsTableViewControllerRowAction)(FLEXGlobalsTableViewController *host);
|
||||
|
||||
@interface FLEXGlobalsTableViewControllerEntry : NSObject
|
||||
|
||||
@property (nonatomic, readonly, copy) FLEXGlobalsTableViewControllerEntryNameFuture entryNameFuture;
|
||||
@property (nonatomic, readonly, copy) FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture;
|
||||
@property (nonatomic, readonly, copy) FLEXGlobalsTableViewControllerRowAction rowAction;
|
||||
|
||||
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture;
|
||||
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture action:(FLEXGlobalsTableViewControllerRowAction)rowSelectedAction;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// FLEXGlobalsTableViewControllerEntry.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Javier Soto on 7/26/14.
|
||||
// Copyright (c) 2014 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXGlobalsTableViewControllerEntry.h"
|
||||
|
||||
@implementation FLEXGlobalsTableViewControllerEntry
|
||||
|
||||
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture
|
||||
viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture
|
||||
{
|
||||
NSParameterAssert(nameFuture);
|
||||
NSParameterAssert(viewControllerFuture);
|
||||
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
|
||||
entry->_entryNameFuture = [nameFuture copy];
|
||||
entry->_viewControllerFuture = [viewControllerFuture copy];
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture
|
||||
action:(FLEXGlobalsTableViewControllerRowAction)rowSelectedAction
|
||||
{
|
||||
NSParameterAssert(nameFuture);
|
||||
NSParameterAssert(rowSelectedAction);
|
||||
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
|
||||
entry->_entryNameFuture = [nameFuture copy];
|
||||
entry->_rowAction = [rowSelectedAction copy];
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@end
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXLayerExplorerViewController.h
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 12/14/14.
|
||||
// Copyright (c) 2014 f. All rights reserved.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// FLEXLayerExplorerViewController.m
|
||||
// UICatalog
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 12/14/14.
|
||||
// Copyright (c) 2014 f. All rights reserved.
|
||||
+1
@@ -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.
|
||||
|
||||
+295
-156
@@ -15,8 +15,23 @@
|
||||
#import "FLEXIvarEditorViewController.h"
|
||||
#import "FLEXMethodCallingViewController.h"
|
||||
#import "FLEXInstancesTableViewController.h"
|
||||
#import "FLEXTableView.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,45 +51,59 @@
|
||||
@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
|
||||
|
||||
@implementation FLEXObjectExplorerViewController
|
||||
|
||||
- (id)initWithStyle:(UITableViewStyle)style
|
||||
+ (void)initialize
|
||||
{
|
||||
// Force grouped style
|
||||
return [super initWithStyle:UITableViewStyleGrouped];
|
||||
if (self == [FLEXObjectExplorerViewController class]) {
|
||||
// Initialize custom menu items for entire app
|
||||
UIMenuItem *copyObjectAddress = [[UIMenuItem alloc] initWithTitle:@"Copy Address" action:@selector(copyObjectAddress:)];
|
||||
[UIMenuController sharedMenuController].menuItems = @[copyObjectAddress];
|
||||
[[UIMenuController sharedMenuController] update];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
self.tableView = [[FLEXTableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
@@ -85,8 +114,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 +144,29 @@ 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 +179,64 @@ 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 +245,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
|
||||
@@ -192,20 +286,24 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (BOOL)shouldShowDescription
|
||||
{
|
||||
BOOL showDescription = YES;
|
||||
|
||||
// Not if it's empty or nil.
|
||||
NSString *descripition = [FLEXUtility safeDescriptionForObject:self.object];
|
||||
if (showDescription) {
|
||||
showDescription = [descripition length] > 0;
|
||||
}
|
||||
|
||||
// Not if we have filter text that doesn't match the desctiption.
|
||||
if (showDescription && [self.filterText length] > 0) {
|
||||
showDescription = [descripition rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
|
||||
if (self.filterText.length) {
|
||||
NSString *description = [self displayedObjectDescription];
|
||||
return [description rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
|
||||
}
|
||||
|
||||
return showDescription;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)displayedObjectDescription {
|
||||
NSString *desc = [FLEXUtility safeDescriptionForObject:self.object];
|
||||
|
||||
if (!desc.length) {
|
||||
NSString *address = [FLEXUtility addressOfObject:self.object];
|
||||
desc = [NSString stringWithFormat:@"Object at %@ returned empty description", address];
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
|
||||
@@ -215,12 +313,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 +338,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 +350,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 +384,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 +399,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 +423,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 +435,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 +469,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 +483,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 +499,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 +530,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 +557,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 +581,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 +598,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 +613,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 +630,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];
|
||||
@@ -588,7 +705,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
NSString *title = nil;
|
||||
switch (section) {
|
||||
case FLEXObjectExplorerSectionDescription:
|
||||
title = [FLEXUtility safeDescriptionForObject:self.object];
|
||||
title = [self displayedObjectDescription];
|
||||
break;
|
||||
|
||||
case FLEXObjectExplorerSectionCustom:
|
||||
@@ -708,19 +825,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
return canDrillIn;
|
||||
}
|
||||
|
||||
- (BOOL)canCopyRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
|
||||
- (BOOL)sectionHasActions:(NSInteger)section
|
||||
{
|
||||
BOOL canCopy = NO;
|
||||
|
||||
switch (section) {
|
||||
case FLEXObjectExplorerSectionDescription:
|
||||
canCopy = YES;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return canCopy;
|
||||
return [self explorerSectionAtIndex:section] == FLEXObjectExplorerSectionDescription;
|
||||
}
|
||||
|
||||
- (NSString *)titleForExplorerSection:(FLEXObjectExplorerSection)section
|
||||
@@ -736,34 +843,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 +953,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 +970,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 +996,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;
|
||||
}
|
||||
|
||||
@@ -917,45 +1026,65 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
|
||||
BOOL canCopy = [self canCopyRow:indexPath.row inExplorerSection:explorerSection];
|
||||
return canCopy;
|
||||
return [self sectionHasActions:indexPath.section];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
BOOL canPerformAction = NO;
|
||||
|
||||
if (action == @selector(copy:)) {
|
||||
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
|
||||
BOOL canCopy = [self canCopyRow:indexPath.row inExplorerSection:explorerSection];
|
||||
canPerformAction = canCopy;
|
||||
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
|
||||
switch (explorerSection) {
|
||||
case FLEXObjectExplorerSectionDescription:
|
||||
return action == @selector(copy:) || action == @selector(copyObjectAddress:);
|
||||
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
|
||||
return canPerformAction;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
if (action == @selector(copy:)) {
|
||||
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
|
||||
NSString *stringToCopy = @"";
|
||||
|
||||
NSString *title = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
|
||||
if ([title length] > 0) {
|
||||
stringToCopy = [stringToCopy stringByAppendingString:title];
|
||||
}
|
||||
|
||||
NSString *subtitle = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
|
||||
if ([subtitle length] > 0) {
|
||||
if ([stringToCopy length] > 0) {
|
||||
stringToCopy = [stringToCopy stringByAppendingString:@"\n\n"];
|
||||
}
|
||||
stringToCopy = [stringToCopy stringByAppendingString:subtitle];
|
||||
}
|
||||
|
||||
[[UIPasteboard generalPasteboard] setString:stringToCopy];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[self performSelector:action withObject:indexPath];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UIMenuController
|
||||
|
||||
/// Prevent the search bar from trying to use us as a responder
|
||||
///
|
||||
/// Our table cells will use the UITableViewDelegate methods
|
||||
/// to make sure we can perform the actions we want to
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)copy:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
|
||||
NSString *stringToCopy = @"";
|
||||
|
||||
NSString *title = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
|
||||
if (title.length) {
|
||||
stringToCopy = [stringToCopy stringByAppendingString:title];
|
||||
}
|
||||
|
||||
NSString *subtitle = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
|
||||
if (subtitle.length) {
|
||||
if (stringToCopy.length) {
|
||||
stringToCopy = [stringToCopy stringByAppendingString:@"\n\n"];
|
||||
}
|
||||
stringToCopy = [stringToCopy stringByAppendingString:subtitle];
|
||||
}
|
||||
|
||||
[UIPasteboard generalPasteboard].string = stringToCopy;
|
||||
}
|
||||
|
||||
- (void)copyObjectAddress:(NSIndexPath *)indexPath
|
||||
{
|
||||
[UIPasteboard generalPasteboard].string = [FLEXUtility addressOfObject:self.object];
|
||||
}
|
||||
|
||||
|
||||
@@ -1020,6 +1149,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;
|
||||
+8
-8
@@ -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]];
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// FLEXGlobalsTableViewControllerEntry.m
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Javier Soto on 7/26/14.
|
||||
// Copyright (c) 2014 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXGlobalsTableViewControllerEntry.h"
|
||||
|
||||
@implementation FLEXGlobalsTableViewControllerEntry
|
||||
|
||||
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture
|
||||
{
|
||||
NSParameterAssert(nameFuture);
|
||||
NSParameterAssert(viewControllerFuture);
|
||||
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
|
||||
entry->_entryNameFuture = [nameFuture copy];
|
||||
entry->_viewControllerFuture = [viewControllerFuture copy];
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user