因为一直在用 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…..