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,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 exigé que le présent contrat et tous les documents
connexes soient rédigés 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."

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

View File

@@ -0,0 +1,85 @@
//
// FLEXAlert.h
// FLEX
//
// Created by Tanner Bennett on 8/20/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class FLEXAlert, FLEXAlertAction;
typedef void (^FLEXAlertReveal)(void);
typedef void (^FLEXAlertBuilder)(FLEXAlert *make);
typedef FLEXAlert * _Nonnull (^FLEXAlertStringProperty)(NSString * _Nullable);
typedef FLEXAlert * _Nonnull (^FLEXAlertStringArg)(NSString * _Nullable);
typedef FLEXAlert * _Nonnull (^FLEXAlertTextField)(void(^configurationHandler)(UITextField *textField));
typedef FLEXAlertAction * _Nonnull (^FLEXAlertAddAction)(NSString *title);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionStringProperty)(NSString * _Nullable);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionProperty)(void);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionBOOLProperty)(BOOL);
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionHandler)(void(^handler)(NSArray<NSString *> *strings));
@interface FLEXAlert : NSObject
/// Shows a simple alert with one button which says "Dismiss"
+ (void)showAlert:(NSString * _Nullable)title message:(NSString * _Nullable)message from:(UIViewController *)viewController;
/// Construct and display an alert
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController;
/// Construct and display an action sheet-style alert
+ (void)makeSheet:(FLEXAlertBuilder)block
showFrom:(UIViewController *)viewController
source:(id)viewOrBarItem;
/// Construct an alert
+ (UIAlertController *)makeAlert:(FLEXAlertBuilder)block;
/// Construct an action sheet-style alert
+ (UIAlertController *)makeSheet:(FLEXAlertBuilder)block;
/// Set the alert's title.
///
/// Call in succession to append strings to the title.
@property (nonatomic, readonly) FLEXAlertStringProperty title;
/// Set the alert's message.
///
/// Call in succession to append strings to the message.
@property (nonatomic, readonly) FLEXAlertStringProperty message;
/// Add a button with a given title with the default style and no action.
@property (nonatomic, readonly) FLEXAlertAddAction button;
/// Add a text field with the given (optional) placeholder text.
@property (nonatomic, readonly) FLEXAlertStringArg textField;
/// Add and configure the given text field.
///
/// Use this if you need to more than set the placeholder, such as
/// supply a delegate, make it secure entry, or change other attributes.
@property (nonatomic, readonly) FLEXAlertTextField configuredTextField;
@end
@interface FLEXAlertAction : NSObject
/// Set the action's title.
///
/// Call in succession to append strings to the title.
@property (nonatomic, readonly) FLEXAlertActionStringProperty title;
/// Make the action destructive. It appears with red text.
@property (nonatomic, readonly) FLEXAlertActionProperty destructiveStyle;
/// Make the action cancel-style. It appears with a bolder font.
@property (nonatomic, readonly) FLEXAlertActionProperty cancelStyle;
/// Enable or disable the action. Enabled by default.
@property (nonatomic, readonly) FLEXAlertActionBOOLProperty enabled;
/// Give the button an action. The action takes an array of text field strings.
@property (nonatomic, readonly) FLEXAlertActionHandler handler;
/// Access the underlying UIAlertAction, should you need to change it while
/// the encompassing alert is being displayed. For example, you may want to
/// enable or disable a button based on the input of some text fields in the alert.
/// Do not call this more than once per instance.
@property (nonatomic, readonly) UIAlertAction *action;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,233 @@
//
// FLEXAlert.m
// FLEX
//
// Created by Tanner Bennett on 8/20/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXAlert.h"
#import "FLEXMacros.h"
@interface FLEXAlert ()
@property (nonatomic, readonly) UIAlertController *_controller;
@property (nonatomic, readonly) NSMutableArray<FLEXAlertAction *> *_actions;
@end
#define FLEXAlertActionMutationAssertion() \
NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAlertAction");
@interface FLEXAlertAction ()
@property (nonatomic) UIAlertController *_controller;
@property (nonatomic) NSString *_title;
@property (nonatomic) UIAlertActionStyle _style;
@property (nonatomic) BOOL _disable;
@property (nonatomic) void(^_handler)(UIAlertAction *action);
@property (nonatomic) UIAlertAction *_action;
@end
@implementation FLEXAlert
+ (void)showAlert:(NSString *)title message:(NSString *)message from:(UIViewController *)viewController {
[self makeAlert:^(FLEXAlert *make) {
make.title(title).message(message).button(@"Dismiss").cancelStyle();
} showFrom:viewController];
}
#pragma mark Initialization
- (instancetype)initWithController:(UIAlertController *)controller {
self = [super init];
if (self) {
__controller = controller;
__actions = [NSMutableArray new];
}
return self;
}
+ (UIAlertController *)make:(FLEXAlertBuilder)block withStyle:(UIAlertControllerStyle)style {
// Create alert builder
FLEXAlert *alert = [[self alloc] initWithController:
[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:style]
];
// Configure alert
block(alert);
// Add actions
for (FLEXAlertAction *builder in alert._actions) {
[alert._controller addAction:builder.action];
}
return alert._controller;
}
+ (void)make:(FLEXAlertBuilder)block
withStyle:(UIAlertControllerStyle)style
showFrom:(UIViewController *)viewController
source:(id)viewOrBarItem {
UIAlertController *alert = [self make:block withStyle:style];
if ([viewOrBarItem isKindOfClass:[UIBarButtonItem class]]) {
alert.popoverPresentationController.barButtonItem = viewOrBarItem;
} else if ([viewOrBarItem isKindOfClass:[UIView class]]) {
alert.popoverPresentationController.sourceView = viewOrBarItem;
alert.popoverPresentationController.sourceRect = [viewOrBarItem bounds];
} else if (viewOrBarItem) {
NSParameterAssert(
[viewOrBarItem isKindOfClass:[UIBarButtonItem class]] ||
[viewOrBarItem isKindOfClass:[UIView class]] ||
!viewOrBarItem
);
}
[viewController presentViewController:alert animated:YES completion:nil];
}
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
[self make:block withStyle:UIAlertControllerStyleAlert showFrom:controller source:nil];
}
+ (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
[self make:block withStyle:UIAlertControllerStyleActionSheet showFrom:controller source:nil];
}
/// Construct and display an action sheet-style alert
+ (void)makeSheet:(FLEXAlertBuilder)block
showFrom:(UIViewController *)controller
source:(id)viewOrBarItem {
[self make:block
withStyle:UIAlertControllerStyleActionSheet
showFrom:controller
source:viewOrBarItem];
}
+ (UIAlertController *)makeAlert:(FLEXAlertBuilder)block {
return [self make:block withStyle:UIAlertControllerStyleAlert];
}
+ (UIAlertController *)makeSheet:(FLEXAlertBuilder)block {
return [self make:block withStyle:UIAlertControllerStyleActionSheet];
}
#pragma mark Configuration
- (FLEXAlertStringProperty)title {
return ^FLEXAlert *(NSString *title) {
if (self._controller.title) {
self._controller.title = [self._controller.title stringByAppendingString:title ?: @""];
} else {
self._controller.title = title;
}
return self;
};
}
- (FLEXAlertStringProperty)message {
return ^FLEXAlert *(NSString *message) {
if (self._controller.message) {
self._controller.message = [self._controller.message stringByAppendingString:message ?: @""];
} else {
self._controller.message = message;
}
return self;
};
}
- (FLEXAlertAddAction)button {
return ^FLEXAlertAction *(NSString *title) {
FLEXAlertAction *action = FLEXAlertAction.new.title(title);
action._controller = self._controller;
[self._actions addObject:action];
return action;
};
}
- (FLEXAlertStringArg)textField {
return ^FLEXAlert *(NSString *placeholder) {
[self._controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = placeholder;
}];
return self;
};
}
- (FLEXAlertTextField)configuredTextField {
return ^FLEXAlert *(void(^configurationHandler)(UITextField *)) {
[self._controller addTextFieldWithConfigurationHandler:configurationHandler];
return self;
};
}
@end
@implementation FLEXAlertAction
- (FLEXAlertActionStringProperty)title {
return ^FLEXAlertAction *(NSString *title) {
FLEXAlertActionMutationAssertion();
if (self._title) {
self._title = [self._title stringByAppendingString:title ?: @""];
} else {
self._title = title;
}
return self;
};
}
- (FLEXAlertActionProperty)destructiveStyle {
return ^FLEXAlertAction *() {
FLEXAlertActionMutationAssertion();
self._style = UIAlertActionStyleDestructive;
return self;
};
}
- (FLEXAlertActionProperty)cancelStyle {
return ^FLEXAlertAction *() {
FLEXAlertActionMutationAssertion();
self._style = UIAlertActionStyleCancel;
return self;
};
}
- (FLEXAlertActionBOOLProperty)enabled {
return ^FLEXAlertAction *(BOOL enabled) {
FLEXAlertActionMutationAssertion();
self._disable = !enabled;
return self;
};
}
- (FLEXAlertActionHandler)handler {
return ^FLEXAlertAction *(void(^handler)(NSArray<NSString *> *)) {
FLEXAlertActionMutationAssertion();
// Get weak reference to the alert to avoid block <--> alert retain cycle
UIAlertController *controller = self._controller; weakify(controller)
self._handler = ^(UIAlertAction *action) { strongify(controller)
// Strongify that reference and pass the text field strings to the handler
NSArray *strings = [controller.textFields valueForKeyPath:@"text"];
handler(strings);
};
return self;
};
}
- (UIAlertAction *)action {
if (self._action) {
return self._action;
}
self._action = [UIAlertAction
actionWithTitle:self._title
style:self._style
handler:self._handler
];
self._action.enabled = !self._disable;
return self._action;
}
@end

View File

@@ -0,0 +1,47 @@
//
// FLEXColor.h
// FLEX
//
// Created by Benny Wong on 6/18/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FLEXColor : NSObject
@property (readonly, class) UIColor *primaryBackgroundColor;
+ (UIColor *)primaryBackgroundColorWithAlpha:(CGFloat)alpha;
@property (readonly, class) UIColor *secondaryBackgroundColor;
+ (UIColor *)secondaryBackgroundColorWithAlpha:(CGFloat)alpha;
@property (readonly, class) UIColor *tertiaryBackgroundColor;
+ (UIColor *)tertiaryBackgroundColorWithAlpha:(CGFloat)alpha;
@property (readonly, class) UIColor *groupedBackgroundColor;
+ (UIColor *)groupedBackgroundColorWithAlpha:(CGFloat)alpha;
@property (readonly, class) UIColor *secondaryGroupedBackgroundColor;
+ (UIColor *)secondaryGroupedBackgroundColorWithAlpha:(CGFloat)alpha;
// Text colors
@property (readonly, class) UIColor *primaryTextColor;
@property (readonly, class) UIColor *deemphasizedTextColor;
// UI element colors
@property (readonly, class) UIColor *tintColor;
@property (readonly, class) UIColor *scrollViewBackgroundColor;
@property (readonly, class) UIColor *iconColor;
@property (readonly, class) UIColor *borderColor;
@property (readonly, class) UIColor *toolbarItemHighlightedColor;
@property (readonly, class) UIColor *toolbarItemSelectedColor;
@property (readonly, class) UIColor *hairlineColor;
@property (readonly, class) UIColor *destructiveColor;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,136 @@
//
// FLEXColor.m
// FLEX
//
// Created by Benny Wong on 6/18/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXColor.h"
#import "FLEXUtility.h"
#define FLEXDynamicColor(dynamic, static) ({ \
UIColor *c; \
if (@available(iOS 13.0, *)) { \
c = [UIColor dynamic]; \
} else { \
c = [UIColor static]; \
} \
c; \
});
@implementation FLEXColor
#pragma mark - Background Colors
+ (UIColor *)primaryBackgroundColor {
return FLEXDynamicColor(systemBackgroundColor, whiteColor);
}
+ (UIColor *)primaryBackgroundColorWithAlpha:(CGFloat)alpha {
return [[self primaryBackgroundColor] colorWithAlphaComponent:alpha];
}
+ (UIColor *)secondaryBackgroundColor {
return FLEXDynamicColor(
secondarySystemBackgroundColor,
colorWithHue:2.0/3.0 saturation:0.02 brightness:0.97 alpha:1
);
}
+ (UIColor *)secondaryBackgroundColorWithAlpha:(CGFloat)alpha {
return [[self secondaryBackgroundColor] colorWithAlphaComponent:alpha];
}
+ (UIColor *)tertiaryBackgroundColor {
// All the background/fill colors are varying shades
// of white and black with really low alpha levels.
// We use systemGray4Color instead to avoid alpha issues.
return FLEXDynamicColor(systemGray4Color, lightGrayColor);
}
+ (UIColor *)tertiaryBackgroundColorWithAlpha:(CGFloat)alpha {
return [[self tertiaryBackgroundColor] colorWithAlphaComponent:alpha];
}
+ (UIColor *)groupedBackgroundColor {
return FLEXDynamicColor(
systemGroupedBackgroundColor,
colorWithHue:2.0/3.0 saturation:0.02 brightness:0.97 alpha:1
);
}
+ (UIColor *)groupedBackgroundColorWithAlpha:(CGFloat)alpha {
return [[self groupedBackgroundColor] colorWithAlphaComponent:alpha];
}
+ (UIColor *)secondaryGroupedBackgroundColor {
return FLEXDynamicColor(secondarySystemGroupedBackgroundColor, whiteColor);
}
+ (UIColor *)secondaryGroupedBackgroundColorWithAlpha:(CGFloat)alpha {
return [[self secondaryGroupedBackgroundColor] colorWithAlphaComponent:alpha];
}
#pragma mark - Text colors
+ (UIColor *)primaryTextColor {
return FLEXDynamicColor(labelColor, blackColor);
}
+ (UIColor *)deemphasizedTextColor {
return FLEXDynamicColor(secondaryLabelColor, lightGrayColor);
}
#pragma mark - UI Element Colors
+ (UIColor *)tintColor {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
return UIColor.systemBlueColor;
} else {
return UIApplication.sharedApplication.keyWindow.tintColor;
}
#else
return UIApplication.sharedApplication.keyWindow.tintColor;
#endif
}
+ (UIColor *)scrollViewBackgroundColor {
return FLEXDynamicColor(
systemGroupedBackgroundColor,
colorWithHue:2.0/3.0 saturation:0.02 brightness:0.95 alpha:1
);
}
+ (UIColor *)iconColor {
return FLEXDynamicColor(labelColor, blackColor);
}
+ (UIColor *)borderColor {
return [self primaryBackgroundColor];
}
+ (UIColor *)toolbarItemHighlightedColor {
return FLEXDynamicColor(
quaternaryLabelColor,
colorWithHue:2.0/3.0 saturation:0.1 brightness:0.25 alpha:0.6
);
}
+ (UIColor *)toolbarItemSelectedColor {
return FLEXDynamicColor(
secondaryLabelColor,
colorWithHue:2.0/3.0 saturation:0.1 brightness:0.25 alpha:0.68
);
}
+ (UIColor *)hairlineColor {
return FLEXDynamicColor(systemGray3Color, colorWithWhite:0.75 alpha:1);
}
+ (UIColor *)destructiveColor {
return FLEXDynamicColor(systemRedColor, redColor);
}
@end

View File

@@ -0,0 +1,25 @@
//
// FLEXHeapEnumerator.h
// Flipboard
//
// Created by Ryan Olson on 5/28/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
@class FLEXObjectRef;
typedef void (^flex_object_enumeration_block_t)(__unsafe_unretained id object, __unsafe_unretained Class actualClass);
@interface FLEXHeapEnumerator : NSObject
+ (void)enumerateLiveObjectsUsingBlock:(flex_object_enumeration_block_t)block;
/// Returned references are not validated beyond containing a valid isa.
/// To validate them yourself, pass each reference's object to \c FLEXPointerIsValidObjcObject
+ (NSArray<FLEXObjectRef *> *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain;
+ (NSArray<FLEXObjectRef *> *)subclassesOfClassWithName:(NSString *)className;
/// Returned references have been validated via \c FLEXPointerIsValidObjcObject
+ (NSArray<FLEXObjectRef *> *)objectsWithReferencesToObject:(id)object retained:(BOOL)retain;
@end

View File

@@ -0,0 +1,197 @@
//
// FLEXHeapEnumerator.m
// Flipboard
//
// Created by Ryan Olson on 5/28/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXHeapEnumerator.h"
#import "FLEXObjcInternal.h"
#import "FLEXObjectRef.h"
#import "NSObject+FLEX_Reflection.h"
#import "NSString+FLEX.h"
#import <malloc/malloc.h>
#import <mach/mach.h>
#import <objc/runtime.h>
static CFMutableSetRef registeredClasses;
// Mimics the objective-c object structure for checking if a range of memory is an object.
typedef struct {
Class isa;
} flex_maybe_object_t;
@implementation FLEXHeapEnumerator
static void range_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount) {
if (!context) {
return;
}
for (unsigned int i = 0; i < rangeCount; i++) {
vm_range_t range = ranges[i];
flex_maybe_object_t *tryObject = (flex_maybe_object_t *)range.address;
Class tryClass = NULL;
#ifdef __arm64__
// See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
extern uint64_t objc_debug_isa_class_mask WEAK_IMPORT_ATTRIBUTE;
tryClass = (__bridge Class)((void *)((uint64_t)tryObject->isa & objc_debug_isa_class_mask));
#else
tryClass = tryObject->isa;
#endif
// If the class pointer matches one in our set of class pointers from the runtime, then we should have an object.
if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
(*(flex_object_enumeration_block_t __unsafe_unretained *)context)((__bridge id)tryObject, tryClass);
}
}
}
static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_address, __unused vm_size_t size, void **local_memory) {
*local_memory = (void *)remote_address;
return KERN_SUCCESS;
}
+ (void)enumerateLiveObjectsUsingBlock:(flex_object_enumeration_block_t)block {
if (!block) {
return;
}
// Refresh the class list on every call in case classes are added to the runtime.
[self updateRegisteredClasses];
// Inspired by:
// https://llvm.org/svn/llvm-project/lldb/tags/RELEASE_34/final/examples/darwin/heap_find/heap/heap_find.cpp
// https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396
vm_address_t *zones = NULL;
unsigned int zoneCount = 0;
kern_return_t result = malloc_get_all_zones(TASK_NULL, reader, &zones, &zoneCount);
if (result == KERN_SUCCESS) {
for (unsigned int i = 0; i < zoneCount; i++) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
malloc_introspection_t *introspection = zone->introspect;
// This may explain why some zone functions are
// sometimes invalid; perhaps not all zones support them?
if (!introspection) {
continue;
}
void (*lock_zone)(malloc_zone_t *zone) = introspection->force_lock;
void (*unlock_zone)(malloc_zone_t *zone) = introspection->force_unlock;
// Callback has to unlock the zone so we freely allocate memory inside the given block
flex_object_enumeration_block_t callback = ^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
unlock_zone(zone);
block(object, actualClass);
lock_zone(zone);
};
BOOL lockZoneValid = FLEXPointerIsReadable(lock_zone);
BOOL unlockZoneValid = FLEXPointerIsReadable(unlock_zone);
// 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 whether the pointer is readable
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);
}
}
}
}
+ (void)updateRegisteredClasses {
if (!registeredClasses) {
registeredClasses = CFSetCreateMutable(NULL, 0, NULL);
} else {
CFSetRemoveAllValues(registeredClasses);
}
unsigned int count = 0;
Class *classes = objc_copyClassList(&count);
for (unsigned int i = 0; i < count; i++) {
CFSetAddValue(registeredClasses, (__bridge const void *)(classes[i]));
}
free(classes);
}
+ (NSArray<FLEXObjectRef *> *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain {
const char *classNameCString = className.UTF8String;
NSMutableArray *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
// Note: objects of certain classes crash when retain is called.
// It is up to the user to avoid tapping into instance lists for these classes.
// Ex. OS_dispatch_queue_specific_queue
// In the future, we could provide some kind of warning for classes that are known to be problematic.
if (malloc_size((__bridge const void *)(object)) > 0) {
[instances addObject:object];
}
}
}];
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances retained:retain];
return references;
}
+ (NSArray<FLEXObjectRef *> *)subclassesOfClassWithName:(NSString *)className {
NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
return references;
}
+ (NSArray<FLEXObjectRef *> *)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
static Class SwiftObjectClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SwiftObjectClass = NSClassFromString(@"SwiftObject");
if (!SwiftObjectClass) {
SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
}
});
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Skip known-invalid objects
if (!FLEXPointerIsValidObjcObject((__bridge void *)tryObject)) {
return;
}
// Get all the ivars on the object. Start with the class and and travel up the
// inheritance chain. Once we find a match, record it and move on to the next object.
// There's no reason to find multiple matches within the same object.
Class tryClass = actualClass;
while (tryClass) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
Ivar ivar = ivars[ivarIndex];
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
if (typeEncoding.flex_typeIsObjectOrClass) {
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
id ref = [FLEXObjectRef referencing:tryObject ivar:ivarName retained:retain];
[instances addObject:ref];
return;
}
}
}
free(ivars);
tryClass = class_getSuperclass(tryClass);
}
}];
return instances;
}
@end

View File

@@ -0,0 +1,95 @@
//
// FLEXMacros.h
// FLEX
//
// Created by Tanner on 3/12/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#ifndef FLEXMacros_h
#define FLEXMacros_h
#define flex_keywordify class NSObject;
#define ctor flex_keywordify __attribute__((constructor)) void __flex_ctor_##__LINE__()
#define dtor flex_keywordify __attribute__((destructor)) void __flex_dtor_##__LINE__()
#ifndef strongify
#define weakify(var) __weak __typeof(var) __weak__##var = var;
#define strongify(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong typeof(var) var = __weak__##var; \
_Pragma("clang diagnostic pop")
#endif
// A macro to check if we are running in a test environment
#define FLEX_IS_TESTING() (NSClassFromString(@"XCTest") != nil)
/// Whether we want the majority of constructors to run upon load or not.
extern BOOL FLEXConstructorsShouldRun(void);
/// A macro to return from the current procedure if we don't want to run constructors
#define FLEX_EXIT_IF_NO_CTORS() if (!FLEXConstructorsShouldRun()) return;
/// Rounds down to the nearest "point" coordinate
NS_INLINE CGFloat FLEXFloor(CGFloat x) {
return floor(UIScreen.mainScreen.scale * (x)) / UIScreen.mainScreen.scale;
}
/// Returns the given number of points in pixels
NS_INLINE CGFloat FLEXPointsToPixels(CGFloat points) {
return points / UIScreen.mainScreen.scale;
}
/// Creates a CGRect with all members rounded down to the nearest "point" coordinate
NS_INLINE CGRect FLEXRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) {
return CGRectMake(FLEXFloor(x), FLEXFloor(y), FLEXFloor(width), FLEXFloor(height));
}
/// Adjusts the origin of an existing rect
NS_INLINE CGRect FLEXRectSetOrigin(CGRect r, CGPoint origin) {
r.origin = origin; return r;
}
/// Adjusts the size of an existing rect
NS_INLINE CGRect FLEXRectSetSize(CGRect r, CGSize size) {
r.size = size; return r;
}
/// Adjusts the origin.x of an existing rect
NS_INLINE CGRect FLEXRectSetX(CGRect r, CGFloat x) {
r.origin.x = x; return r;
}
/// Adjusts the origin.y of an existing rect
NS_INLINE CGRect FLEXRectSetY(CGRect r, CGFloat y) {
r.origin.y = y ; return r;
}
/// Adjusts the size.width of an existing rect
NS_INLINE CGRect FLEXRectSetWidth(CGRect r, CGFloat width) {
r.size.width = width; return r;
}
/// Adjusts the size.height of an existing rect
NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
r.size.height = height; return r;
}
#define FLEXPluralString(count, plural, singular) [NSString \
stringWithFormat:@"%@ %@", @(count), (count == 1 ? singular : plural) \
]
#define FLEXPluralFormatString(count, pluralFormat, singularFormat) [NSString \
stringWithFormat:(count == 1 ? singularFormat : pluralFormat), @(count) \
]
#define flex_dispatch_after(nSeconds, onQueue, block) \
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, \
(int64_t)(nSeconds * NSEC_PER_SEC)), onQueue, block)
#endif /* FLEXMacros_h */

View File

