mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:03 -04: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
	 Balackburn
					Balackburn