added files via upload

This commit is contained in:
Balackburn
2023-06-27 09:54:41 +02:00
commit 2ff6aac218
1420 changed files with 88898 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
//
// CALayer+FLEX.h
// FLEX
//
// Created by Tanner on 2/28/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
@interface CALayer (FLEX)
@property (nonatomic) BOOL flex_continuousCorners;
@end

View File

@@ -0,0 +1,46 @@
//
// CALayer+FLEX.m
// FLEX
//
// Created by Tanner on 2/28/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "CALayer+FLEX.h"
@interface CALayer (Private)
@property (nonatomic) BOOL continuousCorners;
@end
@implementation CALayer (FLEX)
static BOOL respondsToContinuousCorners = NO;
+ (void)load {
respondsToContinuousCorners = [CALayer
instancesRespondToSelector:@selector(setContinuousCorners:)
];
}
- (BOOL)flex_continuousCorners {
if (respondsToContinuousCorners) {
return self.continuousCorners;
}
return NO;
}
- (void)setFlex_continuousCorners:(BOOL)enabled {
if (respondsToContinuousCorners) {
if (@available(iOS 13, *)) {
self.cornerCurve = kCACornerCurveContinuous;
} else {
self.continuousCorners = enabled;
// self.masksToBounds = NO;
// self.allowsEdgeAntialiasing = YES;
// self.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge;
}
}
}
@end

View File

@@ -0,0 +1,29 @@
//
// FLEXRuntime+Compare.h
// FLEX
//
// Created by Tanner Bennett on 8/28/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FLEXProperty.h"
#import "FLEXIvar.h"
#import "FLEXMethodBase.h"
#import "FLEXProtocol.h"
@interface FLEXProperty (Compare)
- (NSComparisonResult)compare:(FLEXProperty *)other;
@end
@interface FLEXIvar (Compare)
- (NSComparisonResult)compare:(FLEXIvar *)other;
@end
@interface FLEXMethodBase (Compare)
- (NSComparisonResult)compare:(FLEXMethodBase *)other;
@end
@interface FLEXProtocol (Compare)
- (NSComparisonResult)compare:(FLEXProtocol *)other;
@end

View File

@@ -0,0 +1,47 @@
//
// FLEXRuntime+Compare.m
// FLEX
//
// Created by Tanner Bennett on 8/28/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntime+Compare.h"
@implementation FLEXProperty (Compare)
- (NSComparisonResult)compare:(FLEXProperty *)other {
NSComparisonResult r = [self.name caseInsensitiveCompare:other.name];
if (r == NSOrderedSame) {
// TODO make sure empty image name sorts above an image name
return [self.imageName ?: @"" compare:other.imageName];
}
return r;
}
@end
@implementation FLEXIvar (Compare)
- (NSComparisonResult)compare:(FLEXIvar *)other {
return [self.name caseInsensitiveCompare:other.name];
}
@end
@implementation FLEXMethodBase (Compare)
- (NSComparisonResult)compare:(FLEXMethodBase *)other {
return [self.name caseInsensitiveCompare:other.name];
}
@end
@implementation FLEXProtocol (Compare)
- (NSComparisonResult)compare:(FLEXProtocol *)other {
return [self.name caseInsensitiveCompare:other.name];
}
@end

View File