@@ -0,0 +1,59 @@
//
// FLEXResources.h
// FLEX
//
// Created by Ryan Olson on 6/8/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FLEXResources : NSObject
#pragma mark - FLEX Toolbar Icons
@property (readonly, class) UIImage *closeIcon;
@property (readonly, class) UIImage *dragHandle;
@property (readonly, class) UIImage *globalsIcon;
@property (readonly, class) UIImage *hierarchyIcon;
@property (readonly, class) UIImage *recentIcon;
@property (readonly, class) UIImage *moveIcon;
@property (readonly, class) UIImage *selectIcon;
#pragma mark - Toolbar Icons
@property (readonly, class) UIImage *bookmarksIcon;
@property (readonly, class) UIImage *openTabsIcon;
@property (readonly, class) UIImage *moreIcon;
@property (readonly, class) UIImage *gearIcon;
@property (readonly, class) UIImage *scrollToBottomIcon;
#pragma mark - Content Type Icons
@property (readonly, class) UIImage *jsonIcon;
@property (readonly, class) UIImage *textPlainIcon;
@property (readonly, class) UIImage *htmlIcon;
@property (readonly, class) UIImage *audioIcon;
@property (readonly, class) UIImage *jsIcon;
@property (readonly, class) UIImage *plistIcon;
@property (readonly, class) UIImage *textIcon;
@property (readonly, class) UIImage *videoIcon;
@property (readonly, class) UIImage *xmlIcon;
@property (readonly, class) UIImage *binaryIcon;
#pragma mark - 3D Explorer Icons
@property (readonly, class) UIImage *toggle2DIcon;
@property (readonly, class) UIImage *toggle3DIcon;
@property (readonly, class) UIImage *rangeSliderLeftHandle;
@property (readonly, class) UIImage *rangeSliderRightHandle;
@property (readonly, class) UIImage *rangeSliderTrack;
@property (readonly, class) UIImage *rangeSliderFill;
#pragma mark - Misc Icons
@property (readonly, class) UIImage *checkerPattern;
@property (readonly, class) UIColor *checkerPatternColor;
@property (readonly, class) UIImage *hierarchyIndentPattern;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
//
// FLEXUtility.h
// Flipboard
//
// Created by Ryan Olson on 4/18/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Availability.h>
#import <AvailabilityInternal.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import "FLEXTypeEncodingParser.h"
#import "FLEXAlert.h"
#import "NSArray+FLEX.h"
#import "UIFont+FLEX.h"
#import "FLEXMacros.h"
@interface FLEXUtility : NSObject
/// The key window of the app, if it is not a \c FLEXWindow.
/// If it is, then \c FLEXWindow.previousKeyWindow is returned.
@property (nonatomic, readonly, class) UIWindow *appKeyWindow;
/// @return the result of +[UIWindow allWindowsIncludingInternalWindows:onlyVisibleWindows:]
@property (nonatomic, readonly, class) NSArray<UIWindow *> *allWindows;
/// The first active \c UIWindowScene of the app.
@property (nonatomic, readonly, class) UIWindowScene *activeScene API_AVAILABLE(ios(13.0));
/// @return top-most view controller of the given window
+ (UIViewController *)topViewControllerInWindow:(UIWindow *)window;
+ (UIColor *)consistentRandomColorForObject:(id)object;
+ (NSString *)descriptionForView:(UIView *)view includingFrame:(BOOL)includeFrame;
+ (NSString *)stringForCGRect:(CGRect)rect;
+ (UIViewController *)viewControllerForView:(UIView *)view;
+ (UIViewController *)viewControllerForAncestralView:(UIView *)view;
+ (UIImage *)previewImageForView:(UIView *)view;
+ (UIImage *)previewImageForLayer:(CALayer *)layer;
+ (NSString *)detailDescriptionForView:(UIView *)view;
+ (UIImage *)circularImageWithColor:(UIColor *)color radius:(CGFloat)radius;
+ (UIColor *)hierarchyIndentPatternColor;
+ (NSString *)pointerToString:(void *)ptr;
+ (NSString *)addressOfObject:(id)object;
+ (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString;
+ (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask;
+ (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data;
+ (NSString *)stringFromRequestDuration:(NSTimeInterval)duration;
+ (NSString *)statusCodeStringFromURLResponse:(NSURLResponse *)response;
+ (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response;
+ (NSArray<NSURLQueryItem *> *)itemsFromQueryString:(NSString *)query;
+ (NSString *)prettyJSONStringFromData:(NSData *)data;
+ (BOOL)isValidJSONData:(NSData *)data;
+ (NSData *)inflatedDataFromCompressedData:(NSData *)compressedData;
// Swizzling utilities
+ (SEL)swizzledSelectorForSelector:(SEL)selector;
+ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls;
+ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector onClass:(Class)cls withBlock:(id)block swizzledSelector:(SEL)swizzledSelector;
+ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock;
@end

View File

@@ -0,0 +1,527 @@
//
// FLEXUtility.m
// Flipboard
//
// Created by Ryan Olson on 4/18/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXColor.h"
#import "FLEXUtility.h"
#import "FLEXResources.h"
#import "FLEXWindow.h"
#import <ImageIO/ImageIO.h>
#import <objc/runtime.h>
#import <zlib.h>
BOOL FLEXConstructorsShouldRun() {
#if FLEX_DISABLE_CTORS
return NO;
#else
static BOOL _FLEXConstructorsShouldRun_storage = YES;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *key = @"FLEX_SKIP_INIT";
if (getenv(key.UTF8String) || [NSUserDefaults.standardUserDefaults boolForKey:key]) {
_FLEXConstructorsShouldRun_storage = NO;
}
});
return _FLEXConstructorsShouldRun_storage;
#endif
}
@implementation FLEXUtility
+ (UIWindow *)appKeyWindow {
// First, check UIApplication.keyWindow
FLEXWindow *window = (id)UIApplication.sharedApplication.keyWindow;
if (window) {
if ([window isKindOfClass:[FLEXWindow class]]) {
return window.previousKeyWindow;
}
return window;
}
// As of iOS 13, UIApplication.keyWindow does not return nil,
// so this is more of a safeguard against it returning nil in the future.
//
// Also, these are obviously not all FLEXWindows; FLEXWindow is used
// so we can call window.previousKeyWindow without an ugly cast
for (FLEXWindow *window in UIApplication.sharedApplication.windows) {
if (window.isKeyWindow) {
if ([window isKindOfClass:[FLEXWindow class]]) {
return window.previousKeyWindow;
}
return window;
}
}
return nil;
}
+ (UIWindowScene *)activeScene {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
// Look for an active UIWindowScene
if (scene.activationState == UISceneActivationStateForegroundActive &&
[scene isKindOfClass:[UIWindowScene class]]) {
return (UIWindowScene *)scene;
}
}
return nil;
}
+ (UIViewController *)topViewControllerInWindow:(UIWindow *)window {
UIViewController *topViewController = window.rootViewController;
while (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
}
return topViewController;
}
+ (UIColor *)consistentRandomColorForObject:(id)object {
CGFloat hue = (((NSUInteger)object >> 4) % 256) / 255.0;
return [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0];
}
+ (NSString *)descriptionForView:(UIView *)view includingFrame:(BOOL)includeFrame {
NSString *description = [[view class] description];
NSString *viewControllerDescription = [[[self viewControllerForView:view] class] description];
if (viewControllerDescription.length > 0) {
description = [description stringByAppendingFormat:@" (%@)", viewControllerDescription];
}
if (includeFrame) {
description = [description stringByAppendingFormat:@" %@", [self stringForCGRect:view.frame]];
}
if (view.accessibilityLabel.length > 0) {
description = [description stringByAppendingFormat:@" · %@", view.accessibilityLabel];
}
return description;
}
+ (NSString *)stringForCGRect:(CGRect)rect {
return [NSString stringWithFormat:@"{(%g, %g), (%g, %g)}",
rect.origin.x, rect.origin.y, rect.size.width, rect.size.height
];
}
+ (UIViewController *)viewControllerForView:(UIView *)view {
NSString *viewDelegate = @"_viewDelegate";
if ([view respondsToSelector:NSSelectorFromString(viewDelegate)]) {
return [view valueForKey:viewDelegate];
}
return nil;
}
+ (UIViewController *)viewControllerForAncestralView:(UIView *)view {
NSString *_viewControllerForAncestor = @"_viewControllerForAncestor";
if ([view respondsToSelector:NSSelectorFromString(_viewControllerForAncestor)]) {
return [view valueForKey:_viewControllerForAncestor];
}
return nil;
}
+ (UIImage *)previewImageForView:(UIView *)view {
if (CGRectIsEmpty(view.bounds)) {
return [UIImage new];
}
CGSize viewSize = view.bounds.size;
UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
[view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return previewImage;
}
+ (UIImage *)previewImageForLayer:(CALayer *)layer {
if (CGRectIsEmpty(layer.bounds)) {
return nil;
}
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, NO, 0.0);
CGContextRef imageContext = UIGraphicsGetCurrentContext();
[layer renderInContext:imageContext];
UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return previewImage;
}
+ (NSString *)detailDescriptionForView:(UIView *)view {
return [NSString stringWithFormat:@"frame %@", [self stringForCGRect:view.frame]];
}
+ (UIImage *)circularImageWithColor:(UIColor *)color radius:(CGFloat)radius {
CGFloat diameter = radius * 2.0;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), NO, 0.0);
CGContextRef imageContext = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(imageContext, color.CGColor);
CGContextFillEllipseInRect(imageContext, CGRectMake(0, 0, diameter, diameter));
UIImage *circularImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return circularImage;
}
+ (UIColor *)hierarchyIndentPatternColor {
static UIColor *patternColor = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIImage *indentationPatternImage = FLEXResources.hierarchyIndentPattern;
patternColor = [UIColor colorWithPatternImage:indentationPatternImage];
if (@available(iOS 13.0, *)) {
// Create a dark mode version
UIGraphicsBeginImageContextWithOptions(
indentationPatternImage.size, NO, indentationPatternImage.scale
);
[FLEXColor.iconColor set];
[indentationPatternImage drawInRect:CGRectMake(
0, 0, indentationPatternImage.size.width, indentationPatternImage.size.height
)];
UIImage *darkModePatternImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Create dynamic color provider
patternColor = [UIColor colorWithDynamicProvider:^UIColor *(UITraitCollection *traitCollection) {
return (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight
? [UIColor colorWithPatternImage:indentationPatternImage]
: [UIColor colorWithPatternImage:darkModePatternImage]);
}];
}
});
return patternColor;
}
+ (NSString *)applicationImageName {
return NSBundle.mainBundle.executablePath;
}
+ (NSString *)applicationName {
return FLEXUtility.applicationImageName.lastPathComponent;
}
+ (NSString *)pointerToString:(void *)ptr {
return [NSString stringWithFormat:@"%p", ptr];
}
+ (NSString *)addressOfObject:(id)object {
return [NSString stringWithFormat:@"%p", object];
}
+ (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString {
static NSDictionary<NSString *, NSString *> *escapingDictionary = nil;
static NSRegularExpression *regex = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
escapingDictionary = @{ @" " : @"&nbsp;",
@">" : @"&gt;",
@"<" : @"&lt;",
@"&" : @"&amp;",
@"'" : @"&apos;",
@"\"" : @"&quot;",
@"«" : @"&laquo;",
@"»" : @"&raquo;"
};
regex = [NSRegularExpression regularExpressionWithPattern:@"(&|>|<|'|\"|«|»)" options:0 error:NULL];
});
NSMutableString *mutableString = originalString.mutableCopy;
NSArray<NSTextCheckingResult *> *matches = [regex
matchesInString:mutableString options:0 range:NSMakeRange(0, mutableString.length)
];
for (NSTextCheckingResult *result in matches.reverseObjectEnumerator) {
NSString *foundString = [mutableString substringWithRange:result.range];
NSString *replacementString = escapingDictionary[foundString];
if (replacementString) {
[mutableString replaceCharactersInRange:result.range withString:replacementString];
}
}
return [mutableString copy];
}
+ (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask {
NSArray<NSString *> *supportedOrientations = NSBundle.mainBundle.infoDictionary[@"UISupportedInterfaceOrientations"];
UIInterfaceOrientationMask supportedOrientationsMask = 0;
if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortrait"]) {
supportedOrientationsMask |= UIInterfaceOrientationMaskPortrait;
}
if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskLandscapeRight"]) {
supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeRight;
}
if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskPortraitUpsideDown"]) {
supportedOrientationsMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
}
if ([supportedOrientations containsObject:@"UIInterfaceOrientationLandscapeLeft"]) {
supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeLeft;
}
return supportedOrientationsMask;
}
+ (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data {
UIImage *thumbnail = nil;
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, 0);
if (imageSource) {
NSDictionary<NSString *, id> *options = @{
(__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(dimension)
};
CGImageRef scaledImageRef = CGImageSourceCreateThumbnailAtIndex(
imageSource, 0, (__bridge CFDictionaryRef)options
);
if (scaledImageRef) {
thumbnail = [UIImage imageWithCGImage:scaledImageRef];
CFRelease(scaledImageRef);
}
CFRelease(imageSource);
}
return thumbnail;
}
+ (NSString *)stringFromRequestDuration:(NSTimeInterval)duration {
NSString *string = @"0s";
if (duration > 0.0) {
if (duration < 1.0) {
string = [NSString stringWithFormat:@"%dms", (int)(duration * 1000)];
} else if (duration < 10.0) {
string = [NSString stringWithFormat:@"%.2fs", duration];
} else {
string = [NSString stringWithFormat:@"%.1fs", duration];
}
}
return string;
}
+ (NSString *)statusCodeStringFromURLResponse:(NSURLResponse *)response {
NSString *httpResponseString = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSString *statusCodeDescription = nil;
if (httpResponse.statusCode == 200) {
// Prefer OK to the default "no error"
statusCodeDescription = @"OK";
} else {
statusCodeDescription = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
}
httpResponseString = [NSString stringWithFormat:@"%ld %@", (long)httpResponse.statusCode, statusCodeDescription];
}
return httpResponseString;
}
+ (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return httpResponse.statusCode >= 400;
}
return NO;
}
+ (NSArray<NSURLQueryItem *> *)itemsFromQueryString:(NSString *)query {
NSMutableArray<NSURLQueryItem *> *items = [NSMutableArray new];
// [a=1, b=2, c=3]
NSArray<NSString *> *queryComponents = [query componentsSeparatedByString:@"&"];
for (NSString *keyValueString in queryComponents) {
// [a, 1]
NSArray<NSString *> *components = [keyValueString componentsSeparatedByString:@"="];
if (components.count == 2) {
NSString *key = components.firstObject.stringByRemovingPercentEncoding;
NSString *value = components.lastObject.stringByRemovingPercentEncoding;
[items addObject:[NSURLQueryItem queryItemWithName:key value:value]];
}
}
return items.copy;
}
+ (NSString *)prettyJSONStringFromData:(NSData *)data {
NSString *prettyString = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
if ([NSJSONSerialization isValidJSONObject:jsonObject]) {
// Thanks RaziPour1993
prettyString = [[NSString alloc]
initWithData:[NSJSONSerialization
dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL
]
encoding:NSUTF8StringEncoding
];
// NSJSONSerialization escapes forward slashes.
// We want pretty json, so run through and unescape the slashes.
prettyString = [prettyString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
} else {
prettyString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return prettyString;
}
+ (BOOL)isValidJSONData:(NSData *)data {
return [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL] ? YES : NO;
}
// Thanks to the following links for help with this method
// https://www.cocoanetics.com/2012/02/decompressing-files-into-memory/
// https://github.com/nicklockwood/GZIP
+ (NSData *)inflatedDataFromCompressedData:(NSData *)compressedData {
NSData *inflatedData = nil;
NSUInteger compressedDataLength = compressedData.length;
if (compressedDataLength > 0) {
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.avail_in = (uInt)compressedDataLength;
stream.next_in = (void *)compressedData.bytes;
stream.total_out = 0;
stream.avail_out = 0;
NSMutableData *mutableData = [NSMutableData dataWithLength:compressedDataLength * 1.5];
if (inflateInit2(&stream, 15 + 32) == Z_OK) {
int status = Z_OK;
while (status == Z_OK) {
if (stream.total_out >= mutableData.length) {
mutableData.length += compressedDataLength / 2;
}
stream.next_out = (uint8_t *)[mutableData mutableBytes] + stream.total_out;
stream.avail_out = (uInt)(mutableData.length - stream.total_out);
status = inflate(&stream, Z_SYNC_FLUSH);
}
if (inflateEnd(&stream) == Z_OK) {
if (status == Z_STREAM_END) {
mutableData.length = stream.total_out;
inflatedData = [mutableData copy];
}
}
}
}
return inflatedData;
}
+ (NSArray<UIWindow *> *)allWindows {
BOOL includeInternalWindows = YES;
BOOL onlyVisibleWindows = NO;
// Obfuscating selector allWindowsIncludingInternalWindows:onlyVisibleWindows:
NSArray<NSString *> *allWindowsComponents = @[
@"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:"
];
SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = [UIWindow class];
invocation.selector = allWindowsSelector;
[invocation setArgument:&includeInternalWindows atIndex:2];
[invocation setArgument:&onlyVisibleWindows atIndex:3];
[invocation invoke];
__unsafe_unretained NSArray<UIWindow *> *windows = nil;
[invocation getReturnValue:&windows];
return windows;
}
+ (UIAlertController *)alert:(NSString *)title message:(NSString *)message {
return [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert
];
}
+ (SEL)swizzledSelectorForSelector:(SEL)selector {
return NSSelectorFromString([NSString stringWithFormat:
@"_flex_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)
]);
}
+ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls {
if ([cls instancesRespondToSelector:selector]) {
unsigned int numMethods = 0;
Method *methods = class_copyMethodList(cls, &numMethods);
BOOL implementsSelector = NO;
for (int index = 0; index < numMethods; index++) {
SEL methodSelector = method_getName(methods[index]);
if (selector == methodSelector) {
implementsSelector = YES;
break;
}
}
free(methods);
if (!implementsSelector) {
return YES;
}
}
return NO;
}
+ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector
onClass:(Class)class
withBlock:(id)block
swizzledSelector:(SEL)swizzledSelector {
// This method is only intended for swizzling methods that are know to exist on the class.
// Bail if that isn't the case.
Method originalMethod = class_getInstanceMethod(class, originalSelector);
if (!originalMethod) {
return;
}
IMP implementation = imp_implementationWithBlock(block);
class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalMethod));
Method newMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, newMethod);
}
+ (void)replaceImplementationOfSelector:(SEL)selector
withSelector:(SEL)swizzledSelector
forClass:(Class)cls
withMethodDescription:(struct objc_method_description)methodDescription
implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock {
if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
return;
}
IMP implementation = imp_implementationWithBlock((id)(
[cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock)
);
Method oldMethod = class_getInstanceMethod(cls, selector);
const char *types = methodDescription.types;
if (oldMethod) {
if (!types) {
types = method_getTypeEncoding(oldMethod);
}
class_addMethod(cls, swizzledSelector, implementation, types);
Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(oldMethod, newMethod);
} else {
if (!types) {
// Some protocol method descriptions don't have .types populated
// Set the return type to void and ignore arguments
types = "v@:";
}
class_addMethod(cls, selector, implementation, types);
}
}
@end

View File

@@ -0,0 +1,13 @@
//
// FLEXKeyboardHelpViewController.h
// FLEX
//
// Created by Ryan Olson on 9/19/15.
// Copyright © 2015 f. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FLEXKeyboardHelpViewController : UIViewController
@end

View File

