Explorar o código

Better block introspection

blocks
Tanner Bennett %!s(int64=6) %!d(string=hai) anos
pai
achega
804fd376b9

+ 4 - 0
Classes/ObjectExplorers/FLEXObjectExplorerFactory.m

@@ -17,6 +17,7 @@
 #import "FLEXColorPreviewSection.h"
 #import "FLEXDefaultsContentSection.h"
 #import "FLEXBundleShortcuts.h"
+#import "FLEXBlockShortcuts.h"
 #import <objc/runtime.h>
 
 @implementation FLEXObjectExplorerFactory
@@ -26,6 +27,7 @@ static NSMutableDictionary<Class, Class> *classesToRegisteredSections = nil;
 {
     if (self == [FLEXObjectExplorerFactory class]) {
         #define ClassKey(name) (Class<NSCopying>)[name class]
+        #define ClassKeyByName(str) (Class<NSCopying>)NSClassFromString(@ #str)
         classesToRegisteredSections = [NSMutableDictionary dictionaryWithDictionary:@{
             ClassKey(NSArray)          : [FLEXCollectionContentSection class],
             ClassKey(NSSet)            : [FLEXCollectionContentSection class],
@@ -37,8 +39,10 @@ static NSMutableDictionary<Class, Class> *classesToRegisteredSections = nil;
             ClassKey(CALayer)          : [FLEXLayerShortcuts class],
             ClassKey(UIColor)          : [FLEXColorPreviewSection class],
             ClassKey(NSBundle)         : [FLEXBundleShortcuts class],
+            ClassKeyByName(NSBlock)    : [FLEXBlockShortcuts class],
         }];
         #undef ClassKey
+        #undef ClassKeyByName
     }
 }
 

+ 19 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXBlockShortcuts.h

@@ -0,0 +1,19 @@
+//
+// FLEXBlockShortcuts.h
+//  FLEX
+//
+//  Created by Tanner on 1/30/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Provides a description of the block's signature
+/// and access to an NSMethodSignature of the block
+@interface FLEXBlockShortcuts : FLEXShortcutsSection
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 59 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXBlockShortcuts.m

@@ -0,0 +1,59 @@
+//
+// FLEXBlockShortcuts.m
+//  FLEX
+//
+//  Created by Tanner on 1/30/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXBlockShortcuts.h"
+#import "FLEXShortcut.h"
+#import "FLEXBlockDescription.h"
+#import "FLEXObjectExplorerFactory.h"
+
+#pragma mark - 
+@implementation FLEXBlockShortcuts
+
+#pragma mark Overrides
+
++ (instancetype)forObject:(id)block {
+    NSParameterAssert([block isKindOfClass:NSClassFromString(@"NSBlock")]);
+    
+    FLEXBlockDescription *blockInfo = [FLEXBlockDescription describing:block];
+    NSMethodSignature *signature = blockInfo.signature;
+    NSArray *blockShortcutRows = @[blockInfo.summary];
+    
+    if (signature) {
+        blockShortcutRows = @[
+            blockInfo.summary,
+            blockInfo.sourceDeclaration,
+            signature.debugDescription,
+            [FLEXActionShortcut title:@"View Method Signature"
+                subtitle:^NSString *(id block) {
+                    return signature.description ?: @"unsupported signature";
+                }
+                viewer:^UIViewController *(id block) {
+                    return [FLEXObjectExplorerFactory explorerViewControllerForObject:signature];
+                }
+                accessoryType:^UITableViewCellAccessoryType(id view) {
+                    if (signature) {
+                        return UITableViewCellAccessoryDisclosureIndicator;
+                    }
+                    return UITableViewCellAccessoryNone;
+                }
+            ]
+        ];
+    }
+    
+    return [self forObject:block additionalRows:blockShortcutRows];
+}
+
+- (NSString *)title {
+    return @"Metadata";
+}
+
+- (NSInteger)numberOfLines {
+    return 0;
+}
+
+@end

+ 1 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXBundleShortcuts.h

@@ -10,6 +10,7 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+/// Provides a "Browse Bundle Directory" action
 @interface FLEXBundleShortcuts : FLEXShortcutsSection
 
 @end

+ 2 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsFactory+Defaults.h

@@ -19,3 +19,5 @@
 @interface FLEXShortcutsFactory (Classes) @end
 
 @interface FLEXShortcutsFactory (Activities) @end
+
+@interface FLEXShortcutsFactory (Blocks) @end

+ 11 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsFactory+Defaults.m

@@ -147,3 +147,14 @@
 }
 
 @end
+
+
+#pragma mark - Activities
+
+@implementation FLEXShortcutsFactory (Blocks)
+
++ (void)load {
+    self.append.methods(@[@"invoke"]).forClass(NSClassFromString(@"NSBlock"));
+}
+
+@end

+ 58 - 0
Classes/Utility/FLEXBlockDescription.h

@@ -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) 2012 EBF-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

+ 157 - 0
Classes/Utility/FLEXBlockDescription.m

@@ -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) 2012 EBF-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] initWithBlock:block];
+}
+
+- (id)initWithBlock:(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

+ 16 - 0
FLEX.xcodeproj/project.pbxproj

@@ -144,6 +144,10 @@
 		C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
 		C31C4A6923342A2200C35F12 /* FLEXMetadataSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C31C4A6723342A2200C35F12 /* FLEXMetadataSection.h */; };
 		C31C4A6A23342A2200C35F12 /* FLEXMetadataSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C31C4A6823342A2200C35F12 /* FLEXMetadataSection.m */; };