@@ -0,0 +1,90 @@
//
// FLEXRuntime+UIKitHelpers.h
// FLEX
//
// Created by Tanner Bennett on 12/16/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXProperty.h"
#import "FLEXIvar.h"
#import "FLEXMethod.h"
#import "FLEXProtocol.h"
#import "FLEXTableViewSection.h"
@class FLEXObjectExplorerDefaults;
/// Model objects of an object explorer screen adopt this
/// protocol in order respond to user defaults changes
@protocol FLEXObjectExplorerItem <NSObject>
/// Current explorer settings. Set when settings change.
@property (nonatomic) FLEXObjectExplorerDefaults *defaults;
/// YES for properties and ivars which surely support editing, NO for all methods.
@property (nonatomic, readonly) BOOL isEditable;
/// NO for ivars, YES for supported methods and properties
@property (nonatomic, readonly) BOOL isCallable;
@end
@protocol FLEXRuntimeMetadata <FLEXObjectExplorerItem>
/// Used as the main title of the row
- (NSString *)description;
/// Used to compare metadata objects for uniqueness
@property (nonatomic, readonly) NSString *name;
/// For internal use
@property (nonatomic) id tag;
/// Should return \c nil if not applicable
- (id)currentValueWithTarget:(id)object;
/// Used as the subtitle or description of a property, ivar, or method
- (NSString *)previewWithTarget:(id)object;
/// For methods, a method calling screen. For all else, an object explorer.
- (UIViewController *)viewerWithTarget:(id)object;
/// For methods and protocols, nil. For all else, an a field editor screen.
/// The given section is reloaded on commit of any changes.
- (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section;
/// Used to determine present which interactions are possible to the user
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object;
/// Return nil to use the default reuse identifier
- (NSString *)reuseIdentifierWithTarget:(id)object;
/// An array of actions to place in the first section of the context menu.
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender API_AVAILABLE(ios(13.0));
/// An array where every 2 elements are a key-value pair. The key is a description
/// of what to copy like "Name" and the values are what will be copied.
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object;
/// Properties and ivars return the address of an object, if they hold one.
- (NSString *)contextualSubtitleWithTarget:(id)object;
@end
// Even if a property is readonly, it still may be editable
// via a setter. Checking isEditable will not reflect that
// unless the property was initialized with a class.
@interface FLEXProperty (UIKitHelpers) <FLEXRuntimeMetadata> @end
@interface FLEXIvar (UIKitHelpers) <FLEXRuntimeMetadata> @end
@interface FLEXMethodBase (UIKitHelpers) <FLEXRuntimeMetadata> @end
@interface FLEXMethod (UIKitHelpers) <FLEXRuntimeMetadata> @end
@interface FLEXProtocol (UIKitHelpers) <FLEXRuntimeMetadata> @end
typedef NS_ENUM(NSUInteger, FLEXStaticMetadataRowStyle) {
FLEXStaticMetadataRowStyleSubtitle,
FLEXStaticMetadataRowStyleKeyValue,
FLEXStaticMetadataRowStyleDefault = FLEXStaticMetadataRowStyleSubtitle,
};
/// Displays a small row as a static key-value pair of information.
@interface FLEXStaticMetadata : NSObject <FLEXRuntimeMetadata>
+ (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title string:(NSString *)string;
+ (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title number:(NSNumber *)number;
+ (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes;
@end
/// This is assigned to the \c tag property of each metadata.

View File

@@ -0,0 +1,634 @@
//
// FLEXRuntime+UIKitHelpers.m
// FLEX
//
// Created by Tanner Bennett on 12/16/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntime+UIKitHelpers.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXFieldEditorViewController.h"
#import "FLEXMethodCallingViewController.h"
#import "FLEXObjectListViewController.h"
#import "FLEXTableView.h"
#import "FLEXUtility.h"
#import "NSArray+FLEX.h"
#import "NSString+FLEX.h"
#define FLEXObjectExplorerDefaultsImpl \
- (FLEXObjectExplorerDefaults *)defaults { \
return self.tag; \
} \
\
- (void)setDefaults:(FLEXObjectExplorerDefaults *)defaults { \
self.tag = defaults; \
}
#pragma mark FLEXProperty
@implementation FLEXProperty (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
/// Decide whether to use potentialTarget or [potentialTarget class] to get or set property
- (id)appropriateTargetForPropertyType:(id)potentialTarget {
if (!object_isClass(potentialTarget)) {
if (self.isClassProperty) {
return [potentialTarget class];
} else {
return potentialTarget;
}
} else {
if (self.isClassProperty) {
return potentialTarget;
} else {
// Instance property with a class object
return nil;
}
}
}
- (BOOL)isEditable {
if (self.attributes.isReadOnly) {
return self.likelySetterExists;
}
const FLEXTypeEncoding *typeEncoding = self.attributes.typeEncoding.UTF8String;
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
}
- (BOOL)isCallable {
return YES;
}
- (id)currentValueWithTarget:(id)object {
return [self getPotentiallyUnboxedValue:
[self appropriateTargetForPropertyType:object]
];
}
- (id)currentValueBeforeUnboxingWithTarget:(id)object {
return [self getValue:
[self appropriateTargetForPropertyType:object]
];
}
- (NSString *)previewWithTarget:(id)object {
if (object_isClass(object) && !self.isClassProperty) {
return self.attributes.fullDeclaration;
} else if (self.defaults.wantsDynamicPreviews) {
return [FLEXRuntimeUtility
summaryForObject:[self currentValueWithTarget:object]
];
}
return nil;
}
- (UIViewController *)viewerWithTarget:(id)object {
id value = [self currentValueWithTarget:object];
return [FLEXObjectExplorerFactory explorerViewControllerForObject:value];
}
- (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
id target = [self appropriateTargetForPropertyType:object];
return [FLEXFieldEditorViewController target:target property:self commitHandler:^{
[section reloadData:YES];
}];
}
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
id targetForValueCheck = [self appropriateTargetForPropertyType:object];
if (!targetForValueCheck) {
// Instance property with a class object
return UITableViewCellAccessoryNone;
}
// We use .tag to store the cached value of .isEditable that is
// initialized by FLEXObjectExplorer in -reloadMetada
if ([self getPotentiallyUnboxedValue:targetForValueCheck]) {
if (self.defaults.isEditable) {
// Editable non-nil value, both
return UITableViewCellAccessoryDetailDisclosureButton;
} else {
// Uneditable non-nil value, chevron only
return UITableViewCellAccessoryDisclosureIndicator;
}
} else {
if (self.defaults.isEditable) {
// Editable nil value, just (i)
return UITableViewCellAccessoryDetailButton;
} else {
// Non-editable nil value, neither
return UITableViewCellAccessoryNone;
}
}
}
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
BOOL returnsObject = self.attributes.typeEncoding.flex_typeIsObjectOrClass;
BOOL targetNotNil = [self appropriateTargetForPropertyType:object] != nil;
// "Explore PropertyClass" for properties with a concrete class name
if (returnsObject) {
NSMutableArray<UIAction *> *actions = [NSMutableArray new];
// Action for exploring class of this property
Class propertyClass = self.attributes.typeEncoding.flex_typeClass;
if (propertyClass) {
NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(propertyClass)];
[actions addObject:[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:propertyClass];
[sender.navigationController pushViewController:explorer animated:YES];
}]];
}
// Action for exploring references to this object
if (targetNotNil) {
// Since the property holder is not nil, check if the property value is nil
id value = [self currentValueBeforeUnboxingWithTarget:object];
if (value) {
NSString *title = @"List all references";
[actions addObject:[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
UIViewController *list = [FLEXObjectListViewController
objectsWithReferencesToObject:value
retained:NO
];
[sender.navigationController pushViewController:list animated:YES];
}]];
}
}
return actions;
}
return nil;
}
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
BOOL returnsObject = self.attributes.typeEncoding.flex_typeIsObjectOrClass;
BOOL targetNotNil = [self appropriateTargetForPropertyType:object] != nil;
NSMutableArray *items = [NSMutableArray arrayWithArray:@[
@"Name", self.name ?: @"",
@"Type", self.attributes.typeEncoding ?: @"",
@"Declaration", self.fullDescription ?: @"",
]];
if (targetNotNil) {
id value = [self currentValueBeforeUnboxingWithTarget:object];
[items addObjectsFromArray:@[
@"Value Preview", [self previewWithTarget:object] ?: @"",
@"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
]];
}
[items addObjectsFromArray:@[
@"Getter", NSStringFromSelector(self.likelyGetter) ?: @"",
@"Setter", self.likelySetterExists ? NSStringFromSelector(self.likelySetter) : @"",
@"Image Name", self.imageName ?: @"",
@"Attributes", self.attributes.string ?: @"",
@"objc_property", [FLEXUtility pointerToString:self.objc_property],
@"objc_property_attribute_t", [FLEXUtility pointerToString:self.attributes.list],
]];
return items;
}
- (NSString *)contextualSubtitleWithTarget:(id)object {
id target = [self appropriateTargetForPropertyType:object];
if (target && self.attributes.typeEncoding.flex_typeIsObjectOrClass) {
return [FLEXUtility addressOfObject:[self currentValueBeforeUnboxingWithTarget:target]];
}
return nil;
}
@end
#pragma mark FLEXIvar
@implementation FLEXIvar (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
- (BOOL)isEditable {
const FLEXTypeEncoding *typeEncoding = self.typeEncoding.UTF8String;
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
}
- (BOOL)isCallable {
return NO;
}
- (id)currentValueWithTarget:(id)object {
if (!object_isClass(object)) {
return [self getPotentiallyUnboxedValue:object];
}
return nil;
}
- (NSString *)previewWithTarget:(id)object {
if (object_isClass(object)) {
return self.details;
} else if (self.defaults.wantsDynamicPreviews) {
return [FLEXRuntimeUtility
summaryForObject:[self currentValueWithTarget:object]
];
}
return nil;
}
- (UIViewController *)viewerWithTarget:(id)object {
NSAssert(!object_isClass(object), @"Unreachable state: viewing ivar on class object");
id value = [self currentValueWithTarget:object];
return [FLEXObjectExplorerFactory explorerViewControllerForObject:value];
}
- (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
NSAssert(!object_isClass(object), @"Unreachable state: editing ivar on class object");
return [FLEXFieldEditorViewController target:object ivar:self commitHandler:^{
[section reloadData:YES];
}];
}
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
if (object_isClass(object)) {
return UITableViewCellAccessoryNone;
}
// Could use .isEditable here, but we use .tag for speed since it is cached
if ([self getPotentiallyUnboxedValue:object]) {
if (self.defaults.isEditable) {
// Editable non-nil value, both
return UITableViewCellAccessoryDetailDisclosureButton;
} else {
// Uneditable non-nil value, chevron only
return UITableViewCellAccessoryDisclosureIndicator;
}
} else {
if (self.defaults.isEditable) {
// Editable nil value, just (i)
return UITableViewCellAccessoryDetailButton;
} else {
// Non-editable nil value, neither
return UITableViewCellAccessoryNone;
}
}
}
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
Class ivarClass = self.typeEncoding.flex_typeClass;
// "Explore PropertyClass" for properties with a concrete class name
if (ivarClass) {
NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(ivarClass)];
return @[[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:ivarClass];
[sender.navigationController pushViewController:explorer animated:YES];
}]];
}
return nil;
}
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
BOOL isInstance = !object_isClass(object);
BOOL returnsObject = self.typeEncoding.flex_typeIsObjectOrClass;
id value = isInstance ? [self getValue:object] : nil;
NSMutableArray *items = [NSMutableArray arrayWithArray:@[
@"Name", self.name ?: @"",
@"Type", self.typeEncoding ?: @"",
@"Declaration", self.description ?: @"",
]];
if (isInstance) {
[items addObjectsFromArray:@[
@"Value Preview", isInstance ? [self previewWithTarget:object] : @"",
@"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
]];
}
[items addObjectsFromArray:@[
@"Size", @(self.size).stringValue,
@"Offset", @(self.offset).stringValue,
@"objc_ivar", [FLEXUtility pointerToString:self.objc_ivar],
]];
return items;
}
- (NSString *)contextualSubtitleWithTarget:(id)object {
if (!object_isClass(object) && self.typeEncoding.flex_typeIsObjectOrClass) {
return [FLEXUtility addressOfObject:[self getValue:object]];
}
return nil;
}
@end
#pragma mark FLEXMethod
@implementation FLEXMethodBase (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
- (BOOL)isEditable {
return NO;
}
- (BOOL)isCallable {
return NO;
}
- (id)currentValueWithTarget:(id)object {
// Methods can't be "edited" and have no "value"
return nil;
}
- (NSString *)previewWithTarget:(id)object {
return [self.selectorString stringByAppendingFormat:@" — %@", self.typeEncoding];
}
- (UIViewController *)viewerWithTarget:(id)object {
// We disallow calling of FLEXMethodBase methods
@throw NSInternalInconsistencyException;
return nil;
}
- (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
// Methods cannot be edited
@throw NSInternalInconsistencyException;
return nil;
}
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
// We shouldn't be using any FLEXMethodBase objects for this
@throw NSInternalInconsistencyException;
return UITableViewCellAccessoryNone;
}
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
return nil;
}
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
return @[
@"Selector", self.name ?: @"",
@"Type Encoding", self.typeEncoding ?: @"",
@"Declaration", self.description ?: @"",
];
}
- (NSString *)contextualSubtitleWithTarget:(id)object {
return nil;
}
@end
@implementation FLEXMethod (UIKitHelpers)
- (BOOL)isCallable {
return self.signature != nil;
}
- (UIViewController *)viewerWithTarget:(id)object {
object = self.isInstanceMethod ? object : (object_isClass(object) ? object : [object class]);
return [FLEXMethodCallingViewController target:object method:self];
}
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
if (self.isInstanceMethod) {
if (object_isClass(object)) {
// Instance method from class, can't call
return UITableViewCellAccessoryNone;
} else {
// Instance method from instance, can call
return UITableViewCellAccessoryDisclosureIndicator;
}
} else {
return UITableViewCellAccessoryDisclosureIndicator;
}
}
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
return [[super copiableMetadataWithTarget:object] arrayByAddingObjectsFromArray:@[
@"NSMethodSignature *", [FLEXUtility addressOfObject:self.signature],
@"Signature String", self.signatureString ?: @"",
@"Number of Arguments", @(self.numberOfArguments).stringValue,
@"Return Type", @(self.returnType ?: ""),
@"Return Size", @(self.returnSize).stringValue,
@"objc_method", [FLEXUtility pointerToString:self.objc_method],
]];
}
@end
#pragma mark FLEXProtocol
@implementation FLEXProtocol (UIKitHelpers)
FLEXObjectExplorerDefaultsImpl
- (BOOL)isEditable {
return NO;
}
- (BOOL)isCallable {
return NO;
}
- (id)currentValueWithTarget:(id)object {
return nil;
}
- (NSString *)previewWithTarget:(id)object {
return nil;
}
- (UIViewController *)viewerWithTarget:(id)object {
return [FLEXObjectExplorerFactory explorerViewControllerForObject:self];
}
- (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
// Protocols cannot be edited
@throw NSInternalInconsistencyException;
return nil;
}
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
return UITableViewCellAccessoryDisclosureIndicator;
}
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
return nil;
}
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
NSArray<NSString *> *conformanceNames = [self.protocols valueForKeyPath:@"name"];
NSString *conformances = [conformanceNames componentsJoinedByString:@"\n"];
return @[
@"Name", self.name ?: @"",
@"Conformances", conformances ?: @"",
];
}
- (NSString *)contextualSubtitleWithTarget:(id)object {
return nil;
}
@end
#pragma mark FLEXStaticMetadata
@interface FLEXStaticMetadata () {
@protected
NSString *_name;
}
@property (nonatomic) FLEXTableViewCellReuseIdentifier reuse;
@property (nonatomic) NSString *subtitle;
@property (nonatomic) id metadata;
@end
@interface FLEXStaticMetadata_Class : FLEXStaticMetadata
+ (instancetype)withClass:(Class)cls;
@end
@implementation FLEXStaticMetadata
@synthesize name = _name;
@synthesize tag = _tag;
FLEXObjectExplorerDefaultsImpl
+ (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes {
return [classes flex_mapped:^id(Class cls, NSUInteger idx) {
return [FLEXStaticMetadata_Class withClass:cls];
}];
}
+ (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title string:(NSString *)string {
return [[self alloc] initWithStyle:style title:title subtitle:string];
}
+ (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title number:(NSNumber *)number {
return [[self alloc] initWithStyle:style title:title subtitle:number.stringValue];
}
- (id)initWithStyle:(FLEXStaticMetadataRowStyle)style title:(NSString *)title subtitle:(NSString *)subtitle {
self = [super init];
if (self) {
if (style == FLEXStaticMetadataRowStyleKeyValue) {
_reuse = kFLEXKeyValueCell;
} else {
_reuse = kFLEXMultilineDetailCell;
}
_name = title;
_subtitle = subtitle;
}
return self;
}
- (NSString *)description {
return self.name;
}
- (NSString *)reuseIdentifierWithTarget:(id)object {
return self.reuse;
}
- (BOOL)isEditable {
return NO;
}
- (BOOL)isCallable {
return NO;
}
- (id)currentValueWithTarget:(id)object {
return nil;
}
- (NSString *)previewWithTarget:(id)object {
return self.subtitle;
}
- (UIViewController *)viewerWithTarget:(id)object {
return nil;
}
- (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
// Static metadata cannot be edited
@throw NSInternalInconsistencyException;
return nil;
}
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
return UITableViewCellAccessoryNone;
}
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
return nil;
}
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
return @[self.name, self.subtitle];
}
- (NSString *)contextualSubtitleWithTarget:(id)object {
return nil;
}
@end
#pragma mark FLEXStaticMetadata_Class
@implementation FLEXStaticMetadata_Class
+ (instancetype)withClass:(Class)cls {
NSParameterAssert(cls);
FLEXStaticMetadata_Class *metadata = [self new];
metadata.metadata = cls;
metadata->_name = NSStringFromClass(cls);
metadata.reuse = kFLEXDefaultCell;
return metadata;
}
- (id)initWithStyle:(FLEXStaticMetadataRowStyle)style title:(NSString *)title subtitle:(NSString *)subtitle {
@throw NSInternalInconsistencyException;
return nil;
}
- (UIViewController *)viewerWithTarget:(id)object {
return [FLEXObjectExplorerFactory explorerViewControllerForObject:self.metadata];
}
- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
return UITableViewCellAccessoryDisclosureIndicator;
}
- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
return @[
@"Class Name", self.name,
@"Class", [FLEXUtility addressOfObject:self.metadata]
];
}
- (NSString *)contextualSubtitleWithTarget:(id)object {
return [FLEXUtility addressOfObject:self.metadata];
}
@end

View File