@@ -0,0 +1,42 @@
//
// FLEXKeyboardHelpViewController.m
// FLEX
//
// Created by Ryan Olson on 9/19/15.
// Copyright © 2015 f. All rights reserved.
//
#import "FLEXKeyboardHelpViewController.h"
#import "FLEXKeyboardShortcutManager.h"
@interface FLEXKeyboardHelpViewController ()
@property (nonatomic) UITextView *textView;
@end
@implementation FLEXKeyboardHelpViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.textView = [[UITextView alloc] initWithFrame:self.view.bounds];
self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.textView];
#if TARGET_OS_SIMULATOR
self.textView.text = FLEXKeyboardShortcutManager.sharedManager.keyboardShortcutsDescription;
#endif
self.textView.backgroundColor = UIColor.blackColor;
self.textView.textColor = UIColor.whiteColor;
self.textView.font = [UIFont boldSystemFontOfSize:14.0];
self.navigationController.navigationBar.barStyle = UIBarStyleBlackOpaque;
self.title = @"Simulator Shortcuts";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(donePressed:)];
}
- (void)donePressed:(id)sender {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
@end

View File

@@ -0,0 +1,29 @@
//
// FLEXKeyboardShortcutManager.h
// FLEX
//
// Created by Ryan Olson on 9/19/15.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FLEXKeyboardShortcutManager : NSObject
@property (nonatomic, readonly, class) FLEXKeyboardShortcutManager *sharedManager;
/// @param key A single character string matching a key on the keyboard
/// @param modifiers Modifier keys such as shift, command, or alt/option
/// @param action The block to run on the main thread when the key & modifier combination is recognized.
/// @param description Shown the the keyboard shortcut help menu, which is accessed via the '?' key.
/// @param allowOverride Allow registering even if there's an existing action associated with that key/modifier.
- (void)registerSimulatorShortcutWithKey:(NSString *)key
modifiers:(UIKeyModifierFlags)modifiers
action:(dispatch_block_t)action
description:(NSString *)description
allowOverride:(BOOL)allowOverride;
@property (nonatomic, getter=isEnabled) BOOL enabled;
@property (nonatomic, readonly) NSString *keyboardShortcutsDescription;
@end

View File

@@ -0,0 +1,326 @@
//
// FLEXKeyboardShortcutManager.m
// FLEX
//
// Created by Ryan Olson on 9/19/15.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXKeyboardShortcutManager.h"
#import "FLEXUtility.h"
#import <objc/runtime.h>
#import <objc/message.h>
#if TARGET_OS_SIMULATOR
@interface UIEvent (UIPhysicalKeyboardEvent)
@property (nonatomic) NSString *_modifiedInput;
@property (nonatomic) NSString *_unmodifiedInput;
@property (nonatomic) UIKeyModifierFlags _modifierFlags;
@property (nonatomic) BOOL _isKeyDown;
@property (nonatomic) long _keyCode;
@end
@interface FLEXKeyInput : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, readonly) UIKeyModifierFlags flags;
@property (nonatomic, copy, readonly) NSString *helpDescription;
@end
@implementation FLEXKeyInput
- (BOOL)isEqual:(id)object {
BOOL isEqual = NO;
if ([object isKindOfClass:[FLEXKeyInput class]]) {
FLEXKeyInput *keyCommand = (FLEXKeyInput *)object;
BOOL equalKeys = self.key == keyCommand.key || [self.key isEqual:keyCommand.key];
BOOL equalFlags = self.flags == keyCommand.flags;
isEqual = equalKeys && equalFlags;
}
return isEqual;
}
- (NSUInteger)hash {
return self.key.hash ^ self.flags;
}
- (id)copyWithZone:(NSZone *)zone {
return [[self class] keyInputForKey:self.key flags:self.flags helpDescription:self.helpDescription];
}
- (NSString *)description {
NSDictionary<NSString *, NSString *> *keyMappings = @{
UIKeyInputUpArrow : @"↑",
UIKeyInputDownArrow : @"↓",
UIKeyInputLeftArrow : @"←",
UIKeyInputRightArrow : @"→",
UIKeyInputEscape : @"␛",
@" " : @"␠"
};
NSString *prettyKey = nil;
if (self.key && keyMappings[self.key]) {
prettyKey = keyMappings[self.key];
} else {
prettyKey = [self.key uppercaseString];
}
NSString *prettyFlags = @"";
if (self.flags & UIKeyModifierControl) {
prettyFlags = [prettyFlags stringByAppendingString:@"⌃"];
}
if (self.flags & UIKeyModifierAlternate) {
prettyFlags = [prettyFlags stringByAppendingString:@"⌥"];
}
if (self.flags & UIKeyModifierShift) {
prettyFlags = [prettyFlags stringByAppendingString:@"⇧"];
}
if (self.flags & UIKeyModifierCommand) {
prettyFlags = [prettyFlags stringByAppendingString:@"⌘"];
}
// Fudging to get easy columns with tabs
if (prettyFlags.length < 2) {
prettyKey = [prettyKey stringByAppendingString:@"\t"];
}
return [NSString stringWithFormat:@"%@%@\t%@", prettyFlags, prettyKey, self.helpDescription];
}
+ (instancetype)keyInputForKey:(NSString *)key flags:(UIKeyModifierFlags)flags {
return [self keyInputForKey:key flags:flags helpDescription:nil];
}
+ (instancetype)keyInputForKey:(NSString *)key
flags:(UIKeyModifierFlags)flags
helpDescription:(NSString *)helpDescription {
FLEXKeyInput *keyInput = [self new];
if (keyInput) {
keyInput->_key = key;
keyInput->_flags = flags;
keyInput->_helpDescription = helpDescription;
}
return keyInput;
}
@end
@interface FLEXKeyboardShortcutManager ()
@property (nonatomic) NSMutableDictionary<FLEXKeyInput *, dispatch_block_t> *actionsForKeyInputs;
@property (nonatomic, getter=isPressingShift) BOOL pressingShift;
@property (nonatomic, getter=isPressingCommand) BOOL pressingCommand;
@property (nonatomic, getter=isPressingControl) BOOL pressingControl;
@end
@implementation FLEXKeyboardShortcutManager
+ (instancetype)sharedManager {
static FLEXKeyboardShortcutManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [self new];
});
return sharedManager;
}
+ (void)load {
SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:");
SEL swizzledKeyEventSelector = [FLEXUtility swizzledSelectorForSelector:originalKeyEventSelector];
void (^handleKeyUIEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
[[[self class] sharedManager] handleKeyboardEvent:event];
((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledKeyEventSelector, event);
};
[FLEXUtility replaceImplementationOfKnownSelector:originalKeyEventSelector
onClass:[UIApplication class]
withBlock:handleKeyUIEventSwizzleBlock
swizzledSelector:swizzledKeyEventSelector
];
if ([[UITouch class] instancesRespondToSelector:@selector(maximumPossibleForce)]) {
SEL originalSendEventSelector = NSSelectorFromString(@"sendEvent:");
SEL swizzledSendEventSelector = [FLEXUtility swizzledSelectorForSelector:originalSendEventSelector];
void (^sendEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
if (event.type == UIEventTypeTouches) {
FLEXKeyboardShortcutManager *keyboardManager = FLEXKeyboardShortcutManager.sharedManager;
NSInteger pressureLevel = 0;
if (keyboardManager.isPressingShift) {
pressureLevel++;
}
if (keyboardManager.isPressingCommand) {
pressureLevel++;
}
if (keyboardManager.isPressingControl) {
pressureLevel++;
}
if (pressureLevel > 0) {
if (@available(iOS 9.0, *)) {
for (UITouch *touch in [event allTouches]) {
double adjustedPressureLevel = pressureLevel * 20 * touch.maximumPossibleForce;
[touch setValue:@(adjustedPressureLevel) forKey:@"_pressure"];
}
}
}
}
((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledSendEventSelector, event);
};
[FLEXUtility replaceImplementationOfKnownSelector:originalSendEventSelector
onClass:[UIApplication class]
withBlock:sendEventSwizzleBlock
swizzledSelector:swizzledSendEventSelector
];
SEL originalSupportsTouchPressureSelector = NSSelectorFromString(@"_supportsForceTouch");
SEL swizzledSupportsTouchPressureSelector = [FLEXUtility swizzledSelectorForSelector:originalSupportsTouchPressureSelector];
BOOL (^supportsTouchPressureSwizzleBlock)(UIDevice *) = ^BOOL(UIDevice *slf) {
return YES;
};
[FLEXUtility replaceImplementationOfKnownSelector:originalSupportsTouchPressureSelector
onClass:[UIDevice class]
withBlock:supportsTouchPressureSwizzleBlock
swizzledSelector:swizzledSupportsTouchPressureSelector
];
}
}
- (instancetype)init {
self = [super init];
if (self) {
_actionsForKeyInputs = [NSMutableDictionary new];
_enabled = YES;
}
return self;
}
- (void)registerSimulatorShortcutWithKey:(NSString *)key
modifiers:(UIKeyModifierFlags)modifiers
action:(dispatch_block_t)action
description:(NSString *)description
allowOverride:(BOOL)allowOverride {
FLEXKeyInput *keyInput = [FLEXKeyInput keyInputForKey:key flags:modifiers helpDescription:description];
if (!allowOverride && self.actionsForKeyInputs[keyInput] != nil) {
return;
} else {
[self.actionsForKeyInputs setObject:action forKey:keyInput];
}
}
static const long kFLEXControlKeyCode = 0xe0;
static const long kFLEXShiftKeyCode = 0xe1;
static const long kFLEXCommandKeyCode = 0xe3;
- (void)handleKeyboardEvent:(UIEvent *)event {
if (!self.enabled) {
return;
}
NSString *modifiedInput = nil;
NSString *unmodifiedInput = nil;
UIKeyModifierFlags flags = 0;
BOOL isKeyDown = NO;
if ([event respondsToSelector:@selector(_modifiedInput)]) {
modifiedInput = [event _modifiedInput];
}
if ([event respondsToSelector:@selector(_unmodifiedInput)]) {
unmodifiedInput = [event _unmodifiedInput];
}
if ([event respondsToSelector:@selector(_modifierFlags)]) {
flags = [event _modifierFlags];
}
if ([event respondsToSelector:@selector(_isKeyDown)]) {
isKeyDown = [event _isKeyDown];
}
BOOL interactionEnabled = !UIApplication.sharedApplication.isIgnoringInteractionEvents;
BOOL hasFirstResponder = NO;
if (isKeyDown && modifiedInput.length > 0 && interactionEnabled) {
UIResponder *firstResponder = nil;
for (UIWindow *window in FLEXUtility.allWindows) {
firstResponder = [window valueForKey:@"firstResponder"];
if (firstResponder) {
hasFirstResponder = YES;
break;
}
}
// Ignore key commands (except escape) when there's an active responder
if (firstResponder) {
if ([unmodifiedInput isEqual:UIKeyInputEscape]) {
[firstResponder resignFirstResponder];
}
} else {
FLEXKeyInput *exactMatch = [FLEXKeyInput keyInputForKey:unmodifiedInput flags:flags];
dispatch_block_t actionBlock = self.actionsForKeyInputs[exactMatch];
if (!actionBlock) {
FLEXKeyInput *shiftMatch = [FLEXKeyInput
keyInputForKey:modifiedInput flags:flags&(~UIKeyModifierShift)
];
actionBlock = self.actionsForKeyInputs[shiftMatch];
}
if (!actionBlock) {
FLEXKeyInput *capitalMatch = [FLEXKeyInput
keyInputForKey:[unmodifiedInput uppercaseString] flags:flags
];
actionBlock = self.actionsForKeyInputs[capitalMatch];
}
if (actionBlock) {
actionBlock();
}
}
}
// Calling _keyCode on events from the simulator keyboard will crash.
// It is only safe to call _keyCode when there's not an active responder.
if (!hasFirstResponder && [event respondsToSelector:@selector(_keyCode)]) {
long keyCode = [event _keyCode];
if (keyCode == kFLEXControlKeyCode) {
self.pressingControl = isKeyDown;
} else if (keyCode == kFLEXCommandKeyCode) {
self.pressingCommand = isKeyDown;
} else if (keyCode == kFLEXShiftKeyCode) {
self.pressingShift = isKeyDown;
}
}
}
- (NSString *)keyboardShortcutsDescription {
NSMutableString *description = [NSMutableString new];
NSArray<FLEXKeyInput *> *keyInputs = [self.actionsForKeyInputs.allKeys
sortedArrayUsingComparator:^NSComparisonResult(FLEXKeyInput *input1, FLEXKeyInput *input2) {
return [input1.key caseInsensitiveCompare:input2.key];
}
];
for (FLEXKeyInput *keyInput in keyInputs) {
[description appendFormat:@"%@\n", keyInput];
}
return [description copy];
}
@end
#endif

View File

@@ -0,0 +1,108 @@
//
// FLEXRuntimeUtility.h
// Flipboard
//
// Created by Ryan Olson on 6/8/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntimeConstants.h"
#define PropertyKey(suffix) kFLEXPropertyAttributeKey##suffix : @""
#define PropertyKeyGetter(getter) kFLEXPropertyAttributeKeyCustomGetter : NSStringFromSelector(@selector(getter))
#define PropertyKeySetter(setter) kFLEXPropertyAttributeKeyCustomSetter : NSStringFromSelector(@selector(setter))
/// Takes: min iOS version, property name, target class, property type, and a list of attributes
#define FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, type, ...) ({ \
if (@available(iOS iOS_atLeast, *)) { \
NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithDictionary:@{ \
kFLEXPropertyAttributeKeyTypeEncoding : @(type), \
__VA_ARGS__ \
}]; \
[FLEXRuntimeUtility \
tryAddPropertyWithName:#name \
attributes:attrs \
toClass:cls \
]; \
} \
})
/// Takes: min iOS version, property name, target class, property type, and a list of attributes
#define FLEXRuntimeUtilityTryAddNonatomicProperty(iOS_atLeast, name, cls, type, ...) \
FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, @encode(type), PropertyKey(NonAtomic), __VA_ARGS__);
/// Takes: min iOS version, property name, target class, property type (class name), and a list of attributes
#define FLEXRuntimeUtilityTryAddObjectProperty(iOS_atLeast, name, cls, type, ...) \
FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, FLEXEncodeClass(type), PropertyKey(NonAtomic), __VA_ARGS__);
extern NSString * const FLEXRuntimeUtilityErrorDomain;
typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
// Start at a random value instead of 0 to avoid confusion with an absent code
FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0xbabe,
FLEXRuntimeUtilityErrorCodeInvocationFailed,
FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch
};
@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;
/// Some fields have a name in their encoded string (e.g. \"width\"d)
/// @return the offset to skip the field name, 0 if there is no name
+ (NSUInteger)fieldNameOffsetForTypeEncoding:(const FLEXTypeEncoding *)typeEncoding;
/// Given name "foo" and type "int" this would return "int foo", but
/// given name "foo" and type "T *" it would return "T *foo"
+ (NSString *)appendName:(NSString *)name toType:(NSString *)typeEncoding;
/// @return The class hierarchy for the given object or class,
/// from the current class to the root-most class.
+ (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass;
/// Used to describe an object in brief within an explorer row
+ (NSString *)summaryForObject:(id)value;
+ (NSString *)safeClassNameForObject:(id)object;
+ (NSString *)safeDescriptionForObject:(id)object;
+ (NSString *)safeDebugDescriptionForObject:(id)object;
+ (BOOL)safeObject:(id)object isKindOfClass:(Class)cls;
+ (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel;
// Property Helpers
+ (BOOL)tryAddPropertyWithName:(const char *)name
attributes:(NSDictionary<NSString *, NSString *> *)attributePairs
toClass:(__unsafe_unretained Class)theClass;
+ (NSArray<NSString *> *)allPropertyAttributeKeys;
// Method Helpers
+ (NSArray *)prettyArgumentComponentsForMethod:(Method)method;
// Method Calling/Field Editing
+ (id)performSelector:(SEL)selector onObject:(id)object;
+ (id)performSelector:(SEL)selector
onObject:(id)object
withArguments:(NSArray *)arguments
error:(NSError * __autoreleasing *)error;
+ (id)performSelector:(SEL)selector
onObject:(id)object
withArguments:(NSArray *)arguments
allowForwarding:(BOOL)mightForwardMsgSend
error:(NSError * __autoreleasing *)error;
+ (NSString *)editableJSONStringForObject:(id)object;
+ (id)objectValueFromEditableJSONString:(NSString *)string;
+ (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString;
+ (void)enumerateTypesInStructEncoding:(const char *)structEncoding
usingBlock:(void (^)(NSString *structName,
const char *fieldTypeEncoding,
NSString *prettyTypeEncoding,
NSUInteger fieldIndex,
NSUInteger fieldOffset))typeBlock;
+ (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type;
#pragma mark - Metadata Helpers
+ (NSString *)readableTypeForEncoding:(NSString *)encodingString;
@end

View File

@@ -0,0 +1,881 @@
//
// FLEXRuntimeUtility.m
// Flipboard
//
// Created by Ryan Olson on 6/8/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXRuntimeUtility.h"
#import "FLEXObjcInternal.h"
#import "FLEXTypeEncodingParser.h"
#import "FLEXMethod.h"
NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
@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) {
// Skip NSNumber instances
if ([returnedObjectOrNil isKindOfClass:[NSNumber class]]) {
return returnedObjectOrNil;
}
// 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 = string ? [NSString stringWithCString:string encoding:NSUTF8StringEncoding] : NULL;
} else if (returnsVoidPointer) {
// Cast valid objects disguised as void * to id
if ([FLEXRuntimeUtility pointerIsValidObjcObject:value.pointerValue]) {
returnedObjectOrNil = (__bridge id)value.pointerValue;
}
}
}
return returnedObjectOrNil;
}
+ (NSUInteger)fieldNameOffsetForTypeEncoding:(const FLEXTypeEncoding *)typeEncoding {
NSUInteger beginIndex = 0;
while (typeEncoding[beginIndex] == FLEXTypeEncodingQuote) {
NSUInteger endIndex = beginIndex + 1;
while (typeEncoding[endIndex] != FLEXTypeEncodingQuote) {
++endIndex;
}
beginIndex = endIndex + 1;
}
return beginIndex;
}
+ (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass {
NSMutableArray<Class> *superClasses = [NSMutableArray new];
id cls = [objectOrClass class];
do {
[superClasses addObject:cls];
} while ((cls = [cls superclass]));
return superClasses;
}
+ (NSString *)safeClassNameForObject:(id)object {
// Don't assume that we have an NSObject subclass
if ([self safeObject:object respondsToSelector:@selector(class)]) {
return NSStringFromClass([object class]);
}
return NSStringFromClass(object_getClass(object));
}
/// Could be nil
+ (NSString *)safeDescriptionForObject:(id)object {
// Don't assume that we have an NSObject subclass; not all objects respond to -description
if ([self safeObject:object respondsToSelector:@selector(description)]) {
@try {
return [object description];
} @catch (NSException *exception) {
return nil;
}
}
return nil;
}
/// Never nil
+ (NSString *)safeDebugDescriptionForObject:(id)object {
NSString *description = nil;
if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) {
@try {
description = [object debugDescription];
} @catch (NSException *exception) { }
} else {
description = [self safeDescriptionForObject:object];
}
if (!description.length) {
NSString *cls = NSStringFromClass(object_getClass(object));
if (object_isClass(object)) {
description = [cls stringByAppendingString:@" class (no description)"];
} else {
description = [cls stringByAppendingString:@" instance (no description)"];
}
}
return description;
}
+ (NSString *)summaryForObject:(id)value {
NSString *description = nil;
// Special case BOOL for better readability.
if ([self safeObject:value isKindOfClass:[NSValue class]]) {
const char *type = [value objCType];
if (strcmp(type, @encode(BOOL)) == 0) {
BOOL boolValue = NO;
[value getValue:&boolValue];
return boolValue ? @"YES" : @"NO";
} else if (strcmp(type, @encode(SEL)) == 0) {
SEL selector = NULL;
[value getValue:&selector];
return NSStringFromSelector(selector);
}
}
@try {
// Single line display - replace newlines and tabs with spaces.
description = [[self safeDescriptionForObject:value] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
} @catch (NSException *e) {
description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"];
}
if (!description) {
description = @"nil";
}
return description;
}
+ (BOOL)safeObject:(id)object isKindOfClass:(Class)cls {
static BOOL (*isKindOfClass)(id, SEL, Class) = nil;
static BOOL (*isKindOfClass_meta)(id, SEL, Class) = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
isKindOfClass = (BOOL(*)(id, SEL, Class))[NSObject instanceMethodForSelector:@selector(isKindOfClass:)];
isKindOfClass_meta = (BOOL(*)(id, SEL, Class))[NSObject methodForSelector:@selector(isKindOfClass:)];
});
BOOL isClass = object_isClass(object);
return (isClass ? isKindOfClass_meta : isKindOfClass)(object, @selector(isKindOfClass:), cls);
}
+ (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel {
// If we're given a class, we want to know if classes respond to this selector.
// Similarly, if we're given an instance, we want to know if instances respond.
BOOL isClass = object_isClass(object);
Class cls = isClass ? object : object_getClass(object);
// BOOL isMetaclass = class_isMetaClass(cls);
if (isClass) {
// In theory, this should also work for metaclasses...
return class_getClassMethod(cls, sel) != nil;
} else {
return class_getInstanceMethod(cls, sel) != nil;
}
}
#pragma mark - Property Helpers (Public)
+ (BOOL)tryAddPropertyWithName:(const char *)name
attributes:(NSDictionary<NSString *, NSString *> *)attributePairs
toClass:(__unsafe_unretained Class)theClass {
objc_property_t property = class_getProperty(theClass, name);
if (!property) {
unsigned int totalAttributesCount = (unsigned int)attributePairs.count;
objc_property_attribute_t *attributes = malloc(sizeof(objc_property_attribute_t) * totalAttributesCount);
if (attributes) {
unsigned int attributeIndex = 0;
for (NSString *attributeName in attributePairs.allKeys) {
objc_property_attribute_t attribute;
attribute.name = attributeName.UTF8String;
attribute.value = attributePairs[attributeName].UTF8String;
attributes[attributeIndex++] = attribute;
}
BOOL success = class_addProperty(theClass, name, attributes, totalAttributesCount);
free(attributes);
return success;
} else {
return NO;
}
}
return YES;
}
+ (NSArray<NSString *> *)allPropertyAttributeKeys {
static NSArray<NSString *> *allPropertyAttributeKeys = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
allPropertyAttributeKeys = @[
kFLEXPropertyAttributeKeyTypeEncoding,
kFLEXPropertyAttributeKeyBackingIvarName,
kFLEXPropertyAttributeKeyReadOnly,
kFLEXPropertyAttributeKeyCopy,
kFLEXPropertyAttributeKeyRetain,
kFLEXPropertyAttributeKeyNonAtomic,
kFLEXPropertyAttributeKeyCustomGetter,
kFLEXPropertyAttributeKeyCustomSetter,
kFLEXPropertyAttributeKeyDynamic,
kFLEXPropertyAttributeKeyWeak,
kFLEXPropertyAttributeKeyGarbageCollectable,
kFLEXPropertyAttributeKeyOldStyleTypeEncoding,
];
});
return allPropertyAttributeKeys;
}
#pragma mark - Method Helpers (Public)
+ (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method {
NSMutableArray<NSString *> *components = [NSMutableArray new];
NSString *selectorName = NSStringFromSelector(method_getName(method));
NSMutableArray<NSString *> *selectorComponents = [selectorName componentsSeparatedByString:@":"].mutableCopy;
// this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods
if (selectorComponents.count == 1) {
return @[];
}
if ([selectorComponents.lastObject isEqualToString:@""]) {
[selectorComponents removeLastObject];
}
for (unsigned int argIndex = 0; argIndex < selectorComponents.count; argIndex++) {
char *argType = method_copyArgumentType(method, argIndex + kFLEXNumberOfImplicitArgs);
NSString *readableArgType = (argType != NULL) ? [self readableTypeForEncoding:@(argType)] : nil;
free(argType);
NSString *prettyComponent = [NSString
stringWithFormat:@"%@:(%@) ",
selectorComponents[argIndex],
readableArgType
];
[components addObject:prettyComponent];
}
return components;
}
#pragma mark - Method Calling/Field Editing (Public)
+ (id)performSelector:(SEL)selector onObject:(id)object {
return [self performSelector:selector onObject:object withArguments:@[] error:nil];
}
+ (id)performSelector:(SEL)selector
onObject:(id)object
withArguments:(NSArray *)arguments
error:(NSError * __autoreleasing *)error {
return [self performSelector:selector
onObject:object
withArguments:arguments
allowForwarding:NO
error:error
];
}
+ (id)performSelector:(SEL)selector
onObject:(id)object
withArguments:(NSArray *)arguments
allowForwarding:(BOOL)mightForwardMsgSend
error:(NSError * __autoreleasing *)error {
static dispatch_once_t onceToken;
static SEL stdStringExclusion = nil;
dispatch_once(&onceToken, ^{
stdStringExclusion = NSSelectorFromString(@"stdString");
});
// Bail if the object won't respond to this selector
if (mightForwardMsgSend || ![self safeObject:object respondsToSelector:selector]) {
if (error) {
NSString *msg = [NSString
stringWithFormat:@"This object does not respond to the selector %@",
NSStringFromSelector(selector)
];
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
*error = [NSError
errorWithDomain:FLEXRuntimeUtilityErrorDomain
code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector
userInfo:userInfo
];
}
return nil;
}
// It is important to use object_getClass and not -class here, as
// object_getClass will return a different result for class objects
Class cls = object_getClass(object);
NSMethodSignature *methodSignature = [FLEXMethod selector:selector class:cls].signature;
if (!methodSignature) {
// Unsupported type encoding
return nil;
}
// Probably an unsupported type encoding, like bitfields.
// In the future, we could calculate the return length
// on our own. For now, we abort.
//
// For future reference, the code here will get the true type encoding.
// NSMethodSignature will convert {?=b8b4b1b1b18[8S]} to {?}
//
// returnType = method_getTypeEncoding(class_getInstanceMethod([object class], selector));
if (!methodSignature.methodReturnLength &&
methodSignature.methodReturnType[0] != FLEXTypeEncodingVoid) {
return nil;
}
// Build the invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setSelector:selector];
[invocation setTarget:object];
[invocation retainArguments];
// Always self and _cmd
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
id argumentObject = arguments.count > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil;
// NSNull in the arguments array can be passed as a placeholder to indicate nil.
// We only need to set the argument if it will be non-nil.
if (argumentObject && ![argumentObject isKindOfClass:[NSNull class]]) {
const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
if (typeEncodingCString[0] == FLEXTypeEncodingObjcObject ||
typeEncodingCString[0] == FLEXTypeEncodingObjcClass ||
[self isTollFreeBridgedValue:argumentObject forCFType:typeEncodingCString]) {
// Object
[invocation setArgument:&argumentObject atIndex:argumentIndex];
} else if (strcmp(typeEncodingCString, @encode(CGColorRef)) == 0 &&
[argumentObject isKindOfClass:[UIColor class]]) {
// Bridging UIColor to CGColorRef
CGColorRef colorRef = [argumentObject CGColor];
[invocation setArgument:&colorRef atIndex:argumentIndex];
} else if ([argumentObject isKindOfClass:[NSValue class]]) {
// Primitive boxed in NSValue
NSValue *argumentValue = (NSValue *)argumentObject;
// Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature
if (strcmp([argumentValue objCType], typeEncodingCString) != 0) {
if (error) {
NSString *msg = [NSString
stringWithFormat:@"Type encoding mismatch for argument at index %lu. "
"Value type: %s; Method argument type: %s.",
(unsigned long)argumentsArrayIndex, argumentValue.objCType, typeEncodingCString
];
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
*error = [NSError
errorWithDomain:FLEXRuntimeUtilityErrorDomain
code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch
userInfo:userInfo
];
}
return nil;
}
@try {
NSUInteger bufferSize = 0;
FLEXGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
if (bufferSize > 0) {
void *buffer = alloca(bufferSize);
[argumentValue getValue:buffer];
[invocation setArgument:buffer atIndex:argumentIndex];
}
} @catch (NSException *exception) { }
}
}
}
// Try to invoke the invocation but guard against an exception being thrown.
id returnObject = nil;
@try {
[invocation invoke];
// Retrieve the return value and box if necessary.
const char *returnType = methodSignature.methodReturnType;
if (returnType[0] == FLEXTypeEncodingObjcObject || returnType[0] == FLEXTypeEncodingObjcClass) {
// Return value is an object.
__unsafe_unretained id objectReturnedFromMethod = nil;
[invocation getReturnValue:&objectReturnedFromMethod];
returnObject = objectReturnedFromMethod;
} else if (returnType[0] != FLEXTypeEncodingVoid) {
NSAssert(methodSignature.methodReturnLength, @"Memory corruption lies ahead");
if (returnType[0] == FLEXTypeEncodingStructBegin) {
if (selector == stdStringExclusion && [object isKindOfClass:[NSString class]]) {
// stdString is a C++ object and we will crash if we try to access it
if (error) {
*error = [NSError
errorWithDomain:FLEXRuntimeUtilityErrorDomain
code:FLEXRuntimeUtilityErrorCodeInvocationFailed
userInfo:@{ NSLocalizedDescriptionKey : @"Skipping -[NSString stdString]" }
];
}
return nil;
}
}
// Will use arbitrary buffer for return value and box it.
void *returnValue = malloc(methodSignature.methodReturnLength);
[invocation getReturnValue:returnValue];
returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
free(returnValue);
}
} @catch (NSException *exception) {
// Bummer...
if (error) {
// "… on <class>" / "… on instance of <class>"
NSString *class = NSStringFromClass([object class]);
NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class];
NSString *message = [NSString
stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@",
exception.name, NSStringFromSelector(selector), calledOn, exception.reason
];
*error = [NSError
errorWithDomain:FLEXRuntimeUtilityErrorDomain
code:FLEXRuntimeUtilityErrorCodeInvocationFailed
userInfo:@{ NSLocalizedDescriptionKey : message }
];
}
}
return returnObject;
}
+ (BOOL)isTollFreeBridgedValue:(id)value forCFType:(const char *)typeEncoding {
// See https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html
#define CASE(cftype, foundationClass) \
if (strcmp(typeEncoding, @encode(cftype)) == 0) { \
return [value isKindOfClass:[foundationClass class]]; \
}
CASE(CFArrayRef, NSArray);
CASE(CFAttributedStringRef, NSAttributedString);
CASE(CFCalendarRef, NSCalendar);
CASE(CFCharacterSetRef, NSCharacterSet);
CASE(CFDataRef, NSData);
CASE(CFDateRef, NSDate);
CASE(CFDictionaryRef, NSDictionary);
CASE(CFErrorRef, NSError);
CASE(CFLocaleRef, NSLocale);
CASE(CFMutableArrayRef, NSMutableArray);
CASE(CFMutableAttributedStringRef, NSMutableAttributedString);
CASE(CFMutableCharacterSetRef, NSMutableCharacterSet);
CASE(CFMutableDataRef, NSMutableData);
CASE(CFMutableDictionaryRef, NSMutableDictionary);
CASE(CFMutableSetRef, NSMutableSet);
CASE(CFMutableStringRef, NSMutableString);
CASE(CFNumberRef, NSNumber);
CASE(CFReadStreamRef, NSInputStream);
CASE(CFRunLoopTimerRef, NSTimer);
CASE(CFSetRef, NSSet);
CASE(CFStringRef, NSString);
CASE(CFTimeZoneRef, NSTimeZone);
CASE(CFURLRef, NSURL);
CASE(CFWriteStreamRef, NSOutputStream);
#undef CASE
return NO;
}
+ (NSString *)editableJSONStringForObject:(id)object {
NSString *editableDescription = nil;
if (object) {
// This is a hack to use JSON serialization for our editable objects.
// NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary.
// We always wrap the object inside an array and then strip the outer square braces off the final string.
NSArray *wrappedObject = @[object];
if ([NSJSONSerialization isValidJSONObject:wrappedObject]) {
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:wrappedObject options:0 error:NULL];
NSString *wrappedDescription = [NSString stringWithUTF8String:jsonData.bytes];
editableDescription = [wrappedDescription substringWithRange:NSMakeRange(1, wrappedDescription.length - 2)];
}
}
return editableDescription;
}
+ (id)objectValueFromEditableJSONString:(NSString *)string {
id value = nil;
// nil for empty string/whitespace
if ([string stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet].length) {
value = [NSJSONSerialization
JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingAllowFragments
error:NULL
];
}
return value;
}
+ (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString {
NSNumberFormatter *formatter = [NSNumberFormatter new];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *number = [formatter numberFromString:inputString];
// Is the type encoding longer than one character?
if (strlen(typeEncoding) > 1) {
NSString *type = @(typeEncoding);
// Is it NSDecimalNumber or NSNumber?
if ([type isEqualToString:@FLEXEncodeClass(NSDecimalNumber)]) {
return [NSDecimalNumber decimalNumberWithString:inputString];
} else if ([type isEqualToString:@FLEXEncodeClass(NSNumber)]) {
return number;
}
return nil;
}
// Type encoding is one character, switch on the type
FLEXTypeEncoding type = typeEncoding[0];
uint8_t value[32];
void *bufferStart = &value[0];
// Make sure we box the number with the correct type encoding
// so it can be properly unboxed later via getValue:
switch (type) {
case FLEXTypeEncodingChar:
*(char *)bufferStart = number.charValue; break;
case FLEXTypeEncodingInt:
*(int *)bufferStart = number.intValue; break;
case FLEXTypeEncodingShort:
*(short *)bufferStart = number.shortValue; break;
case FLEXTypeEncodingLong:
*(long *)bufferStart = number.longValue; break;
case FLEXTypeEncodingLongLong:
*(long long *)bufferStart = number.longLongValue; break;
case FLEXTypeEncodingUnsignedChar:
*(unsigned char *)bufferStart = number.unsignedCharValue; break;
case FLEXTypeEncodingUnsignedInt:
*(unsigned int *)bufferStart = number.unsignedIntValue; break;
case FLEXTypeEncodingUnsignedShort:
*(unsigned short *)bufferStart = number.unsignedShortValue; break;
case FLEXTypeEncodingUnsignedLong:
*(unsigned long *)bufferStart = number.unsignedLongValue; break;
case FLEXTypeEncodingUnsignedLongLong:
*(unsigned long long *)bufferStart = number.unsignedLongLongValue; break;
case FLEXTypeEncodingFloat:
*(float *)bufferStart = number.floatValue; break;
case FLEXTypeEncodingDouble:
*(double *)bufferStart = number.doubleValue; break;
case FLEXTypeEncodingLongDouble:
// NSNumber does not support long double
default:
return nil;
}
return [NSValue value:value withObjCType:typeEncoding];
}
+ (void)enumerateTypesInStructEncoding:(const char *)structEncoding
usingBlock:(void (^)(NSString *structName,
const char *fieldTypeEncoding,
NSString *prettyTypeEncoding,
NSUInteger fieldIndex,
NSUInteger fieldOffset))typeBlock {
if (structEncoding && structEncoding[0] == FLEXTypeEncodingStructBegin) {
const char *equals = strchr(structEncoding, '=');
if (equals) {
const char *nameStart = structEncoding + 1;
NSString *structName = [@(structEncoding)
substringWithRange:NSMakeRange(nameStart - structEncoding, equals - nameStart)
];
NSUInteger fieldAlignment = 0, structSize = 0;
if (FLEXGetSizeAndAlignment(structEncoding, &structSize, &fieldAlignment)) {
NSUInteger runningFieldIndex = 0;
NSUInteger runningFieldOffset = 0;
const char *typeStart = equals + 1;
while (*typeStart != FLEXTypeEncodingStructEnd) {
NSUInteger fieldSize = 0;
// If the struct type encoding was successfully handled by
// FLEXGetSizeAndAlignment above, we *should* be ok with the field here.
const char *nextTypeStart = NSGetSizeAndAlignment(typeStart, &fieldSize, NULL);
NSString *typeEncoding = [@(structEncoding)
substringWithRange:NSMakeRange(typeStart - structEncoding, nextTypeStart - typeStart)
];
// Padding to keep proper alignment. __attribute((packed)) structs
// will break here. The type encoding is no different for packed structs,
// so it's not clear there's anything we can do for those.
const NSUInteger currentSizeSum = runningFieldOffset % fieldAlignment;
if (currentSizeSum != 0 && currentSizeSum + fieldSize > fieldAlignment) {
runningFieldOffset += fieldAlignment - currentSizeSum;
}
typeBlock(
structName,
typeEncoding.UTF8String,
[self readableTypeForEncoding:typeEncoding],
runningFieldIndex,
runningFieldOffset
);
runningFieldOffset += fieldSize;
runningFieldIndex++;
typeStart = nextTypeStart;
}
}
}
}
}
#pragma mark - Metadata Helpers
+ (NSDictionary<NSString *, NSString *> *)attributesForProperty:(objc_property_t)property {
NSString *attributes = @(property_getAttributes(property) ?: "");
// Thanks to MAObjcRuntime for inspiration here.
NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","];
NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary new];
for (NSString *attributePair in attributePairs) {
attributesDictionary[[attributePair substringToIndex:1]] = [attributePair substringFromIndex:1];
}
return attributesDictionary;
}
+ (NSString *)appendName:(NSString *)name toType:(NSString *)type {
if (!type.length) {
type = @"(?)";
}
NSString *combined = nil;
if ([type characterAtIndex:type.length - 1] == FLEXTypeEncodingCString) {
combined = [type stringByAppendingString:name];
} else {
combined = [type stringByAppendingFormat:@" %@", name];
}
return combined;
}
+ (NSString *)readableTypeForEncoding:(NSString *)encodingString {
if (!encodingString.length) {
return @"?";
}
// See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
// class-dump has a much nicer and much more complete implementation for this task, but it is distributed under GPLv2 :/
// See https://github.com/nygard/class-dump/blob/master/Source/CDType.m
// Warning: this method uses multiple middle returns and macros to cut down on boilerplate.
// The use of macros here was inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
const char *encodingCString = encodingString.UTF8String;
// Some fields have a name, such as {Size=\"width\"d\"height\"d}, we need to extract the name out and recursive
const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:encodingCString];
if (fieldNameOffset > 0) {
// According to https://github.com/nygard/class-dump/commit/33fb5ed221810685f57c192e1ce8ab6054949a7c,
// there are some consecutive quoted strings, so use `_` to concatenate the names.
NSString *const fieldNamesString = [encodingString substringWithRange:NSMakeRange(0, fieldNameOffset)];
NSArray<NSString *> *const fieldNames = [fieldNamesString
componentsSeparatedByString:[NSString stringWithFormat:@"%c", FLEXTypeEncodingQuote]
];
NSMutableString *finalFieldNamesString = [NSMutableString new];
for (NSString *const fieldName in fieldNames) {
if (fieldName.length > 0) {
if (finalFieldNamesString.length > 0) {
[finalFieldNamesString appendString:@"_"];
}
[finalFieldNamesString appendString:fieldName];
}
}
NSString *const recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:fieldNameOffset]];
return [NSString stringWithFormat:@"%@ %@", recursiveType, finalFieldNamesString];
}
// Objects
if (encodingCString[0] == FLEXTypeEncodingObjcObject) {
NSString *class = [encodingString substringFromIndex:1];
class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""];
if (class.length == 0 || (class.length == 1 && [class characterAtIndex:0] == FLEXTypeEncodingUnknown)) {
class = @"id";
} else {
class = [class stringByAppendingString:@" *"];
}
return class;
}
// Qualifier Prefixes
// Do this first since some of the direct translations (i.e. Method) contain a prefix.
#define RECURSIVE_TRANSLATE(prefix, formatString) \
if (encodingCString[0] == prefix) { \
NSString *recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:1]]; \
return [NSString stringWithFormat:formatString, recursiveType]; \
}
// If there's a qualifier prefix on the encoding, translate it and then
// recursively call this method with the rest of the encoding string.
RECURSIVE_TRANSLATE('^', @"%@ *");
RECURSIVE_TRANSLATE('r', @"const %@");
RECURSIVE_TRANSLATE('n', @"in %@");
RECURSIVE_TRANSLATE('N', @"inout %@");
RECURSIVE_TRANSLATE('o', @"out %@");
RECURSIVE_TRANSLATE('O', @"bycopy %@");
RECURSIVE_TRANSLATE('R', @"byref %@");
RECURSIVE_TRANSLATE('V', @"oneway %@");
RECURSIVE_TRANSLATE('b', @"bitfield(%@)");
#undef RECURSIVE_TRANSLATE
// C Types
#define TRANSLATE(ctype) \
if (strcmp(encodingCString, @encode(ctype)) == 0) { \
return (NSString *)CFSTR(#ctype); \
}
// Order matters here since some of the cocoa types are typedefed to c types.
// We can't recover the exact mapping, but we choose to prefer the cocoa types.
// This is not an exhaustive list, but it covers the most common types
TRANSLATE(CGRect);
TRANSLATE(CGPoint);
TRANSLATE(CGSize);
TRANSLATE(CGVector);
TRANSLATE(UIEdgeInsets);
if (@available(iOS 11.0, *)) {
TRANSLATE(NSDirectionalEdgeInsets);
}
TRANSLATE(UIOffset);
TRANSLATE(NSRange);
TRANSLATE(CGAffineTransform);
TRANSLATE(CATransform3D);
TRANSLATE(CGColorRef);
TRANSLATE(CGPathRef);
TRANSLATE(CGContextRef);
TRANSLATE(NSInteger);
TRANSLATE(NSUInteger);
TRANSLATE(CGFloat);
TRANSLATE(BOOL);
TRANSLATE(int);
TRANSLATE(short);
TRANSLATE(long);
TRANSLATE(long long);
TRANSLATE(unsigned char);
TRANSLATE(unsigned int);
TRANSLATE(unsigned short);
TRANSLATE(unsigned long);
TRANSLATE(unsigned long long);
TRANSLATE(float);
TRANSLATE(double);
TRANSLATE(long double);
TRANSLATE(char *);
TRANSLATE(Class);
TRANSLATE(objc_property_t);
TRANSLATE(Ivar);
TRANSLATE(Method);
TRANSLATE(Category);
TRANSLATE(NSZone *);
TRANSLATE(SEL);
TRANSLATE(void);
#undef TRANSLATE
// For structs, we only use the name of the structs
if (encodingCString[0] == FLEXTypeEncodingStructBegin) {
// Special case: std::string
if ([encodingString hasPrefix:@"{basic_string<char"]) {
return @"std::string";
}
const char *equals = strchr(encodingCString, '=');
if (equals) {
const char *nameStart = encodingCString + 1;
// For anonymous structs
if (nameStart[0] == FLEXTypeEncodingUnknown) {
return @"anonymous struct";
} else {
NSString *const structName = [encodingString
substringWithRange:NSMakeRange(nameStart - encodingCString, equals - nameStart)
];
return structName;
}
}
}
// If we couldn't translate, just return the original encoding string
return encodingString;
}
#pragma mark - Internal Helpers
+ (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type {
// Remove the field name if there is any (e.g. \"width\"d -> d)
const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:type];
if (fieldNameOffset > 0) {
return [self valueForPrimitivePointer:pointer objCType:type + fieldNameOffset];
}
// CASE macro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
#define CASE(ctype, selectorpart) \
if (strcmp(type, @encode(ctype)) == 0) { \
return [NSNumber numberWith ## selectorpart: *(ctype *)pointer]; \
}
CASE(BOOL, Bool);
CASE(unsigned char, UnsignedChar);
CASE(short, Short);
CASE(unsigned short, UnsignedShort);
CASE(int, Int);
CASE(unsigned int, UnsignedInt);
CASE(long, Long);
CASE(unsigned long, UnsignedLong);
CASE(long long, LongLong);
CASE(unsigned long long, UnsignedLongLong);
CASE(float, Float);
CASE(double, Double);
CASE(long double, Double);
#undef CASE
NSValue *value = nil;
if (FLEXGetSizeAndAlignment(type, nil, nil)) {
@try {
value = [NSValue valueWithBytes:pointer objCType:type];
} @catch (NSException *exception) {
// Certain type encodings are not supported by valueWithBytes:objCType:.
// Just fail silently if an exception is thrown.
}
}
return value;
}
@end

View File

@@ -0,0 +1,73 @@
//
// FLEXObjcInternal.h
// FLEX
//
// Created by Tanner Bennett on 11/1/18.
//
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
// 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
#endif // OBJC_HAVE_TAGGED_POINTERS
//////////////////////////////////////
// originally _objc_isTaggedPointer //
//////////////////////////////////////
NS_INLINE BOOL flex_isTaggedPointer(const void *ptr) {
#if OBJC_HAVE_TAGGED_POINTERS
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
#else
return NO;
#endif
}
#define FLEXPointerIsTaggedPointer(obj) flex_isTaggedPointer((__bridge void *)obj)
BOOL FLEXPointerIsReadable(const void * ptr);
/// @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/
/// https://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

View File

@@ -0,0 +1,196 @@
//
// 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>
#if __arm64e__
#include <ptrauth.h>
#endif
#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 OBJC_HAVE_TAGGED_POINTERS
///////////////////
// objc-object.h //
///////////////////
////////////////////////////////////////////////
// originally objc_object::isExtTaggedPointer //
////////////////////////////////////////////////
NS_INLINE BOOL flex_isExtTaggedPointer(const void *ptr) {
return ((uintptr_t)ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK;
}
#endif // OBJC_HAVE_TAGGED_POINTERS
/////////////////////////////////////
// FLEXObjectInternal //
// No Apple code beyond this point //
/////////////////////////////////////
extern "C" {
BOOL FLEXPointerIsReadable(const void *inPtr) {
kern_return_t error = KERN_SUCCESS;
vm_size_t vmsize;
#if __arm64e__
// On arm64e, we need to strip the PAC from the pointer so the adress is readable
vm_address_t address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
#else
vm_address_t address = (vm_address_t)inPtr;
#endif
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;
}
#if __arm64e__
address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
#else
address = (vm_address_t)inPtr;
#endif
// Read the memory
vm_size_t size = 0;
char buf[sizeof(uintptr_t)];
error = vm_read_overwrite(mach_task_self(), address, sizeof(uintptr_t), (vm_address_t)buf, &size);
if (error != KERN_SUCCESS) {
// vm_read_overwrite 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
// https://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
// We check if the returned class is readable because object_getClass
// can return a garbage value when given a non-nil pointer to a non-object
Class cls = object_getClass((__bridge id)ptr);
if (!cls || !FLEXPointerIsReadable((__bridge void *)cls)) {
return NO;
}
// Just because this pointer is readable doesn't mean whatever is at
// it's ISA offset is readable. We need to do the same checks on it's ISA.
// Even this isn't perfect, because once we call object_isClass, we're
// going to dereference a member of the metaclass, which may or may not
// be readable itself. For the time being there is no way to access it
// to check here, and I have yet to hard-code a solution.
Class metaclass = object_getClass(cls);
if (!metaclass || !FLEXPointerIsReadable((__bridge void *)metaclass)) {
return NO;
}
// Does the class pointer we got appear as a class to the runtime?
if (!object_isClass(cls)) {
return NO;
}
// Is the allocation size at least as large as the expected instance size?
ssize_t instanceSize = class_getInstanceSize(cls);
if (malloc_size(ptr) < instanceSize) {
return NO;
}
return YES;
}
} // End extern "C"

View File

@@ -0,0 +1,79 @@
//
// FLEXRuntimeConstants.h
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#define FLEXEncodeClass(class) ("@\"" #class "\"")
#define FLEXEncodeObject(obj) (obj ? [NSString stringWithFormat:@"@\"%@\"", [obj class]].UTF8String : @encode(id))
// Arguments 0 and 1 are self and _cmd always
extern const unsigned int kFLEXNumberOfImplicitArgs;
// See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
extern NSString *const kFLEXPropertyAttributeKeyTypeEncoding;
extern NSString *const kFLEXPropertyAttributeKeyBackingIvarName;
extern NSString *const kFLEXPropertyAttributeKeyReadOnly;
extern NSString *const kFLEXPropertyAttributeKeyCopy;
extern NSString *const kFLEXPropertyAttributeKeyRetain;
extern NSString *const kFLEXPropertyAttributeKeyNonAtomic;
extern NSString *const kFLEXPropertyAttributeKeyCustomGetter;
extern NSString *const kFLEXPropertyAttributeKeyCustomSetter;
extern NSString *const kFLEXPropertyAttributeKeyDynamic;
extern NSString *const kFLEXPropertyAttributeKeyWeak;
extern NSString *const kFLEXPropertyAttributeKeyGarbageCollectable;
extern NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding;
typedef NS_ENUM(NSUInteger, FLEXPropertyAttribute) {
FLEXPropertyAttributeTypeEncoding = 'T',
FLEXPropertyAttributeBackingIvarName = 'V',
FLEXPropertyAttributeCopy = 'C',
FLEXPropertyAttributeCustomGetter = 'G',
FLEXPropertyAttributeCustomSetter = 'S',
FLEXPropertyAttributeDynamic = 'D',
FLEXPropertyAttributeGarbageCollectible = 'P',
FLEXPropertyAttributeNonAtomic = 'N',
FLEXPropertyAttributeOldTypeEncoding = 't',
FLEXPropertyAttributeReadOnly = 'R',
FLEXPropertyAttributeRetain = '&',
FLEXPropertyAttributeWeak = 'W'
}; //NS_SWIFT_NAME(FLEX.PropertyAttribute);
typedef NS_ENUM(char, FLEXTypeEncoding) {
FLEXTypeEncodingNull = '\0',
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',
FLEXTypeEncodingLongDouble = 'D',
FLEXTypeEncodingCBool = 'B',
FLEXTypeEncodingVoid = 'v',
FLEXTypeEncodingCString = '*',
FLEXTypeEncodingObjcObject = '@',
FLEXTypeEncodingObjcClass = '#',
FLEXTypeEncodingSelector = ':',
FLEXTypeEncodingArrayBegin = '[',
FLEXTypeEncodingArrayEnd = ']',
FLEXTypeEncodingStructBegin = '{',
FLEXTypeEncodingStructEnd = '}',
FLEXTypeEncodingUnionBegin = '(',
FLEXTypeEncodingUnionEnd = ')',
FLEXTypeEncodingQuote = '\"',
FLEXTypeEncodingBitField = 'b',
FLEXTypeEncodingPointer = '^',
FLEXTypeEncodingConst = 'r'
}; //NS_SWIFT_NAME(FLEX.TypeEncoding);

View File

@@ -0,0 +1,24 @@
//
// FLEXRuntimeConstants.m
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntimeConstants.h"
const unsigned int kFLEXNumberOfImplicitArgs = 2;
NSString *const kFLEXPropertyAttributeKeyTypeEncoding = @"T";
NSString *const kFLEXPropertyAttributeKeyBackingIvarName = @"V";
NSString *const kFLEXPropertyAttributeKeyReadOnly = @"R";
NSString *const kFLEXPropertyAttributeKeyCopy = @"C";
NSString *const kFLEXPropertyAttributeKeyRetain = @"&";
NSString *const kFLEXPropertyAttributeKeyNonAtomic = @"N";
NSString *const kFLEXPropertyAttributeKeyCustomGetter = @"G";
NSString *const kFLEXPropertyAttributeKeyCustomSetter = @"S";
NSString *const kFLEXPropertyAttributeKeyDynamic = @"D";
NSString *const kFLEXPropertyAttributeKeyWeak = @"W";
NSString *const kFLEXPropertyAttributeKeyGarbageCollectable = @"P";
NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding = @"t";

View File

@@ -0,0 +1,56 @@
//
// FLEXRuntimeSafety.h
// FLEX
//
// Created by Tanner on 3/25/17.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#pragma mark - Classes
extern NSUInteger const kFLEXKnownUnsafeClassCount;
extern const Class * FLEXKnownUnsafeClassList(void);
extern NSSet * FLEXKnownUnsafeClassNames(void);
extern CFSetRef FLEXKnownUnsafeClasses;
static Class cNSObject = nil, cNSProxy = nil;
__attribute__((constructor))
static void FLEXInitKnownRootClasses(void) {
cNSObject = [NSObject class];
cNSProxy = [NSProxy class];
}
static inline BOOL FLEXClassIsSafe(Class cls) {
// Is it nil or known to be unsafe?
if (!cls || CFSetContainsValue(FLEXKnownUnsafeClasses, (__bridge void *)cls)) {
return NO;
}
// Is it a known root class?
if (!class_getSuperclass(cls)) {
return cls == cNSObject || cls == cNSProxy;
}
// Probably safe
return YES;
}
static inline BOOL FLEXClassNameIsSafe(NSString *cls) {
if (!cls) return NO;
NSSet *ignored = FLEXKnownUnsafeClassNames();
return ![ignored containsObject:cls];
}
#pragma mark - Ivars
extern CFSetRef FLEXKnownUnsafeIvars;
static inline BOOL FLEXIvarIsSafe(Ivar ivar) {
if (!ivar) return NO;
return !CFSetContainsValue(FLEXKnownUnsafeIvars, ivar);
}

View File

@@ -0,0 +1,107 @@
//
// FLEXRuntimeSafety.m
// FLEX
//
// Created by Tanner on 3/25/17.
//
#import "FLEXRuntimeSafety.h"
NSUInteger const kFLEXKnownUnsafeClassCount = 19;
Class * _UnsafeClasses = NULL;
CFSetRef FLEXKnownUnsafeClasses = nil;
CFSetRef FLEXKnownUnsafeIvars = nil;
#define FLEXClassPointerOrCFNull(name) \
(NSClassFromString(name) ?: (__bridge id)kCFNull)
#define FLEXIvarOrCFNull(cls, name) \
(class_getInstanceVariable([cls class], name) ?: (void *)kCFNull)
__attribute__((constructor))
static void FLEXRuntimeSafteyInit() {
FLEXKnownUnsafeClasses = CFSetCreate(
kCFAllocatorDefault,
(const void **)(uintptr_t)FLEXKnownUnsafeClassList(),
kFLEXKnownUnsafeClassCount,
nil
);
Ivar unsafeIvars[] = {
FLEXIvarOrCFNull(NSURL, "_urlString"),
FLEXIvarOrCFNull(NSURL, "_baseURL"),
};
FLEXKnownUnsafeIvars = CFSetCreate(
kCFAllocatorDefault,
(const void **)unsafeIvars,
sizeof(unsafeIvars),
nil
);
}
const Class * FLEXKnownUnsafeClassList() {
if (!_UnsafeClasses) {
const Class ignored[] = {
FLEXClassPointerOrCFNull(@"__ARCLite__"),
FLEXClassPointerOrCFNull(@"__NSCFCalendar"),
FLEXClassPointerOrCFNull(@"__NSCFTimer"),
FLEXClassPointerOrCFNull(@"NSCFTimer"),
FLEXClassPointerOrCFNull(@"__NSGenericDeallocHandler"),
FLEXClassPointerOrCFNull(@"NSAutoreleasePool"),
FLEXClassPointerOrCFNull(@"NSPlaceholderNumber"),
FLEXClassPointerOrCFNull(@"NSPlaceholderString"),
FLEXClassPointerOrCFNull(@"NSPlaceholderValue"),
FLEXClassPointerOrCFNull(@"Object"),
FLEXClassPointerOrCFNull(@"VMUArchitecture"),
FLEXClassPointerOrCFNull(@"JSExport"),
FLEXClassPointerOrCFNull(@"__NSAtom"),
FLEXClassPointerOrCFNull(@"_NSZombie_"),
FLEXClassPointerOrCFNull(@"_CNZombie_"),
FLEXClassPointerOrCFNull(@"__NSMessage"),
FLEXClassPointerOrCFNull(@"__NSMessageBuilder"),
FLEXClassPointerOrCFNull(@"FigIrisAutoTrimmerMotionSampleExport"),
// Temporary until we have our own type encoding parser;
// setVectors: has an invalid type encoding and crashes NSMethodSignature
FLEXClassPointerOrCFNull(@"_UIPointVector"),
};
assert((sizeof(ignored) / sizeof(Class)) == kFLEXKnownUnsafeClassCount);
_UnsafeClasses = (Class *)malloc(sizeof(ignored));
memcpy(_UnsafeClasses, ignored, sizeof(ignored));
}
return _UnsafeClasses;
}
NSSet * FLEXKnownUnsafeClassNames() {
static NSSet *set = nil;
if (!set) {
NSArray *ignored = @[
@"__ARCLite__",
@"__NSCFCalendar",
@"__NSCFTimer",
@"NSCFTimer",
@"__NSGenericDeallocHandler",
@"NSAutoreleasePool",
@"NSPlaceholderNumber",
@"NSPlaceholderString",
@"NSPlaceholderValue",
@"Object",
@"VMUArchitecture",
@"JSExport",
@"__NSAtom",
@"_NSZombie_",
@"_CNZombie_",
@"__NSMessage",
@"__NSMessageBuilder",
@"FigIrisAutoTrimmerMotionSampleExport",
@"_UIPointVector",
];
set = [NSSet setWithArray:ignored];
assert(set.count == kFLEXKnownUnsafeClassCount);
}
return set;
}

View File

@@ -0,0 +1,46 @@
//
// FLEXTypeEncodingParser.h
// FLEX
//
// Created by Tanner Bennett on 8/22/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// @return \c YES if the type is supported, \c NO otherwise
BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger * _Nullable sizep, NSUInteger * _Nullable alignp);
@interface FLEXTypeEncodingParser : NSObject
/// \c cleanedEncoding is necessary because a type encoding may contain a pointer
/// to an unsupported type. \c NSMethodSignature will pass each type to \c NSGetSizeAndAlignment
/// which will throw an exception on unsupported struct pointers, and this exception is caught
/// by \c NSMethodSignature, but it still bothers anyone debugging with \c objc_exception_throw
///
/// @param cleanedEncoding the "safe" type encoding you can pass to \c NSMethodSignature
/// @return whether the given type encoding can be passed to
/// \c NSMethodSignature without it throwing an exception.
+ (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString *_Nonnull*_Nullable)cleanedEncoding;
/// @return The type encoding of an individual argument in a method's type encoding string.
/// Pass 0 to get the type of the return value. 1 and 2 are `self` and `_cmd` respectively.
+ (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx;
/// @return The size in bytes of the typeof an individual argument in a method's type encoding string.
/// Pass 0 to get the size of the return value. 1 and 2 are `self` and `_cmd` respectively.
+ (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx;
/// @param unaligned whether to compute the aligned or unaligned size.
/// @return The size in bytes, or \c -1 if the type encoding is unsupported.
/// Do not pass in the result of \c method_getTypeEncoding
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut unaligned:(BOOL)unaligned;
/// Defaults to \C unaligned:NO
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,900 @@
//
// FLEXTypeEncodingParser.m
// FLEX
//
// Created by Tanner Bennett on 8/22/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import "FLEXTypeEncodingParser.h"
#import "FLEXRuntimeUtility.h"
#define S(__ch) ({ \
unichar __c = __ch; \
[[NSString alloc] initWithCharacters:&__c length:1]; \
})
typedef struct FLEXTypeInfo {
/// The size is unaligned. -1 if not supported at all.
ssize_t size;
ssize_t align;
/// NO if the type cannot be supported at all
/// YES if the type is either fully or partially supported.
BOOL supported;
/// YES if the type was only partially supported, such as in
/// the case of unions in pointer types, or named structure
/// types without member info. These can be corrected manually
/// since they can be fixed or replaced with less info.
BOOL fixesApplied;
/// Whether this type is a union or one of its members
/// recursively contains a union, exlcuding pointers.
///
/// Unions are tricky because they're supported by
/// \c NSGetSizeAndAlignment but not by \c NSMethodSignature
/// so we need to track whenever a type contains a union
/// so that we can clean it out of pointer types.
BOOL containsUnion;
/// size can only be 0 if not void
BOOL isVoid;
} FLEXTypeInfo;
/// Type info for a completely unsupported type.
static FLEXTypeInfo FLEXTypeInfoUnsupported = (FLEXTypeInfo){ -1, 0, NO, NO, NO, NO };
/// Type info for the void return type.
static FLEXTypeInfo FLEXTypeInfoVoid = (FLEXTypeInfo){ 0, 0, YES, NO, NO, YES };
/// Builds type info for a fully or partially supported type.
static inline FLEXTypeInfo FLEXTypeInfoMake(ssize_t size, ssize_t align, BOOL fixed) {
return (FLEXTypeInfo){ size, align, YES, fixed, NO, NO };
}
/// Builds type info for a fully or partially supported type.
static inline FLEXTypeInfo FLEXTypeInfoMakeU(ssize_t size, ssize_t align, BOOL fixed, BOOL hasUnion) {
return (FLEXTypeInfo){ size, align, YES, fixed, hasUnion, NO };
}
BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *alignp) {
NSInteger size = 0;
ssize_t align = 0;
size = [FLEXTypeEncodingParser sizeForTypeEncoding:@(type) alignment:&align];
if (size == -1) {
return NO;
}
if (sizep) {
*sizep = (NSUInteger)size;
}
if (alignp) {
*alignp = (NSUInteger)size;
}
return YES;
}
@interface FLEXTypeEncodingParser ()
@property (nonatomic, readonly) NSScanner *scan;
@property (nonatomic, readonly) NSString *scanned;
@property (nonatomic, readonly) NSString *unscanned;
@property (nonatomic, readonly) char nextChar;
/// Replacements are made to this string as we scan as needed
@property (nonatomic) NSMutableString *cleaned;
/// Offset for \e further replacements to be made within \c cleaned
@property (nonatomic, readonly) NSUInteger cleanedReplacingOffset;
@end
@implementation FLEXTypeEncodingParser
- (NSString *)scanned {
return [self.scan.string substringToIndex:self.scan.scanLocation];
}
- (NSString *)unscanned {
return [self.scan.string substringFromIndex:self.scan.scanLocation];
}
#pragma mark Initialization
- (id)initWithObjCTypes:(NSString *)typeEncoding {
self = [super init];
if (self) {
_scan = [NSScanner scannerWithString:typeEncoding];
_scan.caseSensitive = YES;
_cleaned = typeEncoding.mutableCopy;
}
return self;
}
#pragma mark Public
+ (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString * __autoreleasing *)cleanedEncoding {
if (!typeEncoding.length) {
return NO;
}
FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
while (!parser.scan.isAtEnd) {
FLEXTypeInfo info = [parser parseNextType];
if (!info.supported || info.containsUnion || (info.size == 0 && !info.isVoid)) {
return NO;
}
}
if (cleanedEncoding) {
*cleanedEncoding = parser.cleaned.copy;
}
return YES;
}
+ (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
// Scan up to the argument we want
for (NSUInteger i = 0; i < idx; i++) {
if (![parser scanPastArg]) {
[NSException raise:NSRangeException
format:@"Index %@ out of bounds for type encoding '%@'",
@(idx), typeEncoding
];
}
}
return [parser scanArg];
}
+ (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
return [self sizeForTypeEncoding:[self type:typeEncoding forMethodArgumentAtIndex:idx] alignment:nil];
}
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut {
return [self sizeForTypeEncoding:type alignment:alignOut unaligned:NO];
}
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut unaligned:(BOOL)unaligned {
FLEXTypeInfo info = [self parseType:type];
ssize_t size = info.size;
ssize_t align = info.align;
if (info.supported) {
if (alignOut) {
*alignOut = align;
}
if (!unaligned) {
size += size % align;
}
}
// size is -1 if not supported
return size;
}
+ (FLEXTypeInfo)parseType:(NSString *)type cleaned:(NSString * __autoreleasing *)cleanedEncoding {
FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:type];
FLEXTypeInfo info = [parser parseNextType];
if (cleanedEncoding) {
*cleanedEncoding = parser.cleaned;
}
return info;
}
+ (FLEXTypeInfo)parseType:(NSString *)type {
return [self parseType:type cleaned:nil];
}
#pragma mark Private
- (NSCharacterSet *)identifierFirstCharCharacterSet {
static NSCharacterSet *identifierFirstSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
identifierFirstSet = [NSCharacterSet characterSetWithCharactersInString:allowed];
});
return identifierFirstSet;
}
- (NSCharacterSet *)identifierCharacterSet {
static NSCharacterSet *identifierSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$1234567890";
identifierSet = [NSCharacterSet characterSetWithCharactersInString:allowed];
});
return identifierSet;
}
- (char)nextChar {
NSScanner *scan = self.scan;
return [scan.string characterAtIndex:scan.scanLocation];
}
/// For scanning struct/class names
- (NSString *)scanIdentifier {
NSString *prefix = nil, *suffix = nil;
// Identifiers cannot start with a number
if (![self.scan scanCharactersFromSet:self.identifierFirstCharCharacterSet intoString:&prefix]) {
return nil;
}
// Optional because identifier may just be one character
[self.scan scanCharactersFromSet:self.identifierCharacterSet intoString:&suffix];
if (suffix) {
return [prefix stringByAppendingString:suffix];
}
return prefix;
}
/// @return the size in bytes
- (ssize_t)sizeForType:(FLEXTypeEncoding)type {
switch (type) {
case FLEXTypeEncodingChar: return sizeof(char);
case FLEXTypeEncodingInt: return sizeof(int);
case FLEXTypeEncodingShort: return sizeof(short);
case FLEXTypeEncodingLong: return sizeof(long);
case FLEXTypeEncodingLongLong: return sizeof(long long);
case FLEXTypeEncodingUnsignedChar: return sizeof(unsigned char);
case FLEXTypeEncodingUnsignedInt: return sizeof(unsigned int);
case FLEXTypeEncodingUnsignedShort: return sizeof(unsigned short);
case FLEXTypeEncodingUnsignedLong: return sizeof(unsigned long);
case FLEXTypeEncodingUnsignedLongLong: return sizeof(unsigned long long);
case FLEXTypeEncodingFloat: return sizeof(float);
case FLEXTypeEncodingDouble: return sizeof(double);
case FLEXTypeEncodingLongDouble: return sizeof(long double);
case FLEXTypeEncodingCBool: return sizeof(_Bool);
case FLEXTypeEncodingVoid: return 0;
case FLEXTypeEncodingCString: return sizeof(char *);
case FLEXTypeEncodingObjcObject: return sizeof(id);
case FLEXTypeEncodingObjcClass: return sizeof(Class);
case FLEXTypeEncodingSelector: return sizeof(SEL);
// Unknown / '?' is typically a pointer. In the rare case
// it isn't, such as in '{?=...}', it is never passed here.
case FLEXTypeEncodingUnknown:
case FLEXTypeEncodingPointer: return sizeof(uintptr_t);
default: return -1;
}
}
- (FLEXTypeInfo)parseNextType {
NSUInteger start = self.scan.scanLocation;
// Check for void first
if ([self scanChar:FLEXTypeEncodingVoid]) {
// Skip argument frame for method signatures
[self scanSize];
return FLEXTypeInfoVoid;
}
// Scan optional const
[self scanChar:FLEXTypeEncodingConst];
// Check for pointer, then scan next
if ([self scanChar:FLEXTypeEncodingPointer]) {
// Recurse to scan something else
NSUInteger pointerTypeStart = self.scan.scanLocation;
if ([self scanPastArg]) {
// Make sure the pointer type is supported, and clean it if not
NSUInteger pointerTypeLength = self.scan.scanLocation - pointerTypeStart;
NSString *pointerType = [self.scan.string
substringWithRange:NSMakeRange(pointerTypeStart, pointerTypeLength)
];
// Deeeep nested cleaning info gets lost here
NSString *cleaned = nil;
FLEXTypeInfo info = [self.class parseType:pointerType cleaned:&cleaned];
BOOL needsCleaning = !info.supported || info.containsUnion || info.fixesApplied;
// Clean the type if it is unsupported, malformed, or contains a union.
// (Unions are supported by NSGetSizeAndAlignment but not
// supported by NSMethodSignature for some reason)
if (needsCleaning) {
// If unsupported, no cleaning occurred in parseType:cleaned: above.
// Otherwise, the type is partially supported and we did clean it,
// and we will replace this type with the cleaned type from above.
if (!info.supported || info.containsUnion) {
cleaned = [self cleanPointeeTypeAtLocation:pointerTypeStart];
}
NSInteger offset = self.cleanedReplacingOffset;
NSInteger location = pointerTypeStart - offset;
[self.cleaned replaceCharactersInRange:NSMakeRange(
location, pointerTypeLength
) withString:cleaned];
}
// Skip optional frame offset
[self scanSize];
ssize_t size = [self sizeForType:FLEXTypeEncodingPointer];
return FLEXTypeInfoMake(size, size, !info.supported || info.fixesApplied);
} else {
// Scan failed, abort
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
}
}
// Check for struct/union/array
char next = self.nextChar;
BOOL didScanSUA = YES, structOrUnion = NO, isUnion = NO;
FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull;
switch (next) {
case FLEXTypeEncodingStructBegin:
structOrUnion = YES;
opening = FLEXTypeEncodingStructBegin;
closing = FLEXTypeEncodingStructEnd;
break;
case FLEXTypeEncodingUnionBegin:
structOrUnion = isUnion = YES;
opening = FLEXTypeEncodingUnionBegin;
closing = FLEXTypeEncodingUnionEnd;
break;
case FLEXTypeEncodingArrayBegin:
opening = FLEXTypeEncodingArrayBegin;
closing = FLEXTypeEncodingArrayEnd;
break;
default:
didScanSUA = NO;
break;
}
if (didScanSUA) {
BOOL containsUnion = isUnion;
BOOL fixesApplied = NO;
NSUInteger backup = self.scan.scanLocation;
// Ensure we have a closing tag
if (![self scanPair:opening close:closing]) {
// Scan failed, abort
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
}
// Move cursor just after opening tag (struct/union/array)
NSInteger arrayCount = -1;
self.scan.scanLocation = backup + 1;
if (!structOrUnion) {
arrayCount = [self scanSize];
if (!arrayCount || self.nextChar == FLEXTypeEncodingArrayEnd) {
// Malformed array type:
// 1. Arrays must have a count after the opening brace
// 2. Arrays must have an element type after the count
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
}
} else {
// If we encounter the ?= portion of something like {?=b8b4b1b1b18[8S]}
// then we skip over it, since it means nothing to us in this context.
// It is completely optional, and if it fails, we go right back where we were.
if (![self scanTypeName] && self.nextChar == FLEXTypeEncodingUnknown) {
// Exception: we are trying to parse {?} which is invalid
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
}
}
// Sum sizes of members together:
// Scan for bitfields before checking for other members
//
// Arrays will only have one "member," but
// this logic still works for them
ssize_t sizeSoFar = 0;
ssize_t maxAlign = 0;
NSMutableString *cleanedBackup = self.cleaned.mutableCopy;
while (![self scanChar:closing]) {
next = self.nextChar;
// Check for bitfields, which we cannot support because
// type encodings for bitfields do not include alignment info
if (next == FLEXTypeEncodingBitField) {
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
}
// Structure fields could be named
if (next == FLEXTypeEncodingQuote) {
[self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
}
FLEXTypeInfo info = [self parseNextType];
if (!info.supported || info.containsUnion) {
// The above call is the only time in this method where
// `cleaned` might be mutated recursively, so this is the
// only place where we need to keep and restore a backup
//
// For instance, if we've been iterating over the members
// of a struct and we've encountered a few pointers so far
// that we needed to clean, and suddenly we come across an
// unsupported member, we need to be able to "rewind" and
// undo any changes to `self.cleaned` so that the parent
// call in the call stack can wipe the current structure
// clean entirely if needed. Example below:
//
// Initial: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}}
// v-- here
// 1st clean: ^{foo=^{?=}{^pair<i,i>}{invalid_type<d>}
// v-- here
// 2nd clean: ^{foo=^{?=}{?=}{invalid_type<d>}
// v-- here
// Can't clean: ^{foo=^{?=}{?=}{invalid_type<d>}
// v-- to here
// Rewind: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}}
// Final clean: ^{foo=}
self.cleaned = cleanedBackup;
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
}
// Unions are the size of their largest member,
// arrays are element.size x length, and
// structs are the sum of their members
if (structOrUnion) {
if (isUnion) { // Union
sizeSoFar = MAX(sizeSoFar, info.size);
} else { // Struct
sizeSoFar += info.size;
}
} else { // Array
sizeSoFar = info.size * arrayCount;
}
// Propogate the max alignment and other metadata
maxAlign = MAX(maxAlign, info.align);
containsUnion = containsUnion || info.containsUnion;
fixesApplied = fixesApplied || info.fixesApplied;
}
// Skip optional frame offset
[self scanSize];
return FLEXTypeInfoMakeU(sizeSoFar, maxAlign, fixesApplied, containsUnion);
}
// Scan single thing and possible size and return
ssize_t size = -1;
char t = self.nextChar;
switch (t) {
case FLEXTypeEncodingUnknown:
case FLEXTypeEncodingChar:
case FLEXTypeEncodingInt:
case FLEXTypeEncodingShort:
case FLEXTypeEncodingLong:
case FLEXTypeEncodingLongLong:
case FLEXTypeEncodingUnsignedChar:
case FLEXTypeEncodingUnsignedInt:
case FLEXTypeEncodingUnsignedShort:
case FLEXTypeEncodingUnsignedLong:
case FLEXTypeEncodingUnsignedLongLong:
case FLEXTypeEncodingFloat:
case FLEXTypeEncodingDouble:
case FLEXTypeEncodingLongDouble:
case FLEXTypeEncodingCBool:
case FLEXTypeEncodingCString:
case FLEXTypeEncodingSelector:
case FLEXTypeEncodingBitField: {
self.scan.scanLocation++;
// Skip optional frame offset
[self scanSize];
if (t == FLEXTypeEncodingBitField) {
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
} else {
// Compute size
size = [self sizeForType:t];
}
}
break;
case FLEXTypeEncodingObjcObject:
case FLEXTypeEncodingObjcClass: {
self.scan.scanLocation++;
// These might have numbers OR quotes after them
// Skip optional frame offset
[self scanSize];
[self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
size = sizeof(id);
}
break;
default: break;
}
if (size > 0) {
// Alignment of scalar types is its size
return FLEXTypeInfoMake(size, size, NO);
}
self.scan.scanLocation = start;
return FLEXTypeInfoUnsupported;
}
- (BOOL)scanString:(NSString *)str {
return [self.scan scanString:str intoString:nil];
}
- (BOOL)canScanString:(NSString *)str {
NSScanner *scan = self.scan;
NSUInteger len = str.length;
unichar buff1[len], buff2[len];
[str getCharacters:buff1];
[scan.string getCharacters:buff2 range:NSMakeRange(scan.scanLocation, len)];
if (memcmp(buff1, buff2, len) == 0) {
return YES;
}
return NO;
}
- (BOOL)canScanChar:(char)c {
// By avoiding any ARC calls on these two objects which we know won't be
// free'd out from under us, we're making HUGE performance savings in this
// parser, because this method is one of the most-used methods of the parser.
// This is probably the most performance-critical method in this class.
__unsafe_unretained NSScanner *scan = self.scan;
__unsafe_unretained NSString *string = scan.string;
if (scan.scanLocation >= string.length) return NO;
return [string characterAtIndex:scan.scanLocation] == c;
}
- (BOOL)scanChar:(char)c {
if ([self canScanChar:c]) {
self.scan.scanLocation++;
return YES;
}
return NO;
}
- (BOOL)scanChar:(char)c into:(char *)ref {
if ([self scanChar:c]) {
*ref = c;
return YES;
}
return NO;
}
- (ssize_t)scanSize {
NSInteger size = 0;
if ([self.scan scanInteger:&size]) {
return size;
}
return 0;
}
- (NSString *)scanPair:(char)c1 close:(char)c2 {
// Starting position and string variables
NSUInteger start = self.scan.scanLocation;
NSString *s1 = S(c1);
// Scan opening tag
if (![self scanChar:c1]) {
self.scan.scanLocation = start;
return nil;
}
// Character set for scanning up to either symbol
NSCharacterSet *bothChars = ({
unichar buff[2] = { c1, c2 };
NSString *bothCharsStr = [[NSString alloc] initWithCharacters:buff length:2];
[NSCharacterSet characterSetWithCharactersInString:bothCharsStr];
});
// Stack for finding pairs, starting with the opening symbol
NSMutableArray *stack = [NSMutableArray arrayWithObject:s1];
// Algorithm for scanning to the closing end of a pair of opening/closing symbols
// scanUpToCharactersFromSet:intoString: returns NO if you're already at one of the chars,
// so we need to check if we can actually scan one if it returns NO
while ([self.scan scanUpToCharactersFromSet:bothChars intoString:nil] ||
[self canScanChar:c1] || [self canScanChar:c2]) {
// Closing symbol found
if ([self scanChar:c2]) {
if (!stack.count) {
// Abort, no matching opening symbol
self.scan.scanLocation = start;
return nil;
}
// Pair found, pop opening symbol
[stack removeLastObject];
// Exit loop if we reached the closing brace we needed
if (!stack.count) {
break;
}
}
// Opening symbol found
if ([self scanChar:c1]) {
// Begin pair
[stack addObject:s1];
}
}
if (stack.count) {
// Abort, no matching closing symbol
self.scan.scanLocation = start;
return nil;
}
// Slice out the string we just scanned
return [self.scan.string
substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
];
}
- (BOOL)scanPastArg {
NSUInteger start = self.scan.scanLocation;
// Check for void first
if ([self scanChar:FLEXTypeEncodingVoid]) {
return YES;
}
// Scan optional const
[self scanChar:FLEXTypeEncodingConst];
// Check for pointer, then scan next
if ([self scanChar:FLEXTypeEncodingPointer]) {
// Recurse to scan something else
if ([self scanPastArg]) {
return YES;
} else {
// Scan failed, abort
self.scan.scanLocation = start;
return NO;
}
}
char next = self.nextChar;
// Check for struct/union/array, scan past it
FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull;
BOOL checkPair = YES;
switch (next) {
case FLEXTypeEncodingStructBegin:
opening = FLEXTypeEncodingStructBegin;
closing = FLEXTypeEncodingStructEnd;
break;
case FLEXTypeEncodingUnionBegin:
opening = FLEXTypeEncodingUnionBegin;
closing = FLEXTypeEncodingUnionEnd;
break;
case FLEXTypeEncodingArrayBegin:
opening = FLEXTypeEncodingArrayBegin;
closing = FLEXTypeEncodingArrayEnd;
break;
default:
checkPair = NO;
break;
}
if (checkPair && [self scanPair:opening close:closing]) {
return YES;
}
// Scan single thing and possible size and return
switch (next) {
case FLEXTypeEncodingUnknown:
case FLEXTypeEncodingChar:
case FLEXTypeEncodingInt:
case FLEXTypeEncodingShort:
case FLEXTypeEncodingLong:
case FLEXTypeEncodingLongLong:
case FLEXTypeEncodingUnsignedChar:
case FLEXTypeEncodingUnsignedInt:
case FLEXTypeEncodingUnsignedShort:
case FLEXTypeEncodingUnsignedLong:
case FLEXTypeEncodingUnsignedLongLong:
case FLEXTypeEncodingFloat:
case FLEXTypeEncodingDouble:
case FLEXTypeEncodingLongDouble:
case FLEXTypeEncodingCBool:
case FLEXTypeEncodingCString:
case FLEXTypeEncodingSelector:
case FLEXTypeEncodingBitField: {
self.scan.scanLocation++;
// Size is optional
[self scanSize];
return YES;
}
case FLEXTypeEncodingObjcObject:
case FLEXTypeEncodingObjcClass: {
self.scan.scanLocation++;
// These might have numbers OR quotes after them
[self scanSize] || [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
return YES;
}
default: break;
}
self.scan.scanLocation = start;
return NO;
}
- (NSString *)scanArg {
NSUInteger start = self.scan.scanLocation;
if (![self scanPastArg]) {
return nil;
}
return [self.scan.string
substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
];
}
- (BOOL)scanTypeName {
NSUInteger start = self.scan.scanLocation;
// The ?= portion of something like {?=b8b4b1b1b18[8S]}
if ([self scanChar:FLEXTypeEncodingUnknown]) {
if (![self scanString:@"="]) {
// No size information available for strings like {?=}
self.scan.scanLocation = start;
return NO;
}
} else {
if (![self scanIdentifier] || ![self scanString:@"="]) {
// 1. Not a valid identifier
// 2. No size information available for strings like {CGPoint}
self.scan.scanLocation = start;
return NO;
}
}
return YES;
}
- (NSString *)extractTypeNameFromScanLocation:(BOOL)allowMissingTypeInfo closing:(FLEXTypeEncoding)closeTag {
NSUInteger start = self.scan.scanLocation;
// The ?= portion of something like {?=b8b4b1b1b18[8S]}
if ([self scanChar:FLEXTypeEncodingUnknown]) {
return @"?";
} else {
NSString *typeName = [self scanIdentifier];
char next = self.nextChar;
if (!typeName) {
// Did not scan an identifier
self.scan.scanLocation = start;
return nil;
}
switch (next) {
case '=':
return typeName;
default: {
// = is non-optional unless we allowMissingTypeInfo, in whcih
// case the next character needs to be a closing brace
if (allowMissingTypeInfo && next == closeTag) {
return typeName;
} else {
// Not a valid identifier; possibly a generic C++ type
// i.e. {pair<T, U>} where `name` was found as `pair`
self.scan.scanLocation = start;
return nil;
}
}
}
}
}
- (NSString *)cleanPointeeTypeAtLocation:(NSUInteger)scanLocation {
NSUInteger start = self.scan.scanLocation;
self.scan.scanLocation = scanLocation;
// The return / cleanup code for when the scanned type is already clean
NSString * (^typeIsClean)(void) = ^NSString * {
NSString *clean = [self.scan.string
substringWithRange:NSMakeRange(scanLocation, self.scan.scanLocation - scanLocation)
];
// Reset scan location even on success, because this method is not supposed to change it
self.scan.scanLocation = start;
return clean;
};
// No void, this is not a return type
// Scan optional const
[self scanChar:FLEXTypeEncodingConst];
char next = self.nextChar;
switch (next) {
case FLEXTypeEncodingPointer:
// Recurse to scan something else
[self scanChar:next];
return [self cleanPointeeTypeAtLocation:self.scan.scanLocation];
case FLEXTypeEncodingArrayBegin:
// All arrays are supported, scan past them
if ([self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) {
return typeIsClean();
}
break;
case FLEXTypeEncodingUnionBegin:
// Unions are not supported at all in NSMethodSignature
// We could check for the closing token to be safe, but eh
self.scan.scanLocation = start;
return @"?";
case FLEXTypeEncodingStructBegin: {
FLEXTypeInfo info = [self.class parseType:self.unscanned];
if (info.supported && !info.fixesApplied) {
[self scanPastArg];
return typeIsClean();
}
// The structure we just tried to scan is unsupported, so just return its name
// if it has one. If not, just return a question mark.
self.scan.scanLocation++; // Skip past {
NSString *name = [self extractTypeNameFromScanLocation:YES closing:FLEXTypeEncodingStructEnd];
if (name) {
// Got the name, scan past the closing token
[self.scan scanUpToString:@"}" intoString:nil];
if (![self scanChar:FLEXTypeEncodingStructEnd]) {
// Missing struct close token
self.scan.scanLocation = start;
return nil;
}
} else {
// Did not scan valid identifier, possibly a C++ type
self.scan.scanLocation = start;
return @"{?=}";
}
// Reset scan location even on success, because this method is not supposed to change it
self.scan.scanLocation = start;
return ({ // "{name=}"
NSMutableString *format = @"{".mutableCopy;
[format appendString:name];
[format appendString:@"=}"];
format;
});
}
default:
break;
}
// Check for other types, which in theory are all valid but whatever
FLEXTypeInfo info = [self parseNextType];
if (info.supported && !info.fixesApplied) {
return typeIsClean();
}
self.scan.scanLocation = start;
return @"?";
}
- (NSUInteger)cleanedReplacingOffset {
return self.scan.string.length - self.cleaned.length;
}
@end

View File

@@ -0,0 +1,58 @@
//
// FLEXBlockDescription.h
// FLEX
//
// Created by Oliver Letterer on 2012-09-01
// Forked from CTObjectiveCRuntimeAdditions (MIT License)
// https://github.com/ebf/CTObjectiveCRuntimeAdditions
//
// Copyright (c) 2020 FLEX Team-EDV Beratung Föllmer GmbH
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in 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:
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// 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 AUTHORS 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 IN THE SOFTWARE.
#import <Foundation/Foundation.h>
typedef NS_OPTIONS(NSUInteger, FLEXBlockOptions) {
FLEXBlockOptionHasCopyDispose = (1 << 25),
FLEXBlockOptionHasCtor = (1 << 26), // helpers have C++ code
FLEXBlockOptionIsGlobal = (1 << 28),
FLEXBlockOptionHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
FLEXBlockOptionHasSignature = (1 << 30),
};
NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@interface FLEXBlockDescription : NSObject
+ (instancetype)describing:(id)block;
@property (nonatomic, readonly, nullable) NSMethodSignature *signature;
@property (nonatomic, readonly, nullable) NSString *signatureString;
@property (nonatomic, readonly, nullable) NSString *sourceDeclaration;
@property (nonatomic, readonly) FLEXBlockOptions flags;
@property (nonatomic, readonly) NSUInteger size;
@property (nonatomic, readonly) NSString *summary;
@property (nonatomic, readonly) id block;
- (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature;
@end
#pragma mark -
@interface NSBlock : NSObject
- (void)invoke;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,157 @@
//
// FLEXBlockDescription.m
// FLEX
//
// Created by Oliver Letterer on 2012-09-01
// Forked from CTObjectiveCRuntimeAdditions (MIT License)
// https://github.com/ebf/CTObjectiveCRuntimeAdditions
//
// Copyright (c) 2020 FLEX Team-EDV Beratung Föllmer GmbH
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in 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:
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// 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 AUTHORS 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 IN THE SOFTWARE.
#import "FLEXBlockDescription.h"
#import "FLEXRuntimeUtility.h"
struct block_object {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct block_descriptor {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
@implementation FLEXBlockDescription
+ (instancetype)describing:(id)block {
return [[self alloc] initWithObjcBlock:block];
}
- (id)initWithObjcBlock:(id)block {
self = [super init];
if (self) {
_block = block;
struct block_object *blockRef = (__bridge struct block_object *)block;
_flags = blockRef->flags;
_size = blockRef->descriptor->size;
if (_flags & FLEXBlockOptionHasSignature) {
void *signatureLocation = blockRef->descriptor;
signatureLocation += sizeof(unsigned long int);
signatureLocation += sizeof(unsigned long int);
if (_flags & FLEXBlockOptionHasCopyDispose) {
signatureLocation += sizeof(void(*)(void *dst, void *src));
signatureLocation += sizeof(void (*)(void *src));
}
const char *signature = (*(const char **)signatureLocation);
_signatureString = @(signature);
@try {
_signature = [NSMethodSignature signatureWithObjCTypes:signature];
} @catch (NSException *exception) { }
}
NSMutableString *summary = [NSMutableString stringWithFormat:
@"Type signature: %@\nSize: %@\nIs global: %@\nHas constructor: %@\nIs stret: %@",
self.signatureString ?: @"nil", @(self.size),
@((BOOL)(_flags & FLEXBlockOptionIsGlobal)),
@((BOOL)(_flags & FLEXBlockOptionHasCtor)),
@((BOOL)(_flags & FLEXBlockOptionHasStret))
];
if (!self.signature) {
[summary appendFormat:@"\nNumber of arguments: %@", @(self.signature.numberOfArguments)];
}
_summary = summary.copy;
_sourceDeclaration = [self buildLikelyDeclaration];
}
return self;
}
- (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature {
if (!self.signature) {
return NO;
}
if (self.signature.numberOfArguments != methodSignature.numberOfArguments + 1) {
return NO;
}
if (strcmp(self.signature.methodReturnType, methodSignature.methodReturnType) != 0) {
return NO;
}
for (int i = 0; i < methodSignature.numberOfArguments; i++) {
if (i == 1) {
// SEL in method, IMP in block
if (strcmp([methodSignature getArgumentTypeAtIndex:i], ":") != 0) {
return NO;
}
if (strcmp([self.signature getArgumentTypeAtIndex:i + 1], "^?") != 0) {
return NO;
}
} else {
if (strcmp([self.signature getArgumentTypeAtIndex:i], [self.signature getArgumentTypeAtIndex:i + 1]) != 0) {
return NO;
}
}
}
return YES;
}
- (NSString *)buildLikelyDeclaration {
NSMethodSignature *signature = self.signature;
NSUInteger numberOfArguments = signature.numberOfArguments;
const char *returnType = signature.methodReturnType;
// Return type
NSMutableString *decl = [NSMutableString stringWithString:@"^"];
if (returnType[0] != FLEXTypeEncodingVoid) {
[decl appendString:[FLEXRuntimeUtility readableTypeForEncoding:@(returnType)]];
[decl appendString:@" "];
}
// Arguments
if (numberOfArguments) {
[decl appendString:@"("];
for (NSUInteger i = 1; i < numberOfArguments; i++) {
const char *argType = [self.signature getArgumentTypeAtIndex:i] ?: "?";
NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)];
[decl appendFormat:@"%@ arg%@, ", readableArgType, @(i)];
}
[decl deleteCharactersInRange:NSMakeRange(decl.length-2, 2)];
[decl appendString:@")"];
}
return decl.copy;
}
@end

View File

@@ -0,0 +1,80 @@
//
// FLEXClassBuilder.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/3/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
@class FLEXIvarBuilder, FLEXMethodBase, FLEXProperty, FLEXProtocol;
#pragma mark FLEXClassBuilder
@interface FLEXClassBuilder : NSObject
@property (nonatomic, readonly) Class workingClass;
/// Begins constructing a class with the given name.
///
/// This new class will implicitly inherits from \c NSObject with \c 0 extra bytes.
/// Classes created this way must be registered with \c -registerClass before being used.
+ (instancetype)allocateClass:(NSString *)name;
/// Begins constructing a class with the given name and superclass.
/// @discussion Calls \c -allocateClass:superclass:extraBytes: with \c 0 extra bytes.
/// Classes created this way must be registered with \c -registerClass before being used.
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass;
/// Begins constructing a new class object with the given name and superclass.
/// @discussion Pass \c nil to \e superclass to create a new root class.
/// Classes created this way must be registered with \c -registerClass before being used.
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes;
/// Begins constructing a new root class object with the given name and \c 0 extra bytes.
/// @discussion Classes created this way must be registered with \c -registerClass before being used.
+ (instancetype)allocateRootClass:(NSString *)name;
/// Use this to modify existing classes. @warning You cannot add instance variables to existing classes.
+ (instancetype)builderForClass:(Class)cls;
/// @return Any methods that failed to be added.
- (NSArray<FLEXMethodBase *> *)addMethods:(NSArray<FLEXMethodBase *> *)methods;
/// @return Any properties that failed to be added.
- (NSArray<FLEXProperty *> *)addProperties:(NSArray<FLEXProperty *> *)properties;
/// @return Any protocols that failed to be added.
- (NSArray<FLEXProtocol *> *)addProtocols:(NSArray<FLEXProtocol *> *)protocols;
/// @warning Adding Ivars to existing classes is not supported and will always fail.
- (NSArray<FLEXIvarBuilder *> *)addIvars:(NSArray<FLEXIvarBuilder *> *)ivars;
/// Finalizes construction of a new class.
/// @discussion Once a class is registered, instance variables cannot be added.
/// @note Raises an exception if called on a previously registered class.
- (Class)registerClass;
/// Uses \c objc_lookupClass to determine if the working class is registered.
@property (nonatomic, readonly) BOOL isRegistered;
@end
#pragma mark FLEXIvarBuilder
@interface FLEXIvarBuilder : NSObject
/// Consider using the \c FLEXIvarBuilderWithNameAndType() macro below.
/// @param name The name of the Ivar, such as \c \@"_value".
/// @param size The size of the Ivar. Usually \c sizeof(type). For objects, this is \c sizeof(id).
/// @param alignment The alignment of the Ivar. Usually \c log2(sizeof(type)).
/// @param encoding The type encoding of the Ivar. For objects, this is \c \@(\@encode(id)), and for others it is \c \@(\@encode(type)).
+ (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding;
@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) NSString *encoding;
@property (nonatomic, readonly) size_t size;
@property (nonatomic, readonly) uint8_t alignment;
@end
#define FLEXIvarBuilderWithNameAndType(nameString, type) [FLEXIvarBuilder \
name:nameString \
size:sizeof(type) \
alignment:log2(sizeof(type)) \
typeEncoding:@(@encode(type)) \
]

View File

@@ -0,0 +1,168 @@
//
// FLEXClassBuilder.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/3/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXClassBuilder.h"
#import "FLEXProperty.h"
#import "FLEXMethodBase.h"
#import "FLEXProtocol.h"
#import <objc/runtime.h>
#pragma mark FLEXClassBuilder
@interface FLEXClassBuilder ()
@property (nonatomic) NSString *name;
@end
@implementation FLEXClassBuilder
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
#pragma mark Initializers
+ (instancetype)allocateClass:(NSString *)name {
return [self allocateClass:name superclass:NSObject.class];
}
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass {
return [self allocateClass:name superclass:superclass extraBytes:0];
}
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes {
NSParameterAssert(name);
return [[self alloc] initWithClass:objc_allocateClassPair(superclass, name.UTF8String, bytes)];
}
+ (instancetype)allocateRootClass:(NSString *)name {
NSParameterAssert(name);
return [[self alloc] initWithClass:objc_allocateClassPair(Nil, name.UTF8String, 0)];
}
+ (instancetype)builderForClass:(Class)cls {
return [[self alloc] initWithClass:cls];
}
- (id)initWithClass:(Class)cls {
NSParameterAssert(cls);
self = [super init];
if (self) {
_workingClass = cls;
_name = NSStringFromClass(_workingClass);
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>",
NSStringFromClass(self.class), self.name, self.isRegistered];
}
#pragma mark Building
- (NSArray *)addMethods:(NSArray *)methods {
NSParameterAssert(methods.count);
NSMutableArray *failed = [NSMutableArray new];
for (FLEXMethodBase *m in methods) {
if (!class_addMethod(self.workingClass, m.selector, m.implementation, m.typeEncoding.UTF8String)) {
[failed addObject:m];
}
}
return failed;
}
- (NSArray *)addProperties:(NSArray *)properties {
NSParameterAssert(properties.count);
NSMutableArray *failed = [NSMutableArray new];
for (FLEXProperty *p in properties) {
unsigned int pcount;
objc_property_attribute_t *attributes = [p copyAttributesList:&pcount];
if (!class_addProperty(self.workingClass, p.name.UTF8String, attributes, pcount)) {
[failed addObject:p];
}
free(attributes);
}
return failed;
}
- (NSArray *)addProtocols:(NSArray *)protocols {
NSParameterAssert(protocols.count);
NSMutableArray *failed = [NSMutableArray new];
for (FLEXProtocol *p in protocols) {
if (!class_addProtocol(self.workingClass, p.objc_protocol)) {
[failed addObject:p];
}
}
return failed;
}
- (NSArray *)addIvars:(NSArray *)ivars {
NSParameterAssert(ivars.count);
NSMutableArray *failed = [NSMutableArray new];
for (FLEXIvarBuilder *ivar in ivars) {
if (!class_addIvar(self.workingClass, ivar.name.UTF8String, ivar.size, ivar.alignment, ivar.encoding.UTF8String)) {
[failed addObject:ivar];
}
}
return failed;
}
- (Class)registerClass {
if (self.isRegistered) {
[NSException raise:NSInternalInconsistencyException format:@"Class is already registered"];
}
objc_registerClassPair(self.workingClass);
return self.workingClass;
}
- (BOOL)isRegistered {
return objc_lookUpClass(self.name.UTF8String) != nil;
}
@end
#pragma mark FLEXIvarBuilder
@implementation FLEXIvarBuilder
+ (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding {
return [[self alloc] initWithName:name size:size alignment:alignment typeEncoding:encoding];
}
- (id)initWithName:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding {
NSParameterAssert(name); NSParameterAssert(encoding);
NSParameterAssert(size > 0); NSParameterAssert(alignment > 0);
self = [super init];
if (self) {
_name = name;
_encoding = encoding;
_size = size;
_alignment = alignment;
}
return self;
}
@end

View File

@@ -0,0 +1,51 @@
//
// FLEXIvar.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntimeConstants.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXIvar : NSObject
+ (instancetype)ivar:(Ivar)ivar;
+ (instancetype)named:(NSString *)name onClass:(Class)cls;
/// The underlying \c Ivar data structure.
@property (nonatomic, readonly) Ivar objc_ivar;
/// The name of the instance variable.
@property (nonatomic, readonly) NSString *name;
/// The type of the instance variable.
@property (nonatomic, readonly) FLEXTypeEncoding type;
/// The type encoding string of the instance variable.
@property (nonatomic, readonly) NSString *typeEncoding;
/// The offset of the instance variable.
@property (nonatomic, readonly) NSInteger offset;
/// The size of the instance variable. 0 if unknown.
@property (nonatomic, readonly) NSUInteger size;
/// Describes the type encoding, size, offset, and objc_ivar
@property (nonatomic, readonly) NSString *details;
/// The full path of the image that contains this ivar definition,
/// or \c nil if this ivar was probably defined at runtime.
@property (nonatomic, readonly, nullable) NSString *imagePath;
/// For internal use
@property (nonatomic) id tag;
- (nullable id)getValue:(id)target;
- (void)setValue:(nullable id)value onObject:(id)target;
/// Calls into -getValue: and passes that value into
/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
/// and returns the result
- (nullable id)getPotentiallyUnboxedValue:(id)target;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,158 @@
//
// FLEXIvar.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXIvar.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXRuntimeSafety.h"
#import "FLEXTypeEncodingParser.h"
#import "NSString+FLEX.h"
#include "FLEXObjcInternal.h"
#include <dlfcn.h>
@interface FLEXIvar () {
NSString *_flex_description;
}
@end
@implementation FLEXIvar
#pragma mark Initializers
+ (instancetype)ivar:(Ivar)ivar {
return [[self alloc] initWithIvar:ivar];
}
+ (instancetype)named:(NSString *)name onClass:(Class)cls {
Ivar _Nullable ivar = class_getInstanceVariable(cls, name.UTF8String);
NSAssert(ivar, @"Cannot find ivar with name %@ on class %@", name, cls);
return [self ivar:ivar];
}
- (id)initWithIvar:(Ivar)ivar {
NSParameterAssert(ivar);
self = [super init];
if (self) {
_objc_ivar = ivar;
[self examine];
}
return self;
}
#pragma mark Other
- (NSString *)description {
if (!_flex_description) {
NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.typeEncoding];
_flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType];
}
return _flex_description;
}
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@ name=%@, encoding=%@, offset=%ld>",
NSStringFromClass(self.class), self.name, self.typeEncoding, (long)self.offset];
}
- (void)examine {
_name = @(ivar_getName(self.objc_ivar) ?: "(nil)");
_offset = ivar_getOffset(self.objc_ivar);
_typeEncoding = @(ivar_getTypeEncoding(self.objc_ivar) ?: "");
NSString *typeForDetails = _typeEncoding;
NSString *sizeForDetails = nil;
if (_typeEncoding.length) {
_type = (FLEXTypeEncoding)[_typeEncoding characterAtIndex:0];
FLEXGetSizeAndAlignment(_typeEncoding.UTF8String, &_size, nil);
sizeForDetails = [@(_size).stringValue stringByAppendingString:@" bytes"];
} else {
_type = FLEXTypeEncodingNull;
typeForDetails = @"no type info";
sizeForDetails = @"unknown size";
}
Dl_info exeInfo;
if (dladdr(_objc_ivar, &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
}
_details = [NSString stringWithFormat:
@"%@, offset %@ — %@",
sizeForDetails, @(_offset), typeForDetails
];
}
- (id)getValue:(id)target {
id value = nil;
if (!FLEXIvarIsSafe(_objc_ivar) ||
_type == FLEXTypeEncodingNull ||
FLEXPointerIsTaggedPointer(target)) {
return nil;
}
#ifdef __arm64__
// See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
if (self.type == FLEXTypeEncodingObjcClass && [self.name isEqualToString:@"isa"]) {
value = object_getClass(target);
} else
#endif
if (self.type == FLEXTypeEncodingObjcObject || self.type == FLEXTypeEncodingObjcClass) {
value = object_getIvar(target, self.objc_ivar);
} else {
void *pointer = (__bridge void *)target + self.offset;
value = [FLEXRuntimeUtility
valueForPrimitivePointer:pointer
objCType:self.typeEncoding.UTF8String
];
}
return value;
}
- (void)setValue:(id)value onObject:(id)target {
const char *typeEncodingCString = self.typeEncoding.UTF8String;
if (self.type == FLEXTypeEncodingObjcObject) {
object_setIvar(target, self.objc_ivar, value);
} else if ([value isKindOfClass:[NSValue class]]) {
// Primitive - unbox the NSValue.
NSValue *valueValue = (NSValue *)value;
// Make sure that the box contained the correct type.
NSAssert(
strcmp(valueValue.objCType, typeEncodingCString) == 0,
@"Type encoding mismatch (value: %s; ivar: %s) in setting ivar named: %@ on object: %@",
valueValue.objCType, typeEncodingCString, self.name, target
);
NSUInteger bufferSize = 0;
if (FLEXGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL)) {
void *buffer = calloc(bufferSize, 1);
[valueValue getValue:buffer];
void *pointer = (__bridge void *)target + self.offset;
memcpy(pointer, buffer, bufferSize);
free(buffer);
}
}
}
- (id)getPotentiallyUnboxedValue:(id)target {
NSString *type = self.typeEncoding;
if (type.flex_typeIsNonObjcPointer && type.flex_pointeeType != FLEXTypeEncodingVoid) {
return [self getValue:target];
}
return [FLEXRuntimeUtility
potentiallyUnwrapBoxedPointer:[self getValue:target]
type:type.UTF8String
];
}
@end

View File

@@ -0,0 +1,96 @@
//
// FLEXMethod.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntimeConstants.h"
#import "FLEXMethodBase.h"
NS_ASSUME_NONNULL_BEGIN
/// A class representing a concrete method which already exists in a class.
/// This class contains helper methods for swizzling or invoking the method.
///
/// Any of the initializers will return nil if the type encoding
/// of the method is unsupported by `NSMethodSignature`. In general,
/// any method whose return type or parameters involve a struct with
/// bitfields or arrays is unsupported.
///
/// I do not remember why I didn't include \c signature in the base class
/// when I originally wrote this, but I probably had a good reason. We can
/// always go back and move it to \c FLEXMethodBase if we find we need to.
@interface FLEXMethod : FLEXMethodBase
/// Defaults to instance method
+ (nullable instancetype)method:(Method)method;
+ (nullable instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod;
/// Constructs an \c FLEXMethod for the given method on the given class.
/// @param cls the class, or metaclass if this is a class method
/// @return The newly constructed \c FLEXMethod object, or \c nil if the
/// specified class or its superclasses do not contain a method with the specified selector.
+ (nullable instancetype)selector:(SEL)selector class:(Class)cls;
/// Constructs an \c FLEXMethod for the given method on the given class,
/// only if the given class itself defines or overrides the desired method.
/// @param cls the class, or metaclass if this is a class method
/// @return The newly constructed \c FLEXMethod object, or \c nil \e if the
/// specified class does not define or override, or if the specified class
/// or its superclasses do not contain, a method with the specified selector.
+ (nullable instancetype)selector:(SEL)selector implementedInClass:(Class)cls;
@property (nonatomic, readonly) Method objc_method;
/// The implementation of the method.
/// @discussion Setting \c implementation will change the implementation of this method
/// for the entire class which implements said method. It will also not modify the selector of said method.
@property (nonatomic ) IMP implementation;
/// Whether the method is an instance method or not.
@property (nonatomic, readonly) BOOL isInstanceMethod;
/// The number of arguments to the method.
@property (nonatomic, readonly) NSUInteger numberOfArguments;
/// The \c NSMethodSignature object corresponding to the method's type encoding.
@property (nonatomic, readonly) NSMethodSignature *signature;
/// Same as \e typeEncoding but with parameter sizes up front and offsets after the types.
@property (nonatomic, readonly) NSString *signatureString;
/// The return type of the method.
@property (nonatomic, readonly) FLEXTypeEncoding *returnType;
/// The return size of the method.
@property (nonatomic, readonly) NSUInteger returnSize;
/// The full path of the image that contains this method definition,
/// or \c nil if this ivar was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
/// Like @code - (void)foo:(int)bar @endcode
@property (nonatomic, readonly) NSString *description;
/// Like @code -[Class foo:] @endcode
- (NSString *)debugNameGivenClassName:(NSString *)name;
/// Swizzles the recieving method with the given method.
- (void)swapImplementations:(FLEXMethod *)method;
#define FLEXMagicNumber 0xdeadbeef
#define FLEXArg(expr) FLEXMagicNumber,/// @encode(__typeof__(expr)), (__typeof__(expr) []){ expr }
/// Sends a message to \e target, and returns it's value, or \c nil if not applicable.
/// @discussion You may send any message with this method. Primitive return values will be wrapped
/// in instances of \c NSNumber and \c NSValue. \c void and bitfield returning methods return \c nil.
/// \c SEL return types are converted to strings using \c NSStringFromSelector.
/// @return The object returned by this method, or an instance of \c NSValue or \c NSNumber containing
/// the primitive return type, or a string for \c SEL return types.
- (id)sendMessage:(id)target, ...;
/// Used internally by \c sendMessage:target,. Pass \c NULL to the first parameter for void methods.
- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ...;
@end
@interface FLEXMethod (Comparison)
- (NSComparisonResult)compare:(FLEXMethod *)method;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,430 @@
//
// FLEXMethod.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXMethod.h"
#import "FLEXMirror.h"
#import "FLEXTypeEncodingParser.h"
#import "FLEXRuntimeUtility.h"
#include <dlfcn.h>
@implementation FLEXMethod
@synthesize imagePath = _imagePath;
@dynamic implementation;
+ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation {
[NSException raise:NSInternalInconsistencyException format:@"Class instance should not be created with +buildMethodNamed:withTypes:implementation"]; return nil;
}
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
#pragma mark Initializers
+ (instancetype)method:(Method)method {
return [[self alloc] initWithMethod:method isInstanceMethod:YES];
}
+ (instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod {
return [[self alloc] initWithMethod:method isInstanceMethod:isInstanceMethod];
}
+ (instancetype)selector:(SEL)selector class:(Class)cls {
BOOL instance = !class_isMetaClass(cls);
// class_getInstanceMethod will return an instance method if not given
// not given a metaclass, or a class method if given a metaclass, but
// this isn't documented so we just want to be safe here.
Method m = instance ? class_getInstanceMethod(cls, selector) : class_getClassMethod(cls, selector);
if (m == NULL) return nil;
return [self method:m isInstanceMethod:instance];
}
+ (instancetype)selector:(SEL)selector implementedInClass:(Class)cls {
if (![cls superclass]) { return [self selector:selector class:cls]; }
BOOL unique = [cls methodForSelector:selector] != [[cls superclass] methodForSelector:selector];
if (unique) {
return [self selector:selector class:cls];
}
return nil;
}
- (id)initWithMethod:(Method)method isInstanceMethod:(BOOL)isInstanceMethod {
NSParameterAssert(method);
self = [super init];
if (self) {
_objc_method = method;
_isInstanceMethod = isInstanceMethod;
_signatureString = @(method_getTypeEncoding(method) ?: "?@:");
NSString *cleanSig = nil;
if ([FLEXTypeEncodingParser methodTypeEncodingSupported:_signatureString cleaned:&cleanSig]) {
_signature = [NSMethodSignature signatureWithObjCTypes:cleanSig.UTF8String];
}
[self examine];
}
return self;
}
#pragma mark Other
- (NSString *)description {
if (!_flex_description) {
_flex_description = [self prettyName];
}
return _flex_description;
}
- (NSString *)debugNameGivenClassName:(NSString *)name {
NSMutableString *string = [NSMutableString stringWithString:_isInstanceMethod ? @"-[" : @"+["];
[string appendString:name];
[string appendString:@" "];
[string appendString:self.selectorString];
[string appendString:@"]"];
return string;
}
- (NSString *)prettyName {
NSString *methodTypeString = self.isInstanceMethod ? @"-" : @"+";
NSString *readableReturnType = [FLEXRuntimeUtility readableTypeForEncoding:@(self.signature.methodReturnType ?: "")];
NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType];
NSArray *components = [self prettyArgumentComponents];
if (components.count) {
return [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]];
} else {
return [prettyName stringByAppendingString:self.selectorString];
}
}
- (NSArray *)prettyArgumentComponents {
// NSMethodSignature can't handle some type encodings
// like ^AI@:ir* which happen to very much exist
if (self.signature.numberOfArguments < self.numberOfArguments) {
return nil;
}
NSMutableArray *components = [NSMutableArray new];
NSArray *selectorComponents = [self.selectorString componentsSeparatedByString:@":"];
NSUInteger numberOfArguments = self.numberOfArguments;
for (NSUInteger argIndex = 2; argIndex < numberOfArguments; argIndex++) {
assert(argIndex < self.signature.numberOfArguments);
const char *argType = [self.signature getArgumentTypeAtIndex:argIndex] ?: "?";
NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)];
NSString *prettyComponent = [NSString
stringWithFormat:@"%@:(%@) ",
selectorComponents[argIndex - 2],
readableArgType
];
[components addObject:prettyComponent];
}
return components;
}
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@ selector=%@, signature=%@>",
NSStringFromClass(self.class), self.selectorString, self.signatureString];
}
- (void)examine {
_implementation = method_getImplementation(_objc_method);
_selector = method_getName(_objc_method);
_numberOfArguments = method_getNumberOfArguments(_objc_method);
_name = NSStringFromSelector(_selector);
_returnType = (FLEXTypeEncoding *)_signature.methodReturnType ?: "";
_returnSize = _signature.methodReturnLength;
}
#pragma mark Public
- (void)setImplementation:(IMP)implementation {
NSParameterAssert(implementation);
method_setImplementation(self.objc_method, implementation);
[self examine];
}
- (NSString *)typeEncoding {
if (!_typeEncoding) {
_typeEncoding = [_signatureString
stringByReplacingOccurrencesOfString:@"[0-9]"
withString:@""
options:NSRegularExpressionSearch
range:NSMakeRange(0, _signatureString.length)
];
}
return _typeEncoding;
}
- (NSString *)imagePath {
if (!_imagePath) {
Dl_info exeInfo;
if (dladdr(_implementation, &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : @"";
}
}
return _imagePath;
}
#pragma mark Misc
- (void)swapImplementations:(FLEXMethod *)method {
method_exchangeImplementations(self.objc_method, method.objc_method);
[self examine];
[method examine];
}
// Some code borrowed from MAObjcRuntime, by Mike Ash.
- (id)sendMessage:(id)target, ... {
id ret = nil;
va_list args;
va_start(args, target);
switch (self.returnType[0]) {
case FLEXTypeEncodingUnknown: {
[self getReturnValue:NULL forMessageSend:target arguments:args];
break;
}
case FLEXTypeEncodingChar: {
char val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingInt: {
int val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingShort: {
short val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingLong: {
long val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingLongLong: {
long long val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingUnsignedChar: {
unsigned char val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingUnsignedInt: {
unsigned int val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingUnsignedShort: {
unsigned short val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingUnsignedLong: {
unsigned long val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingUnsignedLongLong: {
unsigned long long val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingFloat: {
float val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingDouble: {
double val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingLongDouble: {
long double val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = [NSValue value:&val withObjCType:self.returnType];
break;
}
case FLEXTypeEncodingCBool: {
bool val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingVoid: {
[self getReturnValue:NULL forMessageSend:target arguments:args];
return nil;
break;
}
case FLEXTypeEncodingCString: {
char *val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = @(val);
break;
}
case FLEXTypeEncodingObjcObject: {
id val = nil;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = val;
break;
}
case FLEXTypeEncodingObjcClass: {
Class val = Nil;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = val;
break;
}
case FLEXTypeEncodingSelector: {
SEL val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = NSStringFromSelector(val);
break;
}
case FLEXTypeEncodingArrayBegin: {
void *val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType];
break;
}
case FLEXTypeEncodingUnionBegin:
case FLEXTypeEncodingStructBegin: {
if (self.signature.methodReturnLength) {
void * val = malloc(self.signature.methodReturnLength);
[self getReturnValue:val forMessageSend:target arguments:args];
ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType];
} else {
[self getReturnValue:NULL forMessageSend:target arguments:args];
}
break;
}
case FLEXTypeEncodingBitField: {
[self getReturnValue:NULL forMessageSend:target arguments:args];
break;
}
case FLEXTypeEncodingPointer: {
void * val = 0;
[self getReturnValue:&val forMessageSend:target arguments:args];
ret = [NSValue valueWithPointer:val];
break;
}
default: {
[NSException raise:NSInvalidArgumentException
format:@"Unsupported type encoding: %s", (char *)self.returnType];
}
}
va_end(args);
return ret;
}
// Code borrowed from MAObjcRuntime, by Mike Ash.
- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ... {
va_list args;
va_start(args, target);
[self getReturnValue:retPtr forMessageSend:target arguments:args];
va_end(args);
}
// Code borrowed from MAObjcRuntime, by Mike Ash.
- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target arguments:(va_list)args {
if (!_signature) {
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_signature];
NSUInteger argumentCount = _signature.numberOfArguments;
invocation.target = target;
for (NSUInteger i = 2; i < argumentCount; i++) {
int cookie = va_arg(args, int);
if (cookie != FLEXMagicNumber) {
[NSException
raise:NSInternalInconsistencyException
format:@"%s: incorrect magic cookie %08x; make sure you didn't forget "
"any arguments and that all arguments are wrapped in FLEXArg().", __func__, cookie
];
}
const char *typeString = va_arg(args, char *);
void *argPointer = va_arg(args, void *);
NSUInteger inSize, sigSize;
NSGetSizeAndAlignment(typeString, &inSize, NULL);
NSGetSizeAndAlignment([_signature getArgumentTypeAtIndex:i], &sigSize, NULL);
if (inSize != sigSize) {
[NSException
raise:NSInternalInconsistencyException
format:@"%s:size mismatch between passed-in argument and "
"required argument; in type:%s (%lu) requested:%s (%lu)",
__func__, typeString, (long)inSize, [_signature getArgumentTypeAtIndex:i], (long)sigSize
];
}
[invocation setArgument:argPointer atIndex:i];
}
// Hack to make NSInvocation invoke the desired implementation
IMP imp = [invocation methodForSelector:NSSelectorFromString(@"invokeUsingIMP:")];
void (*invokeWithIMP)(id, SEL, IMP) = (void *)imp;
invokeWithIMP(invocation, 0, _implementation);
if (_signature.methodReturnLength && retPtr) {
[invocation getReturnValue:retPtr];
}
}
@end
@implementation FLEXMethod (Comparison)
- (NSComparisonResult)compare:(FLEXMethod *)method {
return [self.selectorString compare:method.selectorString];
}
@end

View File

@@ -0,0 +1,43 @@
//
// FLEXMethodBase.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/5/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
/// A base class for methods which encompasses those that may not
/// have been added to a class yet. Useful on it's own for adding
/// methods to a class, or building a new class from the ground up.
@interface FLEXMethodBase : NSObject {
@protected
SEL _selector;
NSString *_name;
NSString *_typeEncoding;
IMP _implementation;
NSString *_flex_description;
}
/// Constructs and returns an \c FLEXSimpleMethod instance with the given name, type encoding, and implementation.
+ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation;
/// The selector of the method.
@property (nonatomic, readonly) SEL selector;
/// The selector string of the method.
@property (nonatomic, readonly) NSString *selectorString;
/// Same as selectorString.
@property (nonatomic, readonly) NSString *name;
/// The type encoding of the method.
@property (nonatomic, readonly) NSString *typeEncoding;
/// The implementation of the method.
@property (nonatomic, readonly) IMP implementation;
/// For internal use
@property (nonatomic) id tag;
@end

View File

@@ -0,0 +1,49 @@
//
// FLEXMethodBase.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/5/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXMethodBase.h"
@implementation FLEXMethodBase
#pragma mark Initializers
+ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation {
return [[self alloc] initWithSelector:sel_registerName(name.UTF8String) types:typeEncoding imp:implementation];
}
- (id)initWithSelector:(SEL)selector types:(NSString *)types imp:(IMP)imp {
NSParameterAssert(selector); NSParameterAssert(types); NSParameterAssert(imp);
self = [super init];
if (self) {
_selector = selector;
_typeEncoding = types;
_implementation = imp;
_name = NSStringFromSelector(self.selector);
}
return self;
}
- (NSString *)selectorString {
return _name;
}
#pragma mark Overrides
- (NSString *)description {
if (!_flex_description) {
_flex_description = [NSString stringWithFormat:@"%@ '%@'", _name, _typeEncoding];
}
return _flex_description;
}
@end

View File

@@ -0,0 +1,97 @@
//
// FLEXMirror.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/29/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol;
NS_ASSUME_NONNULL_BEGIN
#pragma mark FLEXMirror Protocol
NS_SWIFT_NAME(FLEXMirrorProtocol)
@protocol FLEXMirror <NSObject>
/// Swift initializer
/// @throws If a metaclass object is passed in.
- (instancetype)initWithSubject:(id)objectOrClass NS_SWIFT_NAME(init(reflecting:));
/// The underlying object or \c Class used to create this \c FLEXMirror.
@property (nonatomic, readonly) id value;
/// Whether \c value was a class or a class instance.
@property (nonatomic, readonly) BOOL isClass;
/// The name of the \c Class of the \c value property.
@property (nonatomic, readonly) NSString *className;
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
@property (nonatomic, readonly) NSArray<FLEXProperty *> *classProperties;
@property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
@property (nonatomic, readonly) NSArray<FLEXMethod *> *methods;
@property (nonatomic, readonly) NSArray<FLEXMethod *> *classMethods;
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
/// Super mirrors are initialized with the class that corresponds to the value passed in.
/// If you passed in an instance of a class, it's superclass is used to create this mirror.
/// If you passed in a class, then that class's superclass is used.
///
/// @note This property should be computed, not cached.
@property (nonatomic, readonly, nullable) id<FLEXMirror> superMirror NS_SWIFT_NAME(superMirror);
@end
#pragma mark FLEXMirror Class
@interface FLEXMirror : NSObject <FLEXMirror>
/// Reflects an instance of an object or \c Class.
/// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the
/// \c NSObject categories provided if your code will only use a few pieces of information,
/// or if your code needs to run faster.
///
/// Regardless of whether you reflect an instance or a class object, \c methods and \c properties
/// will be populated with instance methods and properties, and \c classMethods and \c classProperties
/// will be populated with class methods and properties.
///
/// @param objectOrClass An instance of an objct or a \c Class object.
/// @throws If a metaclass object is passed in.
/// @return An instance of \c FLEXMirror.
+ (instancetype)reflect:(id)objectOrClass;
@property (nonatomic, readonly) id value;
@property (nonatomic, readonly) BOOL isClass;
@property (nonatomic, readonly) NSString *className;
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
@property (nonatomic, readonly) NSArray<FLEXProperty *> *classProperties;
@property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
@property (nonatomic, readonly) NSArray<FLEXMethod *> *methods;
@property (nonatomic, readonly) NSArray<FLEXMethod *> *classMethods;
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
@property (nonatomic, readonly, nullable) FLEXMirror *superMirror NS_SWIFT_NAME(superMirror);
@end
@interface FLEXMirror (ExtendedMirror)
/// @return The instance method with the given name, or \c nil if one does not exist.
- (nullable FLEXMethod *)methodNamed:(nullable NSString *)name;
/// @return The class method with the given name, or \c nil if one does not exist.
- (nullable FLEXMethod *)classMethodNamed:(nullable NSString *)name;
/// @return The instance property with the given name, or \c nil if one does not exist.
- (nullable FLEXProperty *)propertyNamed:(nullable NSString *)name;
/// @return The class property with the given name, or \c nil if one does not exist.
- (nullable FLEXProperty *)classPropertyNamed:(nullable NSString *)name;
/// @return The instance variable with the given name, or \c nil if one does not exist.
- (nullable FLEXIvar *)ivarNamed:(nullable NSString *)name;
/// @return The protocol with the given name, or \c nil if one does not exist.
- (nullable FLEXProtocol *)protocolNamed:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,145 @@
//
// FLEXMirror.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/29/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXMirror.h"
#import "FLEXProperty.h"
#import "FLEXMethod.h"
#import "FLEXIvar.h"
#import "FLEXProtocol.h"
#import "FLEXUtility.h"
#pragma mark FLEXMirror
@implementation FLEXMirror
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
#pragma mark Initialization
+ (instancetype)reflect:(id)objectOrClass {
return [[self alloc] initWithSubject:objectOrClass];
}
- (id)initWithSubject:(id)objectOrClass {
NSParameterAssert(objectOrClass);
self = [super init];
if (self) {
_value = objectOrClass;
[self examine];
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ %@=%@>",
NSStringFromClass(self.class),
self.isClass ? @"metaclass" : @"class",
self.className
];
}
- (void)examine {
BOOL isClass = object_isClass(self.value);
Class cls = isClass ? self.value : object_getClass(self.value);
Class meta = object_getClass(cls);
_className = NSStringFromClass(cls);
_isClass = isClass;
unsigned int pcount, cpcount, mcount, cmcount, ivcount, pccount;
Ivar *objcIvars = class_copyIvarList(cls, &ivcount);
Method *objcMethods = class_copyMethodList(cls, &mcount);
Method *objcClsMethods = class_copyMethodList(meta, &cmcount);
objc_property_t *objcProperties = class_copyPropertyList(cls, &pcount);
objc_property_t *objcClsProperties = class_copyPropertyList(meta, &cpcount);
Protocol *__unsafe_unretained *protos = class_copyProtocolList(cls, &pccount);
_ivars = [NSArray flex_forEachUpTo:ivcount map:^id(NSUInteger i) {
return [FLEXIvar ivar:objcIvars[i]];
}];
_methods = [NSArray flex_forEachUpTo:mcount map:^id(NSUInteger i) {
return [FLEXMethod method:objcMethods[i] isInstanceMethod:YES];
}];
_classMethods = [NSArray flex_forEachUpTo:cmcount map:^id(NSUInteger i) {
return [FLEXMethod method:objcClsMethods[i] isInstanceMethod:NO];
}];
_properties = [NSArray flex_forEachUpTo:pcount map:^id(NSUInteger i) {
return [FLEXProperty property:objcProperties[i] onClass:cls];
}];
_classProperties = [NSArray flex_forEachUpTo:cpcount map:^id(NSUInteger i) {
return [FLEXProperty property:objcClsProperties[i] onClass:meta];
}];
_protocols = [NSArray flex_forEachUpTo:pccount map:^id(NSUInteger i) {
return [FLEXProtocol protocol:protos[i]];
}];
// Cleanup
free(objcClsProperties);
free(objcProperties);
free(objcClsMethods);
free(objcMethods);
free(objcIvars);
free(protos);
protos = NULL;
}
#pragma mark Misc
- (FLEXMirror *)superMirror {
Class cls = _isClass ? _value : object_getClass(_value);
return [FLEXMirror reflect:class_getSuperclass(cls)];
}
@end
#pragma mark ExtendedMirror
@implementation FLEXMirror (ExtendedMirror)
- (id)filter:(NSArray *)array forName:(NSString *)name {
NSPredicate *filter = [NSPredicate predicateWithFormat:@"%K = %@", @"name", name];
return [array filteredArrayUsingPredicate:filter].firstObject;
}
- (FLEXMethod *)methodNamed:(NSString *)name {
return [self filter:self.methods forName:name];
}
- (FLEXMethod *)classMethodNamed:(NSString *)name {
return [self filter:self.classMethods forName:name];
}
- (FLEXProperty *)propertyNamed:(NSString *)name {
return [self filter:self.properties forName:name];
}
- (FLEXProperty *)classPropertyNamed:(NSString *)name {
return [self filter:self.classProperties forName:name];
}
- (FLEXIvar *)ivarNamed:(NSString *)name {
return [self filter:self.ivars forName:name];
}
- (FLEXProtocol *)protocolNamed:(NSString *)name {
return [self filter:self.protocols forName:name];
}
@end

View File

@@ -0,0 +1,138 @@
//
// FLEXProperty.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntimeConstants.h"
@class FLEXPropertyAttributes, FLEXMethodBase;
#pragma mark FLEXProperty
@interface FLEXProperty : NSObject
/// You may use this initializer instead of \c property:onClass: if you don't need
/// to know anything about the uniqueness of this property or where it comes from.
+ (instancetype)property:(objc_property_t)property;
/// This initializer can be used to access additional information
/// in an efficient manner. That information being whether this property
/// is certainly not unique and the name of the binary image which declares it.
/// @param cls the class, or metaclass if this is a class property.
+ (instancetype)property:(objc_property_t)property onClass:(Class)cls;
/// @param cls the class, or metaclass if this is a class property
+ (instancetype)named:(NSString *)name onClass:(Class)cls;
/// Constructs a new property with the given name and attributes.
+ (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes;
/// \c 0 if the instance was created via \c +propertyWithName:attributes,
/// otherwise this is the first property in \c objc_properties
@property (nonatomic, readonly) objc_property_t objc_property;
@property (nonatomic, readonly) objc_property_t *objc_properties;
@property (nonatomic, readonly) NSInteger objc_propertyCount;
@property (nonatomic, readonly) BOOL isClassProperty;
/// The name of the property.
@property (nonatomic, readonly) NSString *name;
/// The type of the property. Get the full type from the attributes.
@property (nonatomic, readonly) FLEXTypeEncoding type;
/// The property's attributes.
@property (nonatomic ) FLEXPropertyAttributes *attributes;
/// The (likely) setter, regardless of whether the property is readonly.
/// For example, this might be the custom setter.
@property (nonatomic, readonly) SEL likelySetter;
@property (nonatomic, readonly) NSString *likelySetterString;
/// Not valid unless initialized with the owning class.
@property (nonatomic, readonly) BOOL likelySetterExists;
/// The (likely) getter. For example, this might be the custom getter.
@property (nonatomic, readonly) SEL likelyGetter;
@property (nonatomic, readonly) NSString *likelyGetterString;
/// Not valid unless initialized with the owning class.
@property (nonatomic, readonly) BOOL likelyGetterExists;
/// Always \c nil for class properties.
@property (nonatomic, readonly) NSString *likelyIvarName;
/// Not valid unless initialized with the owning class.
@property (nonatomic, readonly) BOOL likelyIvarExists;
/// Whether there are certainly multiple definitions of this property,
/// such as in categories in other binary images or something.
/// @return Whether \c objc_property matches the return value of \c class_getProperty,
/// or \c NO if this property was not created with \c property:onClass
@property (nonatomic, readonly) BOOL multiple;
/// @return The bundle of the image that contains this property definition,
/// or \c nil if this property was not created with \c property:onClass or
/// if this property was probably defined at runtime.
@property (nonatomic, readonly) NSString *imageName;
/// The full path of the image that contains this property definition,
/// or \c nil if this property was not created with \c property:onClass or
/// if this property was probably defined at runtime.
@property (nonatomic, readonly) NSString *imagePath;
/// For internal use
@property (nonatomic) id tag;
/// @return The value of this property on \c target as given by \c -valueForKey:
/// A source-like description of the property, with all of its attributes.
@property (nonatomic, readonly) NSString *fullDescription;
/// If this is a class property, you must class the class object.
- (id)getValue:(id)target;
/// Calls into -getValue: and passes that value into
/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
/// and returns the result.
///
/// If this is a class property, you must class the class object.
- (id)getPotentiallyUnboxedValue:(id)target;
/// Safe to use regardless of how the \c FLEXProperty instance was initialized.
///
/// This uses \c self.objc_property if it exists, otherwise it uses \c self.attributes
- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount;
/// Replace the attributes of the current property in the given class,
/// using the attributes in \c self.attributes
///
/// What happens when the property does not exist is undocumented.
- (void)replacePropertyOnClass:(Class)cls;
#pragma mark Convenience getters and setters
/// @return A getter for the property with the given implementation.
/// @discussion Consider using the \c FLEXPropertyGetter macros.
- (FLEXMethodBase *)getterWithImplementation:(IMP)implementation;
/// @return A setter for the property with the given implementation.
/// @discussion Consider using the \c FLEXPropertySetter macros.
- (FLEXMethodBase *)setterWithImplementation:(IMP)implementation;
#pragma mark FLEXMethod property getter / setter macros
// Easier than using the above methods yourself in most cases
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and
/// uses the \c FLEXProperty's \c attribute's \c backingIvarName to get the Ivar.
#define FLEXPropertyGetter(FLEXProperty, type) [FLEXProperty \
getterWithImplementation:imp_implementationWithBlock(^(id self) { \
return *(type *)[self getIvarAddressByName:FLEXProperty.attributes.backingIvar]; \
}) \
];
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and
/// uses the \c FLEXProperty's \c attribute's \c backingIvarName to set the Ivar.
#define FLEXPropertySetter(FLEXProperty, type) [FLEXProperty \
setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \
[self setIvarByName:FLEXProperty.attributes.backingIvar value:&value size:sizeof(type)]; \
}) \
];
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to get the Ivar.
#define FLEXPropertyGetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \
getterWithImplementation:imp_implementationWithBlock(^(id self) { \
return *(type *)[self getIvarAddressByName:ivarName]; \
}) \
];
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to set the Ivar.
#define FLEXPropertySetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \
setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \
[self setIvarByName:ivarName value:&value size:sizeof(type)]; \
}) \
];
@end

View File

@@ -0,0 +1,295 @@
//
// FLEXProperty.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXProperty.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXMethodBase.h"
#import "FLEXRuntimeUtility.h"
#include <dlfcn.h>
@interface FLEXProperty () {
NSString *_flex_description;
}
@property (nonatomic ) BOOL uniqueCheckFlag;
@property (nonatomic, readonly) Class cls;
@end
@implementation FLEXProperty
@synthesize multiple = _multiple;
@synthesize imageName = _imageName;
@synthesize imagePath = _imagePath;
#pragma mark Initializers
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
+ (instancetype)property:(objc_property_t)property {
return [[self alloc] initWithProperty:property onClass:nil];
}
+ (instancetype)property:(objc_property_t)property onClass:(Class)cls {
return [[self alloc] initWithProperty:property onClass:cls];
}
+ (instancetype)named:(NSString *)name onClass:(Class)cls {
objc_property_t _Nullable property = class_getProperty(cls, name.UTF8String);
NSAssert(property, @"Cannot find property with name %@ on class %@", name, cls);
return [self property:property onClass:cls];
}
+ (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
return [[self alloc] initWithName:name attributes:attributes];
}
- (id)initWithProperty:(objc_property_t)property onClass:(Class)cls {
NSParameterAssert(property);
self = [super init];
if (self) {
_objc_property = property;
_attributes = [FLEXPropertyAttributes attributesForProperty:property];
_name = @(property_getName(property) ?: "(nil)");
_cls = cls;
if (!_attributes) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property attributes"];
if (!_name) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property name"];
[self examine];
}
return self;
}
- (id)initWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
NSParameterAssert(name); NSParameterAssert(attributes);
self = [super init];
if (self) {
_attributes = attributes;
_name = name;
[self examine];
}
return self;
}
#pragma mark Private
- (void)examine {
if (self.attributes.typeEncoding.length) {
_type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
}
// Return the given selector if the class responds to it
Class cls = _cls;
SEL (^selectorIfValid)(SEL) = ^SEL(SEL sel) {
if (!sel || !cls) return nil;
return [cls instancesRespondToSelector:sel] ? sel : nil;
};
SEL customGetter = self.attributes.customGetter;
SEL customSetter = self.attributes.customSetter;
SEL defaultGetter = NSSelectorFromString(self.name);
SEL defaultSetter = NSSelectorFromString([NSString
stringWithFormat:@"set%c%@:",
(char)toupper([self.name characterAtIndex:0]),
[self.name substringFromIndex:1]
]);
// Check if the likely getters/setters exist
SEL validGetter = selectorIfValid(customGetter) ?: selectorIfValid(defaultGetter);
SEL validSetter = selectorIfValid(customSetter) ?: selectorIfValid(defaultSetter);
_likelyGetterExists = validGetter != nil;
_likelySetterExists = validSetter != nil;
// Assign likely getters and setters to the valid one,
// or the default, regardless of whether the default exists
_likelyGetter = validGetter ?: defaultGetter;
_likelySetter = validSetter ?: defaultSetter;
_likelyGetterString = NSStringFromSelector(_likelyGetter);
_likelySetterString = NSStringFromSelector(_likelySetter);
_isClassProperty = _cls ? class_isMetaClass(_cls) : NO;
_likelyIvarName = _isClassProperty ? nil : (
self.attributes.backingIvar ?: [@"_" stringByAppendingString:_name]
);
}
#pragma mark Overrides
- (NSString *)description {
if (!_flex_description) {
NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.attributes.typeEncoding];
_flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType];
}
return _flex_description;
}
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@ name=%@, property=%p, attributes:\n\t%@\n>",
NSStringFromClass(self.class), self.name, self.objc_property, self.attributes];
}
#pragma mark Public
- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount {
if (self.objc_property) {
return property_copyAttributeList(self.objc_property, attributesCount);
} else {
return [self.attributes copyAttributesList:attributesCount];
}
}
- (void)replacePropertyOnClass:(Class)cls {
class_replaceProperty(cls, self.name.UTF8String, self.attributes.list, (unsigned int)self.attributes.count);
}
- (void)computeSymbolInfo:(BOOL)forceBundle {
Dl_info exeInfo;
if (dladdr(_objc_property, &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
}
if ((!_multiple || !_uniqueCheckFlag) && _cls) {
_multiple = _objc_property != class_getProperty(_cls, self.name.UTF8String);
if (_multiple || forceBundle) {
NSString *path = _imagePath.stringByDeletingLastPathComponent;
_imageName = [NSBundle bundleWithPath:path].executablePath.lastPathComponent;
}
}
}
- (BOOL)multiple {
[self computeSymbolInfo:NO];
return _multiple;
}
- (NSString *)imagePath {
[self computeSymbolInfo:YES];
return _imagePath;
}
- (NSString *)imageName {
[self computeSymbolInfo:YES];
return _imageName;
}
- (BOOL)likelyIvarExists {
if (_likelyIvarName && _cls) {
return class_getInstanceVariable(_cls, _likelyIvarName.UTF8String) != nil;
}
return NO;
}
- (NSString *)fullDescription {
NSMutableArray<NSString *> *attributesStrings = [NSMutableArray new];
FLEXPropertyAttributes *attributes = self.attributes;
// Atomicity
if (attributes.isNonatomic) {
[attributesStrings addObject:@"nonatomic"];
} else {
[attributesStrings addObject:@"atomic"];
}
// Storage
if (attributes.isRetained) {
[attributesStrings addObject:@"strong"];
} else if (attributes.isCopy) {
[attributesStrings addObject:@"copy"];
} else if (attributes.isWeak) {
[attributesStrings addObject:@"weak"];
} else {
[attributesStrings addObject:@"assign"];
}
// Mutability
if (attributes.isReadOnly) {
[attributesStrings addObject:@"readonly"];
} else {
[attributesStrings addObject:@"readwrite"];
}
// Class or not
if (self.isClassProperty) {
[attributesStrings addObject:@"class"];
}
// Custom getter/setter
SEL customGetter = attributes.customGetter;
SEL customSetter = attributes.customSetter;
if (customGetter) {
[attributesStrings addObject:[NSString stringWithFormat:@"getter=%s", sel_getName(customGetter)]];
}
if (customSetter) {
[attributesStrings addObject:[NSString stringWithFormat:@"setter=%s", sel_getName(customSetter)]];
}
NSString *attributesString = [attributesStrings componentsJoinedByString:@", "];
return [NSString stringWithFormat:@"@property (%@) %@", attributesString, self.description];
}
- (id)getValue:(id)target {
if (!target) return nil;
// We don't care about checking dynamically whether the getter
// _now_ exists on this object. If the getter doesn't exist
// when this property is initialized, it will never call it.
// Just re-create the property object if you need to call it.
if (self.likelyGetterExists) {
BOOL objectIsClass = object_isClass(target);
BOOL instanceAndInstanceProperty = !objectIsClass && !self.isClassProperty;
BOOL classAndClassProperty = objectIsClass && self.isClassProperty;
if (instanceAndInstanceProperty || classAndClassProperty) {
return [FLEXRuntimeUtility performSelector:self.likelyGetter onObject:target];
}
}
return nil;
}
- (id)getPotentiallyUnboxedValue:(id)target {
if (!target) return nil;
return [FLEXRuntimeUtility
potentiallyUnwrapBoxedPointer:[self getValue:target]
type:self.attributes.typeEncoding.UTF8String
];
}
#pragma mark Suggested getters and setters
- (FLEXMethodBase *)getterWithImplementation:(IMP)implementation {
NSString *types = [NSString stringWithFormat:@"%@%s%s", self.attributes.typeEncoding, @encode(id), @encode(SEL)];
NSString *name = [NSString stringWithFormat:@"%@", self.name];
FLEXMethodBase *getter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
return getter;
}
- (FLEXMethodBase *)setterWithImplementation:(IMP)implementation {
NSString *types = [NSString stringWithFormat:@"%s%s%s%@", @encode(void), @encode(id), @encode(SEL), self.attributes.typeEncoding];
NSString *name = [NSString stringWithFormat:@"set%@:", self.name.capitalizedString];
FLEXMethodBase *setter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
return setter;
}
@end

View File

@@ -0,0 +1,110 @@
//
// FLEXPropertyAttributes.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>
NS_ASSUME_NONNULL_BEGIN
#pragma mark FLEXPropertyAttributes
/// See \e FLEXRuntimeUtilitiy.h for valid string tokens.
/// See this link on how to construct a proper attributes string:
/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
@interface FLEXPropertyAttributes : NSObject <NSCopying, NSMutableCopying> {
// These are necessary for the mutable subclass to function
@protected
NSUInteger _count;
NSString *_string, *_backingIvar, *_typeEncoding, *_oldTypeEncoding, *_fullDeclaration;
NSDictionary *_dictionary;
objc_property_attribute_t *_list;
SEL _customGetter, _customSetter;
BOOL _isReadOnly, _isCopy, _isRetained, _isNonatomic, _isDynamic, _isWeak, _isGarbageCollectable;
}
+ (instancetype)attributesForProperty:(objc_property_t)property;
/// @warning Raises an exception if \e attributes is invalid, \c nil, or contains unsupported keys.
+ (instancetype)attributesFromDictionary:(NSDictionary *)attributes;
/// Copies the attributes list to a buffer you must \c free() yourself.
/// Use \c list instead if you do not need more control over the lifetime of the list.
/// @param attributesCountOut the number of attributes is returned in this parameter.
- (objc_property_attribute_t *)copyAttributesList:(nullable unsigned int *)attributesCountOut;
/// The number of property attributes.
@property (nonatomic, readonly) NSUInteger count;
/// For use with \c class_replaceProperty and the like.
@property (nonatomic, readonly) objc_property_attribute_t *list;
/// The string value of the property attributes.
@property (nonatomic, readonly) NSString *string;
/// A human-readable version of the property attributes.
@property (nonatomic, readonly) NSString *fullDeclaration;
/// A dictionary of the property attributes.
/// Values are either a string or \c YES. Boolean attributes
/// which are false will not be present in the dictionary.
@property (nonatomic, readonly) NSDictionary *dictionary;
/// The name of the instance variable backing the property.
@property (nonatomic, readonly, nullable) NSString *backingIvar;
/// The type encoding of the property.
@property (nonatomic, readonly, nullable) NSString *typeEncoding;
/// The \e old type encoding of the property.
@property (nonatomic, readonly, nullable) NSString *oldTypeEncoding;
/// The property's custom getter, if any.
@property (nonatomic, readonly, nullable) SEL customGetter;
/// The property's custom setter, if any.
@property (nonatomic, readonly, nullable) SEL customSetter;
/// The property's custom getter as a string, if any.
@property (nonatomic, readonly, nullable) NSString *customGetterString;
/// The property's custom setter as a string, if any.
@property (nonatomic, readonly, nullable) NSString *customSetterString;
@property (nonatomic, readonly) BOOL isReadOnly;
@property (nonatomic, readonly) BOOL isCopy;
@property (nonatomic, readonly) BOOL isRetained;
@property (nonatomic, readonly) BOOL isNonatomic;
@property (nonatomic, readonly) BOOL isDynamic;
@property (nonatomic, readonly) BOOL isWeak;
@property (nonatomic, readonly) BOOL isGarbageCollectable;
@end
#pragma mark FLEXPropertyAttributes
@interface FLEXMutablePropertyAttributes : FLEXPropertyAttributes
/// Creates and returns an empty property attributes object.
+ (instancetype)attributes;
/// The name of the instance variable backing the property.
@property (nonatomic, nullable) NSString *backingIvar;
/// The type encoding of the property.
@property (nonatomic, nullable) NSString *typeEncoding;
/// The \e old type encoding of the property.
@property (nonatomic, nullable) NSString *oldTypeEncoding;
/// The property's custom getter, if any.
@property (nonatomic, nullable) SEL customGetter;
/// The property's custom setter, if any.
@property (nonatomic, nullable) SEL customSetter;
@property (nonatomic) BOOL isReadOnly;
@property (nonatomic) BOOL isCopy;
@property (nonatomic) BOOL isRetained;
@property (nonatomic) BOOL isNonatomic;
@property (nonatomic) BOOL isDynamic;
@property (nonatomic) BOOL isWeak;
@property (nonatomic) BOOL isGarbageCollectable;
/// A more convenient method of setting the \c typeEncoding property.
/// @discussion This will not work for complex types like structs and primitive pointers.
- (void)setTypeEncodingChar:(char)type;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,376 @@
//
// FLEXPropertyAttributes.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/5/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXPropertyAttributes.h"
#import "FLEXRuntimeUtility.h"
#import "NSString+ObjcRuntime.h"
#import "NSDictionary+ObjcRuntime.h"
#pragma mark FLEXPropertyAttributes
@interface FLEXPropertyAttributes ()
@property (nonatomic) NSString *backingIvar;
@property (nonatomic) NSString *typeEncoding;
@property (nonatomic) NSString *oldTypeEncoding;
@property (nonatomic) SEL customGetter;
@property (nonatomic) SEL customSetter;
@property (nonatomic) BOOL isReadOnly;
@property (nonatomic) BOOL isCopy;
@property (nonatomic) BOOL isRetained;
@property (nonatomic) BOOL isNonatomic;
@property (nonatomic) BOOL isDynamic;
@property (nonatomic) BOOL isWeak;
@property (nonatomic) BOOL isGarbageCollectable;
- (NSString *)buildFullDeclaration;
@end
@implementation FLEXPropertyAttributes
@synthesize list = _list;
#pragma mark Initializers
+ (instancetype)attributesForProperty:(objc_property_t)property {
return [self attributesFromDictionary:[NSDictionary attributesDictionaryForProperty:property]];
}
+ (instancetype)attributesFromDictionary:(NSDictionary *)attributes {
return [[self alloc] initWithAttributesDictionary:attributes];
}
- (id)initWithAttributesDictionary:(NSDictionary *)attributes {
NSParameterAssert(attributes);
self = [super init];
if (self) {
_dictionary = attributes;
_string = attributes.propertyAttributesString;
_count = attributes.count;
_typeEncoding = attributes[kFLEXPropertyAttributeKeyTypeEncoding];
_backingIvar = attributes[kFLEXPropertyAttributeKeyBackingIvarName];
_oldTypeEncoding = attributes[kFLEXPropertyAttributeKeyOldStyleTypeEncoding];
_customGetterString = attributes[kFLEXPropertyAttributeKeyCustomGetter];
_customSetterString = attributes[kFLEXPropertyAttributeKeyCustomSetter];
_customGetter = NSSelectorFromString(_customGetterString);
_customSetter = NSSelectorFromString(_customSetterString);
_isReadOnly = attributes[kFLEXPropertyAttributeKeyReadOnly] != nil;
_isCopy = attributes[kFLEXPropertyAttributeKeyCopy] != nil;
_isRetained = attributes[kFLEXPropertyAttributeKeyRetain] != nil;
_isNonatomic = attributes[kFLEXPropertyAttributeKeyNonAtomic] != nil;
_isWeak = attributes[kFLEXPropertyAttributeKeyWeak] != nil;
_isGarbageCollectable = attributes[kFLEXPropertyAttributeKeyGarbageCollectable] != nil;
_fullDeclaration = [self buildFullDeclaration];
}
return self;
}
#pragma mark Misc
- (NSString *)description {
return [NSString
stringWithFormat:@"<%@ \"%@\", ivar=%@, readonly=%d, nonatomic=%d, getter=%@, setter=%@>",
NSStringFromClass(self.class),
self.string,
self.backingIvar ?: @"none",
self.isReadOnly,
self.isNonatomic,
NSStringFromSelector(self.customGetter) ?: @"none",
NSStringFromSelector(self.customSetter) ?: @"none"
];
}
- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount {
NSDictionary *attrs = self.string.propertyAttributes;
objc_property_attribute_t *propertyAttributes = malloc(attrs.count * sizeof(objc_property_attribute_t));
if (attributesCount) {
*attributesCount = (unsigned int)attrs.count;
}
NSUInteger i = 0;
for (NSString *key in attrs.allKeys) {
FLEXPropertyAttribute c = (FLEXPropertyAttribute)[key characterAtIndex:0];
switch (c) {
case FLEXPropertyAttributeTypeEncoding: {
objc_property_attribute_t pa = {
kFLEXPropertyAttributeKeyTypeEncoding.UTF8String,
self.typeEncoding.UTF8String
};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeBackingIvarName: {
objc_property_attribute_t pa = {
kFLEXPropertyAttributeKeyBackingIvarName.UTF8String,
self.backingIvar.UTF8String
};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeCopy: {
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyCopy.UTF8String, ""};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeCustomGetter: {
objc_property_attribute_t pa = {
kFLEXPropertyAttributeKeyCustomGetter.UTF8String,
NSStringFromSelector(self.customGetter).UTF8String ?: ""
};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeCustomSetter: {
objc_property_attribute_t pa = {
kFLEXPropertyAttributeKeyCustomSetter.UTF8String,
NSStringFromSelector(self.customSetter).UTF8String ?: ""
};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeDynamic: {
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyDynamic.UTF8String, ""};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeGarbageCollectible: {
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyGarbageCollectable.UTF8String, ""};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeNonAtomic: {
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyNonAtomic.UTF8String, ""};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeOldTypeEncoding: {
objc_property_attribute_t pa = {
kFLEXPropertyAttributeKeyOldStyleTypeEncoding.UTF8String,
self.oldTypeEncoding.UTF8String ?: ""
};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeReadOnly: {
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyReadOnly.UTF8String, ""};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeRetain: {
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyRetain.UTF8String, ""};
propertyAttributes[i] = pa;
break;
}
case FLEXPropertyAttributeWeak: {
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyWeak.UTF8String, ""};
propertyAttributes[i] = pa;
break;
}
}
i++;
}
return propertyAttributes;
}
- (objc_property_attribute_t *)list {
if (!_list) {
_list = [self copyAttributesList:nil];
}
return _list;
}
- (NSString *)buildFullDeclaration {
NSMutableString *decl = [NSMutableString new];
[decl appendFormat:@"%@, ", _isNonatomic ? @"nonatomic" : @"atomic"];
[decl appendFormat:@"%@, ", _isReadOnly ? @"readonly" : @"readwrite"];
BOOL noExplicitMemorySemantics = YES;
if (_isCopy) { noExplicitMemorySemantics = NO;
[decl appendString:@"copy, "];
}
if (_isRetained) { noExplicitMemorySemantics = NO;
[decl appendString:@"strong, "];
}
if (_isWeak) { noExplicitMemorySemantics = NO;
[decl appendString:@"weak, "];
}
if ([_typeEncoding hasPrefix:@"@"] && noExplicitMemorySemantics) {
// *probably* strong if this is an object; strong is the default.
[decl appendString:@"strong, "];
} else if (noExplicitMemorySemantics) {
// *probably* assign if this is not an object
[decl appendString:@"assign, "];
}
if (_customGetter) {
[decl appendFormat:@"getter=%@, ", NSStringFromSelector(_customGetter)];
}
if (_customSetter) {
[decl appendFormat:@"setter=%@, ", NSStringFromSelector(_customSetter)];
}
[decl deleteCharactersInRange:NSMakeRange(decl.length-2, 2)];
return decl.copy;
}
- (void)dealloc {
if (_list) {
free(_list);
_list = nil;
}
}
#pragma mark Copying
- (id)copyWithZone:(NSZone *)zone {
return [[FLEXPropertyAttributes class] attributesFromDictionary:self.dictionary];
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [[FLEXMutablePropertyAttributes class] attributesFromDictionary:self.dictionary];
}
@end
#pragma mark FLEXMutablePropertyAttributes
@interface FLEXMutablePropertyAttributes ()
@property (nonatomic) BOOL countDelta;
@property (nonatomic) BOOL stringDelta;
@property (nonatomic) BOOL dictDelta;
@property (nonatomic) BOOL listDelta;
@property (nonatomic) BOOL declDelta;
@end
#define PropertyWithDeltaFlag(type, name, Name) @dynamic name; \
- (void)set ## Name:(type)name { \
if (name != _ ## name) { \
_countDelta = _stringDelta = _dictDelta = _listDelta = _declDelta = YES; \
_ ## name = name; \
} \
}
@implementation FLEXMutablePropertyAttributes
PropertyWithDeltaFlag(NSString *, backingIvar, BackingIvar);
PropertyWithDeltaFlag(NSString *, typeEncoding, TypeEncoding);
PropertyWithDeltaFlag(NSString *, oldTypeEncoding, OldTypeEncoding);
PropertyWithDeltaFlag(SEL, customGetter, CustomGetter);
PropertyWithDeltaFlag(SEL, customSetter, CustomSetter);
PropertyWithDeltaFlag(BOOL, isReadOnly, IsReadOnly);
PropertyWithDeltaFlag(BOOL, isCopy, IsCopy);
PropertyWithDeltaFlag(BOOL, isRetained, IsRetained);
PropertyWithDeltaFlag(BOOL, isNonatomic, IsNonatomic);
PropertyWithDeltaFlag(BOOL, isDynamic, IsDynamic);
PropertyWithDeltaFlag(BOOL, isWeak, IsWeak);
PropertyWithDeltaFlag(BOOL, isGarbageCollectable, IsGarbageCollectable);
+ (instancetype)attributes {
return [self new];
}
- (void)setTypeEncodingChar:(char)type {
self.typeEncoding = [NSString stringWithFormat:@"%c", type];
}
- (NSUInteger)count {
// Recalculate attribute count after mutations
if (self.countDelta) {
self.countDelta = NO;
_count = self.dictionary.count;
}
return _count;
}
- (objc_property_attribute_t *)list {
// Regenerate list after mutations
if (self.listDelta) {
self.listDelta = NO;
if (_list) {
free(_list);
_list = nil;
}
}
// Super will generate the list if it isn't set
return super.list;
}
- (NSString *)string {
// Regenerate string after mutations
if (self.stringDelta || !_string) {
self.stringDelta = NO;
_string = self.dictionary.propertyAttributesString;
}
return _string;
}
- (NSDictionary *)dictionary {
// Regenerate dictionary after mutations
if (self.dictDelta || !_dictionary) {
// _stringa nd _dictionary depend on each other,
// so we must generate ONE by hand using our properties.
// We arbitrarily choose to generate the dictionary.
NSMutableDictionary *attrs = [NSMutableDictionary new];
if (self.typeEncoding)
attrs[kFLEXPropertyAttributeKeyTypeEncoding] = self.typeEncoding;
if (self.backingIvar)
attrs[kFLEXPropertyAttributeKeyBackingIvarName] = self.backingIvar;
if (self.oldTypeEncoding)
attrs[kFLEXPropertyAttributeKeyOldStyleTypeEncoding] = self.oldTypeEncoding;
if (self.customGetter)
attrs[kFLEXPropertyAttributeKeyCustomGetter] = NSStringFromSelector(self.customGetter);
if (self.customSetter)
attrs[kFLEXPropertyAttributeKeyCustomSetter] = NSStringFromSelector(self.customSetter);
if (self.isReadOnly) attrs[kFLEXPropertyAttributeKeyReadOnly] = @YES;
if (self.isCopy) attrs[kFLEXPropertyAttributeKeyCopy] = @YES;
if (self.isRetained) attrs[kFLEXPropertyAttributeKeyRetain] = @YES;
if (self.isNonatomic) attrs[kFLEXPropertyAttributeKeyNonAtomic] = @YES;
if (self.isDynamic) attrs[kFLEXPropertyAttributeKeyDynamic] = @YES;
if (self.isWeak) attrs[kFLEXPropertyAttributeKeyWeak] = @YES;
if (self.isGarbageCollectable) attrs[kFLEXPropertyAttributeKeyGarbageCollectable] = @YES;
_dictionary = attrs.copy;
}
return _dictionary;
}
- (NSString *)fullDeclaration {
if (self.declDelta || !_fullDeclaration) {
_declDelta = NO;
_fullDeclaration = [self buildFullDeclaration];
}
return _fullDeclaration;
}
- (NSString *)customGetterString {
return _customGetter ? NSStringFromSelector(_customGetter) : nil;
}
- (NSString *)customSetterString {
return _customSetter ? NSStringFromSelector(_customSetter) : nil;
}
@end

View File

@@ -0,0 +1,73 @@
//
// FLEXProtocol.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXRuntimeConstants.h"
@class FLEXProperty, FLEXMethodDescription;
NS_ASSUME_NONNULL_BEGIN
#pragma mark FLEXProtocol
@interface FLEXProtocol : NSObject
/// Every protocol registered with the runtime.
+ (NSArray<FLEXProtocol *> *)allProtocols;
+ (instancetype)protocol:(Protocol *)protocol;
/// The underlying protocol data structure.
@property (nonatomic, readonly) Protocol *objc_protocol;
/// The name of the protocol.
@property (nonatomic, readonly) NSString *name;
/// The required methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods;
/// The optional methods of the protocol, if any. This includes property getters and setters.
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods;
/// All protocols that this protocol conforms to, if any.
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
/// The full path of the image that contains this protocol definition,
/// or \c nil if this protocol was probably defined at runtime.
@property (nonatomic, readonly, nullable) NSString *imagePath;
/// The properties in the protocol, if any. \c nil on iOS 10+
@property (nonatomic, readonly, nullable) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0));
/// The required properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0));
/// The optional properties in the protocol, if any.
@property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0));
/// For internal use
@property (nonatomic) id tag;
/// Not to be confused with \c -conformsToProtocol:, which refers to the current
/// \c FLEXProtocol instance and not the underlying \c Protocol object.
- (BOOL)conformsTo:(Protocol *)protocol;
@end
#pragma mark Method descriptions
@interface FLEXMethodDescription : NSObject
+ (instancetype)description:(struct objc_method_description)description;
+ (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance;
/// The underlying method description data structure.
@property (nonatomic, readonly) struct objc_method_description objc_description;
/// The method's selector.
@property (nonatomic, readonly) SEL selector;
/// The method's type encoding.
@property (nonatomic, readonly) NSString *typeEncoding;
/// The method's return type.
@property (nonatomic, readonly) FLEXTypeEncoding returnType;
/// \c YES if this is an instance method, \c NO if it is a class method, or \c nil if unspecified
@property (nonatomic, readonly) NSNumber *instance;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,212 @@
//
// FLEXProtocol.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 6/30/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXProtocol.h"
#import "FLEXProperty.h"
#import "FLEXRuntimeUtility.h"
#import "NSArray+FLEX.h"
#include <dlfcn.h>
@implementation FLEXProtocol
#pragma mark Initializers
+ (NSArray *)allProtocols {
unsigned int prcount;
Protocol *__unsafe_unretained*protocols = objc_copyProtocolList(&prcount);
NSMutableArray *all = [NSMutableArray new];
for(NSUInteger i = 0; i < prcount; i++)
[all addObject:[self protocol:protocols[i]]];
free(protocols);
return all;
}
+ (instancetype)protocol:(Protocol *)protocol {
return [[self alloc] initWithProtocol:protocol];
}
- (id)initWithProtocol:(Protocol *)protocol {
NSParameterAssert(protocol);
self = [super init];
if (self) {
_objc_protocol = protocol;
[self examine];
}
return self;
}
#pragma mark Other
- (NSString *)description {
return self.name;
}
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@ name=%@, %lu properties, %lu required methods, %lu optional methods, %lu protocols>",
NSStringFromClass(self.class), self.name, (unsigned long)self.properties.count,
(unsigned long)self.requiredMethods.count, (unsigned long)self.optionalMethods.count, (unsigned long)self.protocols.count];
}
- (void)examine {
_name = @(protocol_getName(self.objc_protocol));
// imagePath
Dl_info exeInfo;
if (dladdr((__bridge const void *)(_objc_protocol), &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
}
// Conformances and methods //
unsigned int pccount, mdrcount, mdocount;
struct objc_method_description *objcrMethods, *objcoMethods;
Protocol *protocol = _objc_protocol;
Protocol * __unsafe_unretained *protocols = protocol_copyProtocolList(protocol, &pccount);
// Protocols
_protocols = [NSArray flex_forEachUpTo:pccount map:^id(NSUInteger i) {
return [FLEXProtocol protocol:protocols[i]];
}];
free(protocols);
// Required instance methods
objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, YES, &mdrcount);
NSArray *rMethods = [NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcrMethods[i] instance:YES];
}];
free(objcrMethods);
// Required class methods
objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, NO, &mdrcount);
_requiredMethods = [[NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcrMethods[i] instance:NO];
}] arrayByAddingObjectsFromArray:rMethods];
free(objcrMethods);
// Optional instance methods
objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, YES, &mdocount);
NSArray *oMethods = [NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcoMethods[i] instance:YES];
}];
free(objcoMethods);
// Optional class methods
objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, NO, &mdocount);
_optionalMethods = [[NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) {
return [FLEXMethodDescription description:objcoMethods[i] instance:NO];
}] arrayByAddingObjectsFromArray:oMethods];
free(objcoMethods);
// Properties is a hassle because they didn't fix the API until iOS 10 //
if (@available(iOS 10.0, *)) {
unsigned int prrcount, procount;
Class instance = [NSObject class], meta = objc_getMetaClass("NSObject");
// Required class and instance properties //
// Instance first
objc_property_t *rProps = protocol_copyPropertyList2(protocol, &prrcount, YES, YES);
NSArray *rProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
return [FLEXProperty property:rProps[i] onClass:instance];
}];
free(rProps);
// Then class
rProps = protocol_copyPropertyList2(protocol, &prrcount, NO, YES);
_requiredProperties = [[NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
return [FLEXProperty property:rProps[i] onClass:instance];
}] arrayByAddingObjectsFromArray:rProperties];
free(rProps);
// Optional class and instance properties //
// Instance first
objc_property_t *oProps = protocol_copyPropertyList2(protocol, &procount, YES, YES);
NSArray *oProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
return [FLEXProperty property:oProps[i] onClass:meta];
}];
free(oProps);
// Then class
oProps = protocol_copyPropertyList2(protocol, &procount, NO, YES);
_optionalProperties = [[NSArray flex_forEachUpTo:procount map:^id(NSUInteger i) {
return [FLEXProperty property:oProps[i] onClass:meta];
}] arrayByAddingObjectsFromArray:oProperties];
free(oProps);
} else {
unsigned int prcount;
objc_property_t *objcproperties = protocol_copyPropertyList(protocol, &prcount);
_properties = [NSArray flex_forEachUpTo:prcount map:^id(NSUInteger i) {
return [FLEXProperty property:objcproperties[i]];
}];
_requiredProperties = @[];
_optionalProperties = @[];
free(objcproperties);
}
}
- (BOOL)conformsTo:(Protocol *)protocol {
return protocol_conformsToProtocol(self.objc_protocol, protocol);
}
@end
#pragma mark FLEXMethodDescription
@implementation FLEXMethodDescription
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
+ (instancetype)description:(struct objc_method_description)description {
return [[self alloc] initWithDescription:description instance:nil];
}
+ (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance {
return [[self alloc] initWithDescription:description instance:@(isInstance)];
}
- (id)initWithDescription:(struct objc_method_description)md instance:(NSNumber *)instance {
NSParameterAssert(md.name != NULL);
self = [super init];
if (self) {
_objc_description = md;
_selector = md.name;
_typeEncoding = @(md.types);
_returnType = (FLEXTypeEncoding)[self.typeEncoding characterAtIndex:0];
_instance = instance;
}
return self;
}
- (NSString *)description {
return NSStringFromSelector(self.selector);
}
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@ name=%@, type=%@>",
NSStringFromClass(self.class), NSStringFromSelector(self.selector), self.typeEncoding];
}
@end

