REKit [rikít] 是 NSObject
扩展集合。目前它提供 2 个功能
Blocks 和 GCD(Grand Central Dispatch)在 iOS、OS X 世界中带来了巨大变化。它提供了编写未来执行代码的能力。程序员的领域变得更加灵活。
REKit(特别是 REResponder)以与 GCD 不同方式带来了 Blocks 的潜在能力。REResponder 提供了重建实例的能力。具体来说,它提供了在实例级别添加/覆盖方法的能力。REKit 也可以带来巨大变化。
REKit 在 iPhone 应用程序 SpliTron 中使用。REKit 提高了开发效率和可维护性。因此,SpliTron 走过了某些规范变化。REKit 使项目团队能够专注于用户体验。
REKit 希望为 iOS、OS X 世界做出贡献。
REResponder 提供在实例级别添加/覆盖方法的能力。以下是功能的详情、行为和 高效示例。
您可以使用 Block 在任意实例上动态添加方法。为此,您使用 -respondsToSelector:withKey:usingBlock:
方法。例如,NSObject
没有具有 -sayHello
方法的,但您可以添加如下
id obj;
obj = [[NSObject alloc] init];
[obj respondsToSelector:@selector(sayHello) withKey:nil usingBlock:^(id receiver) {
NSLog(@"Hello World!");
}];
[obj performSelector:@selector(sayHello)]; // Hello World!
它只应用于 obj
,不会影响其他实例。
您可以使用 Block 在任意实例上动态覆盖方法。与 "动态添加方法" 相似,您使用 -respondsToSelector:withKey:usingBlock:
方法。例如,如果您有一个类 MyObject 在 -sayHello
方法中记录 "No!",您可以将它覆盖为记录 "Hello World!"
MyObject *obj;
obj = [[MyObject alloc] init];
// [obj sayHello]; // No!
[obj respondsToSelector:@selector(sayHello) withKey:nil usingBlock:^(id receiver) {
NSLog(@"Hello World!");
}];
[obj sayHello]; // Hello World!
它只应用于 obj
,不会影响其他实例。
如上所示,receiver
参数是必需的。该参数是 -respondsToSelector:withKey:usingBlock:
方法的接收器。即使您在 Block 中使用 receiver
,它也不会导致保持周期,所以请继续。
id obj;
obj = [[NSObject alloc] init];
[obj respondsToSelector:@selector(sayHello) withKey:nil usingBlock:^(id receiver) {
// NSLog(@"obj = %@", obj); // Causes retain cycle! Use receiver instead.
NSLog(@"receiver = %@", receiver);
}];
[obj performSelector:@selector(sayHello)];
REResponder 支持带参数和/或返回值的块。当你想要添加或重写带参数的方法时,在receiver
参数之后列出参数
UIAlertView *alertView;
// …
[alertView
respondsToSelector:@selector(alertViewShouldEnableFirstOtherButton:)
withKey:nil
usingBlock:^(id receiver, UIAlertView *alertView) {
return NO;
}
];
你可以给块分配一个键,然后你可以使用这个键来管理块。要分配键,你可以将任意对象作为方法-respondsToSelector:withKey:usingBlock:
的键参数传递。你可以通过调用方法-hasBlockForSelector:withKey:
来检查一个实例是否有一个块。你可以通过调用方法-removeBlockForSelector:withKey:
来移除一个块。
当一个实例将要被释放时,添加到该实例的块将被自动移除。如果你不需要特别管理一个块,你可以将 nil 作为键参数传递 - 块内部将分配一个 UUID 字符串。
实例按选择器堆叠块。最后添加的(最顶部的)块是在调用选择器时执行的块。当你尝试添加具有现有键的块时,旧块将被移除,然后新块将被堆叠在顶部。
你可以使用方法-supermethodOfCurrentBlock
调用超方法 - 当前块下的块实现,或者硬编码的实现。
[obj respondsToSelector:@selector(description) withKey:nil usingBlock:^(id receiver) {
// Make description…
NSMutableString *description;
description = [NSMutableString string];
// Append original description
IMP supermethod;
if ((supermethod = [receiver supermethodOfCurrentBlock])) {
[description appendString:supermethod(receiver, @selector(description))];
}
// Customize description…
return description;
}];
超方法需要至少接收者和选择器参数。如果选择器有参数,请在选择器参数之后列出它们。
如果需要,请转换超方法。例如,下面是返回 CGRect 的超方法的转换
typedef CGRect (*RectIMP)(id, SEL, ...);
RectIMP supermethod;
if ((supermethod = (RectIMP)[receiver supermethodOfCurrentBlock])) {
rect = supermethod(receiver, @selector(rect));
}
以下段落展示了 REResponder 的一些高效示例。
使用代理模式的项目通过排除应用程序上下文来保持可重用性,并提供代理方法将应用程序上下文连接到它。如果你可以将应用程序上下文插入到一个实例中,你就可以使该实例不依赖于位于应用层上的委托对象。REResponder 就可以实现这一点。
下面的代码片段说明了如何将alertView
本身作为代理设置给alertView
UIAlertView *alertView;
alertView = [[UIAlertView alloc]
initWithTitle:@"title"
message:@"message"
delegate:nil
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"OK", nil
];
[alertView
respondsToSelector:@selector(alertView:didDismissWithButtonIndex:)
withKey:nil
usingBlock:^(id receiver, UIAlertView *alertView, NSInteger buttonIndex) {
// Do something…
}
];
alertView.delegate = alertView;
下面的代码片段说明了如何将animation
本身作为代理设置给animation
CABasicAnimation *animation;
// …
[animation
respondsToSelector:@selector(animationDidStop:finished:)
withKey:nil
usingBlock:^(id receiver, CABasicAnimation *animation, BOOL finished) {
// Do something…
}
];
animation.delegate = animation;
优点
对于目标/动作范例,你可以使用在 "委派自身" 部分中描述的类似技术。下面的代码片段向 UICollectionViewCell
添加了按钮 - 其目标为按钮本身。
UIButton *button;
// …
[button respondsToSelector:@selector(buttonAction) withKey:@"key" usingBlock:^(id receiver) {
// Do something…
}];
[button addTarget:button action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:button];
REResponder 也可用于单元测试。下面的代码片段使用模拟对象检查 BalloonController
的代理方法是否被调用。
__block BOOL called = NO;
// Make mock
id mock;
mock = [[NSObject alloc] init];
[mock
respondsToSelector:@selector(balloonControllerDidDismissBalloon:)
withKey:nil
usingBlock:^(id receiver, BalloonController *balloonController) {
called = YES;
}
];
balloonController.delegate = mock;
// Dismiss balloon
[balloonController dismissBalloonAnimated:NO];
STAssertTrue(called, @"");
下面的代码片段模拟了 AccountManager 的下载过程,然后测试 account-view 的视图控制器。
// Load sample image
__weak UIImage *sampleImage;
NSString *sampleImagePath;
sampleImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"sample" ofType:@"png"];
sampleImage = [UIImage imageWithContentsOfFile:sampleImagePath];
// Stub out download process
[[AccountManager sharedManager]
respondsToSelector:@selector(downloadProfileImageWithCompletion:)
withKey:@"key"
usingBlock:^(id receiver, void (^completion)(UIImage*, NSError*)) {
// Execute completion block with sampleImage
completion(sampleImage, nil);
// Remove current block
[receiver removeCurrentBlock];
}
];
// Call thumbnailButtonAction which causes download of profile image
[acccountViewController thumbnailButtonAction];
STAssertEqualObjects(accountViewController.profileImageView.image, sampleImage, @"");
REResponder帮助您将相关的代码片段收集到一个地方。例如,如果您想添加关于UIKeyboardWillShowNotification
的代码片段,您可以将其收集到以下-_manageKeyboardWillShowNotificationObserver
方法中
- (id)initWithCoder:(NSCoder *)aDecoder
{
// super
self = [super initWithCoder:aDecoder];
if (!self) {
return nil;
}
// Manage _keyboardWillShowNotificationObserver
[self _manageKeyboardWillShowNotificationObserver];
return self;
}
- (void)_manageKeyboardWillShowNotificationObserver
{
__block id observer;
observer = _keyboardWillShowNotificationObserver;
#pragma mark └ [self viewWillAppear:]
[self respondsToSelector:@selector(viewWillAppear:) withKey:nil usingBlock:^(id receiver, BOOL animated) {
// supermethod
REVoidIMP supermethod; // REVoidIMP is defined like this: typedef void (*REVoidIMP)(id, SEL, ...);
if ((supermethod = (REVoidIMP)[receiver supermethodOfCurrentBlock])) {
supermethod(receiver, @selector(viewWillAppear:), animated);
}
// Start observing
if (!observer) {
observer = [[NSNotificationCenter defaultCenter]
addObserverForName:UIKeyboardWillShowNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
// Do something…
}
];
}
}];
#pragma mark └ [self viewDidDisappear:]
[self respondsToSelector:@selector(viewDidDisappear:) withKey:nil usingBlock:^(id receiver, BOOL animated) {
// supermethod
REVoidIMP supermethod;
if ((supermethod = (REVoidIMP)[receiver supermethodOfCurrentBlock])) {
supermethod(receiver, @selector(viewDidDisappear:), animated);
}
// Stop observing
[[NSNotificationCenter defaultCenter] removeObserver:observer];
observer = nil;
}];
}
a. 类已更改
当您添加/覆盖方法时,实例变为名为"REResponder_UUID_OriginalClassName"的类的实例。结果是它打破了KVO的关系。问题已经得到修复,但仍可能发生其他问题。如果出现问题,请使用-willChangeClass:
和-didChangeClass:
或REObjectWillChangeClassNotification
和REObjectDidChangeClassNotification
来处理它们。
REObserver提供了
REObserver提供了-addObserverForKeyPath:options:usingBlock:
方法。您可以将一个块传递给它,当值更改时执行该块
id observer;
observer = [obj addObserverForKeyPath:@"someKeyPath" options:0 usingBlock:^(NSDictionary *change) {
// Do something…
}];
优点
您可以使用-stopObserving
方法停止观察
[observer stopObserving];
如上述代码所示,observer
停止了所有的观察。无需记住观察对象、键路径和上下文。
当观察对象或观察对象被释放时,REObserver会自动停止相关的观察。它解决了以下问题(以下是非ARC代码)
- (void)problem1
{
UIView *view;
view = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
@autoreleasepool {
id observer;
observer = [[[NSObject alloc] init] autorelease];
[view addObserver:observer forKeyPath:@"backgroundColor" options:0 context:nil];
}
NSLog(@"observationInfo = %@", (id)[view observationInfo]); // view is observed by zombie!
view.backgroundColor = [UIColor redColor]; // Crash!
}
- (void)problem2
{
id observer;
observer = [[[NSObject alloc] init] autorelease];
@autoreleasepool {
UIView *view;
view = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
[view addObserver:observer forKeyPath:@"backgroundColor" options:0 context:nil];
}
// observer is observing zombie!
}
iOS 5.0及以上
OS X 10.7及以上
您可以使用CocoaPods安装REKit。
<Podfile for iOS>
platform :ios, '5.0'
pod 'REKit'
<Podfile for OS X>
platform :osx, '10.7'
pod 'REKit'
<Terminal>
$ pod install
如果您想手动安装REKit,将REKit文件夹下的文件添加到您的项目中。
MIT许可证。有关更多详情,请参阅LICENSE文件。