+		C31D93E423E38CBE005517BF /* FLEXBlockShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */; };
+		C31D93E523E38CBE005517BF /* FLEXBlockShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C31D93E323E38CBE005517BF /* FLEXBlockShortcuts.m */; };
+		C31D93E823E38E97005517BF /* FLEXBlockDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = C31D93E623E38E97005517BF /* FLEXBlockDescription.h */; };
+		C31D93E923E38E97005517BF /* FLEXBlockDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = C31D93E723E38E97005517BF /* FLEXBlockDescription.m */; };
 		C32A195E231732E800EB02AC /* FLEXCollectionContentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C32A195C231732E800EB02AC /* FLEXCollectionContentSection.h */; };
 		C32A195F231732E800EB02AC /* FLEXCollectionContentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C32A195D231732E800EB02AC /* FLEXCollectionContentSection.m */; };
 		C32A19622317378C00EB02AC /* FLEXDefaultsContentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C32A19602317378C00EB02AC /* FLEXDefaultsContentSection.h */; };
@@ -455,6 +459,10 @@
 		C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
 		C31C4A6723342A2200C35F12 /* FLEXMetadataSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMetadataSection.h; sourceTree = "<group>"; };
 		C31C4A6823342A2200C35F12 /* FLEXMetadataSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXMetadataSection.m; sourceTree = "<group>"; };
+		C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBlockShortcuts.h; sourceTree = "<group>"; };
+		C31D93E323E38CBE005517BF /* FLEXBlockShortcuts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBlockShortcuts.m; sourceTree = "<group>"; };
+		C31D93E623E38E97005517BF /* FLEXBlockDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXBlockDescription.h; sourceTree = "<group>"; };
+		C31D93E723E38E97005517BF /* FLEXBlockDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXBlockDescription.m; sourceTree = "<group>"; };
 		C32A195C231732E800EB02AC /* FLEXCollectionContentSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCollectionContentSection.h; sourceTree = "<group>"; };
 		C32A195D231732E800EB02AC /* FLEXCollectionContentSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXCollectionContentSection.m; sourceTree = "<group>"; };
 		C32A19602317378C00EB02AC /* FLEXDefaultsContentSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXDefaultsContentSection.h; sourceTree = "<group>"; };
@@ -718,6 +726,8 @@
 				C38F3F30230C958F004E3731 /* FLEXAlert.m */,
 				7349FD6822B93CDF00051810 /* FLEXColor.h */,
 				7349FD6922B93CDF00051810 /* FLEXColor.m */,
+				C31D93E623E38E97005517BF /* FLEXBlockDescription.h */,
+				C31D93E723E38E97005517BF /* FLEXBlockDescription.m */,
 				942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */,
 				942DCD831BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.m */,
 				94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */,
@@ -1183,6 +1193,8 @@
 				C34D4EB723A2B17900C1F903 /* FLEXBundleShortcuts.m */,
 				C38EF26123A2FCD20047A7EC /* FLEXViewControllerShortcuts.h */,
 				C38EF26023A2FCD20047A7EC /* FLEXViewControllerShortcuts.m */,
+				C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */,
+				C31D93E323E38CBE005517BF /* FLEXBlockShortcuts.m */,
 			);
 			path = Shortcuts;
 			sourceTree = "<group>";
@@ -1268,6 +1280,7 @@
 				C3A9423423C3F98E006871A3 /* FHSViewController.h in Headers */,
 				3A4C94E31B5B21410088C3F2 /* FLEXRuntimeUtility.h in Headers */,
 				C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */,
+				C31D93E823E38E97005517BF /* FLEXBlockDescription.h in Headers */,
 				C36FBFCF230F3B98008D95D5 /* FLEXProtocol.h in Headers */,
 				94A515271C4CA2080063292F /* FLEXToolbarItem.h in Headers */,
 				3A4C95341B5B21410088C3F2 /* FLEXSystemLogTableViewController.h in Headers */,
@@ -1353,6 +1366,7 @@
 				3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h in Headers */,
 				C398626523AD70F5007E6793 /* TBToolbarButton.h in Headers */,
 				3A4C95241B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h in Headers */,
+				C31D93E423E38CBE005517BF /* FLEXBlockShortcuts.h in Headers */,
 				94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */,
 				71E1C2152307FBB800F5032A /* FLEXKeychainQuery.h in Headers */,
 				C36B096523E0D4A1008F5D47 /* UIMenu+FLEX.h in Headers */,
@@ -1576,6 +1590,7 @@
 				C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */,
 				779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
 				C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
+				C31D93E523E38CBE005517BF /* FLEXBlockShortcuts.m in Sources */,
 				C34D4EB523A2AF2A00C1F903 /* FLEXColorPreviewSection.m in Sources */,
 				C36B097123E1EDCD008F5D47 /* FLEXTableViewSection.m in Sources */,
 				3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
@@ -1612,6 +1627,7 @@
 				3A4C950A1B5B21410088C3F2 /* FLEXFieldEditorView.m in Sources */,
 				3A4C95061B5B21410088C3F2 /* FLEXArgumentInputViewFactory.m in Sources */,
 				3A4C95291B5B21410088C3F2 /* FLEXInstancesTableViewController.m in Sources */,
+				C31D93E923E38E97005517BF /* FLEXBlockDescription.m in Sources */,
 				71E1C2182307FBB800F5032A /* FLEXKeychainTableViewController.m in Sources */,
 				94A5151E1C4CA1F10063292F /* FLEXExplorerViewController.m in Sources */,
 				C398625A23AD6C88007E6793 /* TBToken.m in Sources */,