@@ -0,0 +1,40 @@
//
// NSArray+FLEX.h
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSArray<T> (Functional)
/// Actually more like flatmap, but it seems like the objc way to allow returning nil to omit objects.
/// So, return nil from the block to omit objects, and return an object to include it in the new array.
/// Unlike flatmap, however, this will not flatten arrays of arrays into a single array.
- (__kindof NSArray *)flex_mapped:(id(^)(T obj, NSUInteger idx))mapFunc;
/// Like flex_mapped, but expects arrays to be returned, and flattens them into one array.
- (__kindof NSArray *)flex_flatmapped:(NSArray *(^)(id, NSUInteger idx))block;
- (instancetype)flex_filtered:(BOOL(^)(T obj, NSUInteger idx))filterFunc;
- (void)flex_forEach:(void(^)(T obj, NSUInteger idx))block;
/// Unlike \c subArrayWithRange: this will not throw an exception if \c maxLength
/// is greater than the size of the array. If the array has one element and
/// \c maxLength is greater than 1, you get an array with 1 element back.
- (instancetype)flex_subArrayUpto:(NSUInteger)maxLength;
+ (instancetype)flex_forEachUpTo:(NSUInteger)bound map:(T(^)(NSUInteger i))block;
+ (instancetype)flex_mapped:(id<NSFastEnumeration>)collection block:(id(^)(T obj, NSUInteger idx))mapFunc;
- (instancetype)flex_sortedUsingSelector:(SEL)selector;
- (T)flex_firstWhere:(BOOL(^)(T obj))meetingCriteria;
@end
@interface NSMutableArray<T> (Functional)
- (void)flex_filter:(BOOL(^)(T obj, NSUInteger idx))filterFunc;
@end

View File

@@ -0,0 +1,143 @@
//
// NSArray+FLEX.m
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "NSArray+FLEX.h"
#define FLEXArrayClassIsMutable(me) ([[self class] isSubclassOfClass:[NSMutableArray class]])
@implementation NSArray (Functional)
- (__kindof NSArray *)flex_mapped:(id (^)(id, NSUInteger))mapFunc {
NSMutableArray *map = [NSMutableArray new];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id ret = mapFunc(obj, idx);
if (ret) {
[map addObject:ret];
}
}];
if (self.count < 2048 && !FLEXArrayClassIsMutable(self)) {
return map.copy;
}
return map;
}
- (__kindof NSArray *)flex_flatmapped:(NSArray *(^)(id, NSUInteger))block {
NSMutableArray *array = [NSMutableArray new];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSArray *toAdd = block(obj, idx);
if (toAdd) {
[array addObjectsFromArray:toAdd];
}
}];
if (array.count < 2048 && !FLEXArrayClassIsMutable(self)) {
return array.copy;
}
return array;
}
- (NSArray *)flex_filtered:(BOOL (^)(id, NSUInteger))filterFunc {
return [self flex_mapped:^id(id obj, NSUInteger idx) {
return filterFunc(obj, idx) ? obj : nil;
}];
}
- (void)flex_forEach:(void(^)(id, NSUInteger))block {
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
block(obj, idx);
}];
}
- (instancetype)flex_subArrayUpto:(NSUInteger)maxLength {
if (maxLength > self.count) {
if (FLEXArrayClassIsMutable(self)) {
return self.mutableCopy;
}
return self;
}
return [self subarrayWithRange:NSMakeRange(0, maxLength)];
}
+ (__kindof NSArray *)flex_forEachUpTo:(NSUInteger)bound map:(id(^)(NSUInteger))block {
NSMutableArray *array = [NSMutableArray new];
for (NSUInteger i = 0; i < bound; i++) {
id obj = block(i);
if (obj) {
[array addObject:obj];
}
}
// For performance reasons, don't copy large arrays
if (bound < 2048 && !FLEXArrayClassIsMutable(self)) {
return array.copy;
}
return array;
}
+ (instancetype)flex_mapped:(id<NSFastEnumeration>)collection block:(id(^)(id obj, NSUInteger idx))mapFunc {
NSMutableArray *array = [NSMutableArray new];
NSInteger idx = 0;
for (id obj in collection) {
id ret = mapFunc(obj, idx++);
if (ret) {
[array addObject:ret];
}
}
// For performance reasons, don't copy large arrays
if (array.count < 2048) {
return array.copy;
}
return array;
}
- (instancetype)flex_sortedUsingSelector:(SEL)selector {
if (FLEXArrayClassIsMutable(self)) {
NSMutableArray *me = (id)self;
[me sortUsingSelector:selector];
return me;
} else {
return [self sortedArrayUsingSelector:selector];
}
}
- (id)flex_firstWhere:(BOOL (^)(id))meetsCriteria {
for (id e in self) {
if (meetsCriteria(e)) {
return e;
}
}
return nil;
}
@end
@implementation NSMutableArray (Functional)
- (void)flex_filter:(BOOL (^)(id, NSUInteger))keepObject {
NSMutableIndexSet *toRemove = [NSMutableIndexSet new];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (!keepObject(obj, idx)) {
[toRemove addIndex:idx];
}
}];
[self removeObjectsAtIndexes:toRemove];
}
@end

View File

