hpi*_*que 5 macos objective-c ios objective-c-blocks
我喜欢积木,当我不能使用它时会让我感到难过.特别是,每次我使用委托时都会发生这种情况(例如:使用UIKit类,主要是预阻塞功能).
所以我想知道......使用ObjC的疯狂力量,做这样的事情是否可能?
// id _delegate; // Most likely declared as class variable or it will be released
_delegate = [DelegateFactory delegateOfProtocol:@protocol(SomeProtocol)];
_delegate performBlock:^{
// Do something
} onSelector:@selector(someProtocolMethod)]; // would execute the given block when the given selector is called on the dynamic delegate object.
theObject.delegate = (id<SomeProtocol>)_delegate;
// Profit!
Run Code Online (Sandbox Code Playgroud)
performBlock:onSelector:
如果YES,怎么样?我们不应该尽可能多地这样做吗?
编辑
看起来有可能.目前的答案集中在问题的第一部分,即如何.但是对" 我们应该这样做 "的部分进行一些讨论会很好.
好吧,我终于把WoolDelegate放到了GitHub上.现在它应该只花我一个月写一个适当的自述文件(虽然我想这是一个好的开始).
委托类本身非常简单.它只是维护一个字典映射SEL到Block.当一个实例接收到它没有响应的消息时,它会进入forwardInvocation:并在字典中查找选择器:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
GenericBlock handler = [self handlerForSelector:sel];
Run Code Online (Sandbox Code Playgroud)
如果找到了,则将块的调用函数指针拉出并传递给多汁位:
IMP handlerIMP = BlockIMP(handler);
[anInvocation Wool_invokeUsingIMP:handlerIMP];
}
Run Code Online (Sandbox Code Playgroud)
(这个BlockIMP()功能,以及其他Block-probing代码,感谢Mike Ash.实际上,很多这个项目都建立在我从他周五的问答环节中学到的东西上.如果你还没读过这些文章,你就错过了.)
我应该注意,每次发送特定消息时,都会通过完整的方法解析机制; 那里的速度很快.另一种选择是Erik H.和EMKPantry各自采用的路径,它为您需要和使用的每个委托对象创建一个新的clas class_addMethod().由于每个实例WoolDelegate都有自己的处理程序字典,我们不需要这样做,但另一方面,没有办法"缓存"查找或调用.方法只能添加到类中,而不能添加到实例中.
我这样做是出于两个原因:这是一个练习,看看我是否可以解决接下来的部分 - 从NSInvocationBlock调用的切换- 以及为每个所需实例创建一个新类似乎对我来说不优雅 无论它不如我的解决方案优雅,我将留给每个读者的判断.
继续,这个过程的内容实际上是在项目中找到的NSInvocation类别.这利用了libffi来调用一个未知的函数,直到运行时 - 块的调用 - 带有在运行时之前也是未知的参数(可以通过它访问NSInvocation).通常情况下,这是不可能的,原因与a va_list无法传递相同:编译器必须知道有多少参数以及它们有多大.libffi包含每个知道/基于这些平台调用约定的平台的汇编程序.
这里有三个步骤:libffi需要一个被调用函数的参数类型列表; 它需要将参数值本身放入特定格式; 然后需要通过libffi调用函数(块的调用指针)并将返回值放回到NSInvocation.
第一部分的实际工作主要由一个函数处理,该函数再次由Mike Ash编写,调用来自Wool_buildFFIArgTypeList.libffi有内部struct用于描述函数参数类型的内部函数.在准备对函数的调用时,库需要一个指向这些结构的指针列表.该NSMethodSignature对NSInvocation允许每个参数的编码字符串的访问; 从那里翻译到正确的ffi_type是由一组if/ elselookups处理:
arg_types[i] = libffi_type_for_objc_encoding([sig getArgumentTypeAtIndex:actual_arg_idx]);
...
if(str[0] == @encode(type)[0]) \
{ \
if(sizeof(type) == 1) \
return &ffi_type_sint8; \
else if(sizeof(type) == 2) \
return &ffi_type_sint16; \
Run Code Online (Sandbox Code Playgroud)
接下来,libffi想要指向参数值本身的指针.这样做Wool_buildArgValList:从每个参数的大小再次获取NSMethodSignature,并分配一大块内存,然后返回列表:
NSUInteger arg_size;
NSGetSizeAndAlignment([sig getArgumentTypeAtIndex:actual_arg_idx],
&arg_size,
NULL);
/* Get a piece of memory that size and put its address in the list. */
arg_list[i] = [self Wool_allocate:arg_size];
/* Put the value into the allocated spot. */
[self getArgument:arg_list[i] atIndex:actual_arg_idx];
Run Code Online (Sandbox Code Playgroud)
(旁白:代码中有几个关于跳过的注释SEL,这是任何方法调用的(隐藏的)第二个传递参数.Block的调用指针没有一个槽来保存它SEL;它只是自己作为第一个参数,其余的是"正常"参数.由于Block在客户端代码中编写,无论如何都无法访问该参数(当时它不存在),我决定忽略它.)
libffi现在需要做一些"准备"; 只要成功(并且可以分配返回值的空间),现在可以"调用"调用函数指针,并且可以设置返回值:
ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals);
if( ret_val ){
[self setReturnValue:ret_val];
free(ret_val);
}
Run Code Online (Sandbox Code Playgroud)
在项目的main.m中有一些功能演示.
最后,关于"应该这样做吗?"的问题,我认为答案是"是的,只要它能让你更有成效".WoolDelegate是完全通用的,实例可以像任何完全写出的类一样.不过,我的目的是制作一个简单的,一次性的代表 - 只需要一两种方法,而不需要经过他们的委托人 - 比写一个全新的课程更少的工作,更清晰/可维护而不是将一些委托方法粘贴到视图控制器中,因为它是放置它们的最简单的地方.充分利用运行时和语言这样的动态可以提高代码的可读性,就像基于块的NSNotification处理程序一样.
我刚刚整理了一个让你做到这一点的小项目......
@interface EJHDelegateObject : NSObject
+ (id)delegateObjectForProtocol:(Protocol*) protocol;
@property (nonatomic, strong) Protocol *protocol;
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector;
@end
@implementation EJHDelegateObject
static NSInteger counter;
+ (id)delegateObjectForProtocol:(Protocol *)protocol
{
NSString *className = [NSString stringWithFormat:@"%s%@%i",protocol_getName(protocol),@"_EJH_implementation_", counter++];
Class protocolClass = objc_allocateClassPair([EJHDelegateObject class], [className cStringUsingEncoding:NSUTF8StringEncoding], 0);
class_addProtocol(protocolClass, protocol);
objc_registerClassPair(protocolClass);
EJHDelegateObject *object = [[protocolClass alloc] init];
object.protocol = protocol;
return object;
}
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector
{
unsigned int outCount;
struct objc_method_description *methodDescriptions = protocol_copyMethodDescriptionList(self.protocol, NO, YES, &outCount);
struct objc_method_description description;
BOOL descriptionFound = NO;
for (int i = 0; i < outCount; i++){
description = methodDescriptions[i];
if (description.name == selector){
descriptionFound = YES;
break;
}
}
if (descriptionFound){
class_addMethod([self class], selector, imp_implementationWithBlock(blockImplementation), description.types);
}
}
@end
Run Code Online (Sandbox Code Playgroud)
并使用EJHDelegateObject:
self.alertViewDelegate = [EJHDelegateObject delegateObjectForProtocol:@protocol(UIAlertViewDelegate)];
[self.alertViewDelegate addImplementation:^(id _self, UIAlertView* alertView, NSInteger buttonIndex){
NSLog(@"%@ dismissed with index %i", alertView, buttonIndex);
} forSelector:@selector(alertView:didDismissWithButtonIndex:)];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Example" message:@"My delegate is an EJHDelegateObject" delegate:self.alertViewDelegate cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
[alertView show];
Run Code Online (Sandbox Code Playgroud)
小智 2
编辑:这是我在了解您的要求后想到的。这只是一个快速技巧,一个让您入门的想法,它没有正确实现,也没有经过测试。它应该适用于将发送者作为唯一参数的委托方法。它可以工作它应该与普通的和返回结构的委托方法一起工作。
typedef void *(^UBDCallback)(id);
typedef void(^UBDCallbackStret)(void *, id);
void *UBDDelegateMethod(UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallback cb = [self blockForSelector:_cmd];
return cb(sender);
}
void UBDelegateMethodStret(void *retadrr, UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallbackStret cb = [self blockForSelector:_cmd];
cb(retaddr, sender);
}
@interface UniversalBlockDelegate: NSObject
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block;
@end
@implementation UniversalBlockDelegate {
SEL selectors[128];
id blocks[128];
int count;
}
- (id)blockForSelector:(SEL)sel
{
int idx = -1;
for (int i = 0; i < count; i++) {
if (selectors[i] == sel) {
return blocks[i];
}
}
return nil;
}
- (void)dealloc
{
for (int i = 0; i < count; i++) {
[blocks[i] release];
}
[super dealloc];
}
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block
{
if (count >= 128) return NO;
selectors[count] = sel;
blocks[count++] = [block copy];
class_addMethod(self.class, sel, (IMP)(stret ? UBDDelegateMethodStret : UBDDelegateMethod), mSig);
return YES;
}
@end
Run Code Online (Sandbox Code Playgroud)
用法:
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
UniversalBlockDelegate *d = [[UniversalBlockDelegate alloc] init];
webView.delegate = d;
[d addDelegateSelector:@selector(webViewDidFinishLoading:) isStret:NO methodSignature:"v@:@" block:^(id webView) {
NSLog(@"Web View '%@' finished loading!", webView);
}];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://google.com"]]];
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1824 次 |
| 最近记录: |