POSScheduling 4.0.0

POSScheduling 4.0.0

Pavel Osipov 维护。



 
依赖
方面>= 0
ReactiveObjC>= 0
POSErrorHandling>= 0
 

  • Pavel Osipov

POSScheduling

Version

POSScheduling 库是实现同名模式的示例。下表列出了模式组件及其实现机制。

组件 实现
事件 Objective-C 块
事件队列 Grand Central Dispatch 中的 dispatch_queue_t 的内部实现
事件循环处理程序 Grand Central Dispatch 中的 dispatch_queue_t 的内部实现
线程 Grand Central Dispatch 中的 dispatch_queue_t 的内部实现
调度器 ReactiveCocoa 中的 RACTargetQueueScheduler

库的核心是 POSSchedulableObject 类。作为可管理对象的基类,它承担以下职责

  1. 指向调度器的引用,通过该调度器与对象进行间接交互。
  2. 自动检查对象继承者方法调用的线程的正确性。通过在初始化时在其所有方法上挂载钩子(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 的根。在这些类中创建对象具有以下两个特点

  1. 对象按需懒加载。这导致 Assembly 对象在生存期间的状态不断变化。此外,它通过继承自 POSSchedulableObject 来保护其免受多线程访问的影响。
  2. 对象返回给请求方是同步的,以避免大量客户端样板代码。从上一段代码中可以看出,使用 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

乍一看,这个过程似乎很简单,除非你需要创建图形对象,这些对象首先在不同的线程中运行,其次,它们在初始化时需要调用一个或多个自己的方法。

payload

在这种场景中,问题在于,为了初始化对象 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;
}

##链接