@@ -0,0 +1,234 @@
//
// NSObject+FLEX_Reflection.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@class FLEXMirror, FLEXMethod, FLEXIvar, FLEXProperty, FLEXMethodBase, FLEXPropertyAttributes, FLEXProtocol;
NS_ASSUME_NONNULL_BEGIN
/// Returns the type encoding string given the encoding for the return type and parameters, if any.
/// @discussion Example usage for a \c void returning method which takes
/// an \c int: @code FLEXTypeEncoding(@encode(void), @encode(int));
/// @param returnType The encoded return type. \c void for exmaple would be \c @encode(void).
/// @param count The number of parameters in this type encoding string.
/// @return The type encoding string, or \c nil if \e returnType is \c NULL.
NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...);
NSArray<Class> * _Nullable FLEXGetAllSubclasses(_Nullable Class cls, BOOL includeSelf);
NSArray<Class> * _Nullable FLEXGetClassHierarchy(_Nullable Class cls, BOOL includeSelf);
NSArray<FLEXProtocol *> * _Nullable FLEXGetConformedProtocols(_Nullable Class cls);
NSArray<FLEXIvar *> * _Nullable FLEXGetAllIvars(_Nullable Class cls);
/// @param cls a class object to get instance properties,
/// or a metaclass object to get class properties
NSArray<FLEXProperty *> * _Nullable FLEXGetAllProperties(_Nullable Class cls);
/// @param cls a class object to get instance methods,
/// or a metaclass object to get class methods
/// @param instance used to mark methods as instance methods or not.
/// Not used to determine whether to get instance or class methods.
NSArray<FLEXMethod *> * _Nullable FLEXGetAllMethods(_Nullable Class cls, BOOL instance);
/// @param cls a class object to get all instance and class methods.
NSArray<FLEXMethod *> * _Nullable FLEXGetAllInstanceAndClassMethods(_Nullable Class cls);
#pragma mark Reflection
@interface NSObject (Reflection)
@property (nonatomic, readonly ) FLEXMirror *flex_reflection;
@property (nonatomic, readonly, class) FLEXMirror *flex_reflection;
/// Calls into /c FLEXGetAllSubclasses
/// @return Every subclass of the receiving class, including the receiver itself.
@property (nonatomic, readonly, class) NSArray<Class> *flex_allSubclasses;
/// @return The \c Class object for the metaclass of the recieving class, or \c Nil if the class is Nil or not registered.
@property (nonatomic, readonly, class) Class flex_metaclass;
/// @return The size in bytes of instances of the recieving class, or \c 0 if \e cls is \c Nil.
@property (nonatomic, readonly, class) size_t flex_instanceSize;
/// Changes the class of an object instance.
/// @return The previous value of the objects \c class, or \c Nil if the object is \c nil.
- (Class)flex_setClass:(Class)cls;
/// Sets the recieving class's superclass. "You should not use this method" — Apple.
/// @return The old superclass.
+ (Class)flex_setSuperclass:(Class)superclass;
/// Calls into \c FLEXGetClassHierarchy()
/// @return a list of classes going up the class hierarchy,
/// starting with the receiver and ending with the root class.
@property (nonatomic, readonly, class) NSArray<Class> *flex_classHierarchy;
/// Calls into \c FLEXGetConformedProtocols
/// @return a list of protocols this class itself conforms to.
@property (nonatomic, readonly, class) NSArray<FLEXProtocol *> *flex_protocols;
@end
#pragma mark Methods
@interface NSObject (Methods)
/// All instance and class methods specific to the recieving class.
/// @discussion This method will only retrieve methods specific to the recieving class.
/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
/// @return An array of \c FLEXMethod objects.
@property (nonatomic, readonly, class) NSArray<FLEXMethod *> *flex_allMethods;
/// All instance methods specific to the recieving class.
/// @discussion This method will only retrieve methods specific to the recieving class.
/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
/// @return An array of \c FLEXMethod objects.
@property (nonatomic, readonly, class) NSArray<FLEXMethod *> *flex_allInstanceMethods;
/// All class methods specific to the recieving class.
/// @discussion This method will only retrieve methods specific to the recieving class.
/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
/// @return An array of \c FLEXMethod objects.
@property (nonatomic, readonly, class) NSArray<FLEXMethod *> *flex_allClassMethods;
/// Retrieves the class's instance method with the given name.
/// @return An initialized \c FLEXMethod object, or \c nil if the method wasn't found.
+ (FLEXMethod *)flex_methodNamed:(NSString *)name;
/// Retrieves the class's class method with the given name.
/// @return An initialized \c FLEXMethod object, or \c nil if the method wasn't found.
+ (FLEXMethod *)flex_classMethodNamed:(NSString *)name;
/// Adds a new method to the recieving class with a given name and implementation.
/// @discussion This method will add an override of a superclass's implementation,
/// but will not replace an existing implementation in the class.
/// To change an existing implementation, use \c replaceImplementationOfMethod:with:.
///
/// Type encodings start with the return type and end with the parameter types in order.
/// The type encoding for \c NSArray's \c count property getter looks like this:
/// @code [NSString stringWithFormat:@"%s%s%s%s", @encode(void), @encode(id), @encode(SEL), @encode(NSUInteger)] @endcode
/// Using the \c FLEXTypeEncoding function for the same method looks like this:
/// @code FLEXTypeEncodingString(@encode(void), 1, @encode(NSUInteger)) @endcode
/// @param typeEncoding The type encoding string. Consider using the \c FLEXTypeEncodingString() function.
/// @param instanceMethod NO to add the method to the class itself or YES to add it as an instance method.
/// @return YES if the method was added successfully, \c NO otherwise
/// (for example, the class already contains a method implementation with that name).
+ (BOOL)addMethod:(SEL)selector
typeEncoding:(NSString *)typeEncoding
implementation:(IMP)implementaiton
toInstances:(BOOL)instanceMethod;
/// Replaces the implementation of a method in the recieving class.
/// @param instanceMethod YES to replace the instance method, NO to replace the class method.
/// @note This function behaves in two different ways:
///
/// - If the method does not yet exist in the recieving class, it is added as if
/// \c addMethod:typeEncoding:implementation were called.
///
/// - If the method does exist, its \c IMP is replaced.
/// @return The previous \c IMP of \e method.
+ (IMP)replaceImplementationOfMethod:(FLEXMethodBase *)method with:(IMP)implementation useInstance:(BOOL)instanceMethod;
/// Swaps the implementations of the given methods.
/// @discussion If one or neither of the given methods exist in the recieving class,
/// they are added to the class with their implementations swapped as if each method did exist.
/// This method will not fail if each \c FLEXSimpleMethod contains a valid selector.
/// @param instanceMethod YES to swizzle the instance method, NO to swizzle the class method.
+ (void)swizzle:(FLEXMethodBase *)original with:(FLEXMethodBase *)other onInstance:(BOOL)instanceMethod;
/// Swaps the implementations of the given methods.
/// @param instanceMethod YES to swizzle the instance method, NO to swizzle the class method.
/// @return \c YES if successful, and \c NO if selectors could not be retrieved from the given strings.
+ (BOOL)swizzleByName:(NSString *)original with:(NSString *)other onInstance:(BOOL)instanceMethod;
/// Swaps the implementations of methods corresponding to the given selectors.
+ (void)swizzleBySelector:(SEL)original with:(SEL)other onInstance:(BOOL)instanceMethod;
@end
#pragma mark Properties
@interface NSObject (Ivars)
/// All of the instance variables specific to the recieving class.
/// @discussion This method will only retrieve instance varibles specific to the recieving class.
/// To retrieve instance variables on a parent class, simply call \c [[self superclass] allIvars].
/// @return An array of \c FLEXIvar objects.
@property (nonatomic, readonly, class) NSArray<FLEXIvar *> *flex_allIvars;
/// Retrieves an instance variable with the corresponding name.
/// @return An initialized \c FLEXIvar object, or \c nil if the Ivar wasn't found.
+ (FLEXIvar *)flex_ivarNamed:(NSString *)name;
/// @return The address of the given ivar in the recieving object in memory,
/// or \c NULL if it could not be found.
- (void *)flex_getIvarAddress:(FLEXIvar *)ivar;
/// @return The address of the given ivar in the recieving object in memory,
/// or \c NULL if it could not be found.
- (void *)flex_getIvarAddressByName:(NSString *)name;
/// @discussion This method faster than creating an \c FLEXIvar and calling
/// \c -getIvarAddress: if you already have an \c Ivar on hand
/// @return The address of the given ivar in the recieving object in memory,
/// or \c NULL if it could not be found\.
- (void *)flex_getObjcIvarAddress:(Ivar)ivar;
/// Sets the value of the given instance variable on the recieving object.
/// @discussion Use only when the target instance variable is an object.
- (void)flex_setIvar:(FLEXIvar *)ivar object:(id)value;
/// Sets the value of the given instance variable on the recieving object.
/// @discussion Use only when the target instance variable is an object.
/// @return \c YES if successful, or \c NO if the instance variable could not be found.
- (BOOL)flex_setIvarByName:(NSString *)name object:(id)value;
/// @discussion Use only when the target instance variable is an object.
/// This method is faster than creating an \c FLEXIvar and calling
/// \c -setIvar: if you already have an \c Ivar on hand.
- (void)flex_setObjcIvar:(Ivar)ivar object:(id)value;
/// Sets the value of the given instance variable on the recieving object to the
/// \e size number of bytes of data at \e value.
/// @discussion Use one of the other methods if you can help it.
- (void)flex_setIvar:(FLEXIvar *)ivar value:(void *)value size:(size_t)size;
/// Sets the value of the given instance variable on the recieving object to the
/// \e size number of bytes of data at \e value.
/// @discussion Use one of the other methods if you can help it
/// @return \c YES if successful, or \c NO if the instance variable could not be found.
- (BOOL)flex_setIvarByName:(NSString *)name value:(void *)value size:(size_t)size;
/// Sets the value of the given instance variable on the recieving object to the
/// \e size number of bytes of data at \e value.
/// @discussion This is faster than creating an \c FLEXIvar and calling
/// \c -setIvar:value:size if you already have an \c Ivar on hand.
- (void)flex_setObjcIvar:(Ivar)ivar value:(void *)value size:(size_t)size;
@end
#pragma mark Properties
@interface NSObject (Properties)
/// All instance and class properties specific to the recieving class.
/// @discussion This method will only retrieve properties specific to the recieving class.
/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
/// @return An array of \c FLEXProperty objects.
@property (nonatomic, readonly, class) NSArray<FLEXProperty *> *flex_allProperties;
/// All instance properties specific to the recieving class.
/// @discussion This method will only retrieve properties specific to the recieving class.
/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
/// @return An array of \c FLEXProperty objects.
@property (nonatomic, readonly, class) NSArray<FLEXProperty *> *flex_allInstanceProperties;
/// All class properties specific to the recieving class.
/// @discussion This method will only retrieve properties specific to the recieving class.
/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
/// @return An array of \c FLEXProperty objects.
@property (nonatomic, readonly, class) NSArray<FLEXProperty *> *flex_allClassProperties;
/// Retrieves the class's property with the given name.
/// @return An initialized \c FLEXProperty object, or \c nil if the property wasn't found.
+ (FLEXProperty *)flex_propertyNamed:(NSString *)name;
/// @return An initialized \c FLEXProperty object, or \c nil if the property wasn't found.
+ (FLEXProperty *)flex_classPropertyNamed:(NSString *)name;
/// Replaces the given property on the recieving class.
+ (void)flex_replaceProperty:(FLEXProperty *)property;
/// Replaces the given property on the recieving class. Useful for changing a property's attributes.
+ (void)flex_replaceProperty:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,426 @@
//
// NSObject+FLEX_Reflection.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "NSObject+FLEX_Reflection.h"
#import "FLEXClassBuilder.h"
#import "FLEXMirror.h"
#import "FLEXProperty.h"
#import "FLEXMethod.h"
#import "FLEXIvar.h"
#import "FLEXProtocol.h"
#import "FLEXPropertyAttributes.h"
#import "NSArray+FLEX.h"
#import "FLEXUtility.h"
NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...) {
if (!returnType) return nil;
NSMutableString *encoding = [NSMutableString new];
[encoding appendFormat:@"%s%s%s", returnType, @encode(id), @encode(SEL)];
va_list args;
va_start(args, count);
char *type = va_arg(args, char *);
for (NSUInteger i = 0; i < count; i++, type = va_arg(args, char *)) {
[encoding appendFormat:@"%s", type];
}
va_end(args);
return encoding.copy;
}
NSArray<Class> *FLEXGetAllSubclasses(Class cls, BOOL includeSelf) {
if (!cls) return nil;
Class *buffer = NULL;
int count, size;
do {
count = objc_getClassList(NULL, 0);
buffer = (Class *)realloc(buffer, count * sizeof(*buffer));
size = objc_getClassList(buffer, count);
} while (size != count);
NSMutableArray *classes = [NSMutableArray new];
if (includeSelf) {
[classes addObject:cls];
}
for (int i = 0; i < count; i++) {
Class candidate = buffer[i];
Class superclass = candidate;
while ((superclass = class_getSuperclass(superclass))) {
if (superclass == cls) {
[classes addObject:candidate];
break;
}
}
}
free(buffer);
return classes.copy;
}
NSArray<Class> *FLEXGetClassHierarchy(Class cls, BOOL includeSelf) {
if (!cls) return nil;
NSMutableArray *classes = [NSMutableArray new];
if (includeSelf) {
[classes addObject:cls];
}
while ((cls = [cls superclass])) {
[classes addObject:cls];
};
return classes.copy;
}
NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
if (!cls) return nil;
unsigned int count = 0;
Protocol *__unsafe_unretained *list = class_copyProtocolList(cls, &count);
NSArray<Protocol *> *protocols = [NSArray arrayWithObjects:list count:count];
free(list);
return [protocols flex_mapped:^id(Protocol *pro, NSUInteger idx) {
return [FLEXProtocol protocol:pro];
}];
}
NSArray<FLEXIvar *> *FLEXGetAllIvars(_Nullable Class cls) {
if (!cls) return nil;
unsigned int ivcount;
Ivar *objcivars = class_copyIvarList(cls, &ivcount);
NSArray *ivars = [NSArray flex_forEachUpTo:ivcount map:^id(NSUInteger i) {
return [FLEXIvar ivar:objcivars[i]];
}];
free(objcivars);
return ivars;
}
NSArray<FLEXProperty *> *FLEXGetAllProperties(_Nullable Class cls) {
if (!cls) return nil;
unsigned int pcount;
objc_property_t *objcproperties = class_copyPropertyList(cls, &pcount);
NSArray *properties = [NSArray flex_forEachUpTo:pcount map:^id(NSUInteger i) {
return [FLEXProperty property:objcproperties[i] onClass:cls];
}];
free(objcproperties);
return properties;
}
NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
if (!cls) return nil;
unsigned int mcount;
Method *objcmethods = class_copyMethodList(cls, &mcount);
NSArray *methods = [NSArray flex_forEachUpTo:mcount map:^id(NSUInteger i) {
return [FLEXMethod method:objcmethods[i] isInstanceMethod:instance];
}];
free(objcmethods);
return methods;
}
#pragma mark NSProxy
@interface NSProxy (AnyObjectAdditions) @end
@implementation NSProxy (AnyObjectAdditions)
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
// We need to get all of the methods in this file and add them to NSProxy.
// To do this we we need the class itself and it's metaclass.
// Edit: also add them to Swift._SwiftObject
Class NSProxyClass = [NSProxy class];
Class NSProxy_meta = object_getClass(NSProxyClass);
Class SwiftObjectClass = (
NSClassFromString(@"SwiftObject") ?: NSClassFromString(@"Swift._SwiftObject")
);
// Copy all of the "flex_" methods from NSObject
id filterFunc = ^BOOL(FLEXMethod *method, NSUInteger idx) {
return [method.name hasPrefix:@"flex_"];
};
NSArray *instanceMethods = [NSObject.flex_allInstanceMethods flex_filtered:filterFunc];
NSArray *classMethods = [NSObject.flex_allClassMethods flex_filtered:filterFunc];
FLEXClassBuilder *proxy = [FLEXClassBuilder builderForClass:NSProxyClass];
FLEXClassBuilder *proxyMeta = [FLEXClassBuilder builderForClass:NSProxy_meta];
[proxy addMethods:instanceMethods];
[proxyMeta addMethods:classMethods];
if (SwiftObjectClass) {
Class SwiftObject_meta = object_getClass(SwiftObjectClass);
FLEXClassBuilder *swiftObject = [FLEXClassBuilder builderForClass:SwiftObjectClass];
FLEXClassBuilder *swiftObjectMeta = [FLEXClassBuilder builderForClass:SwiftObject_meta];
[swiftObject addMethods:instanceMethods];
[swiftObjectMeta addMethods:classMethods];
// So we can put Swift objects into dictionaries...
[swiftObjectMeta addMethods:@[
[NSObject flex_classMethodNamed:@"copyWithZone:"]]
];
}
}
@end
#pragma mark Reflection
@implementation NSObject (Reflection)
+ (FLEXMirror *)flex_reflection {
return [FLEXMirror reflect:self];
}
- (FLEXMirror *)flex_reflection {
return [FLEXMirror reflect:self];
}
/// Code borrowed from MAObjCRuntime by Mike Ash
+ (NSArray *)flex_allSubclasses {
return FLEXGetAllSubclasses(self, YES);
}
- (Class)flex_setClass:(Class)cls {
return object_setClass(self, cls);
}
+ (Class)flex_metaclass {
return objc_getMetaClass(NSStringFromClass(self.class).UTF8String);
}
+ (size_t)flex_instanceSize {
return class_getInstanceSize(self.class);
}
+ (Class)flex_setSuperclass:(Class)superclass {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return class_setSuperclass(self, superclass);
#pragma clang diagnostic pop
}
+ (NSArray<Class> *)flex_classHierarchy {
return FLEXGetClassHierarchy(self, YES);
}
+ (NSArray<FLEXProtocol *> *)flex_protocols {
return FLEXGetConformedProtocols(self);
}
@end
#pragma mark Methods
@implementation NSObject (Methods)
+ (NSArray<FLEXMethod *> *)flex_allMethods {
NSMutableArray *instanceMethods = self.flex_allInstanceMethods.mutableCopy;
[instanceMethods addObjectsFromArray:self.flex_allClassMethods];
return instanceMethods;
}
+ (NSArray<FLEXMethod *> *)flex_allInstanceMethods {
return FLEXGetAllMethods(self, YES);
}
+ (NSArray<FLEXMethod *> *)flex_allClassMethods {
return FLEXGetAllMethods(self.flex_metaclass, NO) ?: @[];
}
+ (FLEXMethod *)flex_methodNamed:(NSString *)name {
Method m = class_getInstanceMethod([self class], NSSelectorFromString(name));
if (m == NULL) {
return nil;
}
return [FLEXMethod method:m isInstanceMethod:YES];
}
+ (FLEXMethod *)flex_classMethodNamed:(NSString *)name {
Method m = class_getClassMethod([self class], NSSelectorFromString(name));
if (m == NULL) {
return nil;
}
return [FLEXMethod method:m isInstanceMethod:NO];
}
+ (BOOL)addMethod:(SEL)selector
typeEncoding:(NSString *)typeEncoding
implementation:(IMP)implementaiton
toInstances:(BOOL)instance {
return class_addMethod(instance ? self.class : self.flex_metaclass, selector, implementaiton, typeEncoding.UTF8String);
}
+ (IMP)replaceImplementationOfMethod:(FLEXMethodBase *)method with:(IMP)implementation useInstance:(BOOL)instance {
return class_replaceMethod(instance ? self.class : self.flex_metaclass, method.selector, implementation, method.typeEncoding.UTF8String);
}
+ (void)swizzle:(FLEXMethodBase *)original with:(FLEXMethodBase *)other onInstance:(BOOL)instance {
[self swizzleBySelector:original.selector with:other.selector onInstance:instance];
}
+ (BOOL)swizzleByName:(NSString *)original with:(NSString *)other onInstance:(BOOL)instance {
SEL originalMethod = NSSelectorFromString(original);
SEL newMethod = NSSelectorFromString(other);
if (originalMethod == 0 || newMethod == 0) {
return NO;
}
[self swizzleBySelector:originalMethod with:newMethod onInstance:instance];
return YES;
}
+ (void)swizzleBySelector:(SEL)original with:(SEL)other onInstance:(BOOL)instance {
Class cls = instance ? self.class : self.flex_metaclass;
Method originalMethod = class_getInstanceMethod(cls, original);
Method newMethod = class_getInstanceMethod(cls, other);
if (class_addMethod(cls, original, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
class_replaceMethod(cls, other, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, newMethod);
}
}
@end
#pragma mark Ivars
@implementation NSObject (Ivars)
+ (NSArray<FLEXIvar *> *)flex_allIvars {
return FLEXGetAllIvars(self);
}
+ (FLEXIvar *)flex_ivarNamed:(NSString *)name {
Ivar i = class_getInstanceVariable([self class], name.UTF8String);
if (i == NULL) {
return nil;
}
return [FLEXIvar ivar:i];
}
#pragma mark Get address
- (void *)flex_getIvarAddress:(FLEXIvar *)ivar {
return (uint8_t *)(__bridge void *)self + ivar.offset;
}
- (void *)flex_getObjcIvarAddress:(Ivar)ivar {
return (uint8_t *)(__bridge void *)self + ivar_getOffset(ivar);
}
- (void *)flex_getIvarAddressByName:(NSString *)name {
Ivar ivar = class_getInstanceVariable(self.class, name.UTF8String);
if (!ivar) return 0;
return (uint8_t *)(__bridge void *)self + ivar_getOffset(ivar);
}
#pragma mark Set ivar object
- (void)flex_setIvar:(FLEXIvar *)ivar object:(id)value {
object_setIvar(self, ivar.objc_ivar, value);
}
- (BOOL)flex_setIvarByName:(NSString *)name object:(id)value {
Ivar ivar = class_getInstanceVariable(self.class, name.UTF8String);
if (!ivar) return NO;
object_setIvar(self, ivar, value);
return YES;
}
- (void)flex_setObjcIvar:(Ivar)ivar object:(id)value {
object_setIvar(self, ivar, value);
}
#pragma mark Set ivar value
- (void)flex_setIvar:(FLEXIvar *)ivar value:(void *)value size:(size_t)size {
void *address = [self flex_getIvarAddress:ivar];
memcpy(address, value, size);
}
- (BOOL)flex_setIvarByName:(NSString *)name value:(void *)value size:(size_t)size {
Ivar ivar = class_getInstanceVariable(self.class, name.UTF8String);
if (!ivar) return NO;
[self flex_setObjcIvar:ivar value:value size:size];
return YES;
}
- (void)flex_setObjcIvar:(Ivar)ivar value:(void *)value size:(size_t)size {
void *address = [self flex_getObjcIvarAddress:ivar];
memcpy(address, value, size);
}
@end
#pragma mark Properties
@implementation NSObject (Properties)
+ (NSArray<FLEXProperty *> *)flex_allProperties {
NSMutableArray *instanceProperties = self.flex_allInstanceProperties.mutableCopy;
[instanceProperties addObjectsFromArray:self.flex_allClassProperties];
return instanceProperties;
}
+ (NSArray<FLEXProperty *> *)flex_allInstanceProperties {
return FLEXGetAllProperties(self);
}
+ (NSArray<FLEXProperty *> *)flex_allClassProperties {
return FLEXGetAllProperties(self.flex_metaclass) ?: @[];
}
+ (FLEXProperty *)flex_propertyNamed:(NSString *)name {
objc_property_t p = class_getProperty([self class], name.UTF8String);
if (p == NULL) {
return nil;
}
return [FLEXProperty property:p onClass:self];
}
+ (FLEXProperty *)flex_classPropertyNamed:(NSString *)name {
objc_property_t p = class_getProperty(object_getClass(self), name.UTF8String);
if (p == NULL) {
return nil;
}
return [FLEXProperty property:p onClass:object_getClass(self)];
}
+ (void)flex_replaceProperty:(FLEXProperty *)property {
[self flex_replaceProperty:property.name attributes:property.attributes];
}
+ (void)flex_replaceProperty:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
unsigned int count;
objc_property_attribute_t *objc_attributes = [attributes copyAttributesList:&count];
class_replaceProperty([self class], name.UTF8String, objc_attributes, count);
free(objc_attributes);
}
@end

