许多编程语言(例如Ruby、Python、Lisp)会将可重用的、自包含的"工作单元"概念化为简化上下文和范围。在这些语言中,它们被称为"闭包"或"lambda"。自OS X 10.6和iOS 4.0以来,Objective-C(以及C和C++)的开发者也有自己的:blocks(块)。
与函数必须在静态或全局范围内定义不同,块可以在行内定义,允许块利用其局部作用域的变量。Apple开发者在其A Short Practical Guide to Blocks中对此进行了澄清
与其它形式的回调相比,块的优势在于块可以共享局部词法作用域中的数据。如果您在一个方法中实现了一个块,该块可以访问该方法的局部变量和参数(包括栈变量),以及函数和全局变量,包括实例变量。
在Objective-C中,有许多地方块比方法更适合用于委托或简单的函数式回调。虽然Apple正在缓慢地向block回调迁移(以通常缓慢的OS框架速度),但是A2DynamicDelegate通过动态实现具有块实现的协议方法,并创建执行相同功能的块属性,从而帮助弥合这一差距。
同样地,由于块可以利用其声明的作用域,所以(块实现的)动态(委托)比普通委托更好。您不需要为要在创建委托对象和接收委托方法之间传递的每份数据设置实例或静态变量,而可以直接从块中访问它们。
A2DynamicDelegate由六个文件组成:`A2DynamicDelegate.{h,m}`、`A2BlockDelegate.{h,m}`和`A2BlockClosure.{h,m}`。它还依赖于libffi,其副本包含在二进制发布中。
为了方便起见,你可以构建iOS库目标,或者下载二进制发行版,并用静态库libA2DynamicDelegate.a
替换源文件。这对于使用ARC的项目非常有用,因为A2DynamicDelegate不支持它。(见下文。)
通过将协议方法关联到块实现,来实现一个类的代理、数据源或其他受委托的协议。
A2DynamicDelegate
实例要获取一个对象的动态代理,请使用在NSObject(A2DynamicDelegate)
中定义的三个getter之一。
-dynamicDataSource
假设为FooBarDataSource
协议处理FooBar
类的实例。-dynamicDelegate
假设为FooBarDelegate
协议处理FooBar
类的实例。-dynamicDelegateForProtocol:
显式接收协议。A2DynamicDelegate
实例?调用上述方法允许我们将动态代理作为受委托对象的关联对象存储。这不仅允许我们稍后检索代理,而且还创建了一个与代理的强关联。由于受委托对象部分上的代理是弱引用,动态代理在其声明范围结束后将立即释放。因此,这个强关联是确保代理的生存期至少与受委托对象的生存期一样长所必需的。
动态委托实例自动使用以下方法实现协议方法
-implementMethod:withBlock:
:实现协议方法。-blockImplementationForMethod:
:获取块实现。(也可以用来检查方法是否有块实现。)-removeBlockImplementationForMethod:
:删除协议方法的块实现。还有方法用于实现类方法。
- (IBAction) annoyUser
{
// Create an alert view
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle: @"Hello World!" message: @"This alert's delegate is implemented using blocks. That's so cool!" delegate: nil cancelButtonTitle: @"Meh." otherButtonTitles: @"Woo!", nil];
// Get the dynamic delegate
A2DynamicDelegate *dd = alertView.dynamicDelegate;
// Implement -alertViewShouldEnableFirstOtherButton:
[dd implementMethod: @selector(alertViewShouldEnableFirstOtherButton:) withBlock: ^(UIAlertView *alertView) {
NSLog(@"Message: %@", alertView.message);
return YES;
}];
// Implement -alertView:willDismissWithButtonIndex:
[dd implementMethod: @selector(alertView:willDismissWithButtonIndex:) withBlock: ^(UIAlertView *alertView, NSInteger buttonIndex) {
NSLog(@"You pushed button #%d (%@)", buttonIndex, [alertView buttonTitleAtIndex: buttonIndex]);
}];
// Set the delegate
alertView.delegate = dd;
[alertView show];
[alertView release];
}
A2DynamicDelegate设计成“即插即用”。它只需要用。相当方便,对吧?
别忘了查看示例项目。
来自一个-dynamic*
方法之一的动态代理实际上是类簇的一部分。对于UIAlertViewDelegate
协议的动态代理,生成的链是以下:
A2DynamicDelegate
A2DynamicUIAlertViewDelegate
A2DynamicUIAlertViewDelegate/983C3E20-285D-11E1-BFC2-0800200C9A66
因此,你可以实际创建A2DynamicDelegate的子类,以提供自定义处理。
UIAlertView+A2DynamicDelegate.h:
#import <dispatch/dispatch.h> // typedef void (^dispatch_block_t)(void);
@interface UIAlertView (A2DynamicDelegate)
- (NSInteger) addButtonWithTitle: (NSString *) title handler: (dispatch_block_t) block;
- (dispatch_block_t) handlerForButtonAtIndex: (NSInteger) index;
@end
@interface A2DynamicUIAlertViewDelegate : A2DynamicDelegate
@end
UIAlertView+A2DynamicDelegate.m:
@interface UIAlertView (A2DynamicDelegate)
- (NSInteger) addButtonWithTitle: (NSString *) title handler: (dispatch_block_t) block
{
NSInteger index = [self addButtonWithTitle: title];
id key = [NSNumber numberWithInteger: index];
if (block)
[self.dynamicDelegate.handlers setObject: block forKey: key];
else
[self.dynamicDelegate.handlers removeObjectForKey: key];
return index;
}
- (dispatch_block_t) handlerForButtonAtIndex: (NSInteger) index
{
id key = [NSNumber numberWithInteger: index];
return [self.dynamicDelegate.handlers objectForKey: key];
}
@end
@implementation A2DynamicUIAlertViewDelegate
- (void) alertView: (UIAlertView *) alertView clickedButtonAtIndex: (NSInteger) buttonIndex
{
id key = [NSNumber numberWithInteger: buttonIndex];
dispatch_block_t block = [self.handlers objectForKey: key];
if (block) block();
void (^buttonClicked)(UIAlertView *, NSInteger) = [self blockImplementationForMethod: _cmd];
if (buttonClicked) buttonClicked(alertView, buttonIndex);
}
@end
在受委托对象的范畴上创建自定义块属性,并将其动态映射到代理方法(例如,UIAlertViewDelegate
)、数据源方法(例如,UITableViewDataSource
),或其他协议方法。
在一个类上调用A2BlockDelegate
范畴中的其中一个方法,向该类添加一个块属性。
就像A2DynamicDelegate一样,A2BlockDelegate会自动推断出用于实现哪种协议。可以使用数据源协议(例如,在FooDelegate
上的FooDataSource
)通过+linkCategoryBlockProperty:withDataSourceMethod:
或+linkDataSourceMethods:
使用。手动指定可以使用+linkCategoryBlockProperty:withProtocol:method:
或+linkProtocol:methods:
。
FooBar
的协议FooBarDataSource
+linkCategoryBlockProperty:withDataSourceMethod
+linkDataSourceMethods
FooBar
的实例使用协议FooBarDelegate
+linkCategoryBlockProperty:withDelegateMethod
+linkDelegateMethods
+linkCategoryBlockProperty:withProtocol:method
+linkProtocol:methods
这些方法应该在类或分组的+load
方法中调用,在应用程序启动之前。与只调用一次且不应在分组中覆盖的+initialize
不同,+load
在调用main()
之前会在所有类和分组中调用。
UIAlertView+A2BlockDelegate.h:
@interface UIAlertView (A2BlockDelegate)
// Block properties must be (nonatomic, copy).
@property (nonatomic, copy) BOOL (^shouldEnableFirstOtherButtonBlock)(UIAlertView *);
@property (nonatomic, copy) void (^willDismissBlock)(UIAlertView *, NSInteger);
@end
UIAlertView+A2BlockDelegate.m:
@implementation UIAlertView (A2BlockDelegate)
// Block properties must be dynamic. This means that the accessors
// are provided at runtime (in this case, by A2BlockDelegate).
@dynamic shouldEnableFirstOtherButtonBlock;
@dynamic willDismissBlock;
+ (void) load
{
/**
* In older code, this would be:
*
* NSAutoreleasePool *pool = [NSAutoreleasePool new];
* /* ... Code ... */
* [pool release];
*
**/
@autoreleasepool
{
[self linkCategoryBlockProperty: @"shouldEnableFirstOtherButtonBlock" withDelegateMethod: @selector(alertViewShouldEnableFirstOtherButton:)];
[self linkCategoryBlockProperty: @"willDismissBlock" withDelegateMethod: @selector(alertView:willDismissWithButtonIndex:)];
}
}
@end
或者,可以用字典来代替
NSDictionary *methods = [NSDictionary dictionaryWithObjectsAndKeys:
@"shouldEnableFirstOtherButtonBlock", @"alertViewShouldEnableFirstOtherButton:",
@"willDismissBlock", @"alertView:willDismissWithButtonIndex:", nil];
[self linkDelegateMethods: methods];
*某处
- (IBAction) annoyUser
{
// Create an alert view
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle: @"Hello World!" message: @"This alert's delegate is implemented using blocks. That's so cool!" delegate: nil cancelButtonTitle: @"Meh." otherButtonTitles: @"Woo!", nil];
// Implement -alertViewShouldEnableFirstOtherButton:
alertView.shouldEnableFirstOtherButtonBlock = ^(UIAlertView *alertView) {
NSLog(@"Message: %@", alertView.message);
return YES;
};
// Implement -alertView:willDismissWithButtonIndex:
alertView.willDismissBlock = ^(UIAlertView *alertView, NSInteger buttonIndex) {
NSLog(@"You pushed button #%d (%@)", buttonIndex, [alertView buttonTitleAtIndex: buttonIndex]);
};
// Set the delegate
alertView.delegate = alertView.dynamicDelegate;
[alertView show];
[alertView release];
}
什么是 ARC? Objective-C 的自动引用计数(Automatic Reference Counting,简称 ARC)使内存管理成为编译器的职责。
A2DynamicDelegate 不支持 ARC。 作为预防措施,如果在ARC下编译,每个 .m 文件中都有一个 #error
编译器指令。如果没有这些预防措施,实现会无错误地构建,但如果使用 A2DynamicDelegate,则应用程序在运行时可能会崩溃。
这是 Objective-C 运行时库的限制。 A2DynamicDelegate 使用 objc_allocateClassPair
函数,如果在 ARC 下调用此函数,会引发一个 EXC_BAD_ACCESS
异常。
A2DynamicDelegate 由Alexsander Akers和Pandamonia LLC根据简化的BSD许可协议许可,其全文如下
版权(c)2011,Alexsander Akers 保留所有权利。
以下条件满足的情况下,源代码的再分发和使用(包括但不限于修改)是允许的
- 源代码的再分发必须保留上述版权声明、本条件列表以及下列免责声明。
- 二进制形式的再分发必须在文档和/或其他提供的材料中复制上述版权声明、本条件列表以及下列免责声明。
本软件按照“现状”提供,并且不得表明或隐含任何明示或暗示的保证,包括但不限于适销性和针对特定目的的适用性。在任何情况下,版权持有人或贡献者均不对因使用本软件而产生的任何直接、间接、意外、特别、示范性或后续损害(包括但不限于替代货物或服务的采购;使用、数据或利润的损失;或业务中断)承担责任,即使已提示此类损害的可能性也是如此。