Compare commits

...

30 Commits

Author SHA1 Message Date
Tanner Bennett fcdb33fce2 Clean up FLEXObjcInternal.mm 2019-04-24 12:27:41 -05:00
Tanner Bennett 1eb8e4f430 Fix various warnings 2019-04-24 11:32:08 -05:00
Tanner Bennett 1d937777c0 Add address explorer 2019-04-24 11:11:25 -05:00
Tanner Bennett 308afda5c2 Improve FLEXPointerIsValidObjcObject
Previously, it assumed any address you gave it was valid and readable. It no longer makes this assumption.
2019-04-24 11:11:25 -05:00
Tanner Bennett f7b00e02ee Change UICatalog bundle ID 2019-04-24 11:11:25 -05:00
Tanner Bennett 0ff29e1f90 Add +[FLEXUtility alert:message:from:] 2019-04-24 11:11:25 -05:00
Tanner Bennett 3fe31e8628 Always display a description
Except while searching of course
2019-04-18 09:34:08 -05:00
Tanner Bennett 33263bfcfa Allow copying object address
Long press on the description row in an explorer screen
2019-04-18 09:34:08 -05:00
Tanner Bennett d6caab29dc Organize ObjectExplorers/
- Group classes into folders (Views, Controllers)
- Add FLEXTableViewCell, FLEXSubtitleTableViewCell
- FLEXMultilineTableViewCell inherits from FLEXTableViewCell
- FLEXTableViewCell makes it easy to add custom UIMenuItem commands to any cell without subclassing it or exposing any state to it
2019-04-18 09:34:08 -05:00
Tanner Bennett 5d181adcb8 Add example archived object in demo app 2019-04-17 11:52:45 -05:00
zhangpeng 4ba2fdc289 Support viewing archived objects in file browser 2019-04-17 11:52:45 -05:00
Tanner Bennett 140dc32775 Update pod version and maintainer 2019-04-16 11:21:05 -05:00
Tanner Bennett 78568cd5be Fix FLEX not being embedded into example app 2019-04-16 11:14:52 -05:00
Tanner Bennett b010cdb072 Refactor file browser view controller
Fix #251
2019-04-16 11:07:35 -05:00
Tanner Bennett 37e299733b Fix #261
Crash: +[NSString stringWithUTF8String:]: NULL cString
2019-04-12 13:46:56 -05:00
Tanner Bennett f3a1587cf1 Replace UICatalog launch images with launch xib 2019-04-12 13:34:11 -05:00
Tanner Bennett 821ca1683b Update project settings, Xcode 10.2 2019-04-12 13:25:00 -05:00
Chengming Liao 7e13ca2757 Update FLEXMultiColumnTableView.m
indentation
2019-04-12 10:13:03 -05:00
Chengming Liao 129c91c876 add compiler flags 2019-04-12 10:13:03 -05:00
Chengming Liao d2f6ff0b40 Fix safeArea for database content view 2019-04-12 10:13:03 -05:00
Alexander Leontev 69414e4174 Don't shorten curl 2019-04-12 10:12:09 -05:00
Lanbo Zhang 0654fb4b5f podfile should include .mm file 2019-04-12 10:10:09 -05:00
Tanner Bennett d7d40e6d27 Fix #140, system log messages work
more progress
2019-03-30 16:56:46 -05:00
Tanner Bennett bec7e0c229 Correct header comments
Some headers were reading UICatalog inside the FLEX project
2019-03-30 15:12:55 -05:00
Tanner Bennett 82a19e41e7 Fix prettyArgumentComponentsForMethod: bug
#178 made methods with no arguments appear to take one argument by forcibly returning the selector name as the only argument. This is not desired behavior. Updating the test to reflect desired behavior reveals this.

This commit makes this method return an empty array when selectors consist of one component, and does some housekeeping on the tests added in #178.
2019-03-30 15:10:59 -05:00
Tanner Bennett 867ae614e5 Detect and unbox pointers to objects from void *
- Also unbox C strings into NSString
- Also adds return type encoding string to method calling view controller
2019-03-30 15:10:58 -05:00
Tanner Bennett 22b7c6ccc7 Add helper methods to FLEXRuntimeUtility
- Add FLEXTypeEncoding enum
- Can check whether arbitrary poiner is valid object
- Can get return type encoding for method
- Can unbox raw pointers from NSValue into actual objects, or unbox C strings into NSStrings

Code copied from the Objc runtime complies with ASPL
2019-03-30 13:35:39 -05:00
ThePantsThief 9e9704580a Fix #168 by restructuring try-catch branching 2019-03-30 13:19:17 -05:00
Tanner Bennett 1ef608cf8a Fix #245
`#if __arm64__` is not a sufficient check for whether a platform is 64 bit. `__LP64__` appears to be a better candidate.

`MAX_REALISTIC_ADDRESS` was wrongly being set to `INT_MAX` on some 64 bit platforms.
2018-11-23 01:20:55 -06:00
Tanner Bennett b64cd37ec6 Add "Get" to readwrite editor screens, fix #235
Previously you could only "Set" mutable ivars or properties. This commit adds a "Get" button to the same screen to allow you to view the current value instead. Also works in the user defaults explorer.