View File

@@ -0,0 +1,19 @@
//
// NSTimer+Blocks.h
// FLEX
//
// Created by Tanner on 3/23/17.
//
#import <Foundation/Foundation.h>
typedef void (^VoidBlock)(void);
@interface NSTimer (Blocks)
+ (instancetype)flex_fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
// Forward declaration
//+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
@end

View File

@@ -0,0 +1,25 @@
//
// NSTimer+Blocks.m
// FLEX
//
// Created by Tanner on 3/23/17.
//
#import "NSTimer+FLEX.h"
@interface Block : NSObject
- (void)invoke;
@end
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation NSTimer (Blocks)
+ (instancetype)flex_fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block {
if (@available(iOS 10, *)) {
return [self scheduledTimerWithTimeInterval:delay repeats:NO block:(id)block];
} else {
return [self scheduledTimerWithTimeInterval:delay target:block selector:@selector(invoke) userInfo:nil repeats:NO];
}
}
@end

View File

@@ -0,0 +1,51 @@
//
// NSUserDefaults+FLEX.h
// FLEX
//
// Created by Tanner on 3/10/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
// Only use these if the getters and setters aren't good enough for whatever reason
extern NSString * const kFLEXDefaultsToolbarTopMarginKey;
extern NSString * const kFLEXDefaultsiOSPersistentOSLogKey;
extern NSString * const kFLEXDefaultsHidePropertyIvarsKey;
extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
extern NSString * const kFLEXDefaultsHidePrivateMethodsKey;
extern NSString * const kFLEXDefaultsShowMethodOverridesKey;
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
extern NSString * const kFLEXDefaultsNetworkObserverEnabledKey;
extern NSString * const kFLEXDefaultsNetworkHostDenylistKey;
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
/// All BOOL preferences are NO by default
@interface NSUserDefaults (FLEX)
- (void)flex_toggleBoolForKey:(NSString *)key;
@property (nonatomic) double flex_toolbarTopMargin;
@property (nonatomic) BOOL flex_networkObserverEnabled;
// Not actually stored in defaults, but written to a file
@property (nonatomic) NSArray<NSString *> *flex_networkHostDenylist;
/// Whether or not to register the object explorer as a JSON viewer on launch
@property (nonatomic) BOOL flex_registerDictionaryJSONViewerOnLaunch;
/// The last selected screen in the network observer
@property (nonatomic) NSInteger flex_lastNetworkObserverMode;
/// Disable os_log and re-enable ASL. May break Console.app output.
@property (nonatomic) BOOL flex_disableOSLog;
@property (nonatomic) BOOL flex_cacheOSLogMessages;
@property (nonatomic) BOOL flex_explorerHidesPropertyIvars;
@property (nonatomic) BOOL flex_explorerHidesPropertyMethods;
@property (nonatomic) BOOL flex_explorerHidesPrivateMethods;
@property (nonatomic) BOOL flex_explorerShowsMethodOverrides;
@property (nonatomic) BOOL flex_explorerHidesVariablePreviews;
@end

View File

