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

1.在写完协议名之后,双击或者单击拖动选中协议名
2.使用快捷键,根据选中的协议名,查找协议里面的所有代理方法
3.再筛选出里面require标记的代理方法
4.将这些代理方法,添加到当前类的实现文件里面

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

选中协议名

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
- (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:方法

1
2
3
4
5
6
[[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这个表示返回方法的数量

1
2
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方法,通过传入协议名来创建

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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];
}

我们看下打印的方法信息

1
2
3
4
5
6
7
8
9
10
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结构体里面也有存放协议信息,那他里面是怎么样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
1
2
3
4
5
struct objc_protocol_list {
struct objc_protocol_list *next;
long count;
Protocol *list[1];
};
1
2
3
4
5
6
7
8
@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;
}
1
2
3
4
struct objc_method_description_list {
int count;
struct objc_method_description list[1];
};
1
2
3
4
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…..

2016年8月10日补充:

后来西兰花提出说根据协议名称去爬开发文档上相关的代理方法或者找本地开发包里面的相关头文件,不考虑是否可行,但是有个共同的问题是只能获取到官方的,自己创建的不行,而且前者没有require标记