View File

@@ -0,0 +1,41 @@
//
// FLEXProtocolBuilder.h
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/4/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import <Foundation/Foundation.h>
@class FLEXProperty, FLEXProtocol, Protocol;
@interface FLEXProtocolBuilder : NSObject
/// Begins to construct a new protocol with the given name.
/// @discussion You must register the protocol with the
/// \c registerProtocol method before you can use it.
+ (instancetype)allocateProtocol:(NSString *)name;
/// Adds a property to a protocol.
/// @param property The property to add.
/// @param isRequired Whether the property is required to implement the protocol.
- (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired;
/// Adds a property to a protocol.
/// @param selector The selector of the method to add.
/// @param typeEncoding The type encoding of the method to add.
/// @param isRequired Whether the method is required to implement the protocol.
/// @param isInstanceMethod \c YES if the method is an instance method, \c NO if it is a class method.
- (void)addMethod:(SEL)selector
typeEncoding:(NSString *)typeEncoding
isRequired:(BOOL)isRequired
isInstanceMethod:(BOOL)isInstanceMethod;
/// Makes the recieving protocol conform to the given protocol.
- (void)addProtocol:(Protocol *)protocol;
/// Registers and returns the recieving protocol, which was previously under construction.
- (FLEXProtocol *)registerProtocol;
/// Whether the protocol is still under construction or already registered.
@property (nonatomic, readonly) BOOL isRegistered;
@end

View File

@@ -0,0 +1,93 @@
//
// FLEXProtocolBuilder.m
// FLEX
//
// Derived from MirrorKit.
// Created by Tanner on 7/4/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
//
#import "FLEXProtocolBuilder.h"
#import "FLEXProtocol.h"
#import "FLEXProperty.h"
#import <objc/runtime.h>
#define MutationAssertion(msg) if (self.isRegistered) { \
[NSException \
raise:NSInternalInconsistencyException \
format:msg \
]; \
}
@interface FLEXProtocolBuilder ()
@property (nonatomic) Protocol *workingProtocol;
@property (nonatomic) NSString *name;
@end
@implementation FLEXProtocolBuilder
- (id)init {
[NSException
raise:NSInternalInconsistencyException
format:@"Class instance should not be created with -init"
];
return nil;
}
#pragma mark Initializers
+ (instancetype)allocateProtocol:(NSString *)name {
NSParameterAssert(name);
return [[self alloc] initWithProtocol:objc_allocateProtocol(name.UTF8String)];
}
- (id)initWithProtocol:(Protocol *)protocol {
NSParameterAssert(protocol);
self = [super init];
if (self) {
_workingProtocol = protocol;
_name = NSStringFromProtocol(self.workingProtocol);
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>",
NSStringFromClass(self.class), self.name, self.isRegistered];
}
#pragma mark Building
- (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired {
MutationAssertion(@"Properties cannot be added once a protocol has been registered");
unsigned int count;
objc_property_attribute_t *attributes = [property copyAttributesList:&count];
protocol_addProperty(self.workingProtocol, property.name.UTF8String, attributes, count, isRequired, YES);
free(attributes);
}
- (void)addMethod:(SEL)selector
typeEncoding:(NSString *)typeEncoding
isRequired:(BOOL)isRequired
isInstanceMethod:(BOOL)isInstanceMethod {
MutationAssertion(@"Methods cannot be added once a protocol has been registered");
protocol_addMethodDescription(self.workingProtocol, selector, typeEncoding.UTF8String, isRequired, isInstanceMethod);
}
- (void)addProtocol:(Protocol *)protocol {
MutationAssertion(@"Protocols cannot be added once a protocol has been registered");
protocol_addProtocol(self.workingProtocol, protocol);
}
- (FLEXProtocol *)registerProtocol {
MutationAssertion(@"Protocol is already registered");
_isRegistered = YES;
objc_registerProtocol(self.workingProtocol);
return [FLEXProtocol protocol:self.workingProtocol];
}
@end

View File

@@ -0,0 +1,290 @@
// Copyright (c) 2013, Facebook, Inc.
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name Facebook nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "flex_fishhook.h"
#include <dlfcn.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/vm_map.h>
#include <mach/vm_region.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct section_64 section_t;
typedef struct nlist_64 nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif
#ifndef SEG_DATA_CONST
#define SEG_DATA_CONST "__DATA_CONST"
#endif
struct rebindings_entry {
struct rebinding *rebindings;
size_t rebindings_nel;
struct rebindings_entry *next;
};
static struct rebindings_entry *_flex_rebindings_head;
/// @return 0 on success
static int flex_prepend_rebindings(struct rebindings_entry **rebindings_head,
struct rebinding rebindings[],
size_t nel) {
struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
if (!new_entry) {
return -1;
}
new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
if (!new_entry->rebindings) {
free(new_entry);
return -1;
}
memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
new_entry->rebindings_nel = nel;
new_entry->next = *rebindings_head;
*rebindings_head = new_entry;
return 0;
}
static vm_prot_t flex_get_protection(void *sectionStart) {
mach_port_t task = mach_task_self();
vm_size_t size = 0;
vm_address_t address = (vm_address_t)sectionStart;
memory_object_name_t object;
#if __LP64__
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
vm_region_basic_info_data_64_t info;
kern_return_t info_ret = vm_region_64(
task, &address, &size, VM_REGION_BASIC_INFO_64,
(vm_region_info_64_t)&info, &count, &object
);
#else
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT;
vm_region_basic_info_data_t info;
kern_return_t info_ret = vm_region(
task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object
);
#endif
if (info_ret == KERN_SUCCESS) {
return info.protection;
} else {
return VM_PROT_READ;
}
}
static void flex_perform_rebinding_with_section(struct rebindings_entry *rebindings,
section_t *section,
intptr_t slide,
nlist_t *symtab,
char *strtab,
uint32_t *indirect_symtab) {
const bool isDataConst = strcmp(section->segname, "__DATA_CONST") == 0;
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
vm_prot_t oldProtection = VM_PROT_READ;
if (isDataConst) {
oldProtection = flex_get_protection(rebindings);
mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE);
}
for (uint i = 0; i < section->size / sizeof(void *); i++) {
uint32_t symtab_index = indirect_symbol_indices[i];
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
continue;
}
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
char *symbol_name = strtab + strtab_offset;
bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
struct rebindings_entry *cur = rebindings;
while (cur) {
for (uint j = 0; j < cur->rebindings_nel; j++) {
if (symbol_name_longer_than_1 &&
strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;
}
if (isDataConst) {
int protection = 0;
if (oldProtection & VM_PROT_READ) {
protection |= PROT_READ;
}
if (oldProtection & VM_PROT_WRITE) {
protection |= PROT_WRITE;
}
if (oldProtection & VM_PROT_EXECUTE) {
protection |= PROT_EXEC;
}
mprotect(indirect_symbol_bindings, section->size, protection);
}
}
static void flex_rebind_symbols_for_image(struct rebindings_entry *rebindings,
const struct mach_header *header,
intptr_t slide) {
Dl_info info;
if (dladdr(header, &info) == 0) {
return;
}
segment_command_t *cur_seg_cmd;
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
linkedit_segment = cur_seg_cmd;
}
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}
if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
!dysymtab_cmd->nindirectsyms) {
return;
}
// Find base symbol/string table addresses
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
continue;
}
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
section_t *sect = (section_t *)(cur + sizeof(segment_command_t)) + j;
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
flex_perform_rebinding_with_section(
rebindings, sect, slide, symtab, strtab, indirect_symtab
);
}
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
flex_perform_rebinding_with_section(
rebindings, sect, slide, symtab, strtab, indirect_symtab
);
}
}
}
}
}
static void _flex_rebind_symbols_for_image(const struct mach_header *header,
intptr_t slide) {
flex_rebind_symbols_for_image(_flex_rebindings_head, header, slide);
}
int flex_rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel) {
struct rebindings_entry *rebindings_head = NULL;
int retval = flex_prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
flex_rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
if (rebindings_head) {
free(rebindings_head->rebindings);
}
free(rebindings_head);
return retval;
}
/// @return 0 on success
int flex_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
int retval = flex_prepend_rebindings(&_flex_rebindings_head, rebindings, rebindings_nel);
if (retval < 0) {
return retval;
}
// If this was the first call, register callback for image additions (which is also invoked for
// existing images, otherwise, just run on existing images
if (!_flex_rebindings_head->next) {
_dyld_register_func_for_add_image(_flex_rebind_symbols_for_image);
} else {
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
_flex_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}
return retval;
}

View File

@@ -0,0 +1,77 @@
// Copyright (c) 2013, Facebook, Inc.
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name Facebook nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef fishhook_h
#define fishhook_h
#include <stddef.h>
#include <stdint.h>
#if !defined(FISHHOOK_EXPORT)
#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
#else
#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
/**
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
/**
* For each rebinding in rebindings, rebinds references to external, indirect
* symbols with the specified name to instead point at replacement for each
* image in the calling process as well as for all future images that are loaded
* by the process. If rebind_functions is called more than once, the symbols to
* rebind are added to the existing list of rebindings, and if a given symbol
* is rebound more than once, the later rebinding will take precedence.
* @return 0 on success
*/
FISHHOOK_VISIBILITY
int flex_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
/**
* Rebinds as above, but only in the specified image. The header should point
* to the mach-o header, the slide should be the slide offset. Others as above.
* @return 0 on success
*/
FISHHOOK_VISIBILITY
int flex_rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //fishhook_h