转载

根据协议名获取所有代理方法

因为一直在用 Peckham 这个插件,能够在编辑器的任意位置使用快捷键快速引用头文件,所以后来在想能不能也写一个类似的插件快速引入什么东西,细细一想平时自己在引用代理协议的时候,基本都是要跑到文件顶部添加好之后,再回到原来的位置继续写,如果有必须实现的代理方法没注意实现的话,可能又要回到implementation看warnings或者跳转到协议里面看哪些是必须实现,拷贝过来,粘贴到自己的实现里面,这样的操作实在是太麻烦了,所以我想可以写个插件,使用快捷键将必须实现的代理方法到implementation底部,这样对于开发者来说能避免很多不必要操作,也能快速明白哪些代理方法必须实现,好,接下来我来构思下怎么实现这个插件(但是后面这个实现出来的效果并不是很完美,所以还是放弃了…原因看更多)

1.在写完协议名之后,双击或者单击拖动选中协议名

2.使用快捷键,根据选中的协议名,查找协议里面的所有代理方法

3.再筛选出里面require标记的代理方法

4.将这些代理方法,添加到当前类的实现文件里面

我接下去讲的都是默认你已经了解了插件的配置以及调试

选中协议名

首先我们在初始化bundle的时候,注册 NSTextViewDidChangeSelectionNotification 通知, - selectString : 用来接收选中文本改变时通知

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectString:)
name:NSTextViewDidChangeSelectionNotification object:nil];

然后在接收方法里面,我们获取到当前操作的编辑页面 NSTextView 对象,然后获取到选中的 range ,取出选中的文本

- (void)selectString:(NSNotification *)notification {
    if ([notification.object isKindOfClass:[NSTextView class]]) {
        NSTextView* textView = (NSTextView *)notification.object;
        NSArray* selectedRanges = [textView selectedRanges];
        if (selectedRanges.count == 0) {
            return;
        }
        NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue];
        NSString* text = textView.textStorage.string;
        self.selectedString = [text substringWithRange:selectedRange];
        NSLog(@"%s %@",__func__,self.selectedString);
    }
}

使用快捷键,查找协议里面的所有代理方法

快捷键的设置在增加 NSMenuItem 对象时就已经设置了,并且设置其快捷键为 ^⎇G ,以及对应的 - searchProtocol: 方法

[[menuItem submenu] addItem:[NSMenuItem separatorItem]];
        
NSMenuItem *protolMenuItem = [[NSMenuItem alloc] initWithTitle:@"Protol Helper" action:@selector(searchProtocol:) keyEquivalent:@"g"];
[protolMenuItem setKeyEquivalentModifierMask:NSAlternateKeyMask|NSControlKeyMask];
protolMenuItem.target = self;
[[menuItem submenu] addItem:protolMenuItem];

然后我们怎么查找到选中文本对应的协议和里面的代理方法呢?

我们先找怎么获取到代理方法,然后倒推回来,首先我们需要用到 runtime ,我们进到 runtime.h 里,通过搜索 protocol 关键字,我们找了 protocol_copyMethodDescriptionList 这个方法, p 是一个 Protocol 对象, isRequiredMethod 筛选是否是必须的方法,这样的话,我们就可以直接通过这个方法来获取必须实现的代理方法, isInstanceMethod 筛选是否是实例方法, outCount 这个表示返回方法的数量

OBJC_EXPORT struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount)
     __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

Protocol 对象我们可以通过 objc_getProtocol 方法,通过传入协议名来创建

const char *protocolName = self.selectedString.UTF8String;
Protocol *protocol = objc_getProtocol(protocolName);

然后我们开始调用 protocol_copyMethodDescriptionList 方法,打印出方法信息,这里我就先不管代码简洁性了,我拿 NSTextViewDelegate 来测试

