Compare commits

...

36 Commits

Author SHA1 Message Date
Tanner Bennett cd695ed106 Bump version 2021-11-14 00:53:41 -06:00
Tanner Bennett 69c1719159 Remove redundant/unused methods in arginputfactory 2021-11-14 00:39:44 -06:00
Tanner Bennett 4019518bf5 Fix #564
entry->log_message.format appears to be garbage on iOS 15, and it doesn't look like it is ever really used in practice anyway, as far as I can tell. Thanks @matrush for pointing this out!
2021-11-14 00:39:44 -06:00
Matt Robinson 3fd8e7c77d Fix CLANG_WARN_STRICT_PROTOTYPES/-Wstrict-prototypes issues
This allows `FLEX` to be linked statically to a binary that has `-Wstrict-prototypes` enabled (for example in CocoaPods without `use_frameworks!`.

Changes:
- Apply `void` to all the empty function/block declarations that don't take arguments.
- Apply `SEL`/`UIImage *` to the others that actually take arguments.
- Remove `CLANG_WARN_STRICT_PROTOTYPES = NO` since the default is enabled.
2021-11-14 00:39:44 -06:00
Tanner Bennett 99c3bcb8c5 Remove init exceptions from flex meta classes 2021-11-14 00:39:44 -06:00
Tanner Bennett b587e96e70 Make FLEXMirror protocol 2021-11-14 00:39:43 -06:00
Tanner Bennett ef8f0a303e Add missing nullability to metadata types 2021-11-14 00:36:52 -06:00
Tanner Bennett cfb1e4caab Add NS_SWIFT_NAME to some enums; does not work
Left it commented out. The enums just disappear for some reason?
2021-11-04 19:11:18 -05:00
daniel 2411c331cd Added a dynamic background color to the WebViewControllers response (Depending on being in Light/Dark mode. See this gist for an example: https://gist.githubusercontent.com/dlevi309/e4d48556836b26125e95cbd82d32a9de/raw/d01112d6e0734db11fa74e2fdb0a1330ddcf82a2/dynamicPage.html) 2021-10-27 13:45:33 -05:00
Tanner Bennett fd4b38f46d Fix bug in tab-close logic 2021-10-26 14:43:20 -05:00
Chaoshuai Lu 269e31894c Remove NSParameterAssert check in closeTab method 2021-10-25 18:24:50 -07:00
Tanner Bennett 2f2da50aed Fix potential crash in FLEXPointerIsReadable
vm_read allocates heap memory, this was intended to be an allocation-free check. Some apps may run out of memory in this code path.
2021-10-20 15:21:26 -05:00
Tanner Bennett d87779212c Add data property to FIRDocumentSnapshot at runtime 2021-10-17 17:33:23 -05:00
Tanner Bennett 5db6a12c6e project.pbxcproj formatting? 2021-10-17 17:33:02 -05:00
Tanner Bennett 6d0f776102 Add a FLEXHeapEnumerator test
Ensure we can spoof an object that is found by FLEXHeapEnumerator and also later prove it isn't a real object
2021-10-17 17:32:09 -05:00
Tanner Bennett 6c83ddc2c7 Refactor FLEXHeapEnumerator and FLEXObjectRef
Move logic that was in FLEXLiveObjectsController into FLEXHeapEnumerator. Also adjust FLEXObjectRef initializers to reflect the type of reference you want to hold to the object. FLEXObjectRef now supports unsafe_unretained and retained references.
2021-10-17 17:31:12 -05:00
Tanner Bennett b510d24e13 Improve FLEXPointerIsValidObjcObject
Compare allocation size to expected instance size
2021-10-17 17:28:59 -05:00
Tanner Bennett 6cdf2e61dc Fix websocket activity not being cleared 2021-10-14 00:28:49 -05:00
Tanner Bennett 9b0ed83ff5 Remove old commented out code 2021-10-13 23:41:13 -05:00
Tanner Bennett dbe1b93f48 Add network observer flag to our NSUserDefaults category 2021-10-13 18:45:52 -05:00
Tanner Bennett 06444f1576 Clean up some comments 2021-10-11 20:21:37 -05:00
Tanner Bennett 9bbf1d0d48 Refactor FLEXNetworkTransaction
Add FLEXURLTransaction to differentiate between NSURL-API-related transactions and other transactions, such as lower level protocols or Firebase ;)
2021-10-11 20:11:04 -05:00
Tanner Bennett 0562f15cd0 Fix incoming WS messages not showing correct details 2021-10-11 20:09:58 -05:00
Tanner Bennett eb63c91481 Send example WS message 2021-10-11 20:09:40 -05:00
Tanner Bennett f916174070 Bump version and update URLs 2021-10-06 19:11:01 -05:00
Tanner Bennett 60e23e126e Fix crash for unsupported property type encodings 2021-10-06 19:11:01 -05:00
Tanner Bennett afeff1b562 Add FLEXMITMDataSource, refactor MITMVC 2021-10-06 18:18:54 -05:00
Tanner Bennett 5acb33005b Refactor HTTP transaction detail controller 2021-10-06 18:18:54 -05:00
Tanner Bennett 3446eff353 Hook and record websocket methods 2021-10-06 18:18:54 -05:00
Tanner Bennett ad1f1f579e Move network transaction model logic into model 2021-10-06 18:18:54 -05:00
Tanner Bennett e03b5f7e5d Send example websocket traffic 2021-10-06 18:18:54 -05:00
Tanner Bennett d010c82dd0 Add OSCache for network caching 2021-10-06 18:18:54 -05:00
Tanner Bennett 652d03c39a Add -[NSArray flex_firstWhere:] 2021-10-06 18:18:54 -05:00
Tanner Bennett 1d39669a52 Add button to dlopen a library in Runtime Browser 2021-10-06 18:18:54 -05:00
Iulian Onofrei d558ca6852 Fix code style 2021-09-22 11:49:00 -07:00
Duraid Abdul ad32ca0f05 Update project.pbxproj 2021-08-31 12:04:22 -07:00
70 changed files with 1971 additions and 588 deletions
@@ -74,7 +74,7 @@
#pragma mark - Search
- (void)updateSearchResults:(NSString *)newText {
NSArray *(^filter)() = ^NSArray *{
NSArray *(^filter)(void) = ^NSArray *{
self.filterText = newText;
// Sections will adjust data based on this property
@@ -96,7 +96,10 @@
- (void)dismissAnimated {
// Tabs are only closed if the done button is pressed; this
// allows you to leave a tab open by dragging down to dismiss
[FLEXTabList.sharedList closeTab:self];
if ([self.presentingViewController isKindOfClass:[FLEXExplorerViewController class]]) {
[FLEXTabList.sharedList closeTab:self];
}
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
@@ -559,7 +559,7 @@ static UITextField *kDummyTextField = nil;
[self.debounceTimer invalidate];
NSString *text = searchController.searchBar.text;
void (^updateSearchResults)() = ^{
void (^updateSearchResults)(void) = ^{
if (self.searchResultsUpdater) {
[self.searchResultsUpdater updateSearchResults:text];
} else {
@@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXDefaultEditorViewController : FLEXVariableEditorViewController
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit;
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit;
+ (BOOL)canEditDefaultWithValue:(nullable id)currentValue;
@@ -21,7 +21,7 @@
@implementation FLEXDefaultEditorViewController
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit {
FLEXDefaultEditorViewController *editor = [self target:defaults data:key commitHandler:onCommit];
editor.title = @"Edit Default";
return editor;
@@ -15,9 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXFieldEditorViewController : FLEXVariableEditorViewController
/// @return nil if the property is readonly or if the type is unsupported
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit;
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit;
/// @return nil if the ivar type is unsupported
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit;
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit;
/// Subclasses can change the button title via the \c title property
@property (nonatomic, readonly) UIBarButtonItem *getterButton;
@@ -30,19 +30,14 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit {
id value = [property getValue:target];
if (![self canEditProperty:property onObject:target currentValue:value]) {
return nil;
}
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:property commitHandler:onCommit];
editor.title = [@"Property: " stringByAppendingString:property.name];
editor.property = property;
return editor;
}
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit];
editor.title = [@"Ivar: " stringByAppendingString:ivar.name];
editor.ivar = ivar;
@@ -151,14 +146,4 @@
}
}
+ (BOOL)canEditProperty:(FLEXProperty *)property onObject:(id)object currentValue:(id)value {
const FLEXTypeEncoding *typeEncoding = property.attributes.typeEncoding.UTF8String;
BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
return canEditType && [object respondsToSelector:property.likelySetter];
}
+ (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value {
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
}
@end
@@ -21,17 +21,17 @@ NS_ASSUME_NONNULL_BEGIN
@protected
id _target;
_Nullable id _data;
void (^_Nullable _commitHandler)();
void (^_Nullable _commitHandler)(void);
}
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit;
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
@property (nonatomic, readonly) id target;
@@ -25,11 +25,11 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
return [[self alloc] initWithTarget:target data:data commitHandler:onCommit];
}
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)())onCommit {
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
self = [super init];
if (self) {
_target = target;
@@ -460,17 +460,12 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
initWithTarget:self action:@selector(handleToolbarDetailsTapGesture:)
];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:self.detailsTapGR];
// Swipe gestures for selecting deeper / higher views at a point
UIPanGestureRecognizer *leftSwipe = [[UIPanGestureRecognizer alloc]
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
];
// UIPanGestureRecognizer *rightSwipe = [[UIPanGestureRecognizer alloc]
// initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
// ];
// leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
// rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:leftSwipe];
// [toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:panGesture];
// Long press gesture to present tabs manager
[toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
@@ -72,7 +72,7 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)showRevertOrDismissAlert:(void(^)())revertBlock {
- (void)showRevertOrDismissAlert:(void(^)(void))revertBlock {
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
[self reloadData];
[self.tableView reloadData];
+3 -3
View File
@@ -80,10 +80,10 @@
- (void)closeTab:(UINavigationController *)tab {
NSParameterAssert(tab);
NSParameterAssert([self.openTabs containsObject:tab]);
NSInteger idx = [self.openTabs indexOfObject:tab];
[self closeTabAtIndex:idx];
if (idx != NSNotFound) {
[self closeTabAtIndex:idx];
}
}
- (void)closeTabAtIndex:(NSInteger)idx {
@@ -226,7 +226,10 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *className = self.filteredClassNames[indexPath.row];
UIViewController *instances = [FLEXObjectListViewController instancesOfClassWithName:className];
UIViewController *instances = [FLEXObjectListViewController
instancesOfClassWithName:className
retained:YES
];
[self.navigationController pushViewController:instances animated:YES];
}
@@ -12,8 +12,8 @@
/// This will either return a list of the instances, or take you straight
/// to the explorer itself if there is only one instance.
+ (UIViewController *)instancesOfClassWithName:(NSString *)className;
+ (UIViewController *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain;
+ (instancetype)subclassesOfClassWithName:(NSString *)className;
+ (instancetype)objectsWithReferencesToObject:(id)object;
+ (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain;
@end
@@ -141,45 +141,33 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
return self;
}
+ (UIViewController *)instancesOfClassWithName:(NSString *)className {
const char *classNameCString = className.UTF8String;
NSMutableArray *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
// Note: objects of certain classes crash when retain is called.
// It is up to the user to avoid tapping into instance lists for these classes.
// Ex. OS_dispatch_queue_specific_queue
// In the future, we could provide some kind of warning for classes that are known to be problematic.
if (malloc_size((__bridge const void *)(object)) > 0) {
[instances addObject:object];
}
}
}];
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
+ (UIViewController *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain {
NSArray<FLEXObjectRef *> *references = [FLEXHeapEnumerator
instancesOfClassWithName:className retained:retain
];
if (references.count == 1) {
return [FLEXObjectExplorerFactory
explorerViewControllerForObject:references.firstObject.object
explorerViewControllerForObject:references.firstObject.object
];
}
FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
controller.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)instances.count];
controller.title = [NSString stringWithFormat:@"%@ (%@)", className, @(references.count)];
return controller;
}
+ (instancetype)subclassesOfClassWithName:(NSString *)className {
NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
NSArray<FLEXObjectRef *> *references = [FLEXHeapEnumerator subclassesOfClassWithName:className];
FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%lu)",
className, (unsigned long)classes.count
controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%@)",
className, @(references.count)
];
return controller;
}
+ (instancetype)objectsWithReferencesToObject:(id)object {
+ (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
static Class SwiftObjectClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -189,35 +177,9 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
}
});
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
// 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;
while (tryClass) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
Ivar ivar = ivars[ivarIndex];
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
if (typeEncoding.flex_typeIsObjectOrClass) {
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
[instances addObject:[FLEXObjectRef referencing:tryObject ivar:ivarName]];
return;
}
}
}
free(ivars);
tryClass = class_getSuperclass(tryClass);
}
}];
NSArray<FLEXObjectRef *> *instances = [FLEXHeapEnumerator
objectsWithReferencesToObject:object retained:retain
];
FLEXObjectListViewController *viewController = [[self alloc]
initWithReferences:instances
+18 -4
View File
@@ -10,10 +10,19 @@
@interface FLEXObjectRef : NSObject
+ (instancetype)referencing:(id)object;
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName;
/// Reference an object without affecting its lifespan or or emitting reference-counting operations.
+ (instancetype)unretained:(__unsafe_unretained id)object;
+ (instancetype)unretained:(__unsafe_unretained id)object ivar:(NSString *)ivarName;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects;
/// Reference an object and control its lifespan.
+ (instancetype)retained:(id)object;
+ (instancetype)retained:(id)object ivar:(NSString *)ivarName;
/// Reference an object and conditionally choose to retain it or not.
+ (instancetype)referencing:(__unsafe_unretained id)object retained:(BOOL)retain;
+ (instancetype)referencing:(__unsafe_unretained id)object ivar:(NSString *)ivarName retained:(BOOL)retain;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects retained:(BOOL)retain;
/// Classes do not have a summary, and the reference is just the class name.
+ (NSArray<FLEXObjectRef *> *)referencingClasses:(NSArray<Class> *)classes;
@@ -22,6 +31,11 @@
/// For instances, this is the result of -[FLEXRuntimeUtility summaryForObject:]
/// For classes, there is no summary.
@property (nonatomic, readonly) NSString *summary;
@property (nonatomic, readonly) id object;
@property (nonatomic, readonly, unsafe_unretained) id object;
/// Retains the referenced object if it is not already retained
- (void)retainObject;
/// Releases the referenced object if it is already retained
- (void)releaseObject;
@end
+47 -11
View File
@@ -10,42 +10,68 @@
#import "FLEXRuntimeUtility.h"
#import "NSArray+FLEX.h"
@interface FLEXObjectRef ()
@interface FLEXObjectRef () {
/// Used to retain the object if desired
id _retainer;
}
@property (nonatomic, readonly) BOOL wantsSummary;
@end
@implementation FLEXObjectRef
@synthesize summary = _summary;
+ (instancetype)referencing:(id)object {
return [self referencing:object showSummary:YES];
+ (instancetype)unretained:(__unsafe_unretained id)object {
return [self referencing:object showSummary:YES retained:NO];
}
+ (instancetype)referencing:(id)object showSummary:(BOOL)showSummary {
return [[self alloc] initWithObject:object ivarName:nil showSummary:showSummary];
+ (instancetype)unretained:(__unsafe_unretained id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES retained:NO];
}
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES];
+ (instancetype)retained:(id)object {
return [self referencing:object showSummary:YES retained:YES];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects {
+ (instancetype)retained:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName showSummary:YES retained:YES];
}
+ (instancetype)referencing:(__unsafe_unretained id)object retained:(BOOL)retain {
return retain ? [self retained:object] : [self unretained:object];
}
+ (instancetype)referencing:(__unsafe_unretained id)object ivar:(NSString *)ivarName retained:(BOOL)retain {
return retain ? [self retained:object ivar:ivarName] : [self unretained:object ivar:ivarName];
}
+ (instancetype)referencing:(__unsafe_unretained id)object showSummary:(BOOL)showSummary retained:(BOOL)retain {
return [[self alloc] initWithObject:object ivarName:nil showSummary:showSummary retained:retain];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects retained:(BOOL)retain {
return [objects flex_mapped:^id(id obj, NSUInteger idx) {
return [self referencing:obj showSummary:YES];
return [self referencing:obj showSummary:YES retained:retain];
}];
}
+ (NSArray<FLEXObjectRef *> *)referencingClasses:(NSArray<Class> *)classes {
return [classes flex_mapped:^id(id obj, NSUInteger idx) {
return [self referencing:obj showSummary:NO];
return [self referencing:obj showSummary:NO retained:NO];
}];
}
- (id)initWithObject:(id)object ivarName:(NSString *)ivar showSummary:(BOOL)showSummary {
- (id)initWithObject:(__unsafe_unretained id)object
ivarName:(NSString *)ivar
showSummary:(BOOL)showSummary
retained:(BOOL)retain {
self = [super init];
if (self) {
_object = object;
_wantsSummary = showSummary;
if (retain) {
_retainer = object;
}
NSString *class = [FLEXRuntimeUtility safeClassNameForObject:object];
if (ivar) {
@@ -73,4 +99,14 @@
}
}
- (void)retainObject {
if (!_retainer) {
_retainer = _object;
}
}
- (void)releaseObject {
_retainer = nil;
}
@end
@@ -38,7 +38,7 @@
self = [self initWithNibName:nil bundle:nil];
if (self) {
self.originalText = text;
NSString *htmlString = [NSString stringWithFormat:@"<head><meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
NSString *htmlString = [NSString stringWithFormat:@"<head><style>:root{ color-scheme: light dark; }</style><meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
[self.webView loadHTMLString:htmlString baseURL:nil];
}
return self;
@@ -195,7 +195,7 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
"/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
RTLD_LAZY
);
void (*WebKitInitialize)() = dlsym(handle, "WebKitInitialize");
void (*WebKitInitialize)(void) = dlsym(handle, "WebKitInitialize");
if (WebKitInitialize) {
NSAssert(NSThread.isMainThread,
@"WebKitInitialize can only be called on the main thread"
@@ -10,10 +10,12 @@
#import "FLEXKeyPathSearchController.h"
#import "FLEXRuntimeBrowserToolbar.h"
#import "UIGestureRecognizer+Blocks.h"
#import "UIBarButtonItem+FLEX.h"
#import "FLEXTableView.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXAlert.h"
#import "FLEXRuntimeClient.h"
#import <dlfcn.h>
@interface FLEXObjcRuntimeViewController () <FLEXKeyPathSearchControllerDelegate>
@@ -43,6 +45,8 @@
]
];
[self addToolbarItems:@[FLEXBarButtonItem(@"dlopen()", self, @selector(dlopenPressed:))]];
// Search bar stuff, must be first because this creates self.searchController
self.showsSearchBar = YES;
self.showSearchBarInitially = YES;
@@ -74,6 +78,63 @@
}
#pragma mark dlopen
/// Prompt user for dlopen shortcuts to choose from
- (void)dlopenPressed:(id)sender {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Dynamically Open Library");
make.message(@"Invoke dlopen() with the given path. Choose an option below.");
make.button(@"System Framework").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:@"/System/Library/Frameworks/%@.framework/%@"];
});
make.button(@"System Private Framework").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:@"/System/Library/PrivateFrameworks/%@.framework/%@"];
});
make.button(@"Arbitrary Binary").handler(^(NSArray<NSString *> *_) {
[self dlopenWithFormat:nil];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
}
/// Prompt user for input and dlopen
- (void)dlopenWithFormat:(NSString *)format {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Dynamically Open Library");
if (format) {
make.message(@"Pass in a framework name, such as CarKit or FrontBoard.");
} else {
make.message(@"Pass in an absolute path to a binary.");
}
make.textField(format ? @"ARKit" : @"/System/Library/Frameworks/ARKit.framework/ARKit");
make.button(@"Cancel").cancelStyle();
make.button(@"Open").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
NSString *path = strings[0];
if (path.length < 2) {
[self dlopenInvalidPath];
} else if (format) {
path = [NSString stringWithFormat:format, path, path];
}
dlopen(path.UTF8String, RTLD_NOW);
});
} showFrom:self];
}
- (void)dlopenInvalidPath {
[FLEXAlert makeAlert:^(FLEXAlert * _Nonnull make) {
make.title(@"Path or Name Too Short");
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
}
#pragma mark Delegate stuff
- (void)didSelectImagePath:(NSString *)path shortName:(NSString *)shortName {
@@ -168,11 +168,10 @@ static uint8_t (*OSLogGetType)(void *);
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;
}
// https://github.com/FLEXTool/FLEX/issues/564
const char *messageText = OSLogCopyFormattedMessage(log_message) ?: "";
// move messageText from stack to heap
NSString *msg = [NSString stringWithUTF8String:messageText];
@@ -0,0 +1,17 @@
//
// FLEXHTTPTransactionDetailController.h
// Flipboard
//
// Created by Ryan Olson on 2/10/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FLEXHTTPTransaction;
@interface FLEXHTTPTransactionDetailController : UITableViewController
+ (instancetype)withTransaction:(FLEXHTTPTransaction *)transaction;
@end
@@ -7,7 +7,7 @@
//
#import "FLEXColor.h"
#import "FLEXNetworkTransactionDetailController.h"
#import "FLEXHTTPTransactionDetailController.h"
#import "FLEXNetworkCurlLogger.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkTransaction.h"
@@ -22,64 +22,63 @@
typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@interface FLEXNetworkDetailRow : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *detailText;
@property (nonatomic, copy) FLEXNetworkDetailRowSelectionFuture selectionFuture;
@end
@implementation FLEXNetworkDetailRow
@end
@interface FLEXNetworkDetailSection : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
@end
@implementation FLEXNetworkDetailSection
@end
@interface FLEXNetworkTransactionDetailController ()
@interface FLEXHTTPTransactionDetailController ()
@property (nonatomic, readonly) FLEXHTTPTransaction *transaction;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
@end
@implementation FLEXNetworkTransactionDetailController
@implementation FLEXHTTPTransactionDetailController
+ (instancetype)withTransaction:(FLEXHTTPTransaction *)transaction {
FLEXHTTPTransactionDetailController *controller = [self new];
controller.transaction = transaction;
return controller;
}
- (instancetype)initWithStyle:(UITableViewStyle)style {
// Force grouped style
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(handleTransactionUpdatedNotification:)
name:kFLEXNetworkRecorderTransactionUpdatedNotification
object:nil
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
flex_itemWithTitle:@"Copy curl"
target:self
action:@selector(copyButtonPressed:)
]
];
}
return self;
return [super initWithStyle:UITableViewStyleGrouped];
}
- (void)viewDidLoad {
[super viewDidLoad];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(handleTransactionUpdatedNotification:)
name:kFLEXNetworkRecorderTransactionUpdatedNotification
object:nil
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
flex_itemWithTitle:@"Copy curl"
target:self
action:@selector(copyButtonPressed:)
]
];
[self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineCell];
}
- (void)setTransaction:(FLEXNetworkTransaction *)transaction {
- (void)setTransaction:(FLEXHTTPTransaction *)transaction {
if (![_transaction isEqual:transaction]) {
_transaction = transaction;
self.title = [transaction.request.URL lastPathComponent];
@@ -189,6 +188,12 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
];
}
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [NSArray flex_forEachUpTo:self.sections.count map:^id(NSUInteger i) {
return @"";
}];
}
- (FLEXNetworkDetailRow *)rowModelAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkDetailSection *sectionModel = self.sections[indexPath.section];
return sectionModel.rows[indexPath.row];
@@ -253,7 +258,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
#pragma mark - Table Data Generation
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXHTTPTransaction *)transaction {
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
FLEXNetworkDetailRow *requestURLRow = [FLEXNetworkDetailRow new];
@@ -407,7 +412,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return generalSection;
}
+ (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *requestHeadersSection = [FLEXNetworkDetailSection new];
requestHeadersSection.title = @"Request Headers";
requestHeadersSection.rows = [self networkDetailRowsFromDictionary:transaction.request.allHTTPHeaderFields];
@@ -415,7 +420,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return requestHeadersSection;
}
+ (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *postBodySection = [FLEXNetworkDetailSection new];
postBodySection.title = @"Request Body Parameters";
if (transaction.cachedRequestBody.length > 0) {
@@ -429,7 +434,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return postBodySection;
}
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
NSArray<NSURLQueryItem *> *queries = [FLEXUtility itemsFromQueryString:transaction.request.URL.query];
FLEXNetworkDetailSection *querySection = [FLEXNetworkDetailSection new];
querySection.title = @"Query Parameters";
@@ -438,7 +443,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return querySection;
}
+ (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
+ (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXHTTPTransaction *)transaction {
FLEXNetworkDetailSection *responseHeadersSection = [FLEXNetworkDetailSection new];
responseHeadersSection.title = @"Response Headers";
if ([transaction.response isKindOfClass:[NSHTTPURLResponse class]]) {
@@ -516,7 +521,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return detailViewController;
}
+ (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction {
+ (NSData *)postBodyDataForTransaction:(FLEXHTTPTransaction *)transaction {
NSData *bodyData = transaction.cachedRequestBody;
if (bodyData.length > 0) {
NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
+34
View File
@@ -0,0 +1,34 @@
//
// FLEXMITMDataSource.h
// FLEX
//
// Created by Tanner Bennett on 8/22/21.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FLEXMITMDataSource<__covariant TransactionType> : NSObject
+ (instancetype)dataSourceWithProvider:(NSArray<TransactionType> *(^)(void))future;
@property (nonatomic, readonly) NSArray<TransactionType> *transactions;
@property (nonatomic, readonly) NSArray<TransactionType> *allTransactions;
/// Equal to \c allTransactions if not filtered
@property (nonatomic, readonly) NSArray<TransactionType> *filteredTransactions;
/// Use this instead of either of the other two as it updates based on whether we have a filter or not
@property (nonatomic) NSInteger bytesReceived;
@property (nonatomic) NSInteger totalBytesReceived;
/// Equal to \c totalBytesReceived if not filtered
@property (nonatomic) NSInteger filteredBytesReceived;
- (void)reloadByteCounts;
- (void)reloadData:(void (^_Nullable)(FLEXMITMDataSource *dataSource))completion;
- (void)filter:(NSString *)searchString completion:(void(^_Nullable)(FLEXMITMDataSource *dataSource))completion;
@end
NS_ASSUME_NONNULL_END
+102
View File
@@ -0,0 +1,102 @@
//
// FLEXMITMDataSource.m
// FLEX
//
// Created by Tanner Bennett on 8/22/21.
//
#import "FLEXMITMDataSource.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXUtility.h"
@interface FLEXMITMDataSource ()
@property (nonatomic, readonly) NSArray *(^dataProvider)(void);
@property (nonatomic) NSString *filterString;
@end
@implementation FLEXMITMDataSource
+ (instancetype)dataSourceWithProvider:(NSArray<id> *(^)(void))future {
FLEXMITMDataSource *ds = [self new];
ds->_dataProvider = future;
[ds reloadData:nil];
return ds;
}
- (NSArray *)transactions {
return _filteredTransactions;
}
- (NSInteger)bytesReceived {
return _filteredBytesReceived;
}
- (void)reloadByteCounts {
[self updateBytesReceived];
[self updateFilteredBytesReceived];
}
- (void)reloadData:(void (^)(FLEXMITMDataSource *dataSource))completion {
self.allTransactions = self.dataProvider();
[self filter:self.filterString completion:completion];
}
- (void)filter:(NSString *)searchString completion:(void (^)(FLEXMITMDataSource *dataSource))completion {
self.filterString = searchString;
if (!searchString.length) {
self.filteredTransactions = self.allTransactions;
if (completion) completion(self);
} else {
[self onBackgroundQueue:^NSArray *{
return [self.allTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) {
return [entry matchesQuery:searchString];
}];
} thenOnMainQueue:^(NSArray *filteredNetworkTransactions) {
if ([self.filterString isEqual:searchString]) {
self.filteredTransactions = filteredNetworkTransactions;
if (completion) completion(self);
}
}];
}
}
- (void)setAllTransactions:(NSArray *)transactions {
_allTransactions = transactions;
[self updateBytesReceived];
}
- (void)setFilteredTransactions:(NSArray *)filteredTransactions {
_filteredTransactions = filteredTransactions;
[self updateFilteredBytesReceived];
}
- (void)updateBytesReceived {
NSInteger bytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.transactions) {
bytesReceived += transaction.receivedDataLength;
}
self.bytesReceived = bytesReceived;
}
- (void)updateFilteredBytesReceived {
NSInteger filteredBytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.filteredTransactions) {
filteredBytesReceived += transaction.receivedDataLength;
}
self.filteredBytesReceived = filteredBytesReceived;
}
- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *items = backgroundBlock();
dispatch_async(dispatch_get_main_queue(), ^{
mainBlock(items);
});
});
}
@end
@@ -9,6 +9,7 @@
#import "FLEXTableViewController.h"
#import "FLEXGlobalsEntry.h"
/// The main screen for the network observer, which displays a list of network transactions.
@interface FLEXNetworkMITMViewController : FLEXTableViewController <FLEXGlobalsEntry>
@end
+193 -135
View File
@@ -8,29 +8,36 @@
#import "FLEXColor.h"
#import "FLEXUtility.h"
#import "FLEXMITMDataSource.h"
#import "FLEXNetworkMITMViewController.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkTransactionCell.h"
#import "FLEXNetworkTransactionDetailController.h"
#import "FLEXHTTPTransactionDetailController.h"
#import "FLEXNetworkSettingsController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXGlobalsViewController.h"
#import "FLEXWebViewController.h"
#import "UIBarButtonItem+FLEX.h"
#import "FLEXResources.h"
typedef NS_ENUM(NSUInteger, FLEXNetworkObserverMode) {
FLEXNetworkObserverModeREST = 0,
FLEXNetworkObserverModeWebsockets,
};
@interface FLEXNetworkMITMViewController ()
/// Backing model
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *networkTransactions;
@property (nonatomic) long long bytesReceived;
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions;
@property (nonatomic) long long filteredBytesReceived;
@property (nonatomic) BOOL rowInsertInProgress;
@property (nonatomic) BOOL isPresentingSearch;
@property (nonatomic) BOOL updateInProgress;
@property (nonatomic) BOOL pendingReload;
@property (nonatomic, readonly) FLEXNetworkObserverMode mode;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXNetworkTransaction *> *dataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXHTTPTransaction *> *HTTPDataSource;
@property (nonatomic, readonly) FLEXMITMDataSource<FLEXWebsocketTransaction *> *websocketDataSource;
@end
@implementation FLEXNetworkMITMViewController
@@ -47,6 +54,18 @@
self.showsSearchBar = YES;
self.showSearchBarInitially = NO;
_HTTPDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * {
return FLEXNetworkRecorder.defaultRecorder.HTTPTransactions;
}];
if (@available(iOS 13.0, *)) {
self.searchController.searchBar.showsScopeBar = YES;
self.searchController.searchBar.scopeButtonTitles = @[@"REST", @"Websockets"];
_websocketDataSource = [FLEXMITMDataSource dataSourceWithProvider:^NSArray * {
return FLEXNetworkRecorder.defaultRecorder.websocketTransactions;
}];
}
[self addToolbarItems:@[
[UIBarButtonItem
flex_itemWithImage:FLEXResources.gearIcon
@@ -61,14 +80,14 @@
]];
[self.tableView
registerClass:[FLEXNetworkTransactionCell class]
forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier
registerClass:FLEXNetworkTransactionCell.class
forCellReuseIdentifier:FLEXNetworkTransactionCell.reuseID
];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.rowHeight = FLEXNetworkTransactionCell.preferredCellHeight;
[self registerForNotifications];
[self updateTransactions];
[self updateTransactions:nil];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -137,44 +156,36 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark Transactions
- (void)updateTransactions {
self.networkTransactions = [FLEXNetworkRecorder.defaultRecorder networkTransactions];
- (FLEXNetworkObserverMode)mode {
return self.searchController.searchBar.selectedScopeButtonIndex;
}
- (void)setNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions {
if (![_networkTransactions isEqual:networkTransactions]) {
_networkTransactions = networkTransactions;
[self updateBytesReceived];
[self updateFilteredBytesReceived];
- (FLEXMITMDataSource<FLEXNetworkTransaction *> *)dataSource {
switch (self.mode) {
case FLEXNetworkObserverModeREST:
return self.HTTPDataSource;
case FLEXNetworkObserverModeWebsockets:
return self.websocketDataSource;
default:
@throw NSInternalInconsistencyException;
}
}
- (void)updateBytesReceived {
long long bytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.networkTransactions) {
bytesReceived += transaction.receivedDataLength;
}
self.bytesReceived = bytesReceived;
[self updateFirstSectionHeader];
- (void)updateTransactions:(void(^)(void))callback {
id completion = ^(FLEXMITMDataSource *dataSource) {
// Update byte count
[self updateFirstSectionHeader];
if (callback && dataSource == self.dataSource) callback();
};
[self.HTTPDataSource reloadData:completion];
[self.websocketDataSource reloadData:completion];
}
- (void)setFilteredNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions {
if (![_filteredNetworkTransactions isEqual:networkTransactions]) {
_filteredNetworkTransactions = networkTransactions;
[self updateFilteredBytesReceived];
}
}
- (void)updateFilteredBytesReceived {
long long filteredBytesReceived = 0;
for (FLEXNetworkTransaction *transaction in self.filteredNetworkTransactions) {
filteredBytesReceived += transaction.receivedDataLength;
}
self.filteredBytesReceived = filteredBytesReceived;
[self updateFirstSectionHeader];
}
#pragma mark Header
@@ -191,11 +202,11 @@
long long bytesReceived = 0;
NSInteger totalRequests = 0;
if (self.searchController.isActive) {
bytesReceived = self.filteredBytesReceived;
totalRequests = self.filteredNetworkTransactions.count;
bytesReceived = self.dataSource.filteredBytesReceived;
totalRequests = self.dataSource.transactions.count;
} else {
bytesReceived = self.bytesReceived;
totalRequests = self.networkTransactions.count;
bytesReceived = self.dataSource.bytesReceived;
totalRequests = self.dataSource.transactions.count;
}
NSString *byteCountText = [NSByteCountFormatter
@@ -253,7 +264,7 @@
- (void)tryUpdateTransactions {
// Don't do any view updating if we aren't in the view hierarchy
if (!self.viewIfLoaded.window) {
[self updateTransactions];
[self updateTransactions:nil];
self.pendingReload = YES;
return;
}
@@ -261,57 +272,71 @@
// Let the previous row insert animation finish before starting a new one to avoid stomping.
// We'll try calling the method again when the insertion completes,
// and we properly no-op if there haven't been changes.
if (self.rowInsertInProgress) {
if (self.updateInProgress) {
return;
}
if (self.searchController.isActive) {
[self updateTransactions];
[self updateSearchResults:self.searchText];
return;
}
self.updateInProgress = YES;
NSInteger existingRowCount = self.networkTransactions.count;
[self updateTransactions];
NSInteger newRowCount = self.networkTransactions.count;
NSInteger addedRowCount = newRowCount - existingRowCount;
if (addedRowCount != 0 && !self.isPresentingSearch) {
// Insert animation if we're at the top.
if (self.tableView.contentOffset.y <= 0.0 && addedRowCount > 0) {
[CATransaction begin];
self.rowInsertInProgress = YES;
[CATransaction setCompletionBlock:^{
self.rowInsertInProgress = NO;
[self tryUpdateTransactions];
}];
NSMutableArray<NSIndexPath *> *indexPathsToReload = [NSMutableArray new];
for (NSInteger row = 0; row < addedRowCount; row++) {
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]];
}
[self.tableView insertRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[CATransaction commit];
} else {
// Maintain the user's position if they've scrolled down.
CGSize existingContentSize = self.tableView.contentSize;
[self.tableView reloadData];
CGFloat contentHeightChange = self.tableView.contentSize.height - existingContentSize.height;
self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + contentHeightChange);
// Get state before update
NSString *currentFilter = self.searchText;
FLEXNetworkObserverMode currentMode = self.mode;
NSInteger existingRowCount = self.dataSource.transactions.count;
[self updateTransactions:^{
// Compare to state after update
NSString *newFilter = self.searchText;
FLEXNetworkObserverMode newMode = self.mode;
NSInteger newRowCount = self.dataSource.transactions.count;
NSInteger rowCountDiff = newRowCount - existingRowCount;
// Abort if the observation mode changed, or if the search field text changed
if (newMode != currentMode || ![currentFilter isEqualToString:newFilter]) {
self.updateInProgress = NO;
return;
}
}
if (rowCountDiff) {
// Insert animation if we're at the top.
if (self.tableView.contentOffset.y <= 0.0 && rowCountDiff > 0) {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
self.updateInProgress = NO;
// This isn't an infinite loop, it won't run a third time
// if there were no new transactions the second time
[self tryUpdateTransactions];
}];
NSMutableArray<NSIndexPath *> *indexPathsToReload = [NSMutableArray new];
for (NSInteger row = 0; row < rowCountDiff; row++) {
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]];
}
[self.tableView insertRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[CATransaction commit];
} else {
// Maintain the user's position if they've scrolled down.
CGSize existingContentSize = self.tableView.contentSize;
[self.tableView reloadData];
CGFloat contentHeightChange = self.tableView.contentSize.height - existingContentSize.height;
self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + contentHeightChange);
self.updateInProgress = NO;
}
} else {
self.updateInProgress = NO;
}
}];
}
- (void)handleTransactionUpdatedNotification:(NSNotification *)notification {
[self updateBytesReceived];
[self updateFilteredBytesReceived];
[self.HTTPDataSource reloadByteCounts];
[self.websocketDataSource reloadByteCounts];
FLEXNetworkTransaction *transaction = notification.userInfo[kFLEXNetworkRecorderUserInfoTransactionKey];
// Update both the main table view and search table view if needed.
for (FLEXNetworkTransactionCell *cell in [self.tableView visibleCells]) {
for (FLEXNetworkTransactionCell *cell in self.tableView.visibleCells) {
if ([cell.transaction isEqual:transaction]) {
// Using -[UITableView reloadRowsAtIndexPaths:withRowAnimation:] is overkill here and kicks off a lot of
// work that can make the table view somewhat unresponsive when lots of updates are streaming in.
@@ -320,12 +345,14 @@
break;
}
}
[self updateFirstSectionHeader];
}
- (void)handleTransactionsClearedNotification:(NSNotification *)notification {
[self updateTransactions];
[self.tableView reloadData];
[self updateTransactions:^{
[self.tableView reloadData];
}];
}
- (void)handleNetworkObserverEnabledStateChangedNotification:(NSNotification *)notification {
@@ -337,7 +364,7 @@
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.searchController.isActive ? self.filteredNetworkTransactions.count : self.networkTransactions.count;
return self.dataSource.transactions.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
@@ -352,7 +379,11 @@
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkTransactionCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath];
FLEXNetworkTransactionCell *cell = [tableView
dequeueReusableCellWithIdentifier:FLEXNetworkTransactionCell.reuseID
forIndexPath:indexPath
];
cell.transaction = [self transactionAtIndexPath:indexPath];
// Since we insert from the top, assign background colors bottom up to keep them consistent for each transaction.
@@ -367,9 +398,33 @@
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FLEXNetworkTransactionDetailController *detailViewController = [FLEXNetworkTransactionDetailController new];
detailViewController.transaction = [self transactionAtIndexPath:indexPath];
[self.navigationController pushViewController:detailViewController animated:YES];
switch (self.mode) {
case FLEXNetworkObserverModeREST: {
FLEXHTTPTransaction *transaction = [self HTTPTransactionAtIndexPath:indexPath];
UIViewController *details = [FLEXHTTPTransactionDetailController withTransaction:transaction];
[self.navigationController pushViewController:details animated:YES];
break;
}
case FLEXNetworkObserverModeWebsockets: {
if (@available(iOS 13.0, *)) { // This check will never fail
FLEXWebsocketTransaction *transaction = [self websocketTransactionAtIndexPath:indexPath];
UIViewController *details = nil;
if (transaction.message.type == NSURLSessionWebSocketMessageTypeData) {
details = [FLEXObjectExplorerFactory explorerViewControllerForObject:transaction.message.data];
} else {
details = [[FLEXWebViewController alloc] initWithText:transaction.message.string];
}
[self.navigationController pushViewController:details animated:YES];
}
break;
}
default:
@throw NSInternalInconsistencyException;
}
}
@@ -385,80 +440,83 @@
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(copy:)) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
UIPasteboard.generalPasteboard.string = [self transactionAtIndexPath:indexPath].copyString;
}
}
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
FLEXNetworkTransaction *transaction = [self transactionAtIndexPath:indexPath];
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
UIAction *copy = [UIAction
actionWithTitle:@"Copy"
actionWithTitle:@"Copy URL"
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
}
];
UIAction *denylist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
[denylist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self tryUpdateTransactions];
UIPasteboard.generalPasteboard.string = transaction.copyString;
}
];
NSArray *children = @[copy];
if (self.mode == FLEXNetworkObserverModeREST) {
NSURLRequest *request = [self HTTPTransactionAtIndexPath:indexPath].request;
UIAction *denylist = [UIAction
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
[denylist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
[self tryUpdateTransactions];
}
];
children = [children arrayByAddingObject:denylist];
}
return [UIMenu
menuWithTitle:@"" image:nil identifier:nil
options:UIMenuOptionsDisplayInline
children:@[copy, denylist]
children:children
];
}
];
}
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath {
return self.searchController.isActive ? self.filteredNetworkTransactions[indexPath.row] : self.networkTransactions[indexPath.row];
return self.dataSource.transactions[indexPath.row];
}
- (FLEXHTTPTransaction *)HTTPTransactionAtIndexPath:(NSIndexPath *)indexPath {
return self.HTTPDataSource.transactions[indexPath.row];
}
- (FLEXWebsocketTransaction *)websocketTransactionAtIndexPath:(NSIndexPath *)indexPath {
return self.websocketDataSource.transactions[indexPath.row];
}
#pragma mark - Search Bar
- (void)updateSearchResults:(NSString *)searchString {
if (!searchString.length) {
self.filteredNetworkTransactions = self.networkTransactions;
[self.tableView reloadData];
} else {
[self onBackgroundQueue:^NSArray *{
return [self.networkTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *entry, NSUInteger idx) {
return [entry.request.URL.absoluteString localizedCaseInsensitiveContainsString:searchString];
}];
} thenOnMainQueue:^(NSArray *filteredNetworkTransactions) {
if ([self.searchText isEqual:searchString]) {
self.filteredNetworkTransactions = filteredNetworkTransactions;
[self.tableView reloadData];
}
}];
}
id callback = ^(FLEXMITMDataSource *dataSource) {
if (self.dataSource == dataSource) {
[self.tableView reloadData];
}
};
[self.HTTPDataSource filter:searchString completion:callback];
[self.websocketDataSource filter:searchString completion:callback];
}
#pragma mark UISearchControllerDelegate
- (void)willPresentSearchController:(UISearchController *)searchController {
self.isPresentingSearch = YES;
}
- (void)didPresentSearchController:(UISearchController *)searchController {
self.isPresentingSearch = NO;
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
[self updateFirstSectionHeader];
[self.tableView reloadData];
}
- (void)willDismissSearchController:(UISearchController *)searchController {
+16 -6
View File
@@ -14,7 +14,7 @@ extern NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification;
extern NSString *const kFLEXNetworkRecorderUserInfoTransactionKey;
extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
@class FLEXNetworkTransaction;
@class FLEXNetworkTransaction, FLEXHTTPTransaction, FLEXWebsocketTransaction;
@interface FLEXNetworkRecorder : NSObject
@@ -37,19 +37,21 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
- (void)synchronizeDenylist;
// Accessing recorded network activity
#pragma mark Accessing recorded network activity
/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first.
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions;
/// Array of FLEXHTTPTransaction objects ordered by start time with the newest first.
@property (nonatomic, readonly) NSArray<FLEXHTTPTransaction *> *HTTPTransactions;
/// Array of FLEXWebsocketTransaction objects ordered by start time with the newest first.
@property (nonatomic, readonly) NSArray<FLEXWebsocketTransaction *> *websocketTransactions API_AVAILABLE(ios(13.0));
/// The full response data IFF it hasn't been purged due to memory pressure.
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction;
- (NSData *)cachedResponseBodyForTransaction:(FLEXHTTPTransaction *)transaction;
/// Dumps all network transactions and cached response bodies.
- (void)clearRecordedActivity;
// Recording network activity
#pragma mark Recording network activity
/// Call when app is about to send HTTP request.
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID
@@ -72,4 +74,12 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
/// This string can be set to anything useful about the API used to make the request.
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID;
- (void)recordWebsocketMessageSend:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task API_AVAILABLE(ios(13.0));
- (void)recordWebsocketMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error API_AVAILABLE(ios(13.0));
- (void)recordWebsocketMessageReceived:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task API_AVAILABLE(ios(13.0));
@end
+84 -49
View File
@@ -12,6 +12,7 @@
#import "FLEXUtility.h"
#import "FLEXResources.h"
#import "NSUserDefaults+FLEX.h"
#import "OSCache.h"
NSString *const kFLEXNetworkRecorderNewTransactionNotification = @"kFLEXNetworkRecorderNewTransactionNotification";
NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification = @"kFLEXNetworkRecorderTransactionUpdatedNotification";
@@ -22,9 +23,10 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
@interface FLEXNetworkRecorder ()
@property (nonatomic) NSCache *responseCache;
@property (nonatomic) NSMutableArray<FLEXNetworkTransaction *> *orderedTransactions;
@property (nonatomic) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *requestIDsToTransactions;
@property (nonatomic) OSCache *restCache;
@property (nonatomic) NSMutableArray<FLEXHTTPTransaction *> *orderedHTTPTransactions;
@property (nonatomic) NSMutableArray<FLEXWebsocketTransaction *> *orderedWSTransactions;
@property (nonatomic) NSMutableDictionary<NSString *, FLEXHTTPTransaction *> *requestIDsToHTTPTransactions;
@property (nonatomic) dispatch_queue_t queue;
@end
@@ -34,17 +36,18 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (instancetype)init {
self = [super init];
if (self) {
self.responseCache = [NSCache new];
self.restCache = [OSCache new];
NSUInteger responseCacheLimit = [[NSUserDefaults.standardUserDefaults
objectForKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey] unsignedIntegerValue
];
// Default to 25 MB max. The cache will purge earlier if there is memory pressure.
self.responseCache.totalCostLimit = responseCacheLimit ?: 25 * 1024 * 1024;
[self.responseCache setTotalCostLimit:responseCacheLimit];
self.restCache.totalCostLimit = responseCacheLimit ?: 25 * 1024 * 1024;
[self.restCache setTotalCostLimit:responseCacheLimit];
self.orderedTransactions = [NSMutableArray new];
self.requestIDsToTransactions = [NSMutableDictionary new];
self.orderedWSTransactions = [NSMutableArray new];
self.orderedHTTPTransactions = [NSMutableArray new];
self.requestIDsToHTTPTransactions = [NSMutableDictionary new];
self.hostDenylist = NSUserDefaults.standardUserDefaults.flex_networkHostDenylist.mutableCopy;
// Serial queue used because we use mutable objects that are not thread safe
@@ -67,34 +70,35 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
#pragma mark - Public Data Access
- (NSUInteger)responseCacheByteLimit {
return self.responseCache.totalCostLimit;
return self.restCache.totalCostLimit;
}
- (void)setResponseCacheByteLimit:(NSUInteger)responseCacheByteLimit {
self.responseCache.totalCostLimit = responseCacheByteLimit;
self.restCache.totalCostLimit = responseCacheByteLimit;
[NSUserDefaults.standardUserDefaults
setObject:@(responseCacheByteLimit)
forKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey
];
}
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions {
__block NSArray<FLEXNetworkTransaction *> *transactions = nil;
dispatch_sync(self.queue, ^{
transactions = self.orderedTransactions.copy;
});
return transactions;
- (NSArray<FLEXHTTPTransaction *> *)HTTPTransactions {
return self.orderedHTTPTransactions.copy;
}
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction {
return [self.responseCache objectForKey:transaction.requestID];
- (NSArray<FLEXWebsocketTransaction *> *)websocketTransactions {
return self.orderedWSTransactions.copy;
}
- (NSData *)cachedResponseBodyForTransaction:(FLEXHTTPTransaction *)transaction {
return [self.restCache objectForKey:transaction.requestID];
}
- (void)clearRecordedActivity {
dispatch_async(self.queue, ^{
[self.responseCache removeAllObjects];
[self.orderedTransactions removeAllObjects];
[self.requestIDsToTransactions removeAllObjects];
[self.restCache removeAllObjects];
[self.orderedWSTransactions removeAllObjects];
[self.orderedHTTPTransactions removeAllObjects];
[self.requestIDsToHTTPTransactions removeAllObjects];
[self notify:kFLEXNetworkRecorderTransactionsClearedNotification transaction:nil];
});
@@ -102,8 +106,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)clearExcludedTransactions {
dispatch_sync(self.queue, ^{
self.orderedTransactions = ({
[self.orderedTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *ta, NSUInteger idx) {
self.orderedHTTPTransactions = ({
[self.orderedHTTPTransactions flex_filtered:^BOOL(FLEXHTTPTransaction *ta, NSUInteger idx) {
NSString *host = ta.request.URL.host;
for (NSString *excluded in self.hostDenylist) {
if ([host hasSuffix:excluded]) {
@@ -132,22 +136,17 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
}
}
// Before async block to stay accurate
NSDate *startDate = [NSDate date];
FLEXHTTPTransaction *transaction = [FLEXHTTPTransaction request:request identifier:requestID];
// Before async block to keep times accurate
if (redirectResponse) {
[self recordResponseReceivedWithRequestID:requestID response:redirectResponse];
[self recordLoadingFinishedWithRequestID:requestID responseBody:nil];
}
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = [FLEXNetworkTransaction new];
transaction.requestID = requestID;
transaction.request = request;
transaction.startTime = startDate;
[self.orderedTransactions insertObject:transaction atIndex:0];
[self.requestIDsToTransactions setObject:transaction forKey:requestID];
[self.orderedHTTPTransactions insertObject:transaction atIndex:0];
[self.requestIDsToHTTPTransactions setObject:transaction forKey:requestID];
transaction.transactionState = FLEXNetworkTransactionStateAwaitingResponse;
[self postNewTransactionNotificationWithTransaction:transaction];
@@ -159,7 +158,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
NSDate *responseDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -174,7 +173,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength {
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -188,7 +187,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
NSDate *finishedDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -205,7 +204,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
}
if (shouldCache) {
[self.responseCache setObject:responseBody forKey:requestID cost:responseBody.length];
[self.restCache setObject:responseBody forKey:requestID cost:responseBody.length];
}
NSString *mimeType = transaction.response.MIMEType;
@@ -213,32 +212,32 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
// Thumbnail image previews on a separate background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSInteger maxPixelDimension = UIScreen.mainScreen.scale * 32.0;
transaction.responseThumbnail = [FLEXUtility
transaction.thumbnail = [FLEXUtility
thumbnailedImageWithMaxPixelDimension:maxPixelDimension
fromImageData:responseBody
];
[self postUpdateNotificationForTransaction:transaction];
});
} else if ([mimeType isEqual:@"application/json"]) {
transaction.responseThumbnail = FLEXResources.jsonIcon;
transaction.thumbnail = FLEXResources.jsonIcon;
} else if ([mimeType isEqual:@"text/plain"]){
transaction.responseThumbnail = FLEXResources.textPlainIcon;
transaction.thumbnail = FLEXResources.textPlainIcon;
} else if ([mimeType isEqual:@"text/html"]) {
transaction.responseThumbnail = FLEXResources.htmlIcon;
transaction.thumbnail = FLEXResources.htmlIcon;
} else if ([mimeType isEqual:@"application/x-plist"]) {
transaction.responseThumbnail = FLEXResources.plistIcon;
transaction.thumbnail = FLEXResources.plistIcon;
} else if ([mimeType isEqual:@"application/octet-stream"] || [mimeType isEqual:@"application/binary"]) {
transaction.responseThumbnail = FLEXResources.binaryIcon;
transaction.thumbnail = FLEXResources.binaryIcon;
} else if ([mimeType containsString:@"javascript"]) {
transaction.responseThumbnail = FLEXResources.jsIcon;
transaction.thumbnail = FLEXResources.jsIcon;
} else if ([mimeType containsString:@"xml"]) {
transaction.responseThumbnail = FLEXResources.xmlIcon;
transaction.thumbnail = FLEXResources.xmlIcon;
} else if ([mimeType hasPrefix:@"audio"]) {
transaction.responseThumbnail = FLEXResources.audioIcon;
transaction.thumbnail = FLEXResources.audioIcon;
} else if ([mimeType hasPrefix:@"video"]) {
transaction.responseThumbnail = FLEXResources.videoIcon;
transaction.thumbnail = FLEXResources.videoIcon;
} else if ([mimeType hasPrefix:@"text"]) {
transaction.responseThumbnail = FLEXResources.textIcon;
transaction.thumbnail = FLEXResources.textIcon;
}
[self postUpdateNotificationForTransaction:transaction];
@@ -247,7 +246,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error {
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -262,7 +261,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID {
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
FLEXHTTPTransaction *transaction = self.requestIDsToHTTPTransactions[requestID];
if (!transaction) {
return;
}
@@ -272,6 +271,42 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
});
}
#pragma mark - Websocket Events
- (void)recordWebsocketMessageSend:(NSURLSessionWebSocketMessage *)message task:(NSURLSessionWebSocketTask *)task {
dispatch_async(self.queue, ^{
FLEXWebsocketTransaction *send = [FLEXWebsocketTransaction
withMessage:message task:task direction:FLEXWebsocketOutgoing
];
[self.orderedWSTransactions addObject:send];
[self postNewTransactionNotificationWithTransaction:send];
});
}
- (void)recordWebsocketMessageSendCompletion:(NSURLSessionWebSocketMessage *)message error:(NSError *)error {
dispatch_async(self.queue, ^{
FLEXWebsocketTransaction *send = [self.orderedWSTransactions flex_firstWhere:^BOOL(FLEXWebsocketTransaction *t) {
return t.message == message;
}];
send.error = error;
send.transactionState = error ? FLEXNetworkTransactionStateFailed : FLEXNetworkTransactionStateFinished;
[self postUpdateNotificationForTransaction:send];
});
}
- (void)recordWebsocketMessageReceived:(NSURLSessionWebSocketMessage *)message task:(NSURLSessionWebSocketTask *)task {
dispatch_async(self.queue, ^{
FLEXWebsocketTransaction *receive = [FLEXWebsocketTransaction
withMessage:message task:task direction:FLEXWebsocketIncoming
];
[self.orderedWSTransactions addObject:receive];
[self postNewTransactionNotificationWithTransaction:receive];
});
}
#pragma mark Notification Posting
- (void)postNewTransactionNotificationWithTransaction:(FLEXNetworkTransaction *)transaction {
+74 -11
View File
@@ -17,28 +17,91 @@ typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
FLEXNetworkTransactionStateFailed
};
typedef NS_ENUM(NSUInteger, FLEXWebsocketMessageDirection) {
FLEXWebsocketIncoming = 1,
FLEXWebsocketOutgoing,
};
/// The shared base class for all types of network transactions.
/// Subclasses should implement the descriptions and details properties, and assign a thumbnail.
@interface FLEXNetworkTransaction : NSObject
@property (nonatomic, copy) NSString *requestID;
+ (instancetype)withStartTime:(NSDate *)startTime;
@property (nonatomic) NSURLRequest *request;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
@property (nonatomic) NSError *error;
/// Subclasses can override to provide error state based on response data as well
@property (nonatomic, readonly) BOOL displayAsError;
@property (nonatomic, readonly) NSDate *startTime;
@property (nonatomic) FLEXNetworkTransactionState transactionState;
@property (nonatomic) int64_t receivedDataLength;
/// A small thumbnail to preview the type of/the response
@property (nonatomic) UIImage *thumbnail;
/// The most prominent line of the cell. Typically a URL endpoint or other distinguishing attribute.
/// This line turns red when the transaction indicates an error.
@property (nonatomic, readonly) NSString *primaryDescription;
/// Something less important, such as a blob of data or the URL's domain.
@property (nonatomic, readonly) NSString *secondaryDescription;
/// Minor details to display at the bottom of the cell, such as a timestamp, HTTP method, or status.
@property (nonatomic, readonly) NSString *tertiaryDescription;
/// Subclasses should implement for when the transaction is complete
@property (nonatomic, readonly) NSArray<NSString *> *details;
/// The string to copy when the user selects the "copy" action
@property (nonatomic, readonly) NSString *copyString;
/// Whether or not this request should show up when the user searches for a given string
- (BOOL)matchesQuery:(NSString *)filterString;
@end
/// The shared base class for all NSURL-API-related transactions.
/// Descriptions are generated by this class using the URL provided by subclasses.
@interface FLEXURLTransaction : FLEXNetworkTransaction
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime;
@property (nonatomic, readonly) NSURLRequest *request;
@end
@interface FLEXHTTPTransaction : FLEXURLTransaction
+ (instancetype)request:(NSURLRequest *)request identifier:(NSString *)requestID;
@property (nonatomic, readonly) NSString *requestID;
@property (nonatomic) NSURLResponse *response;
@property (nonatomic, copy) NSString *requestMechanism;
@property (nonatomic) FLEXNetworkTransactionState transactionState;
@property (nonatomic) NSError *error;
@property (nonatomic) NSDate *startTime;
@property (nonatomic) NSTimeInterval latency;
@property (nonatomic) NSTimeInterval duration;
@property (nonatomic) int64_t receivedDataLength;
/// Only applicable for image downloads. A small thumbnail to preview the full response.
@property (nonatomic) UIImage *responseThumbnail;
/// Populated lazily. Handles both normal HTTPBody data and HTTPBodyStreams.
@property (nonatomic, readonly) NSData *cachedRequestBody;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
@end
@interface FLEXWebsocketTransaction : FLEXURLTransaction
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction API_AVAILABLE(ios(13.0));
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction
startTime:(NSDate *)started API_AVAILABLE(ios(13.0));
//@property (nonatomic, readonly) NSURLSessionWebSocketTask *task;
@property (nonatomic, readonly) NSURLSessionWebSocketMessage *message API_AVAILABLE(ios(13.0));
@property (nonatomic, readonly) FLEXWebsocketMessageDirection direction API_AVAILABLE(ios(13.0));
@property (nonatomic, readonly) int64_t dataLength API_AVAILABLE(ios(13.0));
@end
+255 -28
View File
@@ -7,23 +7,191 @@
//
#import "FLEXNetworkTransaction.h"
#import "FLEXResources.h"
#import "FLEXUtility.h"
@interface FLEXNetworkTransaction ()
@property (nonatomic, readwrite) NSData *cachedRequestBody;
@interface FLEXNetworkTransaction () {
@protected
NSString *_primaryDescription;
NSString *_secondaryDescription;
NSString *_tertiaryDescription;
}
@end
@implementation FLEXNetworkTransaction
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state {
NSString *readableString = nil;
switch (state) {
case FLEXNetworkTransactionStateUnstarted:
readableString = @"Unstarted";
break;
case FLEXNetworkTransactionStateAwaitingResponse:
readableString = @"Awaiting Response";
break;
case FLEXNetworkTransactionStateReceivingData:
readableString = @"Receiving Data";
break;
case FLEXNetworkTransactionStateFinished:
readableString = @"Finished";
break;
case FLEXNetworkTransactionStateFailed:
readableString = @"Failed";
break;
}
return readableString;
}
+ (instancetype)withStartTime:(NSDate *)startTime {
FLEXNetworkTransaction *transaction = [self new];
transaction->_startTime = startTime;
return transaction;
}
- (BOOL)displayAsError {
return _error != nil;
}
- (NSString *)copyString {
return nil;
}
- (BOOL)matchesQuery:(NSString *)filterString {
return NO;
}
@end
@interface FLEXURLTransaction ()
@end
@implementation FLEXURLTransaction
+ (instancetype)withRequest:(NSURLRequest *)request startTime:(NSDate *)startTime {
FLEXURLTransaction *transaction = [self withStartTime:startTime];
transaction->_request = request;
return transaction;
}
- (NSString *)timestampStringFromRequestDate:(NSDate *)date {
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = @"HH:mm:ss";
});
return [dateFormatter stringFromDate:date];
}
- (NSString *)primaryDescription {
if (!_primaryDescription) {
NSString *name = self.request.URL.lastPathComponent;
if (!name.length) {
name = @"/";
}
if (_request.URL.query) {
name = [name stringByAppendingFormat:@"?%@", self.request.URL.query];
}
_primaryDescription = name;
}
return _primaryDescription;
}
- (NSString *)secondaryDescription {
if (!_secondaryDescription) {
NSMutableArray<NSString *> *mutablePathComponents = self.request.URL.pathComponents.mutableCopy;
if (mutablePathComponents.count > 0) {
[mutablePathComponents removeLastObject];
}
NSString *path = self.request.URL.host;
for (NSString *pathComponent in mutablePathComponents) {
path = [path stringByAppendingPathComponent:pathComponent];
}
_secondaryDescription = path;
}
return _secondaryDescription;
}
- (NSString *)tertiaryDescription {
if (!_tertiaryDescription) {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *timestamp = [self timestampStringFromRequestDate:self.startTime];
if (timestamp.length > 0) {
[detailComponents addObject:timestamp];
}
// Omit method for GET (assumed as default)
NSString *httpMethod = self.request.HTTPMethod;
if (httpMethod.length > 0) {
[detailComponents addObject:httpMethod];
}
if (self.transactionState == FLEXNetworkTransactionStateFinished || self.transactionState == FLEXNetworkTransactionStateFailed) {
[detailComponents addObjectsFromArray:self.details];
} else {
// Unstarted, Awaiting Response, Receiving Data, etc.
NSString *state = [self.class readableStringFromTransactionState:self.transactionState];
[detailComponents addObject:state];
}
_tertiaryDescription = [detailComponents componentsJoinedByString:@""];
}
return _tertiaryDescription;
}
- (void)setTransactionState:(FLEXNetworkTransactionState)transactionState {
super.transactionState = transactionState;
// Reset bottom description
_tertiaryDescription = nil;
}
- (NSString *)copyString {
return self.request.URL.absoluteString;
}
- (BOOL)matchesQuery:(NSString *)filterString {
return [self.request.URL.absoluteString localizedCaseInsensitiveContainsString:filterString];
}
@end
@interface FLEXHTTPTransaction ()
@property (nonatomic, readwrite) NSData *cachedRequestBody;
@end
@implementation FLEXHTTPTransaction
+ (instancetype)request:(NSURLRequest *)request identifier:(NSString *)requestID {
FLEXHTTPTransaction *httpt = [self withRequest:request startTime:NSDate.date];
httpt->_requestID = requestID;
return httpt;
}
- (NSString *)description {
NSString *description = [super description];
description = [description stringByAppendingFormat:@" id = %@;", self.requestID];
description = [description stringByAppendingFormat:@" url = %@;", self.request.URL];
description = [description stringByAppendingFormat:@" duration = %f;", self.duration];
description = [description stringByAppendingFormat:@" receivedDataLength = %lld", self.receivedDataLength];
return description;
}
@@ -49,30 +217,89 @@
return _cachedRequestBody;
}
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state {
NSString *readableString = nil;
switch (state) {
case FLEXNetworkTransactionStateUnstarted:
readableString = @"Unstarted";
break;
case FLEXNetworkTransactionStateAwaitingResponse:
readableString = @"Awaiting Response";
break;
case FLEXNetworkTransactionStateReceivingData:
readableString = @"Receiving Data";
break;
case FLEXNetworkTransactionStateFinished:
readableString = @"Finished";
break;
case FLEXNetworkTransactionStateFailed:
readableString = @"Failed";
break;
- (NSArray *)detailString {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *statusCodeString = [FLEXUtility statusCodeStringFromURLResponse:self.response];
if (statusCodeString.length > 0) {
[detailComponents addObject:statusCodeString];
}
return readableString;
if (self.receivedDataLength > 0) {
NSString *responseSize = [NSByteCountFormatter
stringFromByteCount:self.receivedDataLength
countStyle:NSByteCountFormatterCountStyleBinary
];
[detailComponents addObject:responseSize];
}
NSString *totalDuration = [FLEXUtility stringFromRequestDuration:self.duration];
NSString *latency = [FLEXUtility stringFromRequestDuration:self.latency];
NSString *duration = [NSString stringWithFormat:@"%@ (%@)", totalDuration, latency];
[detailComponents addObject:duration];
return detailComponents;
}
- (BOOL)displayAsError {
return [FLEXUtility isErrorStatusCodeFromURLResponse:self.response] || super.displayAsError;
}
@end
@implementation FLEXWebsocketTransaction
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction
startTime:(NSDate *)started {
FLEXWebsocketTransaction *wst = [self withRequest:task.originalRequest startTime:started];
wst->_message = message;
wst->_direction = direction;
// Populate receivedDataLength
if (direction == FLEXWebsocketIncoming) {
wst.receivedDataLength = wst.dataLength;
wst.transactionState = FLEXNetworkTransactionStateFinished;
}
// Populate thumbnail image
if (message.type == NSURLSessionWebSocketMessageTypeData) {
wst.thumbnail = FLEXResources.binaryIcon;
} else {
wst.thumbnail = FLEXResources.textIcon;
}
return wst;
}
+ (instancetype)withMessage:(NSURLSessionWebSocketMessage *)message
task:(NSURLSessionWebSocketTask *)task
direction:(FLEXWebsocketMessageDirection)direction {
return [self withMessage:message task:task direction:direction startTime:NSDate.date];
}
- (NSArray<NSString *> *)details API_AVAILABLE(ios(13.0)) {
return @[
self.direction == FLEXWebsocketOutgoing ? @"SENT →" : @"→ RECEIVED",
[NSByteCountFormatter
stringFromByteCount:self.dataLength
countStyle:NSByteCountFormatterCountStyleBinary
]
];
}
- (int64_t)dataLength {
if (self.message) {
if (self.message.type == NSURLSessionWebSocketMessageTypeString) {
return self.message.string.length;
}
return self.message.data.length;
}
return 0;
}
@end
+2 -3
View File
@@ -8,14 +8,13 @@
#import <UIKit/UIKit.h>
extern NSString * const kFLEXNetworkTransactionCellIdentifier;
@class FLEXNetworkTransaction;
@interface FLEXNetworkTransactionCell : UITableViewCell
@property (nonatomic) FLEXNetworkTransaction *transaction;
+ (CGFloat)preferredCellHeight;
@property (nonatomic, readonly, class) NSString *reuseID;
@property (nonatomic, readonly, class) CGFloat preferredCellHeight;
@end
+10 -68
View File
@@ -12,7 +12,7 @@
#import "FLEXUtility.h"
#import "FLEXResources.h"
NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactionCellIdentifier";
NSString * const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactionCellIdentifier";
@interface FLEXNetworkTransactionCell ()
@@ -69,7 +69,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
CGFloat thumbnailOriginY = round((self.contentView.bounds.size.height - kImageDimension) / 2.0);
self.thumbnailImageView.frame = CGRectMake(kLeftPadding, thumbnailOriginY, kImageDimension, kImageDimension);
self.thumbnailImageView.image = self.transaction.responseThumbnail;
self.thumbnailImageView.image = self.transaction.thumbnail;
CGFloat textOriginX = CGRectGetMaxX(self.thumbnailImageView.frame) + kLeftPadding;
CGFloat availableTextWidth = self.contentView.bounds.size.width - textOriginX;
@@ -77,7 +77,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 || [FLEXUtility isErrorStatusCodeFromURLResponse:self.transaction.response]) ? UIColor.redColor : FLEXColor.primaryTextColor;
self.nameLabel.textColor = self.transaction.displayAsError ? UIColor.redColor : FLEXColor.primaryTextColor;
self.pathLabel.text = [self pathLabelText];
CGSize pathLabelPreferredSize = [self.pathLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
@@ -93,81 +93,23 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
}
- (NSString *)nameLabelText {
NSURL *url = self.transaction.request.URL;
NSString *name = [url lastPathComponent];
if (name.length == 0) {
name = @"/";
}
NSString *query = [url query];
if (query) {
name = [name stringByAppendingFormat:@"?%@", query];
}
return name;
return self.transaction.primaryDescription;
}
- (NSString *)pathLabelText {
NSURL *url = self.transaction.request.URL;
NSMutableArray<NSString *> *mutablePathComponents = url.pathComponents.mutableCopy;
if (mutablePathComponents.count > 0) {
[mutablePathComponents removeLastObject];
}
NSString *path = [url host];
for (NSString *pathComponent in mutablePathComponents) {
path = [path stringByAppendingPathComponent:pathComponent];
}
return path;
return self.transaction.secondaryDescription;
}
- (NSString *)transactionDetailsLabelText {
NSMutableArray<NSString *> *detailComponents = [NSMutableArray new];
NSString *timestamp = [[self class] timestampStringFromRequestDate:self.transaction.startTime];
if (timestamp.length > 0) {
[detailComponents addObject:timestamp];
}
// Omit method for GET (assumed as default)
NSString *httpMethod = self.transaction.request.HTTPMethod;
if (httpMethod.length > 0) {
[detailComponents addObject:httpMethod];
}
if (self.transaction.transactionState == FLEXNetworkTransactionStateFinished || self.transaction.transactionState == FLEXNetworkTransactionStateFailed) {
NSString *statusCodeString = [FLEXUtility statusCodeStringFromURLResponse:self.transaction.response];
if (statusCodeString.length > 0) {
[detailComponents addObject:statusCodeString];
}
if (self.transaction.receivedDataLength > 0) {
NSString *responseSize = [NSByteCountFormatter stringFromByteCount:self.transaction.receivedDataLength countStyle:NSByteCountFormatterCountStyleBinary];
[detailComponents addObject:responseSize];
}
NSString *totalDuration = [FLEXUtility stringFromRequestDuration:self.transaction.duration];
NSString *latency = [FLEXUtility stringFromRequestDuration:self.transaction.latency];
NSString *duration = [NSString stringWithFormat:@"%@ (%@)", totalDuration, latency];
[detailComponents addObject:duration];
} else {
// Unstarted, Awaiting Response, Receiving Data, etc.
NSString *state = [FLEXNetworkTransaction readableStringFromTransactionState:self.transaction.transactionState];
[detailComponents addObject:state];
}
return [detailComponents componentsJoinedByString:@""];
}
+ (NSString *)timestampStringFromRequestDate:(NSDate *)date {
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = @"HH:mm:ss";
});
return [dateFormatter stringFromDate:date];
return self.transaction.tertiaryDescription;
}
+ (CGFloat)preferredCellHeight {
return 65.0;
}
+ (NSString *)reuseID {
return kFLEXNetworkTransactionCellIdentifier;
}
@end
@@ -1,17 +0,0 @@
//
// FLEXNetworkTransactionDetailController.h
// Flipboard
//
// Created by Ryan Olson on 2/10/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FLEXNetworkTransaction;
@interface FLEXNetworkTransactionDetailController : UITableViewController
@property (nonatomic) FLEXNetworkTransaction *transaction;
@end
+20
View File
@@ -0,0 +1,20 @@
OSCache
version 1.2.1, Decembet 18th, 2015
Copyright (C) 2014 Charcoal Design
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
+57
View File
@@ -0,0 +1,57 @@
//
// OSCache.h
//
// Version 1.2.1
//
// Created by Nick Lockwood on 01/01/2014.
// Copyright (C) 2014 Charcoal Design
//
// Distributed under the permissive zlib License
// Get the latest version from here:
//
// https://github.com/nicklockwood/OSCache
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface OSCache <KeyType, ObjectType> : NSCache <NSFastEnumeration>
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) NSUInteger totalCost;
- (id)objectForKeyedSubscript:(KeyType <NSCopying>)key;
- (void)setObject:(ObjectType)obj forKeyedSubscript:(KeyType <NSCopying>)key;
- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(KeyType key, ObjectType obj, BOOL *stop))block;
@end
@protocol OSCacheDelegate <NSCacheDelegate>
@optional
- (BOOL)cache:(OSCache *)cache shouldEvictObject:(id)entry;
- (void)cache:(OSCache *)cache willEvictObject:(id)entry;
@end
NS_ASSUME_NONNULL_END
+409
View File
@@ -0,0 +1,409 @@
//
// OSCache.m
//
// Version 1.2.1
//
// Created by Nick Lockwood on 01/01/2014.
// Copyright (C) 2014 Charcoal Design
//
// Distributed under the permissive zlib License
// Get the latest version from here:
//
// https://github.com/nicklockwood/OSCache
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#import "OSCache.h"
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
#import <Availability.h>
#if !__has_feature(objc_arc)
#error This class requires automatic reference counting
#endif
#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
#pragma GCC diagnostic ignored "-Wdirect-ivar-access"
#pragma GCC diagnostic ignored "-Wgnu"
@interface OSCacheEntry : NSObject
@property (nonatomic, strong) NSObject *object;
@property (nonatomic, assign) NSUInteger cost;
@property (nonatomic, assign) NSInteger sequenceNumber;
@end
@implementation OSCacheEntry
@end
@interface OSCache_Private : NSObject
@property (nonatomic, unsafe_unretained) id<OSCacheDelegate> delegate;
@property (nonatomic, assign) NSUInteger countLimit;
@property (nonatomic, assign) NSUInteger totalCostLimit;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSMutableDictionary *cache;
@property (nonatomic, assign) NSUInteger totalCost;
@property (nonatomic, assign) NSInteger sequenceNumber;
@end
@implementation OSCache_Private
{
BOOL _delegateRespondsToWillEvictObject;
BOOL _delegateRespondsToShouldEvictObject;
BOOL _currentlyCleaning;
NSMutableArray *_entryPool;
NSLock *_lock;
}
- (instancetype)init
{
if ((self = [super init]))
{
//create storage
_cache = [[NSMutableDictionary alloc] init];
_entryPool = [[NSMutableArray alloc] init];
_lock = [[NSLock alloc] init];
_totalCost = 0;
#if TARGET_OS_IPHONE
//clean up in the event of a memory warning
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanUpAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setDelegate:(id<OSCacheDelegate>)delegate
{
_delegate = delegate;
_delegateRespondsToShouldEvictObject = [delegate respondsToSelector:@selector(cache:shouldEvictObject:)];
_delegateRespondsToWillEvictObject = [delegate respondsToSelector:@selector(cache:willEvictObject:)];
}
- (void)setCountLimit:(NSUInteger)countLimit
{
[_lock lock];
_countLimit = countLimit;
[_lock unlock];
[self cleanUp:NO];
}
- (void)setTotalCostLimit:(NSUInteger)totalCostLimit
{
[_lock lock];
_totalCostLimit = totalCostLimit;
[_lock unlock];
[self cleanUp:NO];
}
- (NSUInteger)count
{
return [_cache count];
}
- (void)cleanUp:(BOOL)keepEntries
{
[_lock lock];
NSUInteger maxCount = _countLimit ?: INT_MAX;
NSUInteger maxCost = _totalCostLimit ?: INT_MAX;
NSUInteger totalCount = _cache.count;
NSMutableArray *keys = [_cache.allKeys mutableCopy];
while (totalCount > maxCount || _totalCost > maxCost)
{
NSInteger lowestSequenceNumber = INT_MAX;
OSCacheEntry *lowestEntry = nil;
id lowestKey = nil;
//remove oldest items until within limit
for (id key in keys)
{
OSCacheEntry *entry = _cache[key];
if (entry.sequenceNumber < lowestSequenceNumber)
{
lowestSequenceNumber = entry.sequenceNumber;
lowestEntry = entry;
lowestKey = key;
}
}
if (lowestKey)
{
[keys removeObject:lowestKey];
if (!_delegateRespondsToShouldEvictObject ||
[_delegate cache:(OSCache *)self shouldEvictObject:lowestEntry.object])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
[self.delegate cache:(OSCache *)self willEvictObject:lowestEntry.object];
_currentlyCleaning = NO;
}
[_cache removeObjectForKey:lowestKey];
_totalCost -= lowestEntry.cost;
totalCount --;
if (keepEntries)
{
[_entryPool addObject:lowestEntry];
lowestEntry.object = nil;
}
}
}
}
[_lock unlock];
}
- (void)cleanUpAllObjects
{
[_lock lock];
if (_delegateRespondsToShouldEvictObject || _delegateRespondsToWillEvictObject)
{
NSArray *keys = [_cache allKeys];
if (_delegateRespondsToShouldEvictObject)
{
//sort, oldest first (in case we want to use that information in our eviction test)
keys = [keys sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) {
OSCacheEntry *entry1 = self->_cache[key1];
OSCacheEntry *entry2 = self->_cache[key2];
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
}
//remove all items individually
for (id key in keys)
{
OSCacheEntry *entry = _cache[key];
if (!_delegateRespondsToShouldEvictObject || [_delegate cache:(OSCache *)self shouldEvictObject:entry.object])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
[_delegate cache:(OSCache *)self willEvictObject:entry.object];
_currentlyCleaning = NO;
}
[_cache removeObjectForKey:key];
_totalCost -= entry.cost;
}
}
}
else
{
_totalCost = 0;
[_cache removeAllObjects];
_sequenceNumber = 0;
}
[_lock unlock];
}
- (void)resequence
{
//sort, oldest first
NSArray *entries = [[_cache allValues] sortedArrayUsingComparator:^NSComparisonResult(OSCacheEntry *entry1, OSCacheEntry *entry2) {
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
//renumber items
NSInteger index = 0;
for (OSCacheEntry *entry in entries)
{
entry.sequenceNumber = index++;
}
}
- (id)objectForKey:(id)key
{
[_lock lock];
OSCacheEntry *entry = _cache[key];
entry.sequenceNumber = _sequenceNumber++;
if (_sequenceNumber < 0)
{
[self resequence];
}
id object = entry.object;
[_lock unlock];
return object;
}
- (id)objectForKeyedSubscript:(id<NSCopying>)key
{
return [self objectForKey:key];
}
- (void)setObject:(id)obj forKey:(id)key
{
[self setObject:obj forKey:key cost:0];
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
[self setObject:obj forKey:key cost:0];
}
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g
{
if (!obj)
{
[self removeObjectForKey:key];
return;
}
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost -= [_cache[key] cost];
_totalCost += g;
OSCacheEntry *entry = _cache[key];
if (!entry) {
entry = [[OSCacheEntry alloc] init];
_cache[key] = entry;
}
entry.object = obj;
entry.cost = g;
entry.sequenceNumber = _sequenceNumber++;
if (_sequenceNumber < 0)
{
[self resequence];
}
[_lock unlock];
[self cleanUp:YES];
}
- (void)removeObjectForKey:(id)key
{
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
OSCacheEntry *entry = _cache[key];
if (entry) {
_totalCost -= entry.cost;
entry.object = nil;
[_entryPool addObject:entry];
[_cache removeObjectForKey:key];
}
[_lock unlock];
}
- (void)removeAllObjects
{
NSAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost = 0;
_sequenceNumber = 0;
for (OSCacheEntry *entry in _cache.allValues)
{
entry.object = nil;
[_entryPool addObject:entry];
}
[_cache removeAllObjects];
[_lock unlock];
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id __unsafe_unretained [])buffer
count:(NSUInteger)len
{
[_lock lock];
NSUInteger count = [_cache countByEnumeratingWithState:state objects:buffer count:len];
[_lock unlock];
return count;
}
- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block
{
if (block)
{
[_lock lock];
[_cache enumerateKeysAndObjectsUsingBlock:^(id key, OSCacheEntry *entry, BOOL *stop) {
block(key, entry.object, stop);
}];
[_lock unlock];
}
}
//handle unimplemented methods
- (BOOL)isKindOfClass:(Class)aClass
{
//pretend that we're an NSCache if anyone asks
if (aClass == [OSCache class] || aClass == [NSCache class])
{
return YES;
}
return [super isKindOfClass:aClass];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
//protect against calls to unimplemented NSCache methods
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature)
{
signature = [NSCache instanceMethodSignatureForSelector:selector];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
[invocation invokeWithTarget:nil];
#pragma clang diagnostic pop
}
@end
@implementation OSCache
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
return (OSCache *)[OSCache_Private allocWithZone:zone];
}
- (id)objectForKeyedSubscript:(__unused id<NSCopying>)key { return nil; }
- (void)setObject:(__unused id)obj forKeyedSubscript:(__unused id<NSCopying>)key {}
- (void)enumerateKeysAndObjectsUsingBlock:(__unused void (^)(id, id, BOOL *))block { }
- (NSUInteger)countByEnumeratingWithState:(__unused NSFastEnumerationState *)state
objects:(__unused __unsafe_unretained id [])buffer
count:(__unused NSUInteger)len { return 0; }
@end
@@ -11,10 +11,14 @@
// See the LICENSE file distributed with this work for the terms under
// which Square, Inc. licenses this file to you.
//
// Heavily modified and added to by Tanner Bennett and various other contributors.
// git blame details these modifications.
//
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXUtility.h"
#import "NSUserDefaults+FLEX.h"
#import "NSObject+FLEX_Reflection.h"
#import "FLEXMethod.h"
@@ -24,7 +28,6 @@
#include <dlfcn.h>
NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification";
static NSString *const kFLEXNetworkObserverEnabledDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error);
typedef NSURLSessionTask * (^NSURLSessionNewTaskMethod)(NSURLSession *, id, NSURLSessionAsyncCompletion);
@@ -68,6 +71,15 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
- (void)URLSessionTaskWillResume:(NSURLSessionTask *)task;
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
sendMessagage:(NSURLSessionWebSocketMessage *)message API_AVAILABLE(ios(13.0));
- (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error API_AVAILABLE(ios(13.0));
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
receiveMessagage:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error API_AVAILABLE(ios(13.0));
@end
@interface FLEXNetworkObserver ()
@@ -84,12 +96,12 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
+ (void)setEnabled:(BOOL)enabled {
BOOL previouslyEnabled = [self isEnabled];
[NSUserDefaults.standardUserDefaults setBool:enabled forKey:kFLEXNetworkObserverEnabledDefaultsKey];
NSUserDefaults.standardUserDefaults.flex_networkObserverEnabled = enabled;
if (enabled) {
// Inject if needed. This injection is protected with a dispatch_once, so we're ok calling it multiple times.
// By doing the injection lazily, we keep the impact of the tool lower when this feature isn't enabled.
[self injectIntoAllNSURLConnectionDelegateClasses];
[self injectIntoAllNSURLThings];
}
if (previouslyEnabled != enabled) {
@@ -98,14 +110,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
}
+ (BOOL)isEnabled {
return [[NSUserDefaults.standardUserDefaults objectForKey:kFLEXNetworkObserverEnabledDefaultsKey] boolValue];
return NSUserDefaults.standardUserDefaults.flex_networkObserverEnabled;
}
+ (void)load {
// We don't want to do the swizzling from +load because not all the classes may be loaded at this point.
dispatch_async(dispatch_get_main_queue(), ^{
if ([self isEnabled]) {
[self injectIntoAllNSURLConnectionDelegateClasses];
[self injectIntoAllNSURLThings];
}
});
}
@@ -128,9 +140,11 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
#pragma mark Delegate Injection Convenience Methods
/// All swizzled delegate methods should make use of this guard.
/// This will prevent duplicated sniffing when the original implementation calls up to a superclass implementation which we've also swizzled.
/// The superclass implementation (and implementations in classes above that) will be executed without interference if called from the original implementation.
+ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock {
/// This will prevent duplicated sniffing when the original implementation calls up to a superclass
/// implementation which we've also swizzled. The superclass implementation (and implementations in
/// classes above that) will be executed without interference if called from the original implementation.
+ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector
sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock {
// If we don't have an object to detect nested calls on, just run the original implementation and bail.
// This case can happen if someone besides the URL loading system calls the delegate methods directly.
// See https://github.com/Flipboard/FLEX/issues/61 for an example.
@@ -154,7 +168,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
#pragma mark - Delegate Injection
+ (void)injectIntoAllNSURLConnectionDelegateClasses {
+ (void)injectIntoAllNSURLThings {
// Only allow swizzling once.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -224,6 +238,15 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
[self injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods];
[self injectIntoNSURLSessionAsyncUploadTaskMethods];
if (@available(iOS 13.0, *)) {
Class websocketTask = NSClassFromString(@"__NSURLSessionWebSocketTask");
[self injectWebsocketSendMessage:websocketTask];
[self injectWebsocketReceiveMessage:websocketTask];
websocketTask = [NSURLSessionWebSocketTask class];
[self injectWebsocketSendMessage:websocketTask];
[self injectWebsocketReceiveMessage:websocketTask];
}
});
}
@@ -1266,7 +1289,76 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id<NSUR
implementationBlock:implementationBlock
undefinedBlock:undefinedBlock
];
}
+ (void)injectWebsocketSendMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
SEL selector = @selector(sendMessage:completionHandler:);
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
typedef void (^SendMessageBlock)(
NSURLSessionWebSocketTask *slf,
NSURLSessionWebSocketMessage *message,
void (^completion)(NSError *error)
);
SendMessageBlock implementationBlock = ^(
NSURLSessionWebSocketTask *slf,
NSURLSessionWebSocketMessage *message,
void (^completion)(NSError *error)
) {
[FLEXNetworkObserver.sharedObserver
websocketTask:slf sendMessagage:message
];
completion = ^(NSError *error) {
[FLEXNetworkObserver.sharedObserver
websocketTaskMessageSendCompletion:message
error:error
];
};
((void(*)(id, SEL, id, id))objc_msgSend)(
slf, swizzledSelector, message, completion
);
};
[FLEXUtility replaceImplementationOfKnownSelector:selector
onClass:cls
withBlock:implementationBlock
swizzledSelector:swizzledSelector
];
}
+ (void)injectWebsocketReceiveMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
SEL selector = @selector(receiveMessageWithCompletionHandler:);
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
typedef void (^SendMessageBlock)(
NSURLSessionWebSocketTask *slf,
void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
);
SendMessageBlock implementationBlock = ^(
NSURLSessionWebSocketTask *slf,
void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
) {
id completionHook = ^(NSURLSessionWebSocketMessage *message, NSError *error) {
[FLEXNetworkObserver.sharedObserver
websocketTask:slf receiveMessagage:message error:error
];
completion(message, error);
};
((void(*)(id, SEL, id))objc_msgSend)(
slf, swizzledSelector, completionHook
);
};
[FLEXUtility replaceImplementationOfKnownSelector:selector
onClass:cls
withBlock:implementationBlock
swizzledSelector:swizzledSelector
];
}
static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
@@ -1589,4 +1681,35 @@ didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data
}];
}
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
sendMessagage:(NSURLSessionWebSocketMessage *)message {
[self performBlock:^{
// NSString *requestID = [[self class] requestIDForConnectionOrTask:task];
[FLEXNetworkRecorder.defaultRecorder recordWebsocketMessageSend:message task:task];
}];
}
- (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error {
[self performBlock:^{
[FLEXNetworkRecorder.defaultRecorder
recordWebsocketMessageSendCompletion:message
error:error
];
}];
}
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
receiveMessagage:(NSURLSessionWebSocketMessage *)message
error:(NSError *)error {
[self performBlock:^{
if (!error && message) {
[FLEXNetworkRecorder.defaultRecorder
recordWebsocketMessageReceived:message
task:task
];
}
}];
}
@end
@@ -183,6 +183,7 @@
referencesSection.selectionAction = ^(UIViewController *host) {
UIViewController *references = [FLEXObjectListViewController
objectsWithReferencesToObject:explorer.object
retained:NO
];
[host.navigationController pushViewController:references animated:YES];
};
@@ -27,6 +27,7 @@
viewer:^UIViewController *(id obj) {
return [FLEXObjectListViewController
instancesOfClassWithName:NSStringFromClass(obj)
retained:NO
];
}
accessoryType:^UITableViewCellAccessoryType(id obj) {
@@ -29,3 +29,5 @@
@interface FLEXShortcutsFactory (WebKit_Safari) @end
@interface FLEXShortcutsFactory (Pasteboard) @end
@interface FLEXShortcutsFactory (FirebaseFirestore) @end
@@ -441,3 +441,16 @@
}];
}
@end
#pragma mark - Firebase Firestore
@implementation FLEXShortcutsFactory (FirebaseFirestore)
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
Class FIRDocumentSnap = NSClassFromString(@"FIRDocumentSnapshot");
if (FIRDocumentSnap) {
FLEXRuntimeUtilityTryAddObjectProperty(2, data, FIRDocumentSnap, NSDictionary, PropertyKey(ReadOnly));
}
}
@end
@@ -154,7 +154,10 @@ FLEXObjectExplorerDefaultsImpl
if (value) {
NSString *title = @"List all references";
[actions addObject:[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
UIViewController *list = [FLEXObjectListViewController objectsWithReferencesToObject:value];
UIViewController *list = [FLEXObjectListViewController
objectsWithReferencesToObject:value
retained:NO
];
[sender.navigationController pushViewController:list animated:YES];
}]];
}
@@ -29,6 +29,8 @@
- (instancetype)flex_sortedUsingSelector:(SEL)selector;
- (T)flex_firstWhere:(BOOL(^)(T obj))meetingCriteria;
@end
@interface NSMutableArray<T> (Functional)
+10
View File
@@ -113,6 +113,16 @@
}
}
- (id)flex_firstWhere:(BOOL (^)(id))meetsCriteria {
for (id e in self) {
if (meetsCriteria(e)) {
return e;
}
}
return nil;
}
@end
@@ -16,6 +16,7 @@ extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
extern NSString * const kFLEXDefaultsHidePrivateMethodsKey;
extern NSString * const kFLEXDefaultsShowMethodOverridesKey;
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
extern NSString * const kFLEXDefaultsNetworkObserverEnabledKey;
extern NSString * const kFLEXDefaultsNetworkHostDenylistKey;
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
@@ -27,6 +28,7 @@ extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
@property (nonatomic) double flex_toolbarTopMargin;
@property (nonatomic) BOOL flex_networkObserverEnabled;
// Not actually stored in defaults, but written to a file
@property (nonatomic) NSArray<NSString *> *flex_networkHostDenylist;
@@ -15,6 +15,7 @@ NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide
NSString * const kFLEXDefaultsHidePrivateMethodsKey = @"com.flipboard.FLEX.hide_private_or_namespaced_methods";
NSString * const kFLEXDefaultsShowMethodOverridesKey = @"com.flipboard.FLEX.show_method_overrides";
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
NSString * const kFLEXDefaultsNetworkObserverEnabledKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
NSString * const kFLEXDefaultsNetworkHostDenylistKey = @"com.flipboard.FLEX.network_host_denylist";
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.view_json_as_object";
@@ -62,6 +63,14 @@ NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.vie
[self setDouble:margin forKey:kFLEXDefaultsToolbarTopMarginKey];
}
- (BOOL)flex_networkObserverEnabled {
return [self boolForKey:kFLEXDefaultsNetworkObserverEnabledKey];
}
- (void)setFlex_networkObserverEnabled:(BOOL)enabled {
[self setBool:enabled forKey:kFLEXDefaultsNetworkObserverEnabledKey];
}
- (NSArray<NSString *> *)flex_networkHostDenylist {
return [NSArray arrayWithContentsOfFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
@@ -8,6 +8,8 @@
#import <UIKit/UIKit.h>
#define FLEXBarButtonItem(title, tgt, sel) \
[UIBarButtonItem flex_itemWithTitle:title target:tgt action:sel]
#define FLEXBarButtonItemSystem(item, tgt, sel) \
[UIBarButtonItem flex_systemItem:UIBarButtonSystemItem##item target:tgt action:sel]
+8
View File
@@ -7,6 +7,7 @@
//
#import <Foundation/Foundation.h>
@class FLEXObjectRef;
typedef void (^flex_object_enumeration_block_t)(__unsafe_unretained id object, __unsafe_unretained Class actualClass);
@@ -14,4 +15,11 @@ typedef void (^flex_object_enumeration_block_t)(__unsafe_unretained id object, _
+ (void)enumerateLiveObjectsUsingBlock:(flex_object_enumeration_block_t)block;
/// Returned references are not validated beyond containing a valid isa.
/// To validate them yourself, pass each reference's object to \c FLEXPointerIsValidObjcObject
+ (NSArray<FLEXObjectRef *> *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain;
+ (NSArray<FLEXObjectRef *> *)subclassesOfClassWithName:(NSString *)className;
/// Returned references have been validated via \c FLEXPointerIsValidObjcObject
+ (NSArray<FLEXObjectRef *> *)objectsWithReferencesToObject:(id)object retained:(BOOL)retain;
@end
+78
View File
@@ -8,6 +8,9 @@
#import "FLEXHeapEnumerator.h"
#import "FLEXObjcInternal.h"
#import "FLEXObjectRef.h"
#import "NSObject+FLEX_Reflection.h"
#import "NSString+FLEX.h"
#import <malloc/malloc.h>
#import <mach/mach.h>
#import <objc/runtime.h>
@@ -116,4 +119,79 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
free(classes);
}
+ (NSArray<FLEXObjectRef *> *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain {
const char *classNameCString = className.UTF8String;
NSMutableArray *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
// Note: objects of certain classes crash when retain is called.
// It is up to the user to avoid tapping into instance lists for these classes.
// Ex. OS_dispatch_queue_specific_queue
// In the future, we could provide some kind of warning for classes that are known to be problematic.
if (malloc_size((__bridge const void *)(object)) > 0) {
[instances addObject:object];
}
}
}];
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances retained:retain];
return references;
}
+ (NSArray<FLEXObjectRef *> *)subclassesOfClassWithName:(NSString *)className {
NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
return references;
}
+ (NSArray<FLEXObjectRef *> *)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
static Class SwiftObjectClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SwiftObjectClass = NSClassFromString(@"SwiftObject");
if (!SwiftObjectClass) {
SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
}
});
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Skip known-invalid objects
if (!FLEXPointerIsValidObjcObject((__bridge void *)tryObject)) {
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;
while (tryClass) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
Ivar ivar = ivars[ivarIndex];
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
if (typeEncoding.flex_typeIsObjectOrClass) {
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
id ref = [FLEXObjectRef referencing:tryObject ivar:ivarName retained:retain];
[instances addObject:ref];
return;
}
}
}
free(ivars);
tryClass = class_getSuperclass(tryClass);
}
}];
return instances;
}
@end
+2 -1
View File
@@ -196,7 +196,8 @@ BOOL FLEXConstructorsShouldRun() {
? [UIColor colorWithPatternImage:indentationPatternImage]
: [UIColor colorWithPatternImage:darkModePatternImage]);
}];
} });
}
});
return patternColor;
}
+10 -10
View File
@@ -10,6 +10,7 @@
#import "FLEXRuntimeUtility.h"
#import "FLEXObjcInternal.h"
#import "FLEXTypeEncodingParser.h"
#import "FLEXMethod.h"
NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
@@ -332,15 +333,14 @@ NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain
return nil;
}
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:({
Method method;
if (object_isClass(object)) {
method = class_getClassMethod(object, selector);
} else {
method = class_getInstanceMethod(object_getClass(object), selector);
}
method_getTypeEncoding(method);
})];
// It is important to use object_getClass and not -class here, as
// object_getClass will return a different result for class objects
Class cls = object_getClass(object);
NSMethodSignature *methodSignature = [FLEXMethod selector:selector class:cls].signature;
if (!methodSignature) {
// Unsupported type encoding
return nil;
}
// Probably an unsupported type encoding, like bitfields.
// In the future, we could calculate the return length
@@ -362,7 +362,7 @@ NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain
[invocation retainArguments];
// Always self and _cmd
NSUInteger numberOfArguments = [methodSignature numberOfArguments];
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
id argumentObject = arguments.count > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil;
@@ -106,17 +106,18 @@ BOOL FLEXPointerIsReadable(const void *inPtr) {
return NO;
}
// Read the memory
vm_offset_t readMem = 0;
mach_msg_type_number_t size = 0;
#if __arm64e__
address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
#else
address = (vm_address_t)inPtr;
#endif
error = vm_read(mach_task_self(), address, sizeof(uintptr_t), &readMem, &size);
// Read the memory
vm_size_t size = 0;
char buf[sizeof(uintptr_t)];
error = vm_read_overwrite(mach_task_self(), address, sizeof(uintptr_t), (vm_address_t)buf, &size);
if (error != KERN_SUCCESS) {
// vm_read returned an error
// vm_read_overwrite returned an error
return NO;
}
@@ -162,22 +163,33 @@ BOOL FLEXPointerIsValidObjcObject(const void *ptr) {
// We check if the returned class is readable because object_getClass
// can return a garbage value when given a non-nil pointer to a non-object
Class cls = object_getClass((__bridge id)ptr);
if (cls && FLEXPointerIsReadable((__bridge void *)cls)) {
// Just because this pointer is readable doesn't mean whatever is at
// it's ISA offset is readable. We need to do the same checks on it's ISA.
// Even this isn't perfect, because once we call object_isClass, we're
// going to dereference a member of the metaclass, which may or may not
// be readable itself. For the time being there is no way to access it
// to check here, and I have yet to hard-code a solution.
Class metaclass = object_getClass(cls);
if (metaclass && FLEXPointerIsReadable((__bridge void *)metaclass)) {
if (object_isClass(cls)) {
return YES;
}
}
if (!cls || !FLEXPointerIsReadable((__bridge void *)cls)) {
return NO;
}
// Just because this pointer is readable doesn't mean whatever is at
// it's ISA offset is readable. We need to do the same checks on it's ISA.
// Even this isn't perfect, because once we call object_isClass, we're
// going to dereference a member of the metaclass, which may or may not
// be readable itself. For the time being there is no way to access it
// to check here, and I have yet to hard-code a solution.
Class metaclass = object_getClass(cls);
if (!metaclass || !FLEXPointerIsReadable((__bridge void *)metaclass)) {
return NO;
}
// Does the class pointer we got appear as a class to the runtime?
if (!object_isClass(cls)) {
return NO;
}
// Is the allocation size at least as large as the expected instance size?
ssize_t instanceSize = class_getInstanceSize(cls);
if (malloc_size(ptr) < instanceSize) {
return NO;
}
return NO;
return YES;
}
@@ -42,7 +42,7 @@ typedef NS_ENUM(NSUInteger, FLEXPropertyAttribute) {
FLEXPropertyAttributeReadOnly = 'R',
FLEXPropertyAttributeRetain = '&',
FLEXPropertyAttributeWeak = 'W'
};
}; //NS_SWIFT_NAME(FLEX.PropertyAttribute);
typedef NS_ENUM(char, FLEXTypeEncoding) {
FLEXTypeEncodingNull = '\0',
@@ -76,4 +76,4 @@ typedef NS_ENUM(char, FLEXTypeEncoding) {
FLEXTypeEncodingBitField = 'b',
FLEXTypeEncodingPointer = '^',
FLEXTypeEncodingConst = 'r'
};
}; //NS_SWIFT_NAME(FLEX.TypeEncoding);
@@ -18,7 +18,7 @@ extern CFSetRef FLEXKnownUnsafeClasses;
static Class cNSObject = nil, cNSProxy = nil;
__attribute__((constructor))
static void FLEXInitKnownRootClasses() {
static void FLEXInitKnownRootClasses(void) {
cNSObject = [NSObject class];
cNSProxy = [NSProxy class];
}
@@ -810,7 +810,7 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
self.scan.scanLocation = scanLocation;
// The return / cleanup code for when the scanned type is already clean
NSString * (^typeIsClean)() = ^NSString * {
NSString * (^typeIsClean)(void) = ^NSString * {
NSString *clean = [self.scan.string
substringWithRange:NSMakeRange(scanLocation, self.scan.scanLocation - scanLocation)
];
@@ -9,6 +9,8 @@
#import "FLEXRuntimeConstants.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXIvar : NSObject
+ (instancetype)ivar:(Ivar)ivar;
@@ -31,17 +33,19 @@
@property (nonatomic, readonly) NSString *details;
/// The full path of the image that contains this ivar definition,
/// or \c nil if this ivar was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
@property (nonatomic, readonly, nullable) NSString *imagePath;
/// For internal use
@property (nonatomic) id tag;
- (id)getValue:(id)target;
- (void)setValue:(id)value onObject:(id)target;
- (nullable id)getValue:(id)target;
- (void)setValue:(nullable id)value onObject:(id)target;
/// Calls into -getValue: and passes that value into
/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
/// and returns the result
- (id)getPotentiallyUnboxedValue:(id)target;
- (nullable id)getPotentiallyUnboxedValue:(id)target;
@end
NS_ASSUME_NONNULL_END
@@ -24,14 +24,6 @@
#pragma mark Initializers
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
+ (instancetype)ivar:(Ivar)ivar {
return [[self alloc] initWithIvar:ivar];
}
@@ -8,25 +8,17 @@
//
#import <Foundation/Foundation.h>
@class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol;
#import <objc/runtime.h>
@class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol;
NS_ASSUME_NONNULL_BEGIN
#pragma mark FLEXMirror
@interface FLEXMirror : NSObject
#pragma mark FLEXMirror Protocol
NS_SWIFT_NAME(FLEXMirrorProtocol)
@protocol FLEXMirror <NSObject>
/// Reflects an instance of an object or \c Class.
/// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the
/// \c NSObject categories provided if your code will only use a few pieces of information,
/// or if your code needs to run faster.
///
/// If you reflect an instance of a class then \c methods and \c properties will be populated
/// with instance methods and properties. If you reflect a class itself, then \c methods
/// and \c properties will be populated with class methods and properties as you'd expect.
///
/// @param objectOrClass An instance of an objct or a \c Class object.
/// @return An instance of \c FLEXMirror.
+ (instancetype)reflect:(id)objectOrClass;
/// Swift initializer
- (instancetype)initWithSubject:(id)objectOrClass NS_SWIFT_NAME(init(reflecting:));
/// The underlying object or \c Class used to create this \c FLEXMirror instance.
@property (nonatomic, readonly) id value;
@@ -41,7 +33,36 @@
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
/// @return A reflection of \c value.superClass.
@property (nonatomic, readonly) FLEXMirror *superMirror;
@property (nonatomic, readonly, nullable) id<FLEXMirror> superMirror NS_SWIFT_NAME(superMirror);
@end
#pragma mark FLEXMirror Class
@interface FLEXMirror : NSObject <FLEXMirror>
/// Reflects an instance of an object or \c Class.
/// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the
/// \c NSObject categories provided if your code will only use a few pieces of information,
/// or if your code needs to run faster.
///
/// If you reflect an instance of a class then \c methods and \c properties will be populated
/// with instance methods and properties. If you reflect a class itself, then \c methods
/// and \c properties will be populated with class methods and properties as you'd expect.
///
/// @param objectOrClass An instance of an objct or a \c Class object.
/// @return An instance of \c FLEXMirror.
+ (instancetype)reflect:(id)objectOrClass;
@property (nonatomic, readonly) id value;
@property (nonatomic, readonly) BOOL isClass;
@property (nonatomic, readonly) NSString *className;
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
@property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
@property (nonatomic, readonly) NSArray<FLEXMethod *> *methods;
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
@property (nonatomic, readonly, nullable) FLEXMirror *superMirror NS_SWIFT_NAME(superMirror);
@end
@@ -49,12 +70,14 @@
@interface FLEXMirror (ExtendedMirror)
/// @return The method with the given name, or \c nil if one does not exist.
- (FLEXMethod *)methodNamed:(NSString *)name;
- (nullable FLEXMethod *)methodNamed:(nullable NSString *)name;
/// @return The property with the given name, or \c nil if one does not exist.
- (FLEXProperty *)propertyNamed:(NSString *)name;
- (nullable FLEXProperty *)propertyNamed:(nullable NSString *)name;
/// @return The instance variable with the given name, or \c nil if one does not exist.
- (FLEXIvar *)ivarNamed:(NSString *)name;
- (nullable FLEXIvar *)ivarNamed:(nullable NSString *)name;
/// @return The protocol with the given name, or \c nil if one does not exist.
- (FLEXProtocol *)protocolNamed:(NSString *)name;
- (nullable FLEXProtocol *)protocolNamed:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END
@@ -29,15 +29,15 @@
#pragma mark Initialization
+ (instancetype)reflect:(id)objectOrClass {
return [[self alloc] initWithValue:objectOrClass];
return [[self alloc] initWithSubject:objectOrClass];
}
- (id)initWithValue:(id)value {
NSParameterAssert(value);
- (id)initWithSubject:(id)objectOrClass {
NSParameterAssert(objectOrClass);
self = [super init];
if (self) {
_value = value;
_value = objectOrClass;
[self examine];
}
@@ -96,7 +96,7 @@
// Return the given selector if the class responds to it
Class cls = _cls;
SEL (^selectorIfValid)() = ^SEL(SEL sel) {
SEL (^selectorIfValid)(SEL) = ^SEL(SEL sel) {
if (!sel || !cls) return nil;
return [cls instancesRespondToSelector:sel] ? sel : nil;
};
@@ -10,6 +10,8 @@
#import "FLEXRuntimeConstants.h"
@class FLEXProperty, FLEXMethodDescription;
NS_ASSUME_NONNULL_BEGIN
#pragma mark FLEXProtocol
@interface FLEXProtocol : NSObject
@@ -23,22 +25,22 @@
/// The name of the protocol.
@property (nonatomic, readonly) NSString *name;
/// The required methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods;
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods;
/// The optional methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods;
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods;
/// All protocols that this protocol conforms to, if any.
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
/// The full path of the image that contains this protocol definition,
/// or \c nil if this protocol was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
@property (nonatomic, readonly, nullable) NSString *imagePath;
/// The properties in the protocol, if any. \c nil on iOS 10+
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0));
@property (nonatomic, readonly, nullable) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0));
/// The required properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0));
@property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0));
/// The optional properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0));
@property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0));
/// For internal use
@property (nonatomic) id tag;
@@ -67,3 +69,5 @@
/// \c YES if this is an instance method, \c NO if it is a class method, or \c nil if unspecified
@property (nonatomic, readonly) NSNumber *instance;
@end
NS_ASSUME_NONNULL_END
@@ -15,14 +15,6 @@
@implementation FLEXProtocol
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
#pragma mark Initializers
+ (NSArray *)allProtocols {
@@ -160,6 +152,9 @@
return [FLEXProperty property:objcproperties[i]];
}];
_requiredProperties = @[];
_optionalProperties = @[];
free(objcproperties);
}
}
@@ -37,7 +37,7 @@
- (void)initSubviews {
self.userInteractionEnabled = YES;
UIImageView * (^newSubviewImageView)() = ^UIImageView *(UIImage *image) {
UIImageView * (^newSubviewImageView)(UIImage *) = ^UIImageView *(UIImage *image) {
UIImageView *iv = [UIImageView new];
iv.image = image;
// iv.userInteractionEnabled = YES;
@@ -122,7 +122,7 @@
/// Recursively hides all views that may be obscuring the given view and collects them
/// in the given array. You should unhide them all when you are done.
+ (void)hideViewsCoveringView:(UIView *)view doWhileHidden:(void(^)())block {
+ (void)hideViewsCoveringView:(UIView *)view doWhileHidden:(void(^)(void))block {
NSMutableArray *viewsToUnhide = [NSMutableArray new];
if ([self _hideViewsCoveringView:view root:view.window hiddenViews:viewsToUnhide]) {
block();
+1 -1
View File
@@ -10,7 +10,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface MiscNetworkRequests : NSObject <NSURLConnectionDataDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
@interface MiscNetworkRequests : NSObject <NSURLConnectionDataDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionWebSocketDelegate>
+ (void)sendExampleRequests;
+33 -8
View File
@@ -11,14 +11,21 @@
@implementation MiscNetworkRequests
+ (void)sendExampleRequests {
[[self new] sendExampleNetworkRequests];
MiscNetworkRequests *misc = [self new];
NSURLSessionConfiguration *config = NSURLSessionConfiguration.defaultSessionConfiguration;
config.timeoutIntervalForRequest = 10.0;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:misc delegateQueue:nil];
[misc sendExampleNetworkRequests:session];
[misc sendExampleWebsocketTraffic:session];
}
- (NSMutableURLRequest *)request:(NSString *)url {
return [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
}
- (void)sendExampleNetworkRequests {
- (void)sendExampleNetworkRequests:(NSURLSession *)sessionWithDelegate {
NSString *kFlipboardIcon = @"https://cdn.flipboard.com/serviceIcons/v2/social-icon-flipboard-96.png";
NSString *kRandomAnimal = @"https://lorempixel.com/248/250/animals/";
NSString *kSnowLeopard = @"https://lorempixel.com/248/250/animals/4/";
@@ -35,13 +42,10 @@
// With delegate //
NSURLSessionConfiguration *config = NSURLSessionConfiguration.defaultSessionConfiguration;
config.timeoutIntervalForRequest = 10.0;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
// NSURLSessionDataTask
[pendingTasks addObject:[session dataTaskWithURL:[NSURL URLWithString:kFlipboardIcon]]];
[pendingTasks addObject:[sessionWithDelegate dataTaskWithURL:[NSURL URLWithString:kFlipboardIcon]]];
// NSURLSessionDownloadTask
[pendingTasks addObject:[session downloadTaskWithURL:[NSURL URLWithString:kRandomAnimal]]];
[pendingTasks addObject:[sessionWithDelegate downloadTaskWithURL:[NSURL URLWithString:kRandomAnimal]]];
// Without delegate //
@@ -71,7 +75,7 @@
NSMutableURLRequest *upload = [self request:kImgurUpload];
upload.HTTPMethod = @"POST";
[upload setValue:@"Client-ID 0e8a1cb2eb594ef" forHTTPHeaderField:@"Authorization"];
[pendingTasks addObject:[session
[pendingTasks addObject:[sessionWithDelegate
uploadTaskWithRequest:upload
fromFile:[NSBundle.mainBundle URLForResource:@"image" withExtension:@"jpg"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
@@ -148,6 +152,27 @@
#pragma clang diagnostic pop
}
- (void)sendExampleWebsocketTraffic:(NSURLSession *)sessionWithDelegate {
NSString *APIKey = @"oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm";
NSString *wsurl = [NSString stringWithFormat:@"wss://demo.piesocket.com/v3/channel_1?api_key=%@&notify_self", APIKey];
NSURLSessionWebSocketTask *task = [sessionWithDelegate webSocketTaskWithURL:[NSURL URLWithString:wsurl]];
[task resume];
}
- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)task didOpenWithProtocol:(NSString *)protocol {
[task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) {
if (!error) {
NSLog(@"Received WS message: %@", message.string);
id message = [[NSURLSessionWebSocketMessage alloc] initWithString:@"Hello"];
[task sendMessage:message completionHandler:^(NSError *error) {
if (error) {
NSLog(@"Error sending WS message: %@", error.localizedDescription);
}
}];
}
}];
}
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
NSLog(@"URLSession didBecomeInvalidWithError: %@", error.localizedDescription);
}
+4 -4
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "FLEX"
spec.version = "4.5.0"
spec.version = "4.6.1"
spec.summary = "A set of in-app debugging and exploration tools for iOS"
spec.description = <<-DESC
- Inspect and modify views in the hierarchy.
@@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
- Dynamically view and modify `NSUserDefaults` values.
DESC
spec.homepage = "https://github.com/Flipboard/FLEX"
spec.homepage = "https://github.com/FLEXTool/FLEX"
spec.screenshots = [ "http://engineering.flipboard.com/assets/flex/basic-view-exploration.gif",
"http://engineering.flipboard.com/assets/flex/advanced-view-editing.gif",
"http://engineering.flipboard.com/assets/flex/heap-browser.gif",
@@ -30,12 +30,12 @@ Pod::Spec.new do |spec|
spec.author = { "Tanner Bennett" => "tannerbennett@me.com" }
spec.social_media_url = "https://twitter.com/NSExceptional"
spec.platform = :ios, "9.0"
spec.source = { :git => "https://github.com/Flipboard/FLEX.git", :tag => "#{spec.version}" }
spec.source = { :git => "https://github.com/FLEXTool/FLEX.git", :tag => "#{spec.version}" }
spec.source_files = "Classes/**/*.{h,c,m,mm}"
spec.frameworks = [ "Foundation", "UIKit", "CoreGraphics", "ImageIO", "QuartzCore", "WebKit", "Security", "SceneKit" ]
spec.libraries = [ "z", "sqlite3" ]
spec.requires_arc = true
spec.compiler_flags = "-Wno-unsupported-availability-guard -Wno-strict-prototypes"
spec.compiler_flags = "-Wno-unsupported-availability-guard"
spec.public_header_files = [ "Classes/*.h", "Classes/Manager/*.h", "Classes/Toolbar/*.h",
"Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h",
"Classes/Core/**/*.h", "Classes/Utility/Runtime/Objc/**/*.h",
+36 -10
View File
@@ -95,8 +95,6 @@
3A4C953B1B5B21410088C3F2 /* FLEXNetworkSettingsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */; };
3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */; };
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */; };
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */; settings = {ATTRIBUTES = (Private, ); }; };
3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */; };
3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94C21B5B21410088C3F2 /* FLEXNetworkObserver.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -200,6 +198,10 @@
C3531BA623E88A2100A184AD /* FLEXNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA423E88A2100A184AD /* FLEXNavigationController.m */; };
C3531BAA23E88FAC00A184AD /* FLEXTabList.h in Headers */ = {isa = PBXBuildFile; fileRef = C3531BA823E88FAC00A184AD /* FLEXTabList.h */; };
C3531BAB23E88FAC00A184AD /* FLEXTabList.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA923E88FAC00A184AD /* FLEXTabList.m */; };
C35DAD822709140700AA95E6 /* FLEXHTTPTransactionDetailController.h in Headers */ = {isa = PBXBuildFile; fileRef = C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */; };
C35DAD832709140700AA95E6 /* FLEXHTTPTransactionDetailController.m in Sources */ = {isa = PBXBuildFile; fileRef = C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */; };
C35DAD8E2709143000AA95E6 /* FLEXMITMDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */; };
C35DAD8F2709143000AA95E6 /* FLEXMITMDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */; };
C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; settings = {ATTRIBUTES = (Private, ); }; };
C362AE8223C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m in Sources */ = {isa = PBXBuildFile; fileRef = C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */; };
C3694DBA23EA1096006625D7 /* FLEXTabsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3694DB823EA1096006625D7 /* FLEXTabsViewController.h */; };
@@ -317,6 +319,8 @@
C3BFD071233C23ED0015FB82 /* NSArray+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */; };
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
C3DBFD0C26CE2FAF00E0466A /* OSCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DBFD0A26CE2FAF00E0466A /* OSCache.m */; };
C3DBFD0D26CE2FAF00E0466A /* OSCache.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DBFD0B26CE2FAF00E0466A /* OSCache.h */; };
C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
C3DFCD942416BC6500BB7084 /* FLEXFilteringTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DFCD922416BC6500BB7084 /* FLEXFilteringTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C3DFCD952416BC6500BB7084 /* FLEXFilteringTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DFCD932416BC6500BB7084 /* FLEXFilteringTableViewController.m */; };
@@ -462,8 +466,6 @@
3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkSettingsController.m; sourceTree = "<group>"; };
3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransaction.h; sourceTree = "<group>"; };
3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransaction.m; sourceTree = "<group>"; };
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionDetailController.h; sourceTree = "<group>"; };
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionDetailController.m; sourceTree = "<group>"; };
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionCell.h; sourceTree = "<group>"; };
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransactionCell.m; sourceTree = "<group>"; };
3A4C94C21B5B21410088C3F2 /* FLEXNetworkObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkObserver.h; sourceTree = "<group>"; };
@@ -570,6 +572,10 @@
C3531BA423E88A2100A184AD /* FLEXNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNavigationController.m; sourceTree = "<group>"; };
C3531BA823E88FAC00A184AD /* FLEXTabList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTabList.h; sourceTree = "<group>"; };
C3531BA923E88FAC00A184AD /* FLEXTabList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTabList.m; sourceTree = "<group>"; };
C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXHTTPTransactionDetailController.h; sourceTree = "<group>"; };
C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXHTTPTransactionDetailController.m; sourceTree = "<group>"; };
C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMITMDataSource.h; sourceTree = "<group>"; };
C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMITMDataSource.m; sourceTree = "<group>"; };
C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMapTable+FLEX_Subscripting.h"; sourceTree = "<group>"; };
C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMapTable+FLEX_Subscripting.m"; sourceTree = "<group>"; };
C3694DB823EA1096006625D7 /* FLEXTabsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTabsViewController.h; sourceTree = "<group>"; };
@@ -684,6 +690,8 @@
C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+FLEX.m"; sourceTree = "<group>"; };
C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectRef.h; sourceTree = "<group>"; };
C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectRef.m; sourceTree = "<group>"; };
C3DBFD0A26CE2FAF00E0466A /* OSCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSCache.m; sourceTree = "<group>"; };
C3DBFD0B26CE2FAF00E0466A /* OSCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSCache.h; sourceTree = "<group>"; };
C3DFCD922416BC6500BB7084 /* FLEXFilteringTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXFilteringTableViewController.h; sourceTree = "<group>"; };
C3DFCD932416BC6500BB7084 /* FLEXFilteringTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXFilteringTableViewController.m; sourceTree = "<group>"; };
C3DFCD962416E7DD00BB7084 /* FLEXMutableListSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMutableListSection.h; sourceTree = "<group>"; };
@@ -974,12 +982,15 @@
3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */,
3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */,
3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */,
3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h */,
3A4C94BE1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m */,
C35DAD7E2709140700AA95E6 /* FLEXHTTPTransactionDetailController.h */,
C35DAD7F2709140700AA95E6 /* FLEXHTTPTransactionDetailController.m */,
C35DAD8C2709143000AA95E6 /* FLEXMITMDataSource.h */,
C35DAD8D2709143000AA95E6 /* FLEXMITMDataSource.m */,
3A4C94BF1B5B21410088C3F2 /* FLEXNetworkTransactionCell.h */,
3A4C94C01B5B21410088C3F2 /* FLEXNetworkTransactionCell.m */,
2EF6B04C1D494BE50006BDA5 /* FLEXNetworkCurlLogger.h */,
2EF6B04B1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m */,
C3DBFD0926CE2FAF00E0466A /* OSCache */,
3A4C94C11B5B21410088C3F2 /* PonyDebugger */,
);
path = Network;
@@ -1414,6 +1425,15 @@
path = Shortcuts;
sourceTree = "<group>";
};
C3DBFD0926CE2FAF00E0466A /* OSCache */ = {
isa = PBXGroup;
children = (
C3DBFD0A26CE2FAF00E0466A /* OSCache.m */,
C3DBFD0B26CE2FAF00E0466A /* OSCache.h */,
);
path = OSCache;
sourceTree = "<group>";
};
C3E5D9FF2317007F00E655DB /* Sections */ = {
isa = PBXGroup;
children = (
@@ -1481,7 +1501,6 @@
94A515141C4CA1C00063292F /* FLEXManager.h in Headers */,
C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
C3A9422C23C3DA39006871A3 /* FHSView.h in Headers */,
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.h in Headers */,
3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */,
C398625523AD6C67007E6793 /* FLEXObjcRuntimeViewController.h in Headers */,
C3E5DA02231700EE00E655DB /* FLEXObjectInfoSection.h in Headers */,
@@ -1518,6 +1537,7 @@
C312A13023ECB5D300E38049 /* FLEXBookmarkManager.h in Headers */,
3A4C95341B5B21410088C3F2 /* FLEXSystemLogViewController.h in Headers */,
C34C9BDD23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h in Headers */,
C35DAD822709140700AA95E6 /* FLEXHTTPTransactionDetailController.h in Headers */,
C398624F23AD6C67007E6793 /* FLEXRuntimeKeyPathTokenizer.h in Headers */,
C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */,
3A4C95091B5B21410088C3F2 /* FLEXFieldEditorView.h in Headers */,
@@ -1544,6 +1564,7 @@
3A4C94FF1B5B21410088C3F2 /* FLEXArgumentInputSwitchView.h in Headers */,
C398625423AD6C67007E6793 /* FLEXRuntimeBrowserToolbar.h in Headers */,
3A4C94E71B5B21410088C3F2 /* FLEXHierarchyTableViewCell.h in Headers */,
C3DBFD0D26CE2FAF00E0466A /* OSCache.h in Headers */,
224D49AA1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.h in Headers */,
C386D6A9241995A800699085 /* FLEXTypeEncodingParser.h in Headers */,
C30D2962261FAE9E00D89649 /* FLEXNSDataShortcuts.h in Headers */,
@@ -1597,6 +1618,7 @@
C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */,
C34D4EB423A2AF2A00C1F903 /* FLEXColorPreviewSection.h in Headers */,
C383C3BE23B6B398007A321B /* UITextField+Range.h in Headers */,
C35DAD8E2709143000AA95E6 /* FLEXMITMDataSource.h in Headers */,
224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
C313853F23F5C1A10046E63C /* FLEXViewControllersViewController.h in Headers */,
C386D6ED24199EC600699085 /* FLEX-Runtime.h in Headers */,
@@ -1753,6 +1775,7 @@
C3DFCDB92418336D00BB7084 /* NSUserDefaults+FLEX.m in Sources */,
C3878DBE23A74A8F0038FDBE /* FLEXNetworkRecorder.m in Sources */,
C3878DBC23A749F70038FDBE /* FLEXFieldEditorViewController.m in Sources */,
C35DAD832709140700AA95E6 /* FLEXHTTPTransactionDetailController.m in Sources */,
942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */,
C30D2961261FAE9E00D89649 /* FLEXNSDataShortcuts.m in Sources */,
C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */,
@@ -1845,6 +1868,7 @@
3A4C94FC1B5B21410088C3F2 /* FLEXArgumentInputStringView.m in Sources */,
3A4C94F81B5B21410088C3F2 /* FLEXArgumentInputNotSupportedView.m in Sources */,
3A4C95351B5B21410088C3F2 /* FLEXSystemLogViewController.m in Sources */,
C3DBFD0C26CE2FAF00E0466A /* OSCache.m in Sources */,
C383C3C623B6BB81007A321B /* FLEXCodeFontCell.m in Sources */,
3A4C95271B5B21410088C3F2 /* FLEXGlobalsViewController.m in Sources */,
C313854823F5F7D50046E63C /* flex_fishhook.c in Sources */,
@@ -1879,12 +1903,12 @@
3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
C3474C4123DA496400466532 /* FLEXKeyValueTableViewCell.m in Sources */,
779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
C35DAD8F2709143000AA95E6 /* FLEXMITMDataSource.m in Sources */,
C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */,
C36FBFD5230F3B98008D95D5 /* FLEXMethodBase.m in Sources */,
3A4C95001B5B21410088C3F2 /* FLEXArgumentInputSwitchView.m in Sources */,
C386D6F02419A33F00699085 /* FLEXRuntimeConstants.m in Sources */,
C3F31D442267D883003C991A /* FLEXTableView.m in Sources */,
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m in Sources */,
224D49AB1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.m in Sources */,
C312A13C23ECE79000E38049 /* FLEXWindowManagerController.m in Sources */,
C30D2969261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m in Sources */,
@@ -2084,7 +2108,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
@@ -2093,11 +2116,13 @@
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Classes/Info.plist;
INSTALL_PATH = "@rpath";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
@@ -2119,7 +2144,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
@@ -2128,11 +2152,13 @@
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Classes/Info.plist;
INSTALL_PATH = "@rpath";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
+27
View File
@@ -9,6 +9,9 @@
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "NSObject+FLEX_Reflection.h"
#import "FLEXObjcInternal.h"
#import "FLEXHeapEnumerator.h"
#import "FLEXObjectRef.h"
#import "NSArray+FLEX.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXProperty.h"
@@ -25,6 +28,12 @@
@end
@implementation Subclass @end
@interface NeverCreated : Subclass {
NSInteger a, b, c;
}
@end
@implementation NeverCreated @end
@interface FLEXTests : XCTestCase
@property (nonatomic, setter=setMyFoo:) id foo;
@end
@@ -140,4 +149,22 @@
XCTAssertEqualObjects(@"FLEXNewRootClass", className);
}
- (void)testInvalidObjectFinding {
// Create something that looks like an objc object
uintptr_t *pointer = (uintptr_t *)calloc(1, sizeof(uintptr_t));
object_setClass((__bridge id)(void *)pointer, [NeverCreated class]);
// Find that one object and assert that it is the object we just created
NSArray<FLEXObjectRef *> *refs = [FLEXHeapEnumerator
instancesOfClassWithName:@"NeverCreated" retained:NO
];
XCTAssertEqual(refs.count, 1);
XCTAssertEqual((__bridge void *)refs.firstObject.object, pointer);
// Find that the object isn't really an object
XCTAssertFalse(FLEXPointerIsValidObjcObject(pointer));
free(pointer);
}
@end