mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-12-27 00:37:21 -05:00
added files via upload
This commit is contained in:
367
Tweaks/FLEX/Utility/APPLE_LICENSE
Normal file
367
Tweaks/FLEX/Utility/APPLE_LICENSE
Normal 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."
|
||||
15
Tweaks/FLEX/Utility/Categories/CALayer+FLEX.h
Normal file
15
Tweaks/FLEX/Utility/Categories/CALayer+FLEX.h
Normal 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
|
||||
46
Tweaks/FLEX/Utility/Categories/CALayer+FLEX.m
Normal file
46
Tweaks/FLEX/Utility/Categories/CALayer+FLEX.m
Normal 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
|
||||
29
Tweaks/FLEX/Utility/Categories/FLEXRuntime+Compare.h
Normal file
29
Tweaks/FLEX/Utility/Categories/FLEXRuntime+Compare.h
Normal 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
|
||||
47
Tweaks/FLEX/Utility/Categories/FLEXRuntime+Compare.m
Normal file
47
Tweaks/FLEX/Utility/Categories/FLEXRuntime+Compare.m
Normal 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
|
||||
90
Tweaks/FLEX/Utility/Categories/FLEXRuntime+UIKitHelpers.h
Normal file
90
Tweaks/FLEX/Utility/Categories/FLEXRuntime+UIKitHelpers.h
Normal 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.
|
||||
|
||||
634
Tweaks/FLEX/Utility/Categories/FLEXRuntime+UIKitHelpers.m
Normal file
634
Tweaks/FLEX/Utility/Categories/FLEXRuntime+UIKitHelpers.m
Normal 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
|
||||
40
Tweaks/FLEX/Utility/Categories/NSArray+FLEX.h
Normal file
40
Tweaks/FLEX/Utility/Categories/NSArray+FLEX.h
Normal 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
|
||||
143
Tweaks/FLEX/Utility/Categories/NSArray+FLEX.m
Normal file
143
Tweaks/FLEX/Utility/Categories/NSArray+FLEX.m
Normal 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
|
||||
234
Tweaks/FLEX/Utility/Categories/NSObject+FLEX_Reflection.h
Normal file
234
Tweaks/FLEX/Utility/Categories/NSObject+FLEX_Reflection.h
Normal 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
|
||||
426
Tweaks/FLEX/Utility/Categories/NSObject+FLEX_Reflection.m
Normal file
426
Tweaks/FLEX/Utility/Categories/NSObject+FLEX_Reflection.m
Normal 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
|
||||
|
||||
|
||||
19
Tweaks/FLEX/Utility/Categories/NSTimer+FLEX.h
Normal file
19
Tweaks/FLEX/Utility/Categories/NSTimer+FLEX.h
Normal 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
|
||||
25
Tweaks/FLEX/Utility/Categories/NSTimer+FLEX.m
Normal file
25
Tweaks/FLEX/Utility/Categories/NSTimer+FLEX.m
Normal 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
|
||||
51
Tweaks/FLEX/Utility/Categories/NSUserDefaults+FLEX.h
Normal file
51
Tweaks/FLEX/Utility/Categories/NSUserDefaults+FLEX.h
Normal 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
|
||||
188
Tweaks/FLEX/Utility/Categories/NSUserDefaults+FLEX.m
Normal file
188
Tweaks/FLEX/Utility/Categories/NSUserDefaults+FLEX.m
Normal 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
|
||||
13
Tweaks/FLEX/Utility/Categories/Private/Cocoa+FLEXShortcuts.h
Normal file
13
Tweaks/FLEX/Utility/Categories/Private/Cocoa+FLEXShortcuts.h
Normal 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
|
||||
25
Tweaks/FLEX/Utility/Categories/Private/Cocoa+FLEXShortcuts.m
Normal file
25
Tweaks/FLEX/Utility/Categories/Private/Cocoa+FLEXShortcuts.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
33
Tweaks/FLEX/Utility/Categories/Private/NSString+FLEX.h
Normal file
33
Tweaks/FLEX/Utility/Categories/Private/NSString+FLEX.h
Normal 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
|
||||
160
Tweaks/FLEX/Utility/Categories/Private/NSString+FLEX.m
Normal file
160
Tweaks/FLEX/Utility/Categories/Private/NSString+FLEX.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
23
Tweaks/FLEX/Utility/Categories/Private/UIView+FLEX_Layout.h
Normal file
23
Tweaks/FLEX/Utility/Categories/Private/UIView+FLEX_Layout.h
Normal 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
|
||||
66
Tweaks/FLEX/Utility/Categories/Private/UIView+FLEX_Layout.m
Normal file
66
Tweaks/FLEX/Utility/Categories/Private/UIView+FLEX_Layout.m
Normal 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
|
||||
40
Tweaks/FLEX/Utility/Categories/UIBarButtonItem+FLEX.h
Normal file
40
Tweaks/FLEX/Utility/Categories/UIBarButtonItem+FLEX.h
Normal 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
|
||||
72
Tweaks/FLEX/Utility/Categories/UIBarButtonItem+FLEX.m
Normal file
72
Tweaks/FLEX/Utility/Categories/UIBarButtonItem+FLEX.m
Normal 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
|
||||
17
Tweaks/FLEX/Utility/Categories/UIFont+FLEX.h
Normal file
17
Tweaks/FLEX/Utility/Categories/UIFont+FLEX.h
Normal 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
|
||||
43
Tweaks/FLEX/Utility/Categories/UIFont+FLEX.m
Normal file
43
Tweaks/FLEX/Utility/Categories/UIFont+FLEX.m
Normal 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
|
||||
21
Tweaks/FLEX/Utility/Categories/UIGestureRecognizer+Blocks.h
Normal file
21
Tweaks/FLEX/Utility/Categories/UIGestureRecognizer+Blocks.h
Normal 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
|
||||
|
||||
36
Tweaks/FLEX/Utility/Categories/UIGestureRecognizer+Blocks.m
Normal file
36
Tweaks/FLEX/Utility/Categories/UIGestureRecognizer+Blocks.m
Normal 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
|
||||
19
Tweaks/FLEX/Utility/Categories/UIMenu+FLEX.h
Normal file
19
Tweaks/FLEX/Utility/Categories/UIMenu+FLEX.h
Normal 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
|
||||
39
Tweaks/FLEX/Utility/Categories/UIMenu+FLEX.m
Normal file
39
Tweaks/FLEX/Utility/Categories/UIMenu+FLEX.m
Normal 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
|
||||
16
Tweaks/FLEX/Utility/Categories/UIPasteboard+FLEX.h
Normal file
16
Tweaks/FLEX/Utility/Categories/UIPasteboard+FLEX.h
Normal 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
|
||||
31
Tweaks/FLEX/Utility/Categories/UIPasteboard+FLEX.m
Normal file
31
Tweaks/FLEX/Utility/Categories/UIPasteboard+FLEX.m
Normal 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
|
||||
14
Tweaks/FLEX/Utility/Categories/UITextField+Range.h
Normal file
14
Tweaks/FLEX/Utility/Categories/UITextField+Range.h
Normal 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
|
||||
23
Tweaks/FLEX/Utility/Categories/UITextField+Range.m
Normal file
23
Tweaks/FLEX/Utility/Categories/UITextField+Range.m
Normal 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
|
||||
85
Tweaks/FLEX/Utility/FLEXAlert.h
Normal file
85
Tweaks/FLEX/Utility/FLEXAlert.h
Normal 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
|
||||
233
Tweaks/FLEX/Utility/FLEXAlert.m
Normal file
233
Tweaks/FLEX/Utility/FLEXAlert.m
Normal 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
|
||||
47
Tweaks/FLEX/Utility/FLEXColor.h
Normal file
47
Tweaks/FLEX/Utility/FLEXColor.h
Normal 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
|
||||
136
Tweaks/FLEX/Utility/FLEXColor.m
Normal file
136
Tweaks/FLEX/Utility/FLEXColor.m
Normal 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
|
||||
25
Tweaks/FLEX/Utility/FLEXHeapEnumerator.h
Normal file
25
Tweaks/FLEX/Utility/FLEXHeapEnumerator.h
Normal 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
|
||||
197
Tweaks/FLEX/Utility/FLEXHeapEnumerator.m
Normal file
197
Tweaks/FLEX/Utility/FLEXHeapEnumerator.m
Normal 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
|
||||
95
Tweaks/FLEX/Utility/FLEXMacros.h
Normal file
95
Tweaks/FLEX/Utility/FLEXMacros.h
Normal 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 */
|
||||
59
Tweaks/FLEX/Utility/FLEXResources.h
Normal file
59
Tweaks/FLEX/Utility/FLEXResources.h
Normal 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
|
||||
8861
Tweaks/FLEX/Utility/FLEXResources.m
Normal file
8861
Tweaks/FLEX/Utility/FLEXResources.m
Normal file
File diff suppressed because it is too large
Load Diff
62
Tweaks/FLEX/Utility/FLEXUtility.h
Normal file
62
Tweaks/FLEX/Utility/FLEXUtility.h
Normal 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
|
||||
527
Tweaks/FLEX/Utility/FLEXUtility.m
Normal file
527
Tweaks/FLEX/Utility/FLEXUtility.m
Normal 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 = @{ @" " : @" ",
|
||||
@">" : @">",
|
||||
@"<" : @"<",
|
||||
@"&" : @"&",
|
||||
@"'" : @"'",
|
||||
@"\"" : @""",
|
||||
@"«" : @"«",
|
||||
@"»" : @"»"
|
||||
};
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
29
Tweaks/FLEX/Utility/Keyboard/FLEXKeyboardShortcutManager.h
Normal file
29
Tweaks/FLEX/Utility/Keyboard/FLEXKeyboardShortcutManager.h
Normal 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
|
||||
326
Tweaks/FLEX/Utility/Keyboard/FLEXKeyboardShortcutManager.m
Normal file
326
Tweaks/FLEX/Utility/Keyboard/FLEXKeyboardShortcutManager.m
Normal 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
|
||||
108
Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.h
Normal file
108
Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.h
Normal 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
|
||||
881
Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.m
Normal file
881
Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.m
Normal 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
|
||||
73
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.h
Normal file
73
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.h
Normal 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
|
||||
196
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.mm
Normal file
196
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.mm
Normal 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"
|
||||
79
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.h
Normal file
79
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.h
Normal 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);
|
||||
24
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.m
Normal file
24
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.m
Normal 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";
|
||||
56
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.h
Normal file
56
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.h
Normal 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);
|
||||
}
|
||||
107
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.m
Normal file
107
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.m
Normal 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;
|
||||
}
|
||||
46
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.h
Normal file
46
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.h
Normal 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
|
||||
900
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.m
Normal file
900
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)) \
|
||||
]
|
||||
168
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXClassBuilder.m
Normal file
168
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXClassBuilder.m
Normal 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
|
||||
51
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.h
Normal file
51
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.h
Normal 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
|
||||
158
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.m
Normal file
158
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.m
Normal 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
|
||||
96
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.h
Normal file
96
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.h
Normal 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
|
||||
430
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.m
Normal file
430
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.m
Normal 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
|
||||
43
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.h
Normal file
43
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.h
Normal 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
|
||||
49
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.m
Normal file
49
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.m
Normal 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
|
||||
97
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.h
Normal file
97
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.h
Normal 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
|
||||
145
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.m
Normal file
145
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.m
Normal 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
|
||||
138
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.h
Normal file
138
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.h
Normal 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
|
||||
295
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.m
Normal file
295
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
73
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.h
Normal file
73
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.h
Normal 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
|
||||
212
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.m
Normal file
212
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.m
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
290
Tweaks/FLEX/Utility/Runtime/flex_fishhook.c
Normal file
290
Tweaks/FLEX/Utility/Runtime/flex_fishhook.c
Normal 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;
|
||||
}
|
||||
77
Tweaks/FLEX/Utility/Runtime/flex_fishhook.h
Normal file
77
Tweaks/FLEX/Utility/Runtime/flex_fishhook.h
Normal 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
|
||||
Reference in New Issue
Block a user