It may be worth considering other approaches to this entirely, such as an alert that asks you if you want to get or set the ivar/property before a new screen is even pushed, or maybe a "Get" button as an accessory view on the rows of mutable ivars/properties.
2018-11-23 00:00:30 -06:00
128 changed files with 2970 additions and 643 deletions
@@ -1,6 +1,6 @@
//
// FLEXArgumentInputFontsPickerView.h
// UICatalog
// FLEX
//
// Created by 啟倫 陳 on 2014/7/27.
// Copyright (c) 2014年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXArgumentInputFontsPickerView.m
// UICatalog
// FLEX
//
// Created by 啟倫 陳 on 2014/7/27.
// Copyright (c) 2014年 f. All rights reserved.
@@ -8,6 +8,7 @@
#import "FLEXArgumentInputTextView.h"
// #warning TODO This is never supported
@interface FLEXArgumentInputJSONObjectView : FLEXArgumentInputTextView
@end
@@ -39,26 +39,23 @@
+ (Class)argumentInputViewSubclassForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue
{
Class argumentInputViewSubclass = nil;
NSArray<Class> *inputViewClasses = @[[FLEXArgumentInputColorView class],
[FLEXArgumentInputFontView class],
[FLEXArgumentInputStringView class],
[FLEXArgumentInputStructView class],
[FLEXArgumentInputSwitchView class],
[FLEXArgumentInputDateView class],
[FLEXArgumentInputNumberView class],
[FLEXArgumentInputJSONObjectView class]];
// Note that order is important here since multiple subclasses may support the same type.
// An example is the number subclass and the bool subclass for the type @encode(BOOL).
// Both work, but we'd prefer to use the bool subclass.
if ([FLEXArgumentInputColorView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputColorView class];
} else if ([FLEXArgumentInputFontView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputFontView class];
} else if ([FLEXArgumentInputStringView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputStringView class];
} else if ([FLEXArgumentInputStructView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputStructView class];
} else if ([FLEXArgumentInputSwitchView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputSwitchView class];
} else if ([FLEXArgumentInputDateView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputDateView class];
} else if ([FLEXArgumentInputNumberView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputNumberView class];
} else if ([FLEXArgumentInputJSONObjectView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputJSONObjectView class];
for (Class inputView in inputViewClasses) {
if ([inputView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = inputView;
break;
}
}
return argumentInputViewSubclass;
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
@interface FLEXDefaultEditorViewController : FLEXFieldEditorViewController
@interface FLEXDefaultEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
@@ -64,6 +64,13 @@
self.firstInputView.inputValue = [self.defaults objectForKey:self.key];
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [self.defaults objectForKey:self.key];
[self exploreObjectOrPopViewController:returnedObject];
}
+ (BOOL)canEditDefaultWithValue:(id)currentValue
{
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:@encode(id) currentValue:currentValue];
@@ -22,7 +22,11 @@
@property (nonatomic, strong, readonly) id target;
@property (nonatomic, strong, readonly) FLEXFieldEditorView *fieldEditorView;
@property (nonatomic, strong, readonly) UIBarButtonItem *setterButton;
- (void)actionButtonPressed:(id)sender;
- (NSString *)titleForActionButton;
/// Pushes an explorer view controller for the given object
/// or pops the current view controller.
- (void)exploreObjectOrPopViewController:(id)objectOrNil;
@end
@@ -10,8 +10,10 @@
#import "FLEXFieldEditorView.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXUtility.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXArgumentInputView.h"
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXObjectExplorerViewController.h"
@interface FLEXFieldEditorViewController () <UIScrollViewDelegate>
@@ -114,4 +116,16 @@
return @"Set";
}
- (void)exploreObjectOrPopViewController:(id)objectOrNil {
if (objectOrNil) {
// For non-nil (or void) return types, push an explorer view controller to display the object
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:objectOrNil];
[self.navigationController pushViewController:explorerViewController animated:YES];
} else {
// If we didn't get a returned object but the method call succeeded,
// pop this view controller off the stack to indicate that the call went through.
[self.navigationController popViewControllerAnimated:YES];
}
}
@end
@@ -6,10 +6,10 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
#import <objc/runtime.h>
@interface FLEXIvarEditorViewController : FLEXFieldEditorViewController
@interface FLEXIvarEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithTarget:(id)target ivar:(Ivar)ivar;
@@ -55,6 +55,17 @@
[FLEXRuntimeUtility setValue:self.firstInputView.inputValue forIvar:self.ivar onObject:self.target];
self.firstInputView.inputValue = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
// Pop view controller for consistency;
// property setters and method calls also pop on success.
[self.navigationController popViewControllerAnimated:YES];
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
[self exploreObjectOrPopViewController:returnedObject];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
@@ -17,6 +17,7 @@
@interface FLEXMethodCallingViewController ()
@property (nonatomic, assign) Method method;
@property (nonatomic, assign) FLEXTypeEncoding *returnType;
@end
@@ -27,7 +28,8 @@
self = [super initWithTarget:target];
if (self) {
self.method = method;
self.title = [self isClassMethod] ? @"Class Method" : @"Method";
self.returnType = [FLEXRuntimeUtility returnTypeForMethod:method];
self.title = [self isClassMethod] ? @"Class Method" : @"Method";;
}
return self;
}
@@ -36,7 +38,11 @@
{
[super viewDidLoad];
self.fieldEditorView.fieldDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
NSString *returnType = @((const char *)self.returnType);
NSString *methodDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
NSString *format = @"Signature:\n%@\n\nReturn Type:\n%@";
NSString *info = [NSString stringWithFormat:format, methodDescription, returnType];
self.fieldEditorView.fieldDescription = info;
NSArray<NSString *> *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray array];
@@ -54,6 +60,12 @@
self.fieldEditorView.argumentInputViews = argumentInputViews;
}
- (void)dealloc
{
free(self.returnType);
self.returnType = NULL;
}
- (BOOL)isClassMethod
{
return self.target && self.target == [self.target class];
@@ -88,12 +100,11 @@
[alert show];
} else if (returnedObject) {
// For non-nil (or void) return types, push an explorer view controller to display the returned object
returnedObject = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:returnedObject type:self.returnType];
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:returnedObject];
[self.navigationController pushViewController:explorerViewController animated:YES];
} else {
// If we didn't get a returned object but the method call succeeded,
// pop this view controller off the stack to indicate that the call went through.
[self.navigationController popViewControllerAnimated:YES];
[self exploreObjectOrPopViewController:returnedObject];
}
}
@@ -0,0 +1,18 @@
//
// FLEXMutableFieldEditorViewController.h
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
@interface FLEXMutableFieldEditorViewController : FLEXFieldEditorViewController
@property (nonatomic, strong, readonly) UIBarButtonItem *getterButton;
- (void)getterButtonPressed:(id)sender;
- (NSString *)titleForGetterButton;
@end
@@ -0,0 +1,36 @@
//
// FLEXMutableFieldEditorViewController.m
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXMutableFieldEditorViewController.h"
#import "FLEXFieldEditorView.h"
@interface FLEXMutableFieldEditorViewController ()
@property (nonatomic, strong, readwrite) UIBarButtonItem *getterButton;
@end
@implementation FLEXMutableFieldEditorViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.getterButton = [[UIBarButtonItem alloc] initWithTitle:[self titleForGetterButton] style:UIBarButtonItemStyleDone target:self action:@selector(getterButtonPressed:)];
self.navigationItem.rightBarButtonItems = @[self.setterButton, self.getterButton];
}
- (void)getterButtonPressed:(id)sender {
// Subclasses can override
[self.fieldEditorView endEditing:YES];
}
- (NSString *)titleForGetterButton {
return @"Get";
}
@end
@@ -6,10 +6,10 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
#import <objc/runtime.h>
@interface FLEXPropertyEditorViewController : FLEXFieldEditorViewController
@interface FLEXPropertyEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithTarget:(id)target property:(objc_property_t)property;
@@ -76,6 +76,13 @@
}
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
[self exploreObjectOrPopViewController:returnedObject];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
{
if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
@@ -51,6 +51,13 @@ static const CGFloat kColumnMargin = 1;
CGFloat height = self.frame.size.height;
CGFloat topheaderHeight = [self topHeaderHeight];
CGFloat leftHeaderWidth = [self leftHeaderWidth];
CGFloat topInsets = 0.f;
#if FLEX_AT_LEAST_IOS11_SDK
if (@available (iOS 11.0, *)) {
topInsets = self.safeAreaInsets.top;
}
#endif
CGFloat contentWidth = 0.0;
NSInteger rowsCount = [self numberOfColumns];
@@ -58,13 +65,13 @@ static const CGFloat kColumnMargin = 1;
contentWidth += [self contentWidthForColumn:i];
}
self.leftTableView.frame = CGRectMake(0, topheaderHeight, leftHeaderWidth, height - topheaderHeight);
self.headerScrollView.frame = CGRectMake(leftHeaderWidth, 0, width - leftHeaderWidth, topheaderHeight);
self.leftTableView.frame = CGRectMake(0, topheaderHeight + topInsets, leftHeaderWidth, height - topheaderHeight - topInsets);
self.headerScrollView.frame = CGRectMake(leftHeaderWidth, topInsets, width - leftHeaderWidth, topheaderHeight);
self.headerScrollView.contentSize = CGSizeMake( self.contentTableView.frame.size.width, self.headerScrollView.frame.size.height);
self.contentTableView.frame = CGRectMake(0, 0, contentWidth + [self numberOfColumns] * [self columnMargin] , height - topheaderHeight);
self.contentScrollView.frame = CGRectMake(leftHeaderWidth, topheaderHeight, width - leftHeaderWidth, height - topheaderHeight);
self.contentTableView.frame = CGRectMake(0, 0, contentWidth + [self numberOfColumns] * [self columnMargin] , height - topheaderHeight - topInsets);
self.contentScrollView.frame = CGRectMake(leftHeaderWidth, topheaderHeight + topInsets, width - leftHeaderWidth, height - topheaderHeight - topInsets);
self.contentScrollView.contentSize = self.contentTableView.frame.size;
self.leftHeader.frame = CGRectMake(0, 0, [self leftHeaderWidth], [self topHeaderHeight]);
self.leftHeader.frame = CGRectMake(0, topInsets, [self leftHeaderWidth], [self topHeaderHeight]);
}
@@ -1,6 +1,6 @@
//
// FLEXTableContentHeaderCell.h
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/26.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableContentHeaderCell.m
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/26.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableContentCell.h
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableContentCell.m
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableLeftCell.h
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableLeftCell.m
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXFileBrowserSearchOperation.h
// UICatalog
// FLEX
//
// Created by 啟倫 陳 on 2014/8/4.
// Copyright (c) 2014年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXFileBrowserSearchOperation.m
// UICatalog
// FLEX
//
// Created by 啟倫 陳 on 2014/8/4.
// Copyright (c) 2014年 f. All rights reserved.
@@ -12,6 +12,8 @@
#import "FLEXWebViewController.h"
#import "FLEXImagePreviewViewController.h"
#import "FLEXTableListViewController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXObjectExplorerViewController.h"
@interface FLEXFileBrowserTableViewCell : UITableViewCell
@end
@@ -88,6 +90,12 @@
}
#pragma mark - Misc
- (void)alert:(NSString *)title message:(NSString *)message {
[[[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
}
#pragma mark - FLEXFileBrowserSearchOperationDelegate
- (void)fileBrowserSearchOperationResult:(NSArray<NSString *> *)searchResult size:(uint64_t)size
@@ -185,56 +193,70 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
NSString *subpath = [fullPath lastPathComponent];
NSString *pathExtension = [subpath pathExtension];
NSString *subpath = fullPath.lastPathComponent;
NSString *pathExtension = subpath.pathExtension;
BOOL isDirectory = NO;
BOOL stillExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDirectory];
if (stillExists) {
UIViewController *drillInViewController = nil;
if (isDirectory) {
drillInViewController = [[[self class] alloc] initWithPath:fullPath];
} else if ([FLEXUtility isImagePathExtension:pathExtension]) {
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
drillInViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
if (!stillExists) {
[self alert:@"File Not Found" message:@"The file at the specified path no longer exists."];
[self reloadDisplayedPaths];
return;
}
UIViewController *drillInViewController = nil;
if (isDirectory) {
drillInViewController = [[[self class] alloc] initWithPath:fullPath];
} else if ([FLEXUtility isImagePathExtension:pathExtension]) {
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
drillInViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
} else {
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
if (!fileData.length) {
[self alert:@"Empty File" message:@"No data returned from the file."];
return;
}
// Special case keyed archives, json, and plists to get more readable data.
NSString *prettyString = nil;
if ([pathExtension isEqualToString:@"json"]) {
prettyString = [FLEXUtility prettyJSONStringFromData:fileData];
} else {
// Special case keyed archives, json, and plists to get more readable data.
NSString *prettyString = nil;
if ([pathExtension isEqual:@"archive"] || [pathExtension isEqual:@"coded"]) {
prettyString = [[NSKeyedUnarchiver unarchiveObjectWithFile:fullPath] description];
} else if ([pathExtension isEqualToString:@"json"]) {
prettyString = [FLEXUtility prettyJSONStringFromData:[NSData dataWithContentsOfFile:fullPath]];
} else if ([pathExtension isEqualToString:@"plist"]) {
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
@try {
// Try to decode an archived object regardless of file extension
id object = [NSKeyedUnarchiver unarchiveObjectWithData:fileData];
drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:object];
} @catch (NSException *e) {
// Try to decode a property list instead, also regardless of file extension
prettyString = [[NSPropertyListSerialization propertyListWithData:fileData options:0 format:NULL error:NULL] description];
}
if ([prettyString length] > 0) {
drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
} else if ([FLEXWebViewController supportsPathExtension:pathExtension]) {
drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
} else if ([FLEXTableListViewController supportsExtension:subpath.pathExtension]) {
drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
}
else {
NSString *fileString = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:NULL];
if ([fileString length] > 0) {
drillInViewController = [[FLEXWebViewController alloc] initWithText:fileString];
}
}
}
if (drillInViewController) {
drillInViewController.title = [subpath lastPathComponent];
[self.navigationController pushViewController:drillInViewController animated:YES];
} else {
[self openFileController:fullPath];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if (prettyString.length) {
drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
} else if ([FLEXWebViewController supportsPathExtension:pathExtension]) {
drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
} else if ([FLEXTableListViewController supportsExtension:pathExtension]) {
drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
}
else if (!drillInViewController) {
NSString *fileString = [NSString stringWithUTF8String:fileData.bytes];
if (fileString.length) {
drillInViewController = [[FLEXWebViewController alloc] initWithText:fileString];
}
}
}
if (drillInViewController) {
drillInViewController.title = subpath.lastPathComponent;
[self.navigationController pushViewController:drillInViewController animated:YES];
} else {
[[[UIAlertView alloc] initWithTitle:@"File Removed" message:@"The file at the specified path no longer exists." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
[self reloadDisplayedPaths];
// Share the file otherwise
[self openFileController:fullPath];
}
}
@@ -8,6 +8,7 @@
#import "FLEXGlobalsTableViewController.h"
#import "FLEXUtility.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXLibrariesTableViewController.h"
#import "FLEXClassesTableViewController.h"
#import "FLEXObjectExplorerViewController.h"
@@ -26,6 +27,7 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
FLEXGlobalsRowNetworkHistory,
FLEXGlobalsRowSystemLog,
FLEXGlobalsRowLiveObjects,
FLEXGlobalsRowAddressInspector,
FLEXGlobalsRowFileBrowser,
FLEXGlobalsCookies,
FLEXGlobalsRowSystemLibraries,
@@ -56,6 +58,7 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
for (FLEXGlobalsRow defaultRowIndex = 0; defaultRowIndex < FLEXGlobalsRowCount; defaultRowIndex++) {
FLEXGlobalsTableViewControllerEntryNameFuture titleFuture = nil;
FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture = nil;
FLEXGlobalsTableViewControllerRowAction rowAction = nil;
switch (defaultRowIndex) {
case FLEXGlobalsRowAppClasses:
@@ -69,6 +72,49 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
return classesViewController;
};
break;
case FLEXGlobalsRowAddressInspector:
titleFuture = ^NSString *{
return @"🔎 Address Explorer";
};
rowAction = ^(FLEXGlobalsTableViewController *host) {
NSString *title = @"Explore Object at Address";
NSString *message = @"Paste a hexadecimal address below, starting with '0x'. "
"Use the unsafe option if you need to bypass pointer validation, "
"but know that it may crash the app if the address is invalid.";
UIAlertController *addressInput = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
void (^handler)(UIAlertAction *) = ^(UIAlertAction *action) {
if (action.style == UIAlertActionStyleCancel) {
[host deselectSelectedRow]; return;
}
NSString *address = addressInput.textFields.firstObject.text;
[host tryExploreAddress:address safely:action.style == UIAlertActionStyleDefault];
};
[addressInput addTextFieldWithConfigurationHandler:^(UITextField *textField) {
NSString *copied = [UIPasteboard generalPasteboard].string;
textField.placeholder = @"0x00000070deadbeef";
// Go ahead and paste our clipboard if we have an address copied
if ([copied hasPrefix:@"0x"]) {
textField.text = copied;
[textField selectAll:nil];
}
}];
[addressInput addAction:[UIAlertAction actionWithTitle:@"Explore"
style:UIAlertActionStyleDefault
handler:handler]];
[addressInput addAction:[UIAlertAction actionWithTitle:@"Unsafe Explore"
style:UIAlertActionStyleDestructive
handler:handler]];
[addressInput addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:handler]];
[host presentViewController:addressInput animated:YES completion:nil];
};
break;
case FLEXGlobalsRowSystemLibraries: {
NSString *titleString = @"📚 System Libraries";
@@ -212,10 +258,18 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
break;
}
NSParameterAssert(titleFuture);
NSParameterAssert(viewControllerFuture);
NSAssert(viewControllerFuture || rowAction, @"The switch-case above must assign one of these");
if (viewControllerFuture) {
[defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry
entryWithNameFuture:titleFuture
viewControllerFuture:viewControllerFuture]];
} else {
[defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry
entryWithNameFuture:titleFuture
action:rowAction]];
}
[defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry entryWithNameFuture:titleFuture viewControllerFuture:viewControllerFuture]];
}
return defaultGlobalEntries;
@@ -231,6 +285,11 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
return self;
}
- (void)deselectSelectedRow {
NSIndexPath *selected = self.tableView.indexPathForSelectedRow;
[self.tableView deselectRowAtIndexPath:selected animated:YES];
}
#pragma mark - Public
+ (void)setApplicationWindow:(UIWindow *)applicationWindow
@@ -247,13 +306,39 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(donePressed:)];
}
#pragma mark -
#pragma mark - Misc
- (void)donePressed:(id)sender
{
[self.delegate globalsViewControllerDidFinish:self];
}
- (void)tryExploreAddress:(NSString *)addressString safely:(BOOL)safely {
NSScanner *scanner = [NSScanner scannerWithString:addressString];
unsigned long long hexValue = 0;
BOOL didParseAddress = [scanner scanHexLongLong:&hexValue];
const void *pointerValue = (void *)hexValue;
NSString *error = nil;
if (didParseAddress) {
if (safely && ![FLEXRuntimeUtility pointerIsValidObjcObject:pointerValue]) {
error = @"The given address is unlikely to be a valid object.";
}
} else {
error = @"Malformed address. Make sure it's not too long and starts with '0x'.";
}
if (!error) {
id object = (__bridge id)pointerValue;
FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:object];
[self.navigationController pushViewController:explorer animated:YES];
} else {
[FLEXUtility alert:@"Uh-oh" message:error from:self];
[self deselectSelectedRow];
}
}
#pragma mark - Table Data Helpers
- (FLEXGlobalsTableViewControllerEntry *)globalEntryAtIndexPath:(NSIndexPath *)indexPath
@@ -268,13 +353,6 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
return entry.entryNameFuture();
}
- (UIViewController *)viewControllerToPushForRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXGlobalsTableViewControllerEntry *entry = [self globalEntryAtIndexPath:indexPath];
return entry.viewControllerFuture();
}
#pragma mark - Table View Data Source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
@@ -302,14 +380,16 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
return cell;
}
#pragma mark - Table View Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *viewControllerToPush = [self viewControllerToPushForRowAtIndexPath:indexPath];
[self.navigationController pushViewController:viewControllerToPush animated:YES];
FLEXGlobalsTableViewControllerEntry *entry = [self globalEntryAtIndexPath:indexPath];
if (entry.viewControllerFuture) {
[self.navigationController pushViewController:entry.viewControllerFuture() animated:YES];
} else {
entry.rowAction(self);
}
}
@end
@@ -0,0 +1,205 @@
//
// Taken from https://github.com/llvm-mirror/lldb/blob/master/tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h
// by Tanner Bennett on 03/03/2019 with minimal modifications.
//
//===-- ActivityStreamAPI.h -------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef ActivityStreamSPI_h
#define ActivityStreamSPI_h
#include <sys/time.h>
// #include <xpc/xpc.h>
/* By default, XPC objects are declared as Objective-C types when building with
* an Objective-C compiler. This allows them to participate in ARC, in RR
* management by the Blocks runtime and in leaks checking by the static
* analyzer, and enables them to be added to Cocoa collections.
*
* See <os/object.h> for details.
*/
#if OS_OBJECT_USE_OBJC
OS_OBJECT_DECL(xpc_object);
#else
typedef void * xpc_object_t;
#endif
#define OS_ACTIVITY_MAX_CALLSTACK 32
// Enums
typedef NS_ENUM(uint32_t, os_activity_stream_flag_t) {
OS_ACTIVITY_STREAM_PROCESS_ONLY = 0x00000001,
OS_ACTIVITY_STREAM_SKIP_DECODE = 0x00000002,
OS_ACTIVITY_STREAM_PAYLOAD = 0x00000004,
OS_ACTIVITY_STREAM_HISTORICAL = 0x00000008,
OS_ACTIVITY_STREAM_CALLSTACK = 0x00000010,
OS_ACTIVITY_STREAM_DEBUG = 0x00000020,
OS_ACTIVITY_STREAM_BUFFERED = 0x00000040,
OS_ACTIVITY_STREAM_NO_SENSITIVE = 0x00000080,
OS_ACTIVITY_STREAM_INFO = 0x00000100,
OS_ACTIVITY_STREAM_PROMISCUOUS = 0x00000200,
OS_ACTIVITY_STREAM_PRECISE_TIMESTAMPS = 0x00000200
};
typedef NS_ENUM(uint32_t, os_activity_stream_type_t) {
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE = 0x0201,
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION = 0x0202,
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_USERACTION = 0x0203,
OS_ACTIVITY_STREAM_TYPE_TRACE_MESSAGE = 0x0300,
OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE = 0x0400,
OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE = 0x0480,
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_BEGIN = 0x0601,
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_END = 0x0602,
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_EVENT = 0x0603,
OS_ACTIVITY_STREAM_TYPE_STATEDUMP_EVENT = 0x0A00,
};
typedef NS_ENUM(uint32_t, os_activity_stream_event_t) {
OS_ACTIVITY_STREAM_EVENT_STARTED = 1,
OS_ACTIVITY_STREAM_EVENT_STOPPED = 2,
OS_ACTIVITY_STREAM_EVENT_FAILED = 3,
OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED = 4,
OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED = 5,
};
// Types
typedef uint64_t os_activity_id_t;
typedef struct os_activity_stream_s *os_activity_stream_t;
typedef struct os_activity_stream_entry_s *os_activity_stream_entry_t;
#define OS_ACTIVITY_STREAM_COMMON() \
uint64_t trace_id; \
uint64_t timestamp; \
uint64_t thread; \
const uint8_t *image_uuid; \
const char *image_path; \
struct timeval tv_gmt; \
struct timezone tz; \
uint32_t offset
typedef struct os_activity_stream_common_s {
OS_ACTIVITY_STREAM_COMMON();
} * os_activity_stream_common_t;
struct os_activity_create_s {
OS_ACTIVITY_STREAM_COMMON();
const char *name;
os_activity_id_t creator_aid;
uint64_t unique_pid;
};
struct os_activity_transition_s {
OS_ACTIVITY_STREAM_COMMON();
os_activity_id_t transition_id;
};
typedef struct os_log_message_s {
OS_ACTIVITY_STREAM_COMMON();
const char *format;
const uint8_t *buffer;
size_t buffer_sz;
const uint8_t *privdata;
size_t privdata_sz;
const char *subsystem;
const char *category;
uint32_t oversize_id;
uint8_t ttl;
bool persisted;
} * os_log_message_t;
typedef struct os_trace_message_v2_s {
OS_ACTIVITY_STREAM_COMMON();
const char *format;
const void *buffer;
size_t bufferLen;
xpc_object_t __unsafe_unretained payload;
} * os_trace_message_v2_t;
typedef struct os_activity_useraction_s {
OS_ACTIVITY_STREAM_COMMON();
const char *action;
bool persisted;
} * os_activity_useraction_t;
typedef struct os_signpost_s {
OS_ACTIVITY_STREAM_COMMON();
const char *format;
const uint8_t *buffer;
size_t buffer_sz;
const uint8_t *privdata;
size_t privdata_sz;
const char *subsystem;
const char *category;
uint64_t duration_nsec;
uint32_t callstack_depth;
uint64_t callstack[OS_ACTIVITY_MAX_CALLSTACK];
} * os_signpost_t;
typedef struct os_activity_statedump_s {
OS_ACTIVITY_STREAM_COMMON();
char *message;
size_t message_size;
char image_path_buffer[PATH_MAX];
} * os_activity_statedump_t;
struct os_activity_stream_entry_s {
os_activity_stream_type_t type;
// information about the process streaming the data
pid_t pid;
uint64_t proc_id;
const uint8_t *proc_imageuuid;
const char *proc_imagepath;
// the activity associated with this streamed event
os_activity_id_t activity_id;
os_activity_id_t parent_id;
union {
struct os_activity_stream_common_s common;
struct os_activity_create_s activity_create;
struct os_activity_transition_s activity_transition;
struct os_log_message_s log_message;
struct os_trace_message_v2_s trace_message;
struct os_activity_useraction_s useraction;
struct os_signpost_s signpost;
struct os_activity_statedump_s statedump;
};
};
// Blocks
typedef bool (^os_activity_stream_block_t)(os_activity_stream_entry_t entry,
int error);
typedef void (^os_activity_stream_event_block_t)(
os_activity_stream_t stream, os_activity_stream_event_t event);
// SPI entry point prototypes
typedef os_activity_stream_t (*os_activity_stream_for_pid_t)(
pid_t pid, os_activity_stream_flag_t flags,
os_activity_stream_block_t stream_block);
typedef void (*os_activity_stream_resume_t)(os_activity_stream_t stream);
typedef void (*os_activity_stream_cancel_t)(os_activity_stream_t stream);
typedef char *(*os_log_copy_formatted_message_t)(os_log_message_t log_message);
typedef void (*os_activity_stream_set_event_handler_t)(
os_activity_stream_t stream, os_activity_stream_event_block_t block);
#endif /* ActivityStreamSPI_h */
@@ -0,0 +1,18 @@
//
// FLEXASLLogController.h
// FLEX
//
// Created by Tanner on 3/14/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXLogController.h"
@interface FLEXASLLogController : NSObject <FLEXLogController>
/// Guaranteed to call back on the main thread.
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
- (BOOL)startMonitoring;
@end
@@ -0,0 +1,154 @@
//
// FLEXASLLogController.m
// FLEX
//
// Created by Tanner on 3/14/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXASLLogController.h"
#import <asl.h>
// Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
#if TARGET_IPHONE_SIMULATOR
#define updateInterval 5.0
#else
#define updateInterval 1.0
#endif
@interface FLEXASLLogController ()
@property (nonatomic, readonly) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
@property (nonatomic, strong) NSTimer *logUpdateTimer;
@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
// ASL stuff
@property (nonatomic) NSUInteger heapSize;
@property (nonatomic) dispatch_queue_t logQueue;
@property (nonatomic) dispatch_io_t io;
@property (nonatomic) NSString *remaining;
@property (nonatomic) int stderror;
@property (nonatomic) NSString *lastTimestamp;
@end
@implementation FLEXASLLogController
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
{
return [[self alloc] initWithUpdateHandler:newMessagesHandler];
}
- (id)initWithUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
{
NSParameterAssert(newMessagesHandler);
self = [super init];
if (self) {
_updateHandler = newMessagesHandler;
_logMessageIdentifiers = [NSMutableIndexSet indexSet];
self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval
target:self
selector:@selector(updateLogMessages)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)dealloc
{
[self.logUpdateTimer invalidate];
}
- (BOOL)startMonitoring {
[self.logUpdateTimer fire];
return YES;
}
- (void)updateLogMessages
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<FLEXSystemLogMessage *> *newMessages;
@synchronized (self) {
newMessages = [self newLogMessagesForCurrentProcess];
if (!newMessages.count) {
return;
}
for (FLEXSystemLogMessage *message in newMessages) {
[self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
}
self.lastTimestamp = @(asl_get(newMessages.lastObject.aslMessage, ASL_KEY_TIME) ?: "null");
}
dispatch_async(dispatch_get_main_queue(), ^{
self.updateHandler(newMessages);
});
});
}
#pragma mark - Log Message Fetching
- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
{
if (!self.logMessageIdentifiers.count) {
return [self allLogMessagesForCurrentProcess];
}
aslresponse response = [self ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray<FLEXSystemLogMessage *> *newMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
NSUInteger messageID = (NSUInteger)atoll(asl_get(aslMessage, ASL_KEY_MSG_ID));
if (![self.logMessageIdentifiers containsIndex:messageID]) {
[newMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
}
asl_release(response);
return newMessages;
}
- (aslresponse)ASLMessageListForCurrentProcess
{
static NSString *pidString = nil;
if (!pidString) {
pidString = @([[NSProcessInfo processInfo] processIdentifier]).stringValue;
}
// Create system log query object.
asl_object_t query = asl_new(ASL_TYPE_QUERY);
// Filter for messages from the current process.
// Note that this appears to happen by default on device, but is required in the simulator.
asl_set_query(query, ASL_KEY_PID, pidString.UTF8String, ASL_QUERY_OP_EQUAL);
// Filter for messages after the last retreived message.
if (self.lastTimestamp) {
asl_set_query(query, ASL_KEY_TIME, self.lastTimestamp.UTF8String, ASL_QUERY_OP_GREATER);
}
return asl_search(NULL, query);
}
- (NSArray<FLEXSystemLogMessage *> *)allLogMessagesForCurrentProcess
{
aslresponse response = [self ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray<FLEXSystemLogMessage *> *logMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
asl_release(response);
return logMessages;
}
@end
@@ -0,0 +1,19 @@
//
// FLEXLogController.h
// FLEX
//
// Created by Tanner on 3/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FLEXSystemLogMessage.h"
@protocol FLEXLogController <NSObject>
/// Guaranteed to call back on the main thread.
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
- (BOOL)startMonitoring;
@end
@@ -0,0 +1,29 @@
//
// FLEXOSLogController.h
// FLEX
//
// Created by Tanner on 12/19/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXLogController.h"
#define FLEXOSLogAvailable() ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion >= 10)
extern NSString * const kFLEXiOSPersistentOSLogKey;
/// The log controller used for iOS 10 and up.
@interface FLEXOSLogController : NSObject <FLEXLogController>
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
- (BOOL)startMonitoring;
/// Whether log messages are to be recorded and kept in-memory in the background.
/// You do not need to initialize this value, only change it.
@property (nonatomic) BOOL persistent;
/// Used mostly internally, but also used by the log VC to persist messages
/// that were created prior to enabling persistence.
@property (nonatomic) NSMutableArray<FLEXSystemLogMessage *> *messages;
@end
@@ -0,0 +1,218 @@
//
// FLEXOSLogController.m
// FLEX
//
// Created by Tanner on 12/19/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXOSLogController.h"
#include <dlfcn.h>
#include "ActivityStreamAPI.h"
NSString * const kFLEXiOSPersistentOSLogKey = @"com.flex.enablePersistentOSLogLogging";
static os_activity_stream_for_pid_t OSActivityStreamForPID;
static os_activity_stream_resume_t OSActivityStreamResume;
static os_activity_stream_cancel_t OSActivityStreamCancel;
static os_log_copy_formatted_message_t OSLogCopyFormattedMessage;
static os_activity_stream_set_event_handler_t OSActivityStreamSetEventHandler;
static int (*proc_name)(int, char *, unsigned int);
static int (*proc_listpids)(uint32_t, uint32_t, void*, int);
static uint8_t (*OSLogGetType)(void *);
@interface FLEXOSLogController ()
+ (FLEXOSLogController *)sharedLogController;
@property (nonatomic) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
@property (nonatomic) BOOL canPrint;
@property (nonatomic) int filterPid;
@property (nonatomic) BOOL levelInfo;
@property (nonatomic) BOOL subsystemInfo;
@property (nonatomic) os_activity_stream_t stream;
@end
@implementation FLEXOSLogController
+ (void)load
{
// Persist logs when the app launches on iOS 10 if we have persitent logs turned on
if (FLEXOSLogAvailable()) {
BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
if (persistent) {
[self sharedLogController].persistent = YES;
[[self sharedLogController] startMonitoring];
}
}
}
+ (instancetype)sharedLogController {
static FLEXOSLogController *shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [self new];
});
return shared;
}
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
{
FLEXOSLogController *shared = [self sharedLogController];
shared.updateHandler = newMessagesHandler;
return shared;
}
- (id)init
{
NSAssert(FLEXOSLogAvailable(), @"os_log is only available on iOS 10 and up");
self = [super init];
if (self) {
_filterPid = [NSProcessInfo processInfo].processIdentifier;
_levelInfo = NO;
_subsystemInfo = NO;
}
return self;
}
- (void)dealloc {
OSActivityStreamCancel(self.stream);
_stream = nil;
}
- (void)setPersistent:(BOOL)persistent {
if (_persistent == persistent) return;
_persistent = persistent;
self.messages = persistent ? [NSMutableArray array] : nil;
}
- (BOOL)startMonitoring {
if (![self lookupSPICalls]) {
// >= iOS 10 is required
return NO;
}
// Are we already monitoring?
if (self.stream) {
// Should we send out the "persisted" messages?
if (self.updateHandler && self.messages.count) {
dispatch_async(dispatch_get_main_queue(), ^{
self.updateHandler(self.messages);
});
}
return YES;
}
// Stream entry handler
os_activity_stream_block_t block = ^bool(os_activity_stream_entry_t entry, int error) {
return [self handleStreamEntry:entry error:error];
};
// Controls which types of messages we see
// 'Historical' appears to just show NSLog stuff
uint32_t activity_stream_flags = OS_ACTIVITY_STREAM_HISTORICAL;
activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
// activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
self.stream = OSActivityStreamForPID(self.filterPid, activity_stream_flags, block);
// Specify the stream-related event handler
OSActivityStreamSetEventHandler(self.stream, [self streamEventHandlerBlock]);
// Start the stream
OSActivityStreamResume(self.stream);
return YES;
}
- (BOOL)lookupSPICalls {
static BOOL hasSPI = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen("/System/Library/PrivateFrameworks/LoggingSupport.framework/LoggingSupport", RTLD_NOW);
OSActivityStreamForPID = (os_activity_stream_for_pid_t)dlsym(handle, "os_activity_stream_for_pid");
OSActivityStreamResume = (os_activity_stream_resume_t)dlsym(handle, "os_activity_stream_resume");
OSActivityStreamCancel = (os_activity_stream_cancel_t)dlsym(handle, "os_activity_stream_cancel");
OSLogCopyFormattedMessage = (os_log_copy_formatted_message_t)dlsym(handle, "os_log_copy_formatted_message");
OSActivityStreamSetEventHandler = (os_activity_stream_set_event_handler_t)dlsym(handle, "os_activity_stream_set_event_handler");
proc_name = (int(*)(int, char *, unsigned int))dlsym(handle, "proc_name");
proc_listpids = (int(*)(uint32_t, uint32_t, void*, int))dlsym(handle, "proc_listpids");
OSLogGetType = (uint8_t(*)(void *))dlsym(handle, "os_log_get_type");
hasSPI = (OSActivityStreamForPID != NULL) &&
(OSActivityStreamResume != NULL) &&
(OSActivityStreamCancel != NULL) &&
(OSLogCopyFormattedMessage != NULL) &&
(OSActivityStreamSetEventHandler != NULL) &&
(OSLogGetType != NULL) &&
(proc_name != NULL);
});
return hasSPI;
}
- (BOOL)handleStreamEntry:(os_activity_stream_entry_t)entry error:(int)error {
if (!self.canPrint || (self.filterPid != -1 && entry->pid != self.filterPid)) {
return YES;
}
if (!error && entry) {
if (entry->type == OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE ||
entry->type == OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE) {
os_log_message_t log_message = &entry->log_message;
// Get date
NSDate *date = [NSDate dateWithTimeIntervalSince1970:log_message->tv_gmt.tv_sec];
// Get log message text
const char *messageText = OSLogCopyFormattedMessage(log_message);
// https://github.com/limneos/oslog/issues/1
if (entry->log_message.format && !(strcmp(entry->log_message.format, messageText))) {
messageText = (char *)entry->log_message.format;
}
dispatch_async(dispatch_get_main_queue(), ^{
FLEXSystemLogMessage *message = [FLEXSystemLogMessage logMessageFromDate:date text:@(messageText)];
if (self.persistent) {
[self.messages addObject:message];
}
if (self.updateHandler) {
self.updateHandler(@[message]);
}
});
}
}
return YES;
}
- (os_activity_stream_event_block_t)streamEventHandlerBlock {
return [^void(os_activity_stream_t stream, os_activity_stream_event_t event) {
switch (event) {
case OS_ACTIVITY_STREAM_EVENT_STARTED:
self.canPrint = YES;
break;
case OS_ACTIVITY_STREAM_EVENT_STOPPED:
break;
case OS_ACTIVITY_STREAM_EVENT_FAILED:
break;
case OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED:
break;
case OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED:
break;
default:
printf("=== Unhandled case ===\n");
break;
}
} copy];
}
@end
@@ -1,6 +1,6 @@
//
// FLEXSystemLogMessage.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -8,14 +8,24 @@
#import <Foundation/Foundation.h>
#import <asl.h>
#import "ActivityStreamAPI.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXSystemLogMessage : NSObject
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage;
//+ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage;
+ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, copy) NSString *sender;
@property (nonatomic, copy) NSString *messageText;
@property (nonatomic, assign) long long messageID;
// ASL specific properties
@property (nonatomic, readonly, nullable) NSString *sender;
@property (nonatomic, readonly, nullable) aslmsg aslMessage;
@property (nonatomic, readonly) NSDate *date;
@property (nonatomic, readonly) NSString *messageText;
@property (nonatomic, readonly) long long messageID;
@end
NS_ASSUME_NONNULL_END
@@ -1,6 +1,6 @@
//
// FLEXSystemLogMessage.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -12,7 +12,9 @@
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
{
FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
NSDate *date = nil;
NSString *sender = nil, *text = nil;
long long identifier = 0;
const char *timestamp = asl_get(aslMessage, ASL_KEY_TIME);
if (timestamp) {
@@ -21,30 +23,67 @@
if (nanoseconds) {
timeInterval += [@(nanoseconds) doubleValue] / NSEC_PER_SEC;
}
logMessage.date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
}
const char *sender = asl_get(aslMessage, ASL_KEY_SENDER);
if (sender) {
logMessage.sender = @(sender);
const char *s = asl_get(aslMessage, ASL_KEY_SENDER);
if (s) {
sender = @(s);
}
const char *messageText = asl_get(aslMessage, ASL_KEY_MSG);
if (messageText) {
logMessage.messageText = @(messageText);
text = @(messageText);
}
const char *messageID = asl_get(aslMessage, ASL_KEY_MSG_ID);
if (messageID) {
logMessage.messageID = [@(messageID) longLongValue];
identifier = [@(messageID) longLongValue];
}
return logMessage;
FLEXSystemLogMessage *message = [[self alloc] initWithDate:date sender:sender text:text messageID:identifier];
message->_aslMessage = aslMessage;
return message;
}
+ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage
{
abort();
return nil;
}
+ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text
{
return [[self alloc] initWithDate:date sender:nil text:text messageID:0];
}
- (id)initWithDate:(NSDate *)date sender:(NSString *)sender text:(NSString *)text messageID:(long long)identifier
{
self = [super init];
if (self) {
_date = date;
_sender = sender;
_messageText = text;
_messageID = identifier;
}
return self;
}
- (BOOL)isEqual:(id)object
{
return [object isKindOfClass:[FLEXSystemLogMessage class]] && self.messageID == [object messageID];
if ([object isKindOfClass:[self class]]) {
if (self.messageID) {
// Only ASL uses messageID, otherwise it is 0
return self.messageID == [object messageID];
} else {
// Test message texts and dates for OS Log
return [self.messageText isEqual:[object messageText]] &&
[self.date isEqualToDate:[object date]];
}
}
return NO;
}
- (NSUInteger)hash
@@ -52,4 +91,10 @@
return (NSUInteger)self.messageID;
}
- (NSString *)description
{
NSString *escaped = [self.messageText stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
return [NSString stringWithFormat:@"(%@) %@", @(self.messageText.length), escaped];
}
@end
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewCell.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewCell.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewController.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/19/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewController.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/19/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -8,17 +8,16 @@
#import "FLEXSystemLogTableViewController.h"
#import "FLEXUtility.h"
#import "FLEXSystemLogMessage.h"
#import "FLEXASLLogController.h"
#import "FLEXOSLogController.h"
#import "FLEXSystemLogTableViewCell.h"
#import <asl.h>
@interface FLEXSystemLogTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
@property (nonatomic, strong) UISearchController *searchController;
@property (nonatomic, readonly) id<FLEXLogController> logController;
@property (nonatomic, readonly) NSMutableArray<FLEXSystemLogMessage *> *logMessages;
@property (nonatomic, copy) NSArray<FLEXSystemLogMessage *> *filteredLogMessages;
@property (nonatomic, strong) NSTimer *logUpdateTimer;
@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
@end
@@ -28,68 +27,56 @@
{
[super viewDidLoad];
id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) {
self.title = @"System Log";
[self.logMessages addObjectsFromArray:newMessages];
// "Follow" the log as new messages stream in if we were previously near the bottom.
BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
[self.tableView reloadData];
if (wasNearBottom) {
[self scrollToLastRow];
}
};
_logMessages = [NSMutableArray array];
_logMessageIdentifiers = [NSMutableIndexSet indexSet];
if ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion <= 9) {
_logController = [FLEXASLLogController withUpdateHandler:logHandler];
} else {
_logController = [FLEXOSLogController withUpdateHandler:logHandler];
}
[self.tableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.title = @"Loading...";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ " style:UIBarButtonItemStylePlain target:self action:@selector(scrollToLastRow)];
UIBarButtonItem *scrollDown = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ "
style:UIBarButtonItemStylePlain
target:self
action:@selector(scrollToLastRow)];
UIBarButtonItem *settings = [[UIBarButtonItem alloc] initWithTitle:@"Settings"
style:UIBarButtonItemStylePlain
target:self
action:@selector(showLogSettings)];
if (FLEXOSLogAvailable()) {
self.navigationItem.rightBarButtonItems = @[scrollDown, settings];
} else {
self.navigationItem.rightBarButtonItem = scrollDown;
}
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.delegate = self;
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.tableView.tableHeaderView = self.searchController.searchBar;
[self updateLogMessages];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSTimeInterval updateInterval = 1.0;
#if TARGET_IPHONE_SIMULATOR
// Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
updateInterval = 5.0;
#endif
self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval target:self selector:@selector(updateLogMessages) userInfo:nil repeats:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.logUpdateTimer invalidate];
}
- (void)updateLogMessages
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<FLEXSystemLogMessage *> *newMessages = [self newLogMessagesForCurrentProcess];
if (!newMessages.count) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.title = @"System Log";
[self.logMessages addObjectsFromArray:newMessages];
for (FLEXSystemLogMessage *message in newMessages) {
[self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
}
// "Follow" the log as new messages stream in if we were previously near the bottom.
BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
[self.tableView reloadData];
if (wasNearBottom) {
[self scrollToLastRow];
}
});
});
[self.logController startMonitoring];
}
- (void)scrollToLastRow
@@ -101,6 +88,27 @@
}
}
- (void)showLogSettings
{
FLEXOSLogController *logController = (FLEXOSLogController *)self.logController;
BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
NSString *toggle = persistent ? @"Disable" : @"Enable";
NSString *title = [@"Persistent logging: " stringByAppendingString:persistent ? @"ON" : @"OFF"];
NSString *body = @"In iOS 10 and up, ASL is gone. The OS Log API is much more limited. "
"To get as close to the old behavior as possible, logs must be collected manually at launch and stored.\n\n"
"Turn this feature on only when you need it.";
UIAlertController *settings = [UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert];
[settings addAction:[UIAlertAction actionWithTitle:toggle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[[NSUserDefaults standardUserDefaults] setBool:!persistent forKey:kFLEXiOSPersistentOSLogKey];
logController.persistent = !persistent;
[logController.messages addObjectsFromArray:self.logMessages];
}]];
[settings addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:settings animated:YES completion:nil];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
@@ -110,7 +118,7 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.searchController.isActive ? [self.filteredLogMessages count] : [self.logMessages count];
return self.searchController.isActive ? self.filteredLogMessages.count : self.logMessages.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
@@ -149,9 +157,8 @@
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(copy:)) {
FLEXSystemLogMessage *logMessage = [self logMessageAtIndexPath:indexPath];
NSString *stringToCopy = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage] ?: @"";
[[UIPasteboard generalPasteboard] setString:stringToCopy];
// We usually only want to copy the log message itself, not any metadata associated with it.
[UIPasteboard generalPasteboard].string = [self logMessageAtIndexPath:indexPath].messageText;
}
}
@@ -179,59 +186,4 @@
});
}
#pragma mark - Log Message Fetching
- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
{
if (!self.logMessages.count) {
return [[self class] allLogMessagesForCurrentProcess];
}
aslresponse response = [FLEXSystemLogTableViewController ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray<FLEXSystemLogMessage *> *newMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
NSUInteger messageID = (NSUInteger)atoll(asl_get(aslMessage, ASL_KEY_MSG_ID));
if (![self.logMessageIdentifiers containsIndex:messageID]) {
[newMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
}
asl_release(response);
return newMessages;
}
+ (aslresponse)ASLMessageListForCurrentProcess
{
static NSString *pidString = nil;
if (!pidString) {
pidString = @([[NSProcessInfo processInfo] processIdentifier]).stringValue;
}
// Create system log query object.
asl_object_t query = asl_new(ASL_TYPE_QUERY);
// Filter for messages from the current process.
// Note that this appears to happen by default on device, but is required in the simulator.
asl_set_query(query, ASL_KEY_PID, pidString.UTF8String, ASL_QUERY_OP_EQUAL);
return asl_search(NULL, query);
}
+ (NSArray<FLEXSystemLogMessage *> *)allLogMessagesForCurrentProcess
{
aslresponse response = [self ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray<FLEXSystemLogMessage *> *logMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
asl_release(response);
return logMessages;
}
@end
@@ -0,0 +1,276 @@
==============================================================================
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
==============================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
==============================================================================
Software from third parties included in the LLVM Project:
==============================================================================
The LLVM Project contains third party software which is under different license
terms. All such code will be identified clearly using at least one of two
mechanisms:
1) It will be in a separate directory tree with its own `LICENSE.txt` or
`LICENSE` file at the top containing the specific license and restrictions
which apply to that software, or
2) It will contain specific license and restriction terms at the top of every
file.
==============================================================================
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
==============================================================================
University of Illinois/NCSA
Open Source License
Copyright (c) 2010 Apple Inc.
All rights reserved.
Developed by:
LLDB Team
http://lldb.llvm.org/
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.
* Neither the names of the LLDB Team, copyright holders, nor the names of
its contributors may be used to endorse or promote products derived from
this Software without specific prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.
+1 -6
View File
@@ -28,12 +28,7 @@
}
if (request.HTTPBody) {
if ([request.allHTTPHeaderFields[@"Content-Length"] intValue] < 1024) {
[curlCommandString appendFormat:@"-d \'%@\'",
[[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]];
} else {
[curlCommandString appendFormat:@"[TOO MUCH DATA TO INCLUDE]"];
}
[curlCommandString appendFormat:@"-d \'%@\'", [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]];
}
return curlCommandString;
@@ -1,22 +1,28 @@
//
// FLEXGlobalsTableViewControllerEntry.h
// UICatalog
// FLEX
//
// Created by Javier Soto on 7/26/14.
// Copyright (c) 2014 f. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class FLEXGlobalsTableViewController;
typedef NSString *(^FLEXGlobalsTableViewControllerEntryNameFuture)(void);
/// Simply return a view controller to be pushed on the navigation stack
typedef UIViewController *(^FLEXGlobalsTableViewControllerViewControllerFuture)(void);
/// Do something like present an alert, then use the host
/// view controller to present or push another view controller.
typedef void (^FLEXGlobalsTableViewControllerRowAction)(FLEXGlobalsTableViewController *host);
@interface FLEXGlobalsTableViewControllerEntry : NSObject
@property (nonatomic, readonly, copy) FLEXGlobalsTableViewControllerEntryNameFuture entryNameFuture;
@property (nonatomic, readonly, copy) FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture;
@property (nonatomic, readonly, copy) FLEXGlobalsTableViewControllerRowAction rowAction;
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture;
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture action:(FLEXGlobalsTableViewControllerRowAction)rowSelectedAction;
@end
@@ -0,0 +1,39 @@
//
// FLEXGlobalsTableViewControllerEntry.m
// FLEX
//
// Created by Javier Soto on 7/26/14.
// Copyright (c) 2014 f. All rights reserved.
//
#import "FLEXGlobalsTableViewControllerEntry.h"
@implementation FLEXGlobalsTableViewControllerEntry
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture
viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture
{
NSParameterAssert(nameFuture);
NSParameterAssert(viewControllerFuture);
FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
entry->_entryNameFuture = [nameFuture copy];
entry->_viewControllerFuture = [viewControllerFuture copy];
return entry;
}
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture
action:(FLEXGlobalsTableViewControllerRowAction)rowSelectedAction
{
NSParameterAssert(nameFuture);
NSParameterAssert(rowSelectedAction);
FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
entry->_entryNameFuture = [nameFuture copy];
entry->_rowAction = [rowSelectedAction copy];
return entry;
}
@end
@@ -1,6 +1,6 @@
//
// FLEXLayerExplorerViewController.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 12/14/14.
// Copyright (c) 2014 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXLayerExplorerViewController.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 12/14/14.
// Copyright (c) 2014 f. All rights reserved.
@@ -15,6 +15,7 @@
#import "FLEXIvarEditorViewController.h"
#import "FLEXMethodCallingViewController.h"
#import "FLEXInstancesTableViewController.h"
#import "FLEXTableView.h"
#import <objc/runtime.h>
typedef NS_ENUM(NSUInteger, FLEXObjectExplorerScope) {
@@ -90,10 +91,19 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
@implementation FLEXObjectExplorerViewController
- (id)initWithStyle:(UITableViewStyle)style
+ (void)initialize
{
// Force grouped style
return [super initWithStyle:UITableViewStyleGrouped];
if (self == [FLEXObjectExplorerViewController class]) {
// Initialize custom menu items for entire app
UIMenuItem *copyObjectAddress = [[UIMenuItem alloc] initWithTitle:@"Copy Address" action:@selector(copyObjectAddress:)];
[UIMenuController sharedMenuController].menuItems = @[copyObjectAddress];
[[UIMenuController sharedMenuController] update];
}
}
- (void)loadView
{
self.tableView = [[FLEXTableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}
- (void)viewDidLoad
@@ -134,7 +144,8 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
#pragma mark - Search
- (void)refreshScopeTitles {
- (void)refreshScopeTitles
{
if (!self.searchBar) return;
Class parent = [self.object superclass];
@@ -172,7 +183,8 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
[self updateDisplayedData];
}
- (NSArray *)metadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
- (NSArray *)metadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope
{
switch (metadataKind) {
case FLEXMetadataKindProperties:
switch (self.scope) {
@@ -221,7 +233,8 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
}
}
- (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
- (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope
{
return [self metadata:metadataKind forScope:scope].count;
}
@@ -273,20 +286,24 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
- (BOOL)shouldShowDescription
{
BOOL showDescription = YES;
// Not if it's empty or nil.
NSString *descripition = [FLEXUtility safeDescriptionForObject:self.object];
if (showDescription) {
showDescription = [descripition length] > 0;
}
// Not if we have filter text that doesn't match the desctiption.
if (showDescription && [self.filterText length] > 0) {
showDescription = [descripition rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
if (self.filterText.length) {
NSString *description = [self displayedObjectDescription];
return [description rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
}
return showDescription;
return YES;
}
- (NSString *)displayedObjectDescription {
NSString *desc = [FLEXUtility safeDescriptionForObject:self.object];
if (!desc.length) {
NSString *address = [FLEXUtility addressOfObject:self.object];
desc = [NSString stringWithFormat:@"Object at %@ returned empty description", address];
}
return desc;
}
@@ -367,7 +384,10 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
id value = nil;
if ([self canHaveInstanceState]) {
FLEXPropertyBox *propertyBox = self.filteredProperties[index];
NSString *typeString = [FLEXRuntimeUtility typeEncodingForProperty:propertyBox.property];
const FLEXTypeEncoding *encoding = [typeString cStringUsingEncoding:NSUTF8StringEncoding];
value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
}
return value;
}
@@ -449,7 +469,9 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
id value = nil;
if ([self canHaveInstanceState]) {
FLEXIvarBox *ivarBox = self.filteredIvars[index];
const FLEXTypeEncoding *encoding = ivar_getTypeEncoding(ivarBox.ivar);
value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
}
return value;
}
@@ -683,7 +705,7 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
NSString *title = nil;
switch (section) {
case FLEXObjectExplorerSectionDescription:
title = [FLEXUtility safeDescriptionForObject:self.object];
title = [self displayedObjectDescription];
break;
case FLEXObjectExplorerSectionCustom:
@@ -803,19 +825,9 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
return canDrillIn;
}
- (BOOL)canCopyRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
- (BOOL)sectionHasActions:(NSInteger)section
{
BOOL canCopy = NO;
switch (section) {
case FLEXObjectExplorerSectionDescription:
canCopy = YES;
break;
default:
break;
}
return canCopy;
return [self explorerSectionAtIndex:section] == FLEXObjectExplorerSectionDescription;
}
- (NSString *)titleForExplorerSection:(FLEXObjectExplorerSection)section
@@ -1014,45 +1026,65 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
BOOL canCopy = [self canCopyRow:indexPath.row inExplorerSection:explorerSection];
return canCopy;
return [self sectionHasActions:indexPath.section];
}
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
BOOL canPerformAction = NO;
if (action == @selector(copy:)) {
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
BOOL canCopy = [self canCopyRow:indexPath.row inExplorerSection:explorerSection];
canPerformAction = canCopy;
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
switch (explorerSection) {
case FLEXObjectExplorerSectionDescription:
return action == @selector(copy:) || action == @selector(copyObjectAddress:);
default:
return NO;
}
return canPerformAction;
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(copy:)) {
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
NSString *stringToCopy = @"";
NSString *title = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
if ([title length] > 0) {
stringToCopy = [stringToCopy stringByAppendingString:title];
}
NSString *subtitle = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
if ([subtitle length] > 0) {
if ([stringToCopy length] > 0) {
stringToCopy = [stringToCopy stringByAppendingString:@"\n\n"];
}
stringToCopy = [stringToCopy stringByAppendingString:subtitle];
}
[[UIPasteboard generalPasteboard] setString:stringToCopy];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:action withObject:indexPath];
#pragma clang diagnostic pop
}
#pragma mark - UIMenuController
/// Prevent the search bar from trying to use us as a responder
///
/// Our table cells will use the UITableViewDelegate methods
/// to make sure we can perform the actions we want to
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
}
- (void)copy:(NSIndexPath *)indexPath
{
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
NSString *stringToCopy = @"";
NSString *title = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
if (title.length) {
stringToCopy = [stringToCopy stringByAppendingString:title];
}
NSString *subtitle = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
if (subtitle.length) {
if (stringToCopy.length) {
stringToCopy = [stringToCopy stringByAppendingString:@"\n\n"];
}
stringToCopy = [stringToCopy stringByAppendingString:subtitle];
}
[UIPasteboard generalPasteboard].string = stringToCopy;
}
- (void)copyObjectAddress:(NSIndexPath *)indexPath
{
[UIPasteboard generalPasteboard].string = [FLEXUtility addressOfObject:self.object];
}
@@ -1,25 +0,0 @@
//
// FLEXGlobalsTableViewControllerEntry.m
// UICatalog
//
// Created by Javier Soto on 7/26/14.
// Copyright (c) 2014 f. All rights reserved.
//
#import "FLEXGlobalsTableViewControllerEntry.h"
@implementation FLEXGlobalsTableViewControllerEntry
+ (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture
{
NSParameterAssert(nameFuture);
NSParameterAssert(viewControllerFuture);
FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
entry->_entryNameFuture = [nameFuture copy];
entry->_viewControllerFuture = [viewControllerFuture copy];
return entry;
}
@end
@@ -1,16 +1,16 @@
//
// FLEXMultilineTableViewCell.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 2/13/15.
// Copyright (c) 2015 f. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXTableViewCell.h"
extern NSString *const kFLEXMultilineTableViewCellIdentifier;
@interface FLEXMultilineTableViewCell : UITableViewCell
@interface FLEXMultilineTableViewCell : FLEXTableViewCell
+ (CGFloat)preferredHeightWithAttributedText:(NSAttributedString *)attributedText inTableViewWidth:(CGFloat)tableViewWidth style:(UITableViewStyle)style showsAccessory:(BOOL)showsAccessory;
@@ -1,6 +1,6 @@
//
// FLEXMultilineTableViewCell.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 2/13/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -0,0 +1,17 @@
//
// FLEXSubtitleTableViewCell.h
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FLEXSubtitleTableViewCell : UITableViewCell
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,18 @@
//
// FLEXSubtitleTableViewCell.m
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXSubtitleTableViewCell.h"
@implementation FLEXSubtitleTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
return [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
}
@end
@@ -0,0 +1,17 @@
//
// FLEXTableView.h
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FLEXTableView : UITableView
@property (nonatomic, readonly) NSString *defaultReuseIdentifier;
@property (nonatomic, readonly) NSString *subtitleReuseIdentifier;
@property (nonatomic, readonly) NSString *multilineReuseIdentifier;
@end
@@ -0,0 +1,47 @@
//
// FLEXTableView.m
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXTableView.h"
#import "FLEXSubtitleTableViewCell.h"
#import "FLEXMultilineTableViewCell.h"
@implementation FLEXTableView
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{ self = [super initWithFrame:frame style:style];
if (self) {
[self registerCells:@{
self.defaultReuseIdentifier: [FLEXTableViewCell class],
self.subtitleReuseIdentifier: [FLEXSubtitleTableViewCell class],
self.multilineReuseIdentifier: [FLEXMultilineTableViewCell class],
}];
}
return self;
}
- (void)registerCells:(NSDictionary<NSString*,Class> *)registrationMapping
{ [registrationMapping enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, Class cellClass, BOOL *stop) {
[self registerClass:cellClass forCellReuseIdentifier:identifier];
}];
}
- (NSString *)defaultReuseIdentifier
{ return @"kFLEXTableViewCellIdentifier";
}
- (NSString *)subtitleReuseIdentifier
{ return @"kFLEXSubtitleTableViewCellIdentifier";
}
- (NSString *)multilineReuseIdentifier
{
return kFLEXMultilineTableViewCellIdentifier;
}
@end
@@ -0,0 +1,13 @@
//
// FLEXTableViewCell.h
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FLEXTableViewCell : UITableViewCell
@end
@@ -0,0 +1,77 @@
//
// FLEXTableViewCell.m
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXTableViewCell.h"
#import "FLEXUtility.h"
#import "FLEXTableView.h"
@interface UITableView (Internal)
// Exists at least since iOS 5
- (BOOL)_canPerformAction:(SEL)action forCell:(UITableViewCell *)cell sender:(id)sender;
- (void)_performAction:(SEL)action forCell:(UITableViewCell *)cell sender:(id)sender;
@end
@interface UITableViewCell (Internal)
// Exists at least since iOS 5
@property (nonatomic, readonly) FLEXTableView *_tableView;
@end
@implementation FLEXTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
self.textLabel.font = cellFont;
self.detailTextLabel.font = cellFont;
self.detailTextLabel.textColor = [UIColor grayColor];
}
return self;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return [self._tableView _canPerformAction:action forCell:self sender:sender];
}
/// We use this to allow our table view to allow its delegate
/// to handle any action it chooses to support, without
/// explicitly implementing the method ourselelves.
///
/// Alternative considered: override respondsToSelector
/// to return NO. I decided against this for simplicity's
/// sake. I see this as "fixing" a poorly designed API.
/// That approach would require lots of boilerplate to
/// make the menu appear above this cell.
- (void)forwardInvocation:(NSInvocation *)invocation
{
// Must be unretained to avoid over-releasing
__unsafe_unretained id sender;
[invocation getArgument:&sender atIndex:2];
SEL action = invocation.selector;
// [self._tableView _performAction:action forCell:[self retain] sender:[sender retain]];
invocation.selector = @selector(_performAction:forCell:sender:);
[invocation setArgument:&action atIndex:2];
[invocation setArgument:(void *)&self atIndex:3];
[invocation setArgument:(void *)&sender atIndex:4];
[invocation invokeWithTarget:self._tableView];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
if ([self canPerformAction:selector withSender:nil]) {
return [self._tableView methodSignatureForSelector:@selector(_performAction:forCell:sender:)];
}
return [super methodSignatureForSelector:selector];
}
@end
+367
View File
@@ -0,0 +1,367 @@
APPLE PUBLIC SOURCE LICENSE
Version 2.0 - August 6, 2003
Please read this License carefully before downloading this software.
By downloading or using this software, you are agreeing to be bound by
the terms of this License. If you do not or cannot agree to the terms
of this License, please do not download or use the software.
1. General; Definitions. This License applies to any program or other
work which Apple Computer, Inc. ("Apple") makes publicly available and
which contains a notice placed by Apple identifying such program or
work as "Original Code" and stating that it is subject to the terms of
this Apple Public Source License version 2.0 ("License"). As used in
this License:
1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is
the grantor of rights, (i) claims of patents that are now or hereafter
acquired, owned by or assigned to Apple and (ii) that cover subject
matter contained in the Original Code, but only to the extent
necessary to use, reproduce and/or distribute the Original Code
without infringement; and (b) in the case where You are the grantor of
rights, (i) claims of patents that are now or hereafter acquired,
owned by or assigned to You and (ii) that cover subject matter in Your
Modifications, taken alone or in combination with Original Code.
1.2 "Contributor" means any person or entity that creates or
contributes to the creation of Modifications.
1.3 "Covered Code" means the Original Code, Modifications, the
combination of Original Code and any Modifications, and/or any
respective portions thereof.
1.4 "Externally Deploy" means: (a) to sublicense, distribute or
otherwise make Covered Code available, directly or indirectly, to
anyone other than You; and/or (b) to use Covered Code, alone or as
part of a Larger Work, in any way to provide a service, including but
not limited to delivery of content, through electronic communication
with a client other than You.
1.5 "Larger Work" means a work which combines Covered Code or portions
thereof with code not governed by the terms of this License.
1.6 "Modifications" mean any addition to, deletion from, and/or change
to, the substance and/or structure of the Original Code, any previous
Modifications, the combination of Original Code and any previous
Modifications, and/or any respective portions thereof. When code is
released as a series of files, a Modification is: (a) any addition to
or deletion from the contents of a file containing Covered Code;
and/or (b) any new file or other representation of computer program
statements that contains any part of Covered Code.
1.7 "Original Code" means (a) the Source Code of a program or other
work as originally made available by Apple under this License,
including the Source Code of any updates or upgrades to such programs
or works made available by Apple under this License, and that has been
expressly identified by Apple as such in the header file(s) of such
work; and (b) the object code compiled from such Source Code and
originally made available by Apple under this License.
1.8 "Source Code" means the human readable form of a program or other
work that is suitable for making modifications to it, including all
modules it contains, plus any associated interface definition files,
scripts used to control compilation and installation of an executable
(object code).
1.9 "You" or "Your" means an individual or a legal entity exercising
rights under this License. For legal entities, "You" or "Your"
includes any entity which controls, is controlled by, or is under
common control with, You, where "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of fifty percent
(50%) or more of the outstanding shares or beneficial ownership of
such entity.
2. Permitted Uses; Conditions & Restrictions. Subject to the terms
and conditions of this License, Apple hereby grants You, effective on
the date You accept this License and download the Original Code, a
world-wide, royalty-free, non-exclusive license, to the extent of
Apple's Applicable Patent Rights and copyrights covering the Original
Code, to do the following:
2.1 Unmodified Code. You may use, reproduce, display, perform,
internally distribute within Your organization, and Externally Deploy
verbatim, unmodified copies of the Original Code, for commercial or
non-commercial purposes, provided that in each instance:
(a) You must retain and reproduce in all copies of Original Code the
copyright and other proprietary notices and disclaimers of Apple as
they appear in the Original Code, and keep intact all notices in the
Original Code that refer to this License; and
(b) You must include a copy of this License with every copy of Source
Code of Covered Code and documentation You distribute or Externally
Deploy, and You may not offer or impose any terms on such Source Code
that alter or restrict this License or the recipients' rights
hereunder, except as permitted under Section 6.
2.2 Modified Code. You may modify Covered Code and use, reproduce,
display, perform, internally distribute within Your organization, and
Externally Deploy Your Modifications and Covered Code, for commercial
or non-commercial purposes, provided that in each instance You also
meet all of these conditions:
(a) You must satisfy all the conditions of Section 2.1 with respect to
the Source Code of the Covered Code;
(b) You must duplicate, to the extent it does not already exist, the
notice in Exhibit A in each file of the Source Code of all Your
Modifications, and cause the modified files to carry prominent notices
stating that You changed the files and the date of any change; and
(c) If You Externally Deploy Your Modifications, You must make
Source Code of all Your Externally Deployed Modifications either
available to those to whom You have Externally Deployed Your
Modifications, or publicly available. Source Code of Your Externally
Deployed Modifications must be released under the terms set forth in
this License, including the license grants set forth in Section 3
below, for as long as you Externally Deploy the Covered Code or twelve
(12) months from the date of initial External Deployment, whichever is
longer. You should preferably distribute the Source Code of Your
Externally Deployed Modifications electronically (e.g. download from a
web site).
2.3 Distribution of Executable Versions. In addition, if You
Externally Deploy Covered Code (Original Code and/or Modifications) in
object code, executable form only, You must include a prominent
notice, in the code itself as well as in related documentation,
stating that Source Code of the Covered Code is available under the
terms of this License with information on how and where to obtain such
Source Code.
2.4 Third Party Rights. You expressly acknowledge and agree that
although Apple and each Contributor grants the licenses to their
respective portions of the Covered Code set forth herein, no
assurances are provided by Apple or any Contributor that the Covered
Code does not infringe the patent or other intellectual property
rights of any other entity. Apple and each Contributor disclaim any
liability to You for claims brought by any other entity based on
infringement of intellectual property rights or otherwise. As a
condition to exercising the rights and licenses granted hereunder, You
hereby assume sole responsibility to secure any other intellectual
property rights needed, if any. For example, if a third party patent
license is required to allow You to distribute the Covered Code, it is
Your responsibility to acquire that license before distributing the
Covered Code.
3. Your Grants. In consideration of, and as a condition to, the
licenses granted to You under this License, You hereby grant to any
person or entity receiving or distributing Covered Code under this
License a non-exclusive, royalty-free, perpetual, irrevocable license,
under Your Applicable Patent Rights and other intellectual property
rights (other than patent) owned or controlled by You, to use,
reproduce, display, perform, modify, sublicense, distribute and
Externally Deploy Your Modifications of the same scope and extent as
Apple's licenses under Sections 2.1 and 2.2 above.
4. Larger Works. You may create a Larger Work by combining Covered
Code with other code not governed by the terms of this License and
distribute the Larger Work as a single product. In each such instance,
You must make sure the requirements of this License are fulfilled for
the Covered Code or any portion thereof.
5. Limitations on Patent License. Except as expressly stated in
Section 2, no other patent rights, express or implied, are granted by
Apple herein. Modifications and/or Larger Works may require additional
patent licenses from Apple which Apple may grant in its sole
discretion.
6. Additional Terms. You may choose to offer, and to charge a fee for,
warranty, support, indemnity or liability obligations and/or other
rights consistent with the scope of the license granted herein
("Additional Terms") to one or more recipients of Covered Code.
However, You may do so only on Your own behalf and as Your sole
responsibility, and not on behalf of Apple or any Contributor. You
must obtain the recipient's agreement that any such Additional Terms
are offered by You alone, and You hereby agree to indemnify, defend
and hold Apple and every Contributor harmless for any liability
incurred by or claims asserted against Apple or such Contributor by
reason of any such Additional Terms.
7. Versions of the License. Apple may publish revised and/or new
versions of this License from time to time. Each version will be given
a distinguishing version number. Once Original Code has been published
under a particular version of this License, You may continue to use it
under the terms of that version. You may also choose to use such
Original Code under the terms of any subsequent version of this
License published by Apple. No one other than Apple has the right to
modify the terms applicable to Covered Code created under this
License.
8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in
part pre-release, untested, or not fully tested works. The Covered
Code may contain errors that could cause failures or loss of data, and
may be incomplete or contain inaccuracies. You expressly acknowledge
and agree that use of the Covered Code, or any portion thereof, is at
Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND
WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND
APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE
PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM
ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF
MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR
PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD
PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST
INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE
FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS,
THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR
ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO
ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE
AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY.
You acknowledge that the Covered Code is not intended for use in the
operation of nuclear facilities, aircraft navigation, communication
systems, or air traffic control machines in which case the failure of
the Covered Code could lead to death, personal injury, or severe
physical or environmental damage.
9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO
EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL,
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING
TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR
ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY,
TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF
APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY
REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF
INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY
TO YOU. In no event shall Apple's total liability to You for all
damages (other than as may be required by applicable law) under this
License exceed the amount of fifty dollars ($50.00).
10. Trademarks. This License does not grant any rights to use the
trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS",
"QuickTime", "QuickTime Streaming Server" or any other trademarks,
service marks, logos or trade names belonging to Apple (collectively
"Apple Marks") or to any trademark, service mark, logo or trade name
belonging to any Contributor. You agree not to use any Apple Marks in
or as part of the name of products derived from the Original Code or
to endorse or promote products derived from the Original Code other
than as expressly permitted by and in strict compliance at all times
with Apple's third party trademark usage guidelines which are posted
at http://www.apple.com/legal/guidelinesfor3rdparties.html.
11. Ownership. Subject to the licenses granted under this License,
each Contributor retains all rights, title and interest in and to any
Modifications made by such Contributor. Apple retains all rights,
title and interest in and to the Original Code and any Modifications
made by or on behalf of Apple ("Apple Modifications"), and such Apple
Modifications will not be automatically subject to this License. Apple
may, at its sole discretion, choose to license such Apple
Modifications under this License, or on different terms from those
contained in this License or may choose not to license them at all.
12. Termination.
12.1 Termination. This License and the rights granted hereunder will
terminate:
(a) automatically without notice from Apple if You fail to comply with
any term(s) of this License and fail to cure such breach within 30
days of becoming aware of such breach;
(b) immediately in the event of the circumstances described in Section
13.5(b); or
(c) automatically without notice from Apple if You, at any time during
the term of this License, commence an action for patent infringement
against Apple; provided that Apple did not first commence
an action for patent infringement against You in that instance.
12.2 Effect of Termination. Upon termination, You agree to immediately
stop any further use, reproduction, modification, sublicensing and
distribution of the Covered Code. All sublicenses to the Covered Code
which have been properly granted prior to termination shall survive
any termination of this License. Provisions which, by their nature,
should remain in effect beyond the termination of this License shall
survive, including but not limited to Sections 3, 5, 8, 9, 10, 11,
12.2 and 13. No party will be liable to any other for compensation,
indemnity or damages of any sort solely as a result of terminating
this License in accordance with its terms, and termination of this
License will be without prejudice to any other right or remedy of
any party.
13. Miscellaneous.
13.1 Government End Users. The Covered Code is a "commercial item" as
defined in FAR 2.101. Government software and technical data rights in
the Covered Code include only those rights customarily provided to the
public as defined in this License. This customary commercial license
in technical data and software is provided in accordance with FAR
12.211 (Technical Data) and 12.212 (Computer Software) and, for
Department of Defense purchases, DFAR 252.227-7015 (Technical Data --
Commercial Items) and 227.7202-3 (Rights in Commercial Computer
Software or Computer Software Documentation). Accordingly, all U.S.
Government End Users acquire Covered Code with only those rights set
forth herein.
13.2 Relationship of Parties. This License will not be construed as
creating an agency, partnership, joint venture or any other form of
legal association between or among You, Apple or any Contributor, and
You will not represent to the contrary, whether expressly, by
implication, appearance or otherwise.
13.3 Independent Development. Nothing in this License will impair
Apple's right to acquire, license, develop, have others develop for
it, market and/or distribute technology or products that perform the
same or similar functions as, or otherwise compete with,
Modifications, Larger Works, technology or products that You may
develop, produce, market or distribute.
13.4 Waiver; Construction. Failure by Apple or any Contributor to
enforce any provision of this License will not be deemed a waiver of
future enforcement of that or any other provision. Any law or
regulation which provides that the language of a contract shall be
construed against the drafter will not apply to this License.
13.5 Severability. (a) If for any reason a court of competent
jurisdiction finds any provision of this License, or portion thereof,
to be unenforceable, that provision of the License will be enforced to
the maximum extent permissible so as to effect the economic benefits
and intent of the parties, and the remainder of this License will
continue in full force and effect. (b) Notwithstanding the foregoing,
if applicable law prohibits or restricts You from fully and/or
specifically complying with Sections 2 and/or 3 or prevents the
enforceability of either of those Sections, this License will
immediately terminate and You must immediately discontinue any use of
the Covered Code and destroy all copies of it that are in your
possession or control.
13.6 Dispute Resolution. Any litigation or other dispute resolution
between You and Apple relating to this License shall take place in the
Northern District of California, and You and Apple hereby consent to
the personal jurisdiction of, and venue in, the state and federal
courts within that District with respect to this License. The
application of the United Nations Convention on Contracts for the
International Sale of Goods is expressly excluded.
13.7 Entire Agreement; Governing Law. This License constitutes the
entire agreement between the parties with respect to the subject
matter hereof. This License shall be governed by the laws of the
United States and the State of California, except that body of
California law concerning conflicts of law.
Where You are located in the province of Quebec, Canada, the following
clause applies: The parties hereby confirm that they have requested
that this License and all related documents be drafted in English. Les
parties ont exige que le present contrat et tous les documents
connexes soient rediges en anglais.
EXHIBIT A.
"Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
Reserved.
This file contains Original Code and/or Modifications of Original Code
as defined in and that are subject to the Apple Public Source License
Version 2.0 (the 'License'). You may not use this file except in
compliance with the License. Please obtain a copy of the License at
http://www.opensource.apple.com/apsl/ and read it before using this
file.
The Original Code and all software distributed under the License are
distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
Please see the License for the specific language governing rights and
limitations under the License."
+13 -7
View File
@@ -73,6 +73,9 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
malloc_introspection_t *introspection = zone->introspect;
NSString *zoneName = @(zone->zone_name);
// We only need to look at the default malloc zone.
// This may explain why some zone functions are
// sometimes invalid; perhaps not all zones support them?
if (![zoneName isEqualToString:@"DefaultMallocZone"] || !introspection) {
continue;
}
@@ -90,20 +93,23 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
// The largest realistic memory address varies by platform.
// Only 48 bits are used by 64 bit machines while
// 32 bit machines use all bits.
#if __arm64__
static uintptr_t MAX_REALISTIC_ADDRESS = 0xFFFFFFFFFFFF;
//
// __LP64__ is defined as 1 for both arm64 and x86_64
// via: clang -dM -arch [arm64|x86_64] -E -x c /dev/null | grep LP
#if __LP64__
static uintptr_t MAX_REALISTIC_ADDRESS = 0x0000FFFFFFFFFFFF;
BOOL lockZoneValid = lock_zone != nil && (uintptr_t)lock_zone < MAX_REALISTIC_ADDRESS;
BOOL unlockZoneValid = unlock_zone != nil && (uintptr_t)unlock_zone < MAX_REALISTIC_ADDRESS;
#else
static uintptr_t MAX_REALISTIC_ADDRESS = INT_MAX;
BOOL lockZoneValid = lock_zone != nil;
BOOL unlockZoneValid = unlock_zone != nil;
#endif
// There is little documentation on when and why
// any of these function pointers might be NULL
// or garbage, so we resort to checking for NULL
// and impossible memory addresses at least
if (lock_zone && unlock_zone &&
introspection->enumerator &&
(uintptr_t)lock_zone < MAX_REALISTIC_ADDRESS &&
(uintptr_t)unlock_zone < MAX_REALISTIC_ADDRESS) {
if (introspection->enumerator && lockZoneValid && unlockZoneValid) {
lock_zone(zone);
introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
unlock_zone(zone);
@@ -1,6 +1,6 @@
//
// FLEXKeyboardHelpViewController.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 9/19/15.
// Copyright © 2015 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXKeyboardHelpViewController.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 9/19/15.
// Copyright © 2015 f. All rights reserved.
+23
View File
@@ -0,0 +1,23 @@
//
// FLEXObjcInternal.h
// FLEX
//
// Created by Tanner Bennett on 11/1/18.
//
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
/// @brief Assumes memory is valid and readable.
/// @discussion objc-internal.h, objc-private.h, and objc-config.h
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
/// http://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
BOOL FLEXPointerIsValidObjcObject(const void * ptr);
#ifdef __cplusplus
}
#endif
+188
View File
@@ -0,0 +1,188 @@
//
// FLEXObjcInternal.mm
// FLEX
//
// Created by Tanner Bennett on 11/1/18.
//
/*
* Copyright (c) 2005-2007 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#import "FLEXObjcInternal.h"
#import <objc/runtime.h>
// For malloc_size
#import <malloc/malloc.h>
// For vm_region_64
#include <mach/mach.h>
#define ALWAYS_INLINE inline __attribute__((always_inline))
#define NEVER_INLINE inline __attribute__((noinline))
// The macros below are copied straight from
// objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with
// as few modifications as possible. Changes are noted in boxed comments.
// https://opensource.apple.com/source/objc4/objc4-723/
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html
/////////////////////
// objc-internal.h //
/////////////////////
#if __LP64__
#define OBJC_HAVE_TAGGED_POINTERS 1
#endif
#if OBJC_HAVE_TAGGED_POINTERS
#if TARGET_OS_OSX && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
# define _OBJC_TAG_EXT_MASK (0xfUL<<60)
#else
# define _OBJC_TAG_MASK 1UL
# define _OBJC_TAG_EXT_MASK 0xfUL
#endif
//////////////////////////////////////
// originally _objc_isTaggedPointer //
//////////////////////////////////////
static BOOL flex_isTaggedPointer(const void *ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
///////////////////
// objc-object.h //
///////////////////
////////////////////////////////////////////////
// originally objc_object::isExtTaggedPointer //
////////////////////////////////////////////////
static BOOL flex_isExtTaggedPointer(const void *ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK;
}
#endif
/////////////////////////////////////
// FLEXObjectInternal //
// No Apple code beyond this point //
/////////////////////////////////////
extern "C" {
static BOOL FLEXPointerIsReadable(const void *inPtr)
{
kern_return_t error = KERN_SUCCESS;
vm_size_t vmsize;
vm_address_t address = (vm_address_t)inPtr;
vm_region_basic_info_data_t info;
mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
memory_object_name_t object;
error = vm_region_64(
mach_task_self(),
&address,
&vmsize,
VM_REGION_BASIC_INFO,
(vm_region_info_t)&info,
&info_count,
&object
);
if (error != KERN_SUCCESS) {
// vm_region/vm_region_64 returned an error
return NO;
} else if (!(BOOL)(info.protection & VM_PROT_READ)) {
return NO;
}
// Read the memory
vm_offset_t readMem = 0;
mach_msg_type_number_t size = 0;
address = (vm_address_t)inPtr;
error = vm_read(mach_task_self(), address, sizeof(uintptr_t), &readMem, &size);
if (error != KERN_SUCCESS) {
// vm_read returned an error
return NO;
}
return YES;
}
/// Accepts addresses that may or may not be readable.
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
BOOL FLEXPointerIsValidObjcObject(const void *ptr)
{
uintptr_t pointer = (uintptr_t)ptr;
if (!ptr) {
return NO;
}
#if OBJC_HAVE_TAGGED_POINTERS
// Tagged pointers have 0x1 set, no other valid pointers do
// objc-internal.h -> _objc_isTaggedPointer()
if (flex_isTaggedPointer(ptr) || flex_isExtTaggedPointer(ptr)) {
return YES;
}
#endif
// Check pointer alignment
if ((pointer % sizeof(uintptr_t)) != 0) {
return NO;
}
// From LLDB:
// Pointers in a class_t will only have bits 0 through 46 set,
// so if any pointer has bits 47 through 63 high, we know that this is not a valid isa
// http://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
if ((pointer & 0xFFFF800000000000) != 0) {
return NO;
}
// Make sure dereferencing this address won't crash
if (!FLEXPointerIsReadable(ptr)) {
return NO;
}
// http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html :
if (!object_getClass((__bridge id)ptr)) {
return NO;
}
return YES;
}
}
+35
View File
@@ -25,10 +25,44 @@ extern NSString *const kFLEXUtilityAttributeWeak;
extern NSString *const kFLEXUtilityAttributeGarbageCollectable;
extern NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding;
typedef NS_ENUM(char, FLEXTypeEncoding)
{
FLEXTypeEncodingUnknown = '?',
FLEXTypeEncodingChar = 'c',
FLEXTypeEncodingInt = 'i',
FLEXTypeEncodingShort = 's',
FLEXTypeEncodingLong = 'l',
FLEXTypeEncodingLongLong = 'q',
FLEXTypeEncodingUnsignedChar = 'C',
FLEXTypeEncodingUnsignedInt = 'I',
FLEXTypeEncodingUnsignedShort = 'S',
FLEXTypeEncodingUnsignedLong = 'L',
FLEXTypeEncodingUnsignedLongLong = 'Q',
FLEXTypeEncodingFloat = 'f',
FLEXTypeEncodingDouble = 'd',
FLEXTypeEncodingCBool = 'B',
FLEXTypeEncodingVoid = 'v',
FLEXTypeEncodingCString = '*',
FLEXTypeEncodingObjcObject = '@',
FLEXTypeEncodingObjcClass = '#',
FLEXTypeEncodingSelector = ':',
FLEXTypeEncodingArray = '[',
FLEXTypeEncodingStruct = '{',
FLEXTypeEncodingUnion = '(',
FLEXTypeEncodingBitField = 'b',
FLEXTypeEncodingPointer = '^',
FLEXTypeEncodingConst = 'r'
};
#define FLEXEncodeClass(class) ("@\"" #class "\"")
@interface FLEXRuntimeUtility : NSObject
// General Helpers
+ (BOOL)pointerIsValidObjcObject:(const void *)pointer;
/// Unwraps raw pointers to objects stored in NSValue, and re-boxes C strings into NSStrings.
+ (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType;
// Property Helpers
+ (NSString *)prettyNameForProperty:(objc_property_t)property;
+ (NSString *)typeEncodingForProperty:(objc_property_t)property;
@@ -47,6 +81,7 @@ extern NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding;
// Method Helpers
+ (NSString *)prettyNameForMethod:(Method)method isClassMethod:(BOOL)isClassMethod;
+ (NSArray *)prettyArgumentComponentsForMethod:(Method)method;
+ (FLEXTypeEncoding *)returnTypeForMethod:(Method)method;
// Method Calling/Field Editing
+ (id)performSelector:(SEL)selector onObject:(id)object withArguments:(NSArray *)arguments error:(NSError * __autoreleasing *)error;
+89 -31
View File
@@ -8,6 +8,7 @@
#import <UIKit/UIKit.h>
#import "FLEXRuntimeUtility.h"
#import "FLEXObjcInternal.h"
// See https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
NSString *const kFLEXUtilityAttributeTypeEncoding = @"T";
@@ -36,6 +37,57 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
@implementation FLEXRuntimeUtility
#pragma mark - General Helpers (Public)
+ (BOOL)pointerIsValidObjcObject:(const void *)pointer
{
return FLEXPointerIsValidObjcObject(pointer);
}
+ (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType
{
if (!returnedObjectOrNil) {
return nil;
}
NSInteger i = 0;
if (returnType[i] == FLEXTypeEncodingConst) {
i++;
}
BOOL returnsObjectOrClass = returnType[i] == FLEXTypeEncodingObjcObject ||
returnType[i] == FLEXTypeEncodingObjcClass;
BOOL returnsVoidPointer = returnType[i] == FLEXTypeEncodingPointer &&
returnType[i+1] == FLEXTypeEncodingVoid;
BOOL returnsCString = returnType[i] == FLEXTypeEncodingCString;
// If we got back an NSValue and the return type is not an object,
// we check to see if the pointer is of a valid object. If not,
// we just display the NSValue.
if (!returnsObjectOrClass) {
// Can only be NSValue since return type is not an object,
// so we bail if this doesn't add up
if (![returnedObjectOrNil isKindOfClass:[NSValue class]]) {
return returnedObjectOrNil;
}
NSValue *value = (NSValue *)returnedObjectOrNil;
if (returnsCString) {
// Wrap char * in NSString
const char *string = (const char *)value.pointerValue;
returnedObjectOrNil = [NSString stringWithCString:string encoding:NSUTF8StringEncoding];
} else if (returnsVoidPointer) {
// Cast valid objects disguised as void * to id
if ([FLEXRuntimeUtility pointerIsValidObjcObject:value.pointerValue]) {
returnedObjectOrNil = (__bridge id)value.pointerValue;
}
}
}
return returnedObjectOrNil;
}
#pragma mark - Property Helpers (Public)
+ (NSString *)prettyNameForProperty:(objc_property_t)property
@@ -285,7 +337,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
// this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods
if (selectorComponents.count == 1) {
return [selectorComponents copy];
return @[];
}
if ([selectorComponents.lastObject isEqualToString:@""]) {
@@ -303,6 +355,11 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
return components;
}
+ (FLEXTypeEncoding *)returnTypeForMethod:(Method)method
{
return (FLEXTypeEncoding *)method_copyReturnType(method);
}
#pragma mark - Method Calling/Field Editing (Public)
@@ -353,66 +410,67 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
return nil;
}
NSUInteger bufferSize = 0;
@try {
NSUInteger bufferSize = 0;
// NSGetSizeAndAlignment barfs on type encoding for bitfields.
NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
if (bufferSize > 0) {
void *buffer = calloc(bufferSize, 1);
[argumentValue getValue:buffer];
[invocation setArgument:buffer atIndex:argumentIndex];
free(buffer);
}
} @catch (NSException *exception) { }
if (bufferSize > 0) {
void *buffer = calloc(bufferSize, 1);
[argumentValue getValue:buffer];
[invocation setArgument:buffer atIndex:argumentIndex];
free(buffer);
}
}
}
}
// Try to invoke the invocation but guard against an exception being thrown.
BOOL successfullyInvoked = NO;
id returnObject = nil;
@try {
// Some methods are not fit to be called...
// Looking at you -[UIResponder(UITextInputAdditions) _caretRect]
[invocation invoke];
successfullyInvoked = YES;
// Retreive the return value and box if necessary.
const char *returnType = [methodSignature methodReturnType];
if (returnType[0] == @encode(id)[0] || returnType[0] == @encode(Class)[0]) {
// Return value is an object.
__unsafe_unretained id objectReturnedFromMethod = nil;
[invocation getReturnValue:&objectReturnedFromMethod];
returnObject = objectReturnedFromMethod;
} else if (returnType[0] != @encode(void)[0]) {
// Will use arbitrary buffer for return value and box it.
void *returnValue = malloc([methodSignature methodReturnLength]);
if (returnValue) {
[invocation getReturnValue:returnValue];
returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
free(returnValue);
}
}
} @catch (NSException *exception) {
// Bummer...
if (error) {
// "… on <class>" / "… on instance of <class>"
NSString *class = NSStringFromClass([object class]);
NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class];
NSString *message = [NSString stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@",
exception.name,
NSStringFromSelector(selector),
calledOn,
exception.reason];
*error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain
code:FLEXRuntimeUtilityErrorCodeInvocationFailed
userInfo:@{ NSLocalizedDescriptionKey : message }];
}
}
// Retreive the return value and box if necessary.
id returnObject = nil;
if (successfullyInvoked) {
const char *returnType = [methodSignature methodReturnType];
if (returnType[0] == @encode(id)[0] || returnType[0] == @encode(Class)[0]) {
__unsafe_unretained id objectReturnedFromMethod = nil;
[invocation getReturnValue:&objectReturnedFromMethod];
returnObject = objectReturnedFromMethod;
} else if (returnType[0] != @encode(void)[0]) {
void *returnValue = malloc([methodSignature methodReturnLength]);
if (returnValue) {
[invocation getReturnValue:returnValue];
returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
free(returnValue);
}
}
}
return returnObject;
}
+3
View File
@@ -34,6 +34,7 @@
+ (NSString *)applicationImageName;
+ (NSString *)applicationName;
+ (NSString *)safeDescriptionForObject:(id)object;
+ (NSString *)addressOfObject:(id)object;
+ (UIFont *)defaultFontOfSize:(CGFloat)size;
+ (UIFont *)defaultTableViewCellLabelFont;
+ (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString;
@@ -51,6 +52,8 @@
+ (NSArray<UIWindow *> *)allWindows;
+ (void)alert:(NSString *)title message:(NSString *)message from:(UIViewController *)viewController;
// Swizzling utilities
+ (SEL)swizzledSelectorForSelector:(SEL)selector;
+19 -4
View File
@@ -48,7 +48,7 @@
+ (UIViewController *)viewControllerForView:(UIView *)view
{
UIViewController *viewController = nil;
SEL viewDelSel = NSSelectorFromString([NSString stringWithFormat:@"%@ewDelegate", @"_vi"]);
SEL viewDelSel = NSSelectorFromString(@"_viewDelegate");
if ([view respondsToSelector:viewDelSel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
@@ -58,7 +58,8 @@
return viewController;
}
+ (UIViewController *)viewControllerForAncestralView:(UIView *)view{
+ (UIViewController *)viewControllerForAncestralView:(UIView *)view
{
UIViewController *viewController = nil;
SEL viewDelSel = NSSelectorFromString([NSString stringWithFormat:@"%@ewControllerForAncestor", @"_vi"]);
if ([view respondsToSelector:viewDelSel]) {
@@ -126,6 +127,11 @@
return description;
}
+ (NSString *)addressOfObject:(id)object
{
return [NSString stringWithFormat:@"%p", object];
}
+ (UIFont *)defaultFontOfSize:(CGFloat)size
{
return [UIFont fontWithName:@"HelveticaNeue" size:size];
@@ -249,7 +255,8 @@
return httpResponseString;
}
+ (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response {
+ (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response
{
NSIndexSet *errorStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(400, 200)];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
@@ -260,7 +267,6 @@
return NO;
}
+ (NSDictionary<NSString *, id> *)dictionaryFromQuery:(NSString *)query
{
NSMutableDictionary<NSString *, id> *queryDictionary = [NSMutableDictionary dictionary];
@@ -372,6 +378,15 @@
return windows;
}
+ (void)alert:(NSString *)title message:(NSString *)message from:(UIViewController *)viewController
{
[[[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"Dismiss", nil] show];
}
+ (SEL)swizzledSelectorForSelector:(SEL)selector
{
return NSSelectorFromString([NSString stringWithFormat:@"_flex_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)]);
+29 -10
View File
@@ -45,9 +45,12 @@
535682BF18F3670300BAAD62 /* UIColor+AAPLApplicationSpecific.m in Sources */ = {isa = PBXBuildFile; fileRef = 535682A618F3670300BAAD62 /* UIColor+AAPLApplicationSpecific.m */; };
53874F9918F36B1800510922 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53874F9718F36B1800510922 /* Localizable.strings */; };
779B1EF51C0C4F25001F5E49 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 779B1EF41C0C4F25001F5E49 /* libsqlite3.dylib */; };
9420A1AD1C1E84EE00B587DF /* FLEX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9420A1AC1C1E84EE00B587DF /* FLEX.framework */; };
943203FE1978F42F00E24DB3 /* AAPLCatalogTableTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 943203FD1978F42F00E24DB3 /* AAPLCatalogTableTableViewController.m */; };
94CB4D431A97183E0054A905 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 94CB4D421A97183E0054A905 /* libz.dylib */; };
C34C811A22678CF5006C4D4B /* Person.m in Sources */ = {isa = PBXBuildFile; fileRef = C34C811922678CF5006C4D4B /* Person.m */; };
C3EF50CD22610FE200B0BE49 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C3EF50CC22610FE200B0BE49 /* LaunchScreen.xib */; };
C3FB77E52266367F00DF4E73 /* FLEX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FB77E42266367F00DF4E73 /* FLEX.framework */; };
C3FB77E6226636AA00DF4E73 /* FLEX.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3FB77E42266367F00DF4E73 /* FLEX.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -57,6 +60,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
C3FB77E6226636AA00DF4E73 /* FLEX.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -132,10 +136,13 @@
535682A618F3670300BAAD62 /* UIColor+AAPLApplicationSpecific.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+AAPLApplicationSpecific.m"; sourceTree = "<group>"; };
53874F9818F36B1800510922 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
779B1EF41C0C4F25001F5E49 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = ../../../../../../usr/lib/libsqlite3.dylib; sourceTree = "<group>"; };
9420A1AC1C1E84EE00B587DF /* FLEX.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FLEX.framework; path = "../../../Library/Developer/Xcode/DerivedData/FLEX-gixtctkeshzdbwbdapvulcvleeiu/Build/Products/Debug-iphoneos/FLEX.framework"; sourceTree = "<group>"; };
943203FC1978F42F00E24DB3 /* AAPLCatalogTableTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLCatalogTableTableViewController.h; sourceTree = "<group>"; };
943203FD1978F42F00E24DB3 /* AAPLCatalogTableTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLCatalogTableTableViewController.m; sourceTree = "<group>"; };
94CB4D421A97183E0054A905 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
C34C811822678CF5006C4D4B /* Person.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Person.h; sourceTree = "<group>"; };
C34C811922678CF5006C4D4B /* Person.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Person.m; sourceTree = "<group>"; };
C3EF50CC22610FE200B0BE49 /* LaunchScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = "<group>"; };
C3FB77E42266367F00DF4E73 /* FLEX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FLEX.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -143,7 +150,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9420A1AD1C1E84EE00B587DF /* FLEX.framework in Frameworks */,
C3FB77E52266367F00DF4E73 /* FLEX.framework in Frameworks */,
779B1EF51C0C4F25001F5E49 /* libsqlite3.dylib in Frameworks */,
94CB4D431A97183E0054A905 /* libz.dylib in Frameworks */,
5356824018F3656900BAAD62 /* CoreGraphics.framework in Frameworks */,
@@ -188,7 +195,7 @@
5356823C18F3656900BAAD62 /* Frameworks */ = {
isa = PBXGroup;
children = (
9420A1AC1C1E84EE00B587DF /* FLEX.framework */,
C3FB77E42266367F00DF4E73 /* FLEX.framework */,
779B1EF41C0C4F25001F5E49 /* libsqlite3.dylib */,
94CB4D421A97183E0054A905 /* libz.dylib */,
5356823D18F3656900BAAD62 /* Foundation.framework */,
@@ -216,6 +223,7 @@
5356824518F3656900BAAD62 /* UICatalog-Info.plist */,
5356824B18F3656900BAAD62 /* UICatalog-Prefix.pch */,
53874F9718F36B1800510922 /* Localizable.strings */,
C3EF50CC22610FE200B0BE49 /* LaunchScreen.xib */,
);
name = "Supporting Files";
sourceTree = "<group>";
@@ -292,6 +300,8 @@
535682A618F3670300BAAD62 /* UIColor+AAPLApplicationSpecific.m */,
5356824F18F3656900BAAD62 /* Main_iPhone.storyboard */,
5356825218F3656900BAAD62 /* Main_iPad.storyboard */,
C34C811822678CF5006C4D4B /* Person.h */,
C34C811922678CF5006C4D4B /* Person.m */,
);
name = Application;
sourceTree = "<group>";
@@ -332,12 +342,17 @@
5356823218F3656900BAAD62 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = f;
TargetAttributes = {
5356823918F3656900BAAD62 = {
DevelopmentTeam = S6N2F22V2Z;
};
};
};
buildConfigurationList = 5356823518F3656900BAAD62 /* Build configuration list for PBXProject "UICatalog" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -361,6 +376,7 @@
22679D5D1C741A7A002248FC /* dogs.realm in Resources */,
5356825418F3656900BAAD62 /* Main_iPad.storyboard in Resources */,
53874F9918F36B1800510922 /* Localizable.strings in Resources */,
C3EF50CD22610FE200B0BE49 /* LaunchScreen.xib in Resources */,
5356825918F3656900BAAD62 /* Images.xcassets in Resources */,
3EC6487318FF8A5000024205 /* ReadMe.txt in Resources */,
5356825118F3656900BAAD62 /* Main_iPhone.storyboard in Resources */,
@@ -386,6 +402,7 @@
535682AF18F3670300BAAD62 /* AAPLDefaultSearchBarViewController.m in Sources */,
535682BC18F3670300BAAD62 /* AAPLTextViewController.m in Sources */,
535682B418F3670300BAAD62 /* AAPLPickerViewController.m in Sources */,
C34C811A22678CF5006C4D4B /* Person.m in Sources */,
535682BE18F3670300BAAD62 /* AAPLWebViewController.m in Sources */,
535682BB18F3670300BAAD62 /* AAPLTextFieldViewController.m in Sources */,
535682BF18F3670300BAAD62 /* UIColor+AAPLApplicationSpecific.m in Sources */,
@@ -440,6 +457,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@@ -493,6 +511,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@@ -539,7 +558,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
DEVELOPMENT_TEAM = S6N2F22V2Z;
EXCLUDED_SOURCE_FILE_NAMES = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -549,7 +568,7 @@
GCC_PREFIX_HEADER = "UICatalog/UICatalog-Prefix.pch";
INFOPLIST_FILE = "UICatalog/UICatalog-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.UICatalog";
PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEX.example;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
@@ -559,7 +578,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
DEVELOPMENT_TEAM = S6N2F22V2Z;
EXCLUDED_SOURCE_FILE_NAMES = "FLEX*";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -569,7 +588,7 @@
GCC_PREFIX_HEADER = "UICatalog/UICatalog-Prefix.pch";
INFOPLIST_FILE = "UICatalog/UICatalog-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.UICatalog";
PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEX.example;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+5
View File
@@ -49,6 +49,7 @@
#if DEBUG
#import <FLEX/FLEX.h>
#import "Person.h"
#if __has_include(<Realm/Realm.h>)
#import "Dog.h"
#import "Owner.h"
@@ -71,6 +72,10 @@
[self sendExampleNetworkRequests];
self.repeatingLogExampleTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendExampleLogMessage) userInfo:nil repeats:YES];
// For testing unarchiving of objects
NSString *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *whereToSaveBob = [documents stringByAppendingPathComponent:@"Bob.plist"];
[NSKeyedArchiver archiveRootObject:[Person bob] toFile:whereToSaveBob];
#if __has_include(<Realm/Realm.h>)
[self setUpRealm];
#endif
@@ -1,20 +1,55 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
@@ -44,6 +79,16 @@
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -1,74 +0,0 @@
{
"images" : [
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "736h",
"filename" : "iPhone6PlusPortrait.png",
"minimum-system-version" : "8.0",
"orientation" : "portrait",
"scale" : "3x"
},
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "736h",
"filename" : "iPhone6PlusLandscape.png",
"minimum-system-version" : "8.0",
"orientation" : "landscape",
"scale" : "3x"
},
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "667h",
"filename" : "iPhone6Portrait.png",
"minimum-system-version" : "8.0",
"orientation" : "portrait",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"filename" : "Launch@2x.png",
"scale" : "2x"
},
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "retina4",
"filename" : "Launch@2x~568h.png",
"minimum-system-version" : "7.0",
"orientation" : "portrait",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"filename" : "Launch.png",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"filename" : "Launch@2x.png",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"filename" : "Launch@2x~568h.png",
"subtype" : "retina4",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -2,13 +2,17 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "bookmark_icon_1x.png"
"filename" : "bookmark_icon_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "bookmark_icon_2x.png"
"filename" : "bookmark_icon_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
@@ -2,13 +2,17 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "bookmark_icon_highlighted_1x.png"
"filename" : "bookmark_icon_highlighted_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "bookmark_icon_highlighted_2x.png"
"filename" : "bookmark_icon_highlighted_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
@@ -2,13 +2,17 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "checkmark_icon_1x.png"
"filename" : "checkmark_icon_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "checkmark_icon_2x.png"
"filename" : "checkmark_icon_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
@@ -2,12 +2,16 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "image_animal_5.png"
"filename" : "image_animal_5.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
@@ -2,12 +2,16 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "image_animal_2.png"
"filename" : "image_animal_2.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {

Some files were not shown because too many files have changed in this diff Show More