@@ -0,0 +1,188 @@
//
// NSUserDefaults+FLEX.m
// FLEX
//
// Created by Tanner on 3/10/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "NSUserDefaults+FLEX.h"
NSString * const kFLEXDefaultsToolbarTopMarginKey = @"com.flex.FLEXToolbar.topMargin";
NSString * const kFLEXDefaultsiOSPersistentOSLogKey = @"com.flipborad.flex.enable_persistent_os_log";
NSString * const kFLEXDefaultsHidePropertyIvarsKey = @"com.flipboard.FLEX.hide_property_ivars";
NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide_property_methods";
NSString * const kFLEXDefaultsHidePrivateMethodsKey = @"com.flipboard.FLEX.hide_private_or_namespaced_methods";
NSString * const kFLEXDefaultsShowMethodOverridesKey = @"com.flipboard.FLEX.show_method_overrides";
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
NSString * const kFLEXDefaultsNetworkObserverEnabledKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
NSString * const kFLEXDefaultsNetworkObserverLastModeKey = @"com.flex.FLEXNetworkObserver.lastMode";
NSString * const kFLEXDefaultsNetworkHostDenylistKey = @"com.flipboard.FLEX.network_host_denylist";
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.view_json_as_object";
#define FLEXDefaultsPathForFile(name) ({ \
NSArray *paths = NSSearchPathForDirectoriesInDomains( \
NSLibraryDirectory, NSUserDomainMask, YES \
); \
[paths[0] stringByAppendingPathComponent:@"Preferences"]; \
})
@implementation NSUserDefaults (FLEX)
#pragma mark Internal
/// @param filename the name of a plist file without any extension
- (NSString *)flex_defaultsPathForFile:(NSString *)filename {
filename = [filename stringByAppendingPathExtension:@"plist"];
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, YES
);
NSString *preferences = [paths[0] stringByAppendingPathComponent:@"Preferences"];
return [preferences stringByAppendingPathComponent:filename];
}
#pragma mark Helper
- (void)flex_toggleBoolForKey:(NSString *)key {
[self setBool:![self boolForKey:key] forKey:key];
[NSNotificationCenter.defaultCenter postNotificationName:key object:nil];
}
#pragma mark Misc
- (double)flex_toolbarTopMargin {
if ([self objectForKey:kFLEXDefaultsToolbarTopMarginKey]) {
return [self doubleForKey:kFLEXDefaultsToolbarTopMarginKey];
}
return 100;
}
- (void)setFlex_toolbarTopMargin:(double)margin {
[self setDouble:margin forKey:kFLEXDefaultsToolbarTopMarginKey];
}
- (BOOL)flex_networkObserverEnabled {
return [self boolForKey:kFLEXDefaultsNetworkObserverEnabledKey];
}
- (void)setFlex_networkObserverEnabled:(BOOL)enabled {
[self setBool:enabled forKey:kFLEXDefaultsNetworkObserverEnabledKey];
}
- (NSArray<NSString *> *)flex_networkHostDenylist {
return [NSArray arrayWithContentsOfFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
]] ?: @[];
}
- (void)setFlex_networkHostDenylist:(NSArray<NSString *> *)denylist {
NSParameterAssert(denylist);
[denylist writeToFile:[
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
] atomically:YES];
}
- (BOOL)flex_registerDictionaryJSONViewerOnLaunch {
return [self boolForKey:kFLEXDefaultsRegisterJSONExplorerKey];
}
- (void)setFlex_registerDictionaryJSONViewerOnLaunch:(BOOL)enable {
[self setBool:enable forKey:kFLEXDefaultsRegisterJSONExplorerKey];
}
- (NSInteger)flex_lastNetworkObserverMode {
return [self integerForKey:kFLEXDefaultsNetworkObserverLastModeKey];
}
- (void)setFlex_lastNetworkObserverMode:(NSInteger)mode {
[self setInteger:mode forKey:kFLEXDefaultsNetworkObserverLastModeKey];
}
#pragma mark System Log
- (BOOL)flex_disableOSLog {
return [self boolForKey:kFLEXDefaultsDisableOSLogForceASLKey];
}
- (void)setFlex_disableOSLog:(BOOL)disable {
[self setBool:disable forKey:kFLEXDefaultsDisableOSLogForceASLKey];
}
- (BOOL)flex_cacheOSLogMessages {
return [self boolForKey:kFLEXDefaultsiOSPersistentOSLogKey];
}
- (void)setFlex_cacheOSLogMessages:(BOOL)cache {
[self setBool:cache forKey:kFLEXDefaultsiOSPersistentOSLogKey];
[NSNotificationCenter.defaultCenter
postNotificationName:kFLEXDefaultsiOSPersistentOSLogKey
object:nil
];
}
#pragma mark Object Explorer
- (BOOL)flex_explorerHidesPropertyIvars {
return [self boolForKey:kFLEXDefaultsHidePropertyIvarsKey];
}
- (void)setFlex_explorerHidesPropertyIvars:(BOOL)hide {
[self setBool:hide forKey:kFLEXDefaultsHidePropertyIvarsKey];
[NSNotificationCenter.defaultCenter
postNotificationName:kFLEXDefaultsHidePropertyIvarsKey
object:nil
];
}
- (BOOL)flex_explorerHidesPropertyMethods {
return [self boolForKey:kFLEXDefaultsHidePropertyMethodsKey];
}
- (void)setFlex_explorerHidesPropertyMethods:(BOOL)hide {
[self setBool:hide forKey:kFLEXDefaultsHidePropertyMethodsKey];
[NSNotificationCenter.defaultCenter
postNotificationName:kFLEXDefaultsHidePropertyMethodsKey
object:nil
];
}
- (BOOL)flex_explorerHidesPrivateMethods {
return [self boolForKey:kFLEXDefaultsHidePrivateMethodsKey];
}
- (void)setFlex_explorerHidesPrivateMethods:(BOOL)show {
[self setBool:show forKey:kFLEXDefaultsHidePrivateMethodsKey];
[NSNotificationCenter.defaultCenter
postNotificationName:kFLEXDefaultsHidePrivateMethodsKey
object:nil
];
}
- (BOOL)flex_explorerShowsMethodOverrides {
return [self boolForKey:kFLEXDefaultsShowMethodOverridesKey];
}
- (void)setFlex_explorerShowsMethodOverrides:(BOOL)show {
[self setBool:show forKey:kFLEXDefaultsShowMethodOverridesKey];
[NSNotificationCenter.defaultCenter
postNotificationName:kFLEXDefaultsShowMethodOverridesKey
object:nil
];
}
- (BOOL)flex_explorerHidesVariablePreviews {
return [self boolForKey:kFLEXDefaultsHideVariablePreviewsKey];
}
- (void)setFlex_explorerHidesVariablePreviews:(BOOL)hide {
[self setBool:hide forKey:kFLEXDefaultsHideVariablePreviewsKey];
[NSNotificationCenter.defaultCenter
postNotificationName:kFLEXDefaultsHideVariablePreviewsKey
object:nil
];
}
@end

View File

@@ -0,0 +1,13 @@
//
// Cocoa+FLEXShortcuts.h
// Pods
//
// Created by Tanner on 2/24/21.
//
//
#import <UIKit/UIKit.h>
@interface UIAlertAction (FLEXShortcuts)
@property (nonatomic, readonly) NSString *flex_styleName;
@end

View File

@@ -0,0 +1,25 @@
//
// Cocoa+FLEXShortcuts.m
// Pods
//
// Created by Tanner on 2/24/21.
//
//
#import "Cocoa+FLEXShortcuts.h"
@implementation UIAlertAction (FLEXShortcuts)
- (NSString *)flex_styleName {
switch (self.style) {
case UIAlertActionStyleDefault:
return @"Default style";
case UIAlertActionStyleCancel:
return @"Cancel style";
case UIAlertActionStyleDestructive:
return @"Destructive style";
default:
return [NSString stringWithFormat:@"Unknown (%@)", @(self.style)];
}
}
@end

View File

@@ -0,0 +1,21 @@
//
// NSDictionary+ObjcRuntime.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/5/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface NSDictionary (ObjcRuntime)
/// \c kFLEXPropertyAttributeKeyTypeEncoding is the only required key.
/// Keys representing a boolean value should have a value of \c YES instead of an empty string.
- (NSString *)propertyAttributesString;
+ (instancetype)attributesDictionaryForProperty:(objc_property_t)property;
@end

View File

@@ -0,0 +1,107 @@
//
// NSDictionary+ObjcRuntime.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/5/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "NSDictionary+ObjcRuntime.h"
#import "FLEXRuntimeUtility.h"
@implementation NSDictionary (ObjcRuntime)
/// See this link on how to construct a proper attributes string:
/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
- (NSString *)propertyAttributesString {
if (!self[kFLEXPropertyAttributeKeyTypeEncoding]) return nil;
NSMutableString *attributes = [NSMutableString new];
[attributes appendFormat:@"T%@,", self[kFLEXPropertyAttributeKeyTypeEncoding]];
for (NSString *attribute in self.allKeys) {
FLEXPropertyAttribute c = (FLEXPropertyAttribute)[attribute characterAtIndex:0];
switch (c) {
case FLEXPropertyAttributeTypeEncoding:
break;
case FLEXPropertyAttributeBackingIvarName:
[attributes appendFormat:@"%@%@,",
kFLEXPropertyAttributeKeyBackingIvarName,
self[kFLEXPropertyAttributeKeyBackingIvarName]
];
break;
case FLEXPropertyAttributeCopy:
if ([self[kFLEXPropertyAttributeKeyCopy] boolValue])
[attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyCopy];
break;
case FLEXPropertyAttributeCustomGetter:
[attributes appendFormat:@"%@%@,",
kFLEXPropertyAttributeKeyCustomGetter,
self[kFLEXPropertyAttributeKeyCustomGetter]
];
break;
case FLEXPropertyAttributeCustomSetter:
[attributes appendFormat:@"%@%@,",
kFLEXPropertyAttributeKeyCustomSetter,
self[kFLEXPropertyAttributeKeyCustomSetter]
];
break;
case FLEXPropertyAttributeDynamic:
if ([self[kFLEXPropertyAttributeKeyDynamic] boolValue])
[attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyDynamic];
break;
case FLEXPropertyAttributeGarbageCollectible:
[attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyGarbageCollectable];
break;
case FLEXPropertyAttributeNonAtomic:
if ([self[kFLEXPropertyAttributeKeyNonAtomic] boolValue])
[attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyNonAtomic];
break;
case FLEXPropertyAttributeOldTypeEncoding:
[attributes appendFormat:@"%@%@,",
kFLEXPropertyAttributeKeyOldStyleTypeEncoding,
self[kFLEXPropertyAttributeKeyOldStyleTypeEncoding]
];
break;
case FLEXPropertyAttributeReadOnly:
if ([self[kFLEXPropertyAttributeKeyReadOnly] boolValue])
[attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyReadOnly];
break;
case FLEXPropertyAttributeRetain:
if ([self[kFLEXPropertyAttributeKeyRetain] boolValue])
[attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyRetain];
break;
case FLEXPropertyAttributeWeak:
if ([self[kFLEXPropertyAttributeKeyWeak] boolValue])
[attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyWeak];
break;
default:
return nil;
break;
}
}
[attributes deleteCharactersInRange:NSMakeRange(attributes.length-1, 1)];
return attributes.copy;
}
+ (instancetype)attributesDictionaryForProperty:(objc_property_t)property {
NSMutableDictionary *attrs = [NSMutableDictionary new];
for (NSString *key in FLEXRuntimeUtility.allPropertyAttributeKeys) {
char *value = property_copyAttributeValue(property, key.UTF8String);
if (value) {
attrs[key] = [[NSString alloc]
initWithBytesNoCopy:value
length:strlen(value)
encoding:NSUTF8StringEncoding
freeWhenDone:YES
];
}
}
return attrs.copy;
}
@end

View File

