POSScheduling
POSScheduling 库是实现同名模式的示例。下表列出了模式组件及其实现机制。
组件 | 实现 |
---|---|
事件 | Objective-C 块 |
事件队列 | Grand Central Dispatch 中的 dispatch_queue_t 的内部实现 |
事件循环处理程序 | Grand Central Dispatch 中的 dispatch_queue_t 的内部实现 |
线程 | Grand Central Dispatch 中的 dispatch_queue_t 的内部实现 |
调度器 | ReactiveCocoa 中的 RACTargetQueueScheduler |
库的核心是 POSSchedulableObject
类。作为可管理对象的基类,它承担以下职责
- 指向调度器的引用,通过该调度器与对象进行间接交互。
- 自动检查对象继承者方法调用的线程的正确性。通过在初始化时在其所有方法上挂载钩子(hooks)来实现。鉴于此过程代价高昂,默认情况下仅在调试版本的应用程序中执行。
源代码仓库的主体部分是演示应用程序。它将用户在 Dropbox 服务中授权,然后显示其个人资料中的姓名和姓氏。下面将介绍几个使用 POSSchedulableObject 的示例。
###类声明
/// Providers info about account.
@protocol SODAccountInfoProvider <POSSchedulableObject>
/// @return Signal of nonnull SODAccountInfo.
- (RACSignal *)fetchAccountInfo;
@end
@interface SODDropboxAccountInfoProvider
: POSSchedulableObject <SODAccountInfoProvider>
// ...
@end
POSSchedulable
协议包含用于向可管理对象发送事件的方法,这些事件在另一个线程中处理。
@protocol POSSchedulable <NSObject>
/// Scheduler which is used to perform calls to objects of that class.
@property (nonatomic, readonly) RACTargetQueueScheduler *scheduler;
/// @return Signal with this nonnull object delivered in the object's scheduler.
- (RACSignal *)schedule;
/// Schedules that object in the object's scheduler.
- (void)scheduleBlock:(void (^)(id schedulable))block;
@end
POSSchedulableObject
类完全实现了同名协议。此外,它添加了对对象方法调用的线程正确性的检查。带有 atomic 属性的属性除外。存在一个特殊初始化器来手动排除某些方法。
@interface POSSchedulableObject : NSObject <POSSchedulableObject>
/// Schedules object inside main thread scheduler.
- (instancetype)init;
/// Schedules object inside specified scheduler.
- (instancetype)initWithScheduler:(RACTargetQueueScheduler *)scheduler;
/// Schedules object inside specified scheduler with custom excludes.
- (instancetype)initWithScheduler:(RACTargetQueueScheduler *)scheduler
options:(nullable POSScheduleProtectionOptions *)options;
// ...
@end
###与类交互
下面的列表展示了如何获取间接调用的结果,并在调用对象线程的上下文中使用它。
@implementation SODSettingsViewController
// ...
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[[[[_assembly.accountInfoProvider schedule]
flattenMap:^RACStream *(id<SODAccountInfoProvider> provider) {
return [provider fetchAccountInfo];
}]
deliverOnMainThread]
takeUntil:self.rac_willDeallocSignal]
subscribeNext:^(SODAccountInfo *accountInfo) {
self.nameLabel.text = accountInfo.displayName;
} error:^(NSError *error) {
self.nameLabel.text = error.localizedDescription;
}];
}
// ...
@end
通过使用其调度器向 provider
服务发送消息来调用 provider
,然后将结果或错误传回应用程序的主循环。
###构建类
应用程序的业务逻辑对象是在特殊类中创建的,这些类实现了依赖注入容器模式。类似于流行的库 Typhoon,这类类的名称中包含 Assembly 的根。在这些类中创建对象具有以下两个特点
- 对象按需懒加载。这导致 Assembly 对象在生存期间的状态不断变化。此外,它通过继承自
POSSchedulableObject
来保护其免受多线程访问的影响。 - 对象返回给请求方是同步的,以避免大量客户端样板代码。从上一段代码中可以看出,使用
accountInfoProvider
的方式相当冗长。不难想象,如果 Assembly 的接口具有异步特性,这个代码会变得更加复杂。
因此,Assembly 承诺在主线程中创建并返回任何服务,并且只创建一次。创建对象的大致过程如下
@protocol SODAccountAssembly <POSSchedulableObject>
// ...
@property (nonatomic, readonly) id<SODAccountInfoProvider> accountInfoProvider;
@property (nonatomic, readonly) id<SODNodeRepository> nodeRepository;
// ...
@end
@interface SODDropboxAccountAssembly : POSSchedulableObject <SODAccountAssembly>
// ...
@end
@implementation SODDropboxAccountAssembly
// ...
- (id<PFYAccountInfoProvider>)accountInfoProvider {
if (_accountInfoProvider) {
return _accountInfoProvider;
}
self.accountInfoProvider = [[PFYDropboxAccountInfoProvider alloc]
initWithHost:self.dropboxHost
accountID:self.account.ID];
return _accountInfoProvider;
}
// ...
@end
乍一看,这个过程似乎很简单,除非你需要创建图形对象,这些对象首先在不同的线程中运行,其次,它们在初始化时需要调用一个或多个自己的方法。
在这种场景中,问题在于,为了初始化对象 A,必须在红色线程中初始化对象 B。为了让 Assembly 在客户端看起来是同步进行的,在创建红色对象 B 的过程中,蓝色线程必须被阻塞。然而,对象 B 需要对象 C。最后一个只能由蓝色线程创建。类似于前面的步骤,为了在创建对象 C 时暂停,红色线程被阻塞并等待对象 C 的创建。在这一点上的等待将无限期进行,因为发送到蓝色线程的事件永远不会被处理,因为当创建对象 A 时,它已经被阻塞了。
解决这个问题的关键在于使用特殊的自旋锁来阻塞线程。它应该停止执行当前线程,但在此同时,继续处理其消息循环。在 POSSchedulableObject 库中,特别为这种情况提供了一个在 RACSignal 类别中的 posrx_await
方法。
@implementation RACSignal (POSSchedulableObject)
- (id)posrx_await {
__block id result = nil;
__block BOOL done = NO;
[[self take:1] subscribeNext:^(id value) {
result = value;
done = YES;
} error:^(NSError *e) {
done = YES;
}];
if (result) {
return result;
}
NSRunLoop *runLoop = NSRunLoop.currentRunLoop;
while ([runLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.date] && !done) {}
return result;
}
@end
在演示应用程序中,它用于创建 tracker
对象。
- (id<PFYTracker>)tracker {
if (_tracker) {
return _tracker;
}
PFYAppTracker *tracker = [[PFYAppTracker alloc]
initWithScheduler:self.backgroundScheduler
store:self.secureStore
environment:self.environment];
self.tracker = [[[tracker schedule] map:^id(PFYAppTracker *scheduledTracker) {
[scheduledTracker addService:
[[PFYConsoleTracker alloc] initWithScheduler:scheduledTracker.scheduler]];
return scheduledTracker;
}] posrx_await];
return _tracker;
}
##链接