unsigned int count = 999;
struct objc_method_description *methods1;
struct objc_method_description *methods2;
struct objc_method_description *methods3;
struct objc_method_description *methods4;
methods1 = protocol_copyMethodDescriptionList(protocol, NO, YES, &count);
methods2 = protocol_copyMethodDescriptionList(protocol, NO, NO, &count);
methods3 = protocol_copyMethodDescriptionList(protocol, YES, YES, &count);
methods4 = protocol_copyMethodDescriptionList(protocol, YES, NO, &count);

if (methods1 != NULL) {
    NSLog(@"---------------------methods1");
    [self logMethods:methods1];
}

if (methods2 != NULL) {
    NSLog(@"---------------------methods2");
    [self logMethods:methods2];
}

if (methods3 != NULL) {
    NSLog(@"---------------------methods3");
    [self logMethods:methods3];
}

if (methods4 != NULL) {
    NSLog(@"---------------------methods4");
    [self logMethods:methods4];
}

我们看下打印的方法信息

2016-08-07 15:13:39.242 Xcode[1218:86590] ---------------------methods1
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textView:shouldChangeTextInRange:replacementString: c48@0:8@16{_NSRange=QQ}24@40
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textView:willChangeSelectionFromCharacterRange:toCharacterRange: {_NSRange=QQ}56@0:8@16{_NSRange=QQ}24{_NSRange=QQ}40
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textViewDidChangeSelection: v24@0:8@16
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textView:completions:forPartialWordRange:indexOfSelectedItem: @56@0:8@16@24{_NSRange=QQ}32^q48
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textView:doCommandBySelector: c32@0:8@16:24
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textView:clickedOnLink:atIndex: c40@0:8@16@24Q32
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textView:clickedOnCell:inRect:atIndex: v72@0:8@16@24{CGRect={CGPoint=dd}{CGSize=dd}}32Q64
2016-08-07 15:13:39.242 Xcode[1218:86590] methods name textView:doubleClickedOnCell:inRect:atIndex: v72@0:8@16@24{CGRect={CGPoint=dd}{CGSize=dd}}32Q64
...

what?方法名居然不是完整的,不是我们看到 - (BOOL)textView:(NSTextView *)textView clickedOnLink:(id)link atIndex:(NSUInteger)charIndex; 这样的,但是想想也对,方法名应该是这样的,不包含参数名和参数类型,虽然参数类型可以通过 objc_method_description 结构体里面 types 拿到,但是参数名怎么办…我总不能用abc来代替吧,虽然做是可以做,但是用起来还是要改参数名,这不是很麻烦…感觉在这里遇到瓶颈了

然后我想看到 objc_class 结构体里面也有存放协议信息,那他里面是怎么样的

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
struct objc_protocol_list {
    struct objc_protocol_list *next;
    long count;
    Protocol *list[1];
};
@interface Protocol : Object
{
@private
    char *protocol_name OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocol_list OBJC2_UNAVAILABLE;
    struct objc_method_description_list *instance_methods OBJC2_UNAVAILABLE;
    struct objc_method_description_list *class_methods OBJC2_UNAVAILABLE;
}
struct objc_method_description_list {
        int count;
        struct objc_method_description list[1];
};
struct objc_method_description {
	SEL name;               /**< The name of the method */
	char *types;            /**< The types of the method arguments */
};

通过一系列的查找,我们又回到了 objc_method_description ,what?这 objc_class 最终拿到的数据还是从 objc_method_description 来的,那就是说明我们没有办法直接获得 - (BOOL)textView:(NSTextView *)textView clickedOnLink:(id)link atIndex:(NSUInteger)charIndex; ….这样就不能达到我们所预期的那样了

总结

我们可以通过 protocol_copyMethodDescriptionList 方法获取到协议里面所有的代理方法,分为方法名和类型,但是不能获取到 - (BOOL)textView:(NSTextView *)textView clickedOnLink:(id)link atIndex:(NSUInteger)charIndex; 这样的,如果用a,b,c这样的来填充参数名,这样在使用起来,使用方还要自己再替换参数名,这样会比较麻烦,解决不了我们的需求,gg…..

原文  http://zeeyang.com/2016/08/07/get-protocol-methods/
正文到此结束
Loading...