@@ -0,0 +1,20 @@
//
// NSMapTable+FLEX_Subscripting.h
// FLEX
//
// Created by Tanner Bennett on 1/9/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSMapTable<KeyType, ObjectType> (FLEX_Subscripting)
- (nullable ObjectType)objectForKeyedSubscript:(KeyType)key;
- (void)setObject:(nullable ObjectType)obj forKeyedSubscript:(KeyType <NSCopying>)key;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,21 @@
//
// NSMapTable+FLEX_Subscripting.m
// FLEX
//
// Created by Tanner Bennett on 1/9/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "NSMapTable+FLEX_Subscripting.h"
@implementation NSMapTable (FLEX_Subscripting)
- (id)objectForKeyedSubscript:(id)key {
return [self objectForKey:key];
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
[self setObject:obj forKey:key];
}
@end

View File

@@ -0,0 +1,33 @@
//
// NSString+FLEX.h
// FLEX
//
// Created by Tanner on 3/26/17.
// Copyright © 2017 Tanner Bennett. All rights reserved.
//
#import "FLEXRuntimeConstants.h"
@interface NSString (FLEXTypeEncoding)
///@return whether this type starts with the const specifier
@property (nonatomic, readonly) BOOL flex_typeIsConst;
/// @return the first char in the type encoding that is not the const specifier
@property (nonatomic, readonly) FLEXTypeEncoding flex_firstNonConstType;
/// @return the first char in the type encoding after the pointer specifier, if it is a pointer
@property (nonatomic, readonly) FLEXTypeEncoding flex_pointeeType;
/// @return whether this type is an objc object of any kind, even if it's const
@property (nonatomic, readonly) BOOL flex_typeIsObjectOrClass;
/// @return the class named in this type encoding if it is of the form \c @"MYClass"
@property (nonatomic, readonly) Class flex_typeClass;
/// Includes C strings and selectors as well as regular pointers
@property (nonatomic, readonly) BOOL flex_typeIsNonObjcPointer;
@end
@interface NSString (KeyPaths)
- (NSString *)flex_stringByRemovingLastKeyPathComponent;
- (NSString *)flex_stringByReplacingLastKeyPathComponent:(NSString *)replacement;
@end

View File

@@ -0,0 +1,160 @@
//
// NSString+FLEX.m
// FLEX
//
// Created by Tanner on 3/26/17.
// Copyright © 2017 Tanner Bennett. All rights reserved.
//
#import "NSString+FLEX.h"
@interface NSMutableString (Replacement)
- (void)replaceOccurencesOfString:(NSString *)string with:(NSString *)replacement;
- (void)removeLastKeyPathComponent;
@end
@implementation NSMutableString (Replacement)
- (void)replaceOccurencesOfString:(NSString *)string with:(NSString *)replacement {
[self replaceOccurrencesOfString:string withString:replacement options:0 range:NSMakeRange(0, self.length)];
}
- (void)removeLastKeyPathComponent {
if (![self containsString:@"."]) {
[self deleteCharactersInRange:NSMakeRange(0, self.length)];
return;
}
BOOL putEscapesBack = NO;
if ([self containsString:@"\\."]) {
[self replaceOccurencesOfString:@"\\." with:@"\\~"];
// Case like "UIKit\.framework"
if (![self containsString:@"."]) {
[self deleteCharactersInRange:NSMakeRange(0, self.length)];
return;
}
putEscapesBack = YES;
}
// Case like "Bund" or "Bundle.cla"
if (![self hasSuffix:@"."]) {
NSUInteger len = self.pathExtension.length;
[self deleteCharactersInRange:NSMakeRange(self.length-len, len)];
}
if (putEscapesBack) {
[self replaceOccurencesOfString:@"\\~" with:@"\\."];
}
}
@end
@implementation NSString (FLEXTypeEncoding)
- (NSCharacterSet *)flex_classNameAllowedCharactersSet {
static NSCharacterSet *classNameAllowedCharactersSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableCharacterSet *temp = NSMutableCharacterSet.alphanumericCharacterSet;
[temp addCharactersInString:@"_"];
classNameAllowedCharactersSet = temp.copy;
});
return classNameAllowedCharactersSet;
}
- (BOOL)flex_typeIsConst {
if (!self.length) return NO;
return [self characterAtIndex:0] == FLEXTypeEncodingConst;
}
- (FLEXTypeEncoding)flex_firstNonConstType {
if (!self.length) return FLEXTypeEncodingNull;
return [self characterAtIndex:(self.flex_typeIsConst ? 1 : 0)];
}
- (FLEXTypeEncoding)flex_pointeeType {
if (!self.length) return FLEXTypeEncodingNull;
if (self.flex_firstNonConstType == FLEXTypeEncodingPointer) {
return [self characterAtIndex:(self.flex_typeIsConst ? 2 : 1)];
}
return FLEXTypeEncodingNull;
}
- (BOOL)flex_typeIsObjectOrClass {
FLEXTypeEncoding type = self.flex_firstNonConstType;
return type == FLEXTypeEncodingObjcObject || type == FLEXTypeEncodingObjcClass;
}
- (Class)flex_typeClass {
if (!self.flex_typeIsObjectOrClass) {
return nil;
}
NSScanner *scan = [NSScanner scannerWithString:self];
// Skip const
[scan scanString:@"r" intoString:nil];
// Scan leading @"
if (![scan scanString:@"@\"" intoString:nil]) {
return nil;
}
// Scan class name
NSString *name = nil;
if (![scan scanCharactersFromSet:self.flex_classNameAllowedCharactersSet intoString:&name]) {
return nil;
}
// Scan trailing quote
if (![scan scanString:@"\"" intoString:nil]) {
return nil;
}
// Return found class
return NSClassFromString(name);
}
- (BOOL)flex_typeIsNonObjcPointer {
FLEXTypeEncoding type = self.flex_firstNonConstType;
return type == FLEXTypeEncodingPointer ||
type == FLEXTypeEncodingCString ||
type == FLEXTypeEncodingSelector;
}
@end
@implementation NSString (KeyPaths)
- (NSString *)flex_stringByRemovingLastKeyPathComponent {
if (![self containsString:@"."]) {
return @"";
}
NSMutableString *mself = self.mutableCopy;
[mself removeLastKeyPathComponent];
return mself;
}
- (NSString *)flex_stringByReplacingLastKeyPathComponent:(NSString *)replacement {
// replacement should not have any escaped '.' in it,
// so we escape all '.'
if ([replacement containsString:@"."]) {
replacement = [replacement stringByReplacingOccurrencesOfString:@"." withString:@"\\."];
}
// Case like "Foo"
if (![self containsString:@"."]) {
return [replacement stringByAppendingString:@"."];
}
NSMutableString *mself = self.mutableCopy;
[mself removeLastKeyPathComponent];
[mself appendString:replacement];
[mself appendString:@"."];
return mself;
}
@end

View File

@@ -0,0 +1,23 @@
//
// NSString+ObjcRuntime.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/1/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString (Utilities)
/// A dictionary of property attributes if the receiver is a valid property attributes string.
/// Values are either a string or \c YES. Boolean attributes which are false will not be
/// present in the dictionary. See this link on how to construct a proper attributes string:
/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
///
/// Note: this method doesn't work properly for certain type encodings, and neither does
/// the property_copyAttributeValue function in the runtime itself. Radar: FB7499230
- (NSDictionary *)propertyAttributes;
@end

View File

@@ -0,0 +1,75 @@
//
// NSString+ObjcRuntime.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/1/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "NSString+ObjcRuntime.h"
#import "FLEXRuntimeUtility.h"
@implementation NSString (Utilities)
- (NSString *)stringbyDeletingCharacterAtIndex:(NSUInteger)idx {
NSMutableString *string = self.mutableCopy;
[string replaceCharactersInRange:NSMakeRange(idx, 1) withString:@""];
return string;
}
/// See this link on how to construct a proper attributes string:
/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
- (NSDictionary *)propertyAttributes {
if (!self.length) return nil;
NSMutableDictionary *attributes = [NSMutableDictionary new];
NSArray *components = [self componentsSeparatedByString:@","];
for (NSString *attribute in components) {
FLEXPropertyAttribute c = (FLEXPropertyAttribute)[attribute characterAtIndex:0];
switch (c) {
case FLEXPropertyAttributeTypeEncoding:
// Note: the type encoding here is not always correct. Radar: FB7499230
attributes[kFLEXPropertyAttributeKeyTypeEncoding] = [attribute stringbyDeletingCharacterAtIndex:0];
break;
case FLEXPropertyAttributeBackingIvarName:
attributes[kFLEXPropertyAttributeKeyBackingIvarName] = [attribute stringbyDeletingCharacterAtIndex:0];
break;
case FLEXPropertyAttributeCopy:
attributes[kFLEXPropertyAttributeKeyCopy] = @YES;
break;
case FLEXPropertyAttributeCustomGetter:
attributes[kFLEXPropertyAttributeKeyCustomGetter] = [attribute stringbyDeletingCharacterAtIndex:0];
break;
case FLEXPropertyAttributeCustomSetter:
attributes[kFLEXPropertyAttributeKeyCustomSetter] = [attribute stringbyDeletingCharacterAtIndex:0];
break;
case FLEXPropertyAttributeDynamic:
attributes[kFLEXPropertyAttributeKeyDynamic] = @YES;
break;
case FLEXPropertyAttributeGarbageCollectible:
attributes[kFLEXPropertyAttributeKeyGarbageCollectable] = @YES;
break;
case FLEXPropertyAttributeNonAtomic:
attributes[kFLEXPropertyAttributeKeyNonAtomic] = @YES;
break;
case FLEXPropertyAttributeOldTypeEncoding:
attributes[kFLEXPropertyAttributeKeyOldStyleTypeEncoding] = [attribute stringbyDeletingCharacterAtIndex:0];
break;
case FLEXPropertyAttributeReadOnly:
attributes[kFLEXPropertyAttributeKeyReadOnly] = @YES;
break;
case FLEXPropertyAttributeRetain:
attributes[kFLEXPropertyAttributeKeyRetain] = @YES;
break;
case FLEXPropertyAttributeWeak:
attributes[kFLEXPropertyAttributeKeyWeak] = @YES;
break;
}
}
return attributes;
}
@end

View File

@@ -0,0 +1,23 @@
//
// UIView+FLEX_Layout.h
// FLEX
//
// Created by Tanner Bennett on 7/18/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
#define Padding(p) UIEdgeInsetsMake(p, p, p, p)
@interface UIView (FLEX_Layout)
- (void)flex_centerInView:(UIView *)view;
- (void)flex_pinEdgesTo:(UIView *)view;
- (void)flex_pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)insets;
- (void)flex_pinEdgesToSuperview;
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets;
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets aboveView:(UIView *)sibling;
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets belowView:(UIView *)sibling;
@end

View File

@@ -0,0 +1,66 @@
//
// UIView+FLEX_Layout.m
// FLEX
//
// Created by Tanner Bennett on 7/18/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "UIView+FLEX_Layout.h"
@implementation UIView (FLEX_Layout)
- (void)flex_centerInView:(UIView *)view {
[NSLayoutConstraint activateConstraints:@[
[self.centerXAnchor constraintEqualToAnchor:view.centerXAnchor],
[self.centerYAnchor constraintEqualToAnchor:view.centerYAnchor],
]];
}
- (void)flex_pinEdgesTo:(UIView *)view {
[NSLayoutConstraint activateConstraints:@[
[self.topAnchor constraintEqualToAnchor:view.topAnchor],
[self.leftAnchor constraintEqualToAnchor:view.leftAnchor],
[self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
[self.rightAnchor constraintEqualToAnchor:view.rightAnchor],
]];
}
- (void)flex_pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)i {
[NSLayoutConstraint activateConstraints:@[
[self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
[self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
[self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-i.bottom],
[self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right],
]];
}
- (void)flex_pinEdgesToSuperview {
[self flex_pinEdgesTo:self.superview];
}
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets {
[self flex_pinEdgesTo:self.superview withInsets:insets];
}
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i aboveView:(UIView *)sibling {
UIView *view = self.superview;
[NSLayoutConstraint activateConstraints:@[
[self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
[self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
[self.bottomAnchor constraintEqualToAnchor:sibling.topAnchor constant:-i.bottom],
[self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right],
]];
}
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i belowView:(UIView *)sibling {
UIView *view = self.superview;
[NSLayoutConstraint activateConstraints:@[
[self.topAnchor constraintEqualToAnchor:sibling.bottomAnchor constant:i.top],
[self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
[self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-i.bottom],
[self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right],
]];
}
@end

View File

@@ -0,0 +1,40 @@
//
// UIBarButtonItem+FLEX.h
// FLEX
//
// Created by Tanner on 2/4/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
#define FLEXBarButtonItem(title, tgt, sel) \
[UIBarButtonItem flex_itemWithTitle:title target:tgt action:sel]
#define FLEXBarButtonItemSystem(item, tgt, sel) \
[UIBarButtonItem flex_systemItem:UIBarButtonSystemItem##item target:tgt action:sel]
@interface UIBarButtonItem (FLEX)
@property (nonatomic, readonly, class) UIBarButtonItem *flex_flexibleSpace;
@property (nonatomic, readonly, class) UIBarButtonItem *flex_fixedSpace;
+ (instancetype)flex_itemWithCustomView:(UIView *)customView;
+ (instancetype)flex_backItemWithTitle:(NSString *)title;
+ (instancetype)flex_systemItem:(UIBarButtonSystemItem)item target:(id)target action:(SEL)action;
+ (instancetype)flex_itemWithTitle:(NSString *)title target:(id)target action:(SEL)action;
+ (instancetype)flex_doneStyleitemWithTitle:(NSString *)title target:(id)target action:(SEL)action;
+ (instancetype)flex_itemWithImage:(UIImage *)image target:(id)target action:(SEL)action;
+ (instancetype)flex_disabledSystemItem:(UIBarButtonSystemItem)item;
+ (instancetype)flex_disabledItemWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style;
+ (instancetype)flex_disabledItemWithImage:(UIImage *)image;
/// @return the receiver
- (UIBarButtonItem *)flex_withTintColor:(UIColor *)tint;
- (void)_setWidth:(CGFloat)width;
@end

View File

@@ -0,0 +1,72 @@
//
// UIBarButtonItem+FLEX.m
// FLEX
//
// Created by Tanner on 2/4/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "UIBarButtonItem+FLEX.h"
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation UIBarButtonItem (FLEX)
+ (UIBarButtonItem *)flex_flexibleSpace {
return [self flex_systemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
}
+ (UIBarButtonItem *)flex_fixedSpace {
UIBarButtonItem *fixed = [self flex_systemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
fixed.width = 60;
return fixed;
}
+ (instancetype)flex_systemItem:(UIBarButtonSystemItem)item target:(id)target action:(SEL)action {
return [[self alloc] initWithBarButtonSystemItem:item target:target action:action];
}
+ (instancetype)flex_itemWithCustomView:(UIView *)customView {
return [[self alloc] initWithCustomView:customView];
}
+ (instancetype)flex_backItemWithTitle:(NSString *)title {
return [self flex_itemWithTitle:title target:nil action:nil];
}
+ (instancetype)flex_itemWithTitle:(NSString *)title target:(id)target action:(SEL)action {
return [[self alloc] initWithTitle:title style:UIBarButtonItemStylePlain target:target action:action];
}
+ (instancetype)flex_doneStyleitemWithTitle:(NSString *)title target:(id)target action:(SEL)action {
return [[self alloc] initWithTitle:title style:UIBarButtonItemStyleDone target:target action:action];
}
+ (instancetype)flex_itemWithImage:(UIImage *)image target:(id)target action:(SEL)action {
return [[self alloc] initWithImage:image style:UIBarButtonItemStylePlain target:target action:action];
}
+ (instancetype)flex_disabledSystemItem:(UIBarButtonSystemItem)system {
UIBarButtonItem *item = [self flex_systemItem:system target:nil action:nil];
item.enabled = NO;
return item;
}
+ (instancetype)flex_disabledItemWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style {
UIBarButtonItem *item = [self flex_itemWithTitle:title target:nil action:nil];
item.enabled = NO;
return item;
}
+ (instancetype)flex_disabledItemWithImage:(UIImage *)image {
UIBarButtonItem *item = [self flex_itemWithImage:image target:nil action:nil];
item.enabled = NO;
return item;
}
- (UIBarButtonItem *)flex_withTintColor:(UIColor *)tint {
self.tintColor = tint;
return self;
}
@end

View File

@@ -0,0 +1,17 @@
//
// UIFont+FLEX.h
// FLEX
//
// Created by Tanner Bennett on 12/20/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIFont (FLEX)
@property (nonatomic, readonly, class) UIFont *flex_defaultTableCellFont;
@property (nonatomic, readonly, class) UIFont *flex_codeFont;
@property (nonatomic, readonly, class) UIFont *flex_smallCodeFont;
@end

View File

@@ -0,0 +1,43 @@
//
// UIFont+FLEX.m
// FLEX
//
// Created by Tanner Bennett on 12/20/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "UIFont+FLEX.h"
#define kFLEXDefaultCellFontSize 12.0
@implementation UIFont (FLEX)
+ (UIFont *)flex_defaultTableCellFont {
static UIFont *defaultTableCellFont = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultTableCellFont = [UIFont systemFontOfSize:kFLEXDefaultCellFontSize];
});
return defaultTableCellFont;
}
+ (UIFont *)flex_codeFont {
// Actually only available in iOS 13, the SDK headers are wrong
if (@available(iOS 13, *)) {
return [self monospacedSystemFontOfSize:kFLEXDefaultCellFontSize weight:UIFontWeightRegular];
} else {
return [self fontWithName:@"Menlo-Regular" size:kFLEXDefaultCellFontSize];
}
}
+ (UIFont *)flex_smallCodeFont {
// Actually only available in iOS 13, the SDK headers are wrong
if (@available(iOS 13, *)) {
return [self monospacedSystemFontOfSize:self.smallSystemFontSize weight:UIFontWeightRegular];
} else {
return [self fontWithName:@"Menlo-Regular" size:self.smallSystemFontSize];
}
}
@end

View File

@@ -0,0 +1,21 @@
//
// UIGestureRecognizer+Blocks.h
// FLEX
//
// Created by Tanner Bennett on 12/20/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef void (^GestureBlock)(UIGestureRecognizer *gesture);
@interface UIGestureRecognizer (Blocks)
+ (instancetype)flex_action:(GestureBlock)action;
@property (nonatomic, setter=flex_setAction:) GestureBlock flex_action;
@end

View File

@@ -0,0 +1,36 @@
//
// UIGestureRecognizer+Blocks.m
// FLEX
//
// Created by Tanner Bennett on 12/20/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "UIGestureRecognizer+Blocks.h"
#import <objc/runtime.h>
@implementation UIGestureRecognizer (Blocks)
static void * actionKey;
+ (instancetype)flex_action:(GestureBlock)action {
UIGestureRecognizer *gesture = [[self alloc] initWithTarget:nil action:nil];
[gesture addTarget:gesture action:@selector(flex_invoke)];
gesture.flex_action = action;
return gesture;
}
- (void)flex_invoke {
self.flex_action(self);
}
- (GestureBlock)flex_action {
return objc_getAssociatedObject(self, &actionKey);
}
- (void)flex_setAction:(GestureBlock)action {
objc_setAssociatedObject(self, &actionKey, action, OBJC_ASSOCIATION_COPY);
}
@end

View File

@@ -0,0 +1,19 @@
//
// UIMenu+FLEX.h
// FLEX
//
// Created by Tanner on 1/28/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIMenu (FLEX)
+ (instancetype)flex_inlineMenuWithTitle:(NSString *)title
image:(UIImage *)image
children:(NSArray<UIMenuElement *> *)children;
- (instancetype)flex_collapsed;
@end

View File

@@ -0,0 +1,39 @@
//
// UIMenu+FLEX.m
// FLEX
//
// Created by Tanner on 1/28/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "UIMenu+FLEX.h"
@implementation UIMenu (FLEX)
+ (instancetype)flex_inlineMenuWithTitle:(NSString *)title image:(UIImage *)image children:(NSArray *)children {
return [UIMenu
menuWithTitle:title
image:image
identifier:nil
options:UIMenuOptionsDisplayInline
children:children
];
}
- (instancetype)flex_collapsed {
return [UIMenu
menuWithTitle:@""
image:nil
identifier:nil
options:UIMenuOptionsDisplayInline
children:@[[UIMenu
menuWithTitle:self.title
image:self.image
identifier:self.identifier
options:0
children:self.children
]]
];
}
@end

View File

@@ -0,0 +1,16 @@
//
// UIPasteboard+FLEX.h
// FLEX
//
// Created by Tanner Bennett on 12/9/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIPasteboard (FLEX)
/// For copying an object which could be a string, data, or number
- (void)flex_copy:(id)unknownType;
@end

View File

@@ -0,0 +1,31 @@
//
// UIPasteboard+FLEX.m
// FLEX
//
// Created by Tanner Bennett on 12/9/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "UIPasteboard+FLEX.h"
@implementation UIPasteboard (FLEX)
- (void)flex_copy:(id)object {
if (!object) {
return;
}
if ([object isKindOfClass:[NSString class]]) {
UIPasteboard.generalPasteboard.string = object;
} else if([object isKindOfClass:[NSData class]]) {
[UIPasteboard.generalPasteboard setData:object forPasteboardType:@"public.data"];
} else if ([object isKindOfClass:[NSNumber class]]) {
UIPasteboard.generalPasteboard.string = [object stringValue];
} else {
// TODO: make this an alert instead of an exception
[NSException raise:NSInternalInconsistencyException
format:@"Tried to copy unsupported type: %@", [object class]];
}
}
@end

View File

@@ -0,0 +1,14 @@
//
// UITextField+Range.h
// FLEX
//
// Created by Tanner on 6/13/17.
//
#import <UIKit/UIKit.h>
@interface UITextField (Range)
@property (nonatomic, readonly) NSRange flex_selectedRange;
@end

View File

@@ -0,0 +1,23 @@
//
// UITextField+Range.m
// FLEX
//
// Created by Tanner on 6/13/17.
//
#import "UITextField+Range.h"
@implementation UITextField (Range)
- (NSRange)flex_selectedRange {
UITextRange *r = self.selectedTextRange;
if (r) {
NSInteger loc = [self offsetFromPosition:self.beginningOfDocument toPosition:r.start];
NSInteger len = [self offsetFromPosition:r.start toPosition:r.end];
return NSMakeRange(loc, len);
}
return NSMakeRange(NSNotFound, 0);
}
@end