ReactiveCocoa 中 RACSignal 是怎样发送信号
前言
ReactiveCocoa是一个(第一个?)将函数响应式编程范例带入Objective-C的开源库。ReactiveCocoa是由Josh Abernathy和Justin Spahr-Summers 两位大神在对GitHub for Mac的开发过程中编写的。Justin Spahr-Summers 大神在2011年11月13号下午12点35分进行的第一次提交,直到2013年2月13日上午3点05分发布了其1.0 release,达到了第一个重要里程碑。ReactiveCocoa社区也非常活跃,目前最新版已经完成了ReactiveCocoa 5.0.0-alpha.3,目前在5.0.0-alpha.4开发中。
ReactiveCocoa v2.5 是公认的Objective-C最稳定的版本,因此被广大的以OC为主要语言的客户端选中使用。ReactiveCocoa v3.x主要是基于Swift 1.2的版本,而ReactiveCocoa v4.x 主要基于Swift 2.x,ReactiveCocoa 5.0就全面支持Swift 3.0,也许还有以后的Swift 4.0。接下来几篇博客先以ReactiveCocoa v2.5版本为例子,分析一下OC版的RAC具体实现(也许分析完了RAC 5.0就到来了)。也算是写在ReactiveCocoa 5.0正式版到来前夕的祝福吧。
目录
1.什么是ReactiveCocoa?
2.RAC中的核心RACSignal发送与订阅流程
3.RACSignal操作的核心bind实现
4.RACSignal基本操作concat和zipWith实现
5.最后
一. 什么是ReactiveCocoa?
ReactiveCocoa(其简称为RAC)是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程(FP)和响应式编程(RP)的特性。它主要吸取了.Net的 Reactive Extensions的设计和实现。
ReactiveCocoa 的宗旨是Streams of values over time ,随着时间变化而不断流动的数据流。
ReactiveCocoa 主要解决了以下这些问题:
UI数据绑定
UI控件通常需要绑定一个事件,RAC可以很方便的绑定任何数据流到控件上。
用户交互事件绑定
RAC为可交互的UI控件提供了一系列能发送Signal信号的方法。这些数据流会在用户交互中相互传递。
解决状态以及状态之间依赖过多的问题
有了RAC的绑定之后,可以不用在关心各种复杂的状态,isSelect,isFinish……也解决了这些状态在后期很难维护的问题。
消息传递机制的大统一
OC中编程原来消息传递机制有以下几种:Delegate,Block Callback,Target-Action,Timers,KVO,objc上有一篇关于OC中这5种消息传递方式改如何选择的文章Communication Patterns,推荐大家阅读。现在有了RAC之后,以上这5种方式都可以统一用RAC来处理。
二. RAC中的核心RACSignal
ReactiveCocoa 中最核心的概念之一就是信号RACStream。RACRACStream中有两个子类——RACSignal 和 RACSequence。本文先来分析RACSignal。
我们会经常看到以下的代码:
RACSignal *signal = [RACSignal createSignal:
^RACDisposable *(id subscriber)
{
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"subscribe value = %@", x);
} error:^(NSError *error) {
NSLog(@"error: %@", error);
} completed:^{
NSLog(@"completed");
}];
[disposable dispose];
这是一个RACSignal被订阅的完整过程。被订阅的过程中,究竟发生了什么?
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
RACSignal调用createSignal的时候,会调用RACDynamicSignal的createSignal的方法。
RACDynamicSignal是RACSignal的子类。createSignal后面的参数是一个block。
(RACDisposable * (^)(id subscriber))didSubscribe
block的返回值是RACDisposable类型,block名叫didSubscribe。block的唯一一个参数是id类型的subscriber,这个subscriber是必须遵循RACSubscriber协议的。
RACSubscriber是一个协议,其下有以下4个协议方法:
@protocol RACSubscriber
@required
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end
所以新建Signal的任务就全部落在了RACSignal的子类RACDynamicSignal上了。
@interface RACDynamicSignal ()
// The block to invoke for each subscriber.
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id subscriber);
@end
RACDynamicSignal这个类很简单,里面就保存了一个名字叫didSubscribe的block。
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
这个方法中新建了一个RACDynamicSignal对象signal,并把传进来的didSubscribe这个block保存进刚刚新建对象signal里面的didSubscribe属性中。最后再给signal命名+createSignal:。
- (instancetype)setNameWithFormat:(NSString *)format, ... {
if (getenv("RAC_DEBUG_SIGNAL_NAMES") == NULL) return self;
NSCParameterAssert(format != nil);
va_list args;
va_start(args, format);
NSString *str = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
self.name = str;
return self;
}
setNameWithFormat是RACStream里面的方法,由于RACDynamicSignal继承自RACSignal,所以它也能调用这个方法。
RACSignal的block就这样被保存起来了,那什么时候会被执行呢?
block闭包在订阅的时候才会被“释放”出来。
RACSignal调用subscribeNext方法,返回一个RACDisposable。
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
NSCParameterAssert(nextBlock != NULL);
NSCParameterAssert(errorBlock != NULL);
NSCParameterAssert(completedBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];
return [self subscribe:o];
}
在这个方法中会新建一个RACSubscriber对象,并传入nextBlock,errorBlock,completedBlock。
@interface RACSubscriber ()
// These callbacks should only be accessed while synchronized on self.
@property (nonatomic, copy) void (^next)(id value);
@property (nonatomic, copy) void (^error)(NSError *error);
@property (nonatomic, copy) void (^completed)(void);
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
@end
RACSubscriber这个类很简单,里面只有4个属性,分别是nextBlock,errorBlock,completedBlock和一个RACCompoundDisposable信号。
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
RACSubscriber *subscriber = [[self alloc] init];
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
subscriberWithNext方法把传入的3个block都保存分别保存到自己对应的block中。
RACSignal调用subscribeNext方法,最后return的时候,会调用[self subscribe:o],这里实际是调用了RACDynamicSignal类里面的subscribe方法。
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
RACDisposable有3个子类,其中一个就是RACCompoundDisposable。
@interface RACCompoundDisposable : RACDisposable
+ (instancetype)compoundDisposable;
+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables;
- (void)addDisposable:(RACDisposable *)disposable;
- (void)removeDisposable:(RACDisposable *)disposable;
@end
RACCompoundDisposable虽然是RACDisposable的子类,但是它里面可以加入多个RACDisposable对象,在必要的时候可以一口气都调用dispose方法来销毁信号。当RACCompoundDisposable对象被dispose的时候,也会自动dispose容器内的所有RACDisposable对象。
RACPassthroughSubscriber是一个私有的类。
@interface RACPassthroughSubscriber : NSObject
@property (nonatomic, strong, readonly) id innerSubscriber;
@property (nonatomic, unsafe_unretained, readonly) RACSignal *signal;
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;
@end
RACPassthroughSubscriber类就只有这一个方法。目的就是为了把所有的信号事件从一个订阅者subscriber传递给另一个还没有disposed的订阅者subscriber。
RACPassthroughSubscriber类中保存了3个非常重要的对象,RACSubscriber,RACSignal,RACCompoundDisposable。RACSubscriber是待转发的信号的订阅者subscriber。RACCompoundDisposable是订阅者的销毁对象,一旦它被disposed了,innerSubscriber就再也接受不到事件流了。
这里需要注意的是内部还保存了一个RACSignal,并且它的属性是unsafe_unretained。这里和其他两个属性有区别, 其他两个属性都是strong的。这里之所以不是weak,是因为引用RACSignal仅仅只是一个DTrace probes动态跟踪技术的探针。如果设置成weak,会造成没必要的性能损失。所以这里仅仅是unsafe_unretained就够了。
- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
NSCParameterAssert(subscriber != nil);
self = [super init];
if (self == nil) return nil;
_innerSubscriber = subscriber;
_signal = signal;
_disposable = disposable;
[self.innerSubscriber didSubscribeWithDisposable:self.disposable];
return self;
}
回到RACDynamicSignal类里面的subscribe方法中,现在新建好了RACCompoundDisposable和RACPassthroughSubscriber对象了。
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
RACScheduler.subscriptionScheduler是一个全局的单例。
+ (instancetype)subscriptionScheduler {
static dispatch_once_t onceToken;
static RACScheduler *subscriptionScheduler;
dispatch_once(&onceToken, ^{
subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];
});
return subscriptionScheduler;
}
RACScheduler再继续调用schedule方法。
- (RACDisposable *)schedule:(void (^)(void))block {
NSCParameterAssert(block != NULL);
if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];
block();
return nil;
}
+ (BOOL)isOnMainThread {
return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread];
}
+ (instancetype)currentScheduler {
RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
if (scheduler != nil) return scheduler;
if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;
return nil;
}
在取currentScheduler的过程中,会判断currentScheduler是否存在,和是否在主线程中。如果都没有,那么就会调用后台backgroundScheduler去执行schedule。
schedule的入参就是一个block,执行schedule的时候会去执行block。也就是会去执行:
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
这两句关键的语句。之前信号里面保存的block就会在此处被“释放”执行。self.didSubscribe(subscriber)这一句就执行了信号保存的didSubscribe闭包。
在didSubscribe闭包中有sendNext,sendError,sendCompleted,执行这些语句会分别调用RACPassthroughSubscriber里面对应的方法。
- (void)sendNext:(id)value {
if (self.disposable.disposed) return;
if (RACSIGNAL_NEXT_ENABLED()) {
RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
}
[self.innerSubscriber sendNext:value];
}
- (void)sendError:(NSError *)error {
if (self.disposable.disposed) return;
if (RACSIGNAL_ERROR_ENABLED()) {
RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description));
}
[self.innerSubscriber sendError:error];
}
- (void)sendCompleted {
if (self.disposable.disposed) return;
if (RACSIGNAL_COMPLETED_ENABLED()) {
RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description));
}
[self.innerSubscriber sendCompleted];
}
这个时候的订阅者是RACPassthroughSubscriber。RACPassthroughSubscriber里面的innerSubscriber才是最终的实际订阅者,RACPassthroughSubscriber会把值再继续传递给innerSubscriber。
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
- (void)sendError:(NSError *)e {
@synchronized (self) {
void (^errorBlock)(NSError *) = [self.error copy];
[self.disposable dispose];
if (errorBlock == nil) return;
errorBlock(e);
}
}
- (void)sendCompleted {
@synchronized (self) {
void (^completedBlock)(void) = [self.completed copy];
[self.disposable dispose];
if (completedBlock == nil) return;
completedBlock();
}
}
innerSubscriber是RACSubscriber,调用sendNext的时候会先把自己的self.next闭包copy一份,再调用,而且整个过程还是线程安全的,用@synchronized保护着。最终订阅者的闭包在这里被调用。
sendError和sendCompleted也都是同理。
总结一下:
RACSignal调用subscribeNext方法,新建一个RACSubscriber。
新建的RACSubscriber会copy,nextBlock,errorBlock,completedBlock存在自己的属性变量中。
RACSignal的子类RACDynamicSignal调用subscribe方法。
新建RACCompoundDisposable和RACPassthroughSubscriber对象。RACPassthroughSubscriber分别保存对RACSignal,RACSubscriber,RACCompoundDisposable的引用,注意对RACSignal的引用是unsafe_unretained的。
RACDynamicSignal调用didSubscribe闭包。先调用RACPassthroughSubscriber的相应的sendNext,sendError,sendCompleted方法。
RACPassthroughSubscriber再去调用self.innerSubscriber,即RACSubscriber的nextBlock,errorBlock,completedBlock。注意这里调用同样是先copy一份,再调用闭包执行。
三. RACSignal操作的核心bind实现
在RACSignal的源码里面包含了两个基本操作,concat和zipWith。不过在分析这两个操作之前,先来分析一下更加核心的一个函数,bind操作。
先来说说bind函数的作用:
会订阅原始的信号。
任何时刻原始信号发送一个值,都会绑定的block转换一次。
一旦绑定的block转换了值变成信号,就立即订阅,并把值发给订阅者subscriber。
一旦绑定的block要终止绑定,原始的信号就complete。
当所有的信号都complete,发送completed信号给订阅者subscriber。
如果中途信号出现了任何error,都要把这个错误发送给subscriber
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id subscriber) {
RACStreamBindBlock bindingBlock = block();
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) { /*这里暂时省略*/ };
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { /*这里暂时省略*/ };
@autoreleasepool {
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
id signal = bindingBlock(x, &stop);
@autoreleasepool {
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(self, selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(self, selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
为了弄清楚bind函数究竟做了什么,写出测试代码:
RACSignal *signal = [RACSignal createSignal:
^RACDisposable *(id subscriber)
{
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}];
RACSignal *bindSignal = [signal bind:^RACStreamBindBlock{
return ^RACSignal *(NSNumber *value, BOOL *stop){
value = @(value.integerValue * 2);
return [RACSignal return:value];
};
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"subscribe value = %@", x);
}];
由于前面第一章节详细讲解了RACSignal的创建和订阅的全过程,这个也为了方法讲解,创建RACDynamicSignal,RACCompoundDisposable,RACPassthroughSubscriber这些都略过,这里着重分析一下bind的各个闭包传递创建和订阅的过程。
为了防止接下来的分析会让读者看晕,这里先把要用到的block进行编号。
先创建信号signal,didSubscribe把block1 copy保存起来。
bindSignal执行didSubscribe的block,即执行block6。
在block6 的第一句代码,就是调用RACStreamBindBlock bindingBlock = block(),这里的block是外面传进来的block2,于是开始调用block2。执行完block2,会返回一个RACStreamBindBlock的对象。
由于是signal调用的bind函数,所以bind函数里面的self就是signal,在bind内部订阅了signal的信号。subscribeNext所以会执行block1。
执行block1,sendNext调用订阅者subscriber的nextBlock,于是开始执行block10。
block10中会先调用bindingBlock,这个是之前调用block2的返回值,这个RACStreamBindBlock对象里面保存的是block3。所以开始调用block3。
在block3中入参是一个value,这个value是signal中sendNext中发出来的value的值,在block3中可以对value进行变换,变换完成后,返回一个新的信号signal’。
如果返回的signal’为空,则会调用completeSignal,即调用block7。block7中会发送sendCompleted。如果返回的signal’不为空,则会调用addSignal,即调用block8。block8中会继续订阅signal’。执行block9。
block9 中会sendNext,这里的subscriber是block6的入参,于是对subscriber调用sendNext,会调用到bindSignal的订阅者的block4中。
block9 中执行完sendNext,还会调用sendCompleted。这里的是在执行block9里面的completed闭包。completeSignal(signal, selfDisposable);然后又会调用completeSignal,即block7。
执行完block7,就完成了一次从signal 发送信号sendNext的全过程。
bind整个流程就完成了。
四. RACSignal基本操作concat和zipWith实现
接下来再来分析RACSignal中另外2个基本操作。
1. concat
写出测试代码:
concat操作就是把两个信号合并起来。注意合并有先后顺序。
合并前,signal和signals
dSubscribe会把block保存copy起来。
当合并之后的信号被订阅的时候:
调用新的合并信号的didSubscribe。
由于是第一个信号调用的concat方法,所以block中的self是前一个信号signal。合并信号的didSubscribe会先订阅signal。
由于订阅了signal,于是开始执行signal的didSubscribe,sendNext,sendError。
当前一个信号signal发送sendCompleted之后,就会开始订阅后一个信号signals,调用signals的didSubscribe。
由于订阅了后一个信号,于是后一个信号signals开始发送sendNext,sendError,sendCompleted。
这样两个信号就前后有序的拼接到了一起。
这里有二点需要注意的是:
只有当第一个信号完成之后才能收到第二个信号的值,因为第二个信号是在第一个信号completed的闭包里面订阅的,所以第一个信号不结束,第二个信号也不会被订阅。
两个信号concat在一起之后,新的信号的结束信号在第二个信号结束的时候才结束。看上图描述,新的信号的发送长度等于前面两个信号长度之和,concat之后的新信号的结束信号也就是第二个信号的结束信号。
2. zipWith
写出测试代码:
RACSignal *concatSignal = [signal zipWith:signals];
[concatSignal subscribeNext:^(id x) {
NSLog(@"subscribe value = %@", x);
}];
源码如下:
当把两个信号通过
是有对应的,上面的拉链第一个位置只能对着下面拉链第一个位置,这样拉链才能拉到一起去。
具体实现:
zipWith里面有两个数组,分别会存储两个信号的值。
一旦订阅了zipWith之后的信号,就开始执行didSubscribe闭包。
在闭包中会先订阅第一个信号。这里假设第一个信号比第二个信号先发出一个值。第一个信号发出来的每一个值都会被加入到第一个数组中保存起来,然后调用sendNext( )闭包。在sendNext( )闭包中,会先判断两个数组里面是否都为空,如果有一个数组里面是空的,就return。由于第二个信号还没有发送值,即第二个信号的数组里面是空的,所以这里第一个值发送不出来。于是第一个信号被订阅之后,发送的值存储到了第一个数组里面了,没有发出去。
第二个信号的值紧接着发出来了,第二个信号每发送一次值,也会存储到第二个数组中,但是这个时候再调用sendNext( )闭包的时候,不会再return了,因为两个数组里面都有值了,两个数组的第0号位置都有一个值了。有值以后就打包成元组RACTuple发送出去。并清空两个数组0号位置存储的值。
以后两个信号每次发送一个,就先存储在数组中,只要有“配对”的另一个信号,就一起打包成元组RACTuple发送出去。从图中也可以看出,zipWith之后的新信号,每个信号的发送时刻是等于两个信号最晚发出信号的时刻。
新信号的完成时间,是当两者任意一个信号完成并且数组里面为空,就算完成了。所以最后第一个信号发送的5的那个值就被丢弃了。
第一个信号依次发送的1,2,3,4的值和第二个信号依次发送的A,B,C,D的值,一一的合在了一起,就像拉链把他们拉在一起。由于5没法配对,所以拉链也拉不上了。
五. 最后
本来这篇文章想把Map,combineLatest,flattenMap,flatten这些也一起分析了,但是后来看到RACSingnal的操作实在有点多,于是按照源码的文件分开了,这里先把RACSignal文件里面的操作都分析完了。RACSignal文件里面的操作主要就bind,concat和zipWith三个操作。下一篇再分析分析RACSignal+Operations文件里面的所有操作。
ReactiveCocoa 中 RACSignal 是怎样发送信号的更多相关文章
- ReactiveCocoa 中 RACSignal 是如何发送信号的
https://juejin.im/post/5829f4c3570c350063c436ac 前言 ReactiveCocoa是一个(第一个?)将函数响应式编程范例带入Objective-C的开源库 ...
- ReactiveCocoa 中 RACSignal 所有变换操作底层实现分析(上)
前言 在上篇文章中,详细分析了RACSignal是创建和订阅的详细过程.看到底层源码实现后,就能发现,ReactiveCocoa这个FRP的库,实现响应式(RP)是用Block闭包来实现的,而并不是用 ...
- Qt 使用 lambda 表达式做为槽函数时为什么使用 QObject::sender() 获取到的发送信号对象指针为空?
/*! Returns a pointer to the object that sent the signal, if called in a slot activated by a signal; ...
- Linux驱动实践:中断处理函数如何【发送信号】给应用层?
作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...
- ReactiveCocoa中信号的使用
前言: 很早之前就有看过ReactiveCocoa,那会看的时候知道是一个新的框架关于响应式编程,具体什么也没有深入研究,今天也对ReactiveCocoa这个框架的使用进行了一定的了解.在githu ...
- 为什么ReactiveCocoa中推荐使用RACSignal来做信号处理而不是RACSubject
原文解释在这里http://cocoadocs.org/docsets/ReactiveCocoa/0.6.0/ 在标题Creating hot subscribables 底下 先贴原文: The ...
- pthread中向线程发送信号(pthread_kill )
pthread_kill 语法 int pthread_kill(thread_t tid, int sig); #include <pthread.h> #include <sig ...
- Linux 发送信号
使用kill命令 --在命令行执行kill命令.向指定进程发送信号. 使用kill函数 int kill(pid_t pid,int sig); --参数pid指定一个要杀死的进程,而sig是要发送的 ...
- Linux 命令 - killall: 通过进程名向进程发送信号
命令格式 killall [-Z CONTEXT] [-u USER] [ -eIgiqrvw ] [ -SIGNAL ] NAME... killall -l, --list killall -V, ...
随机推荐
- SendMessage消息大全及说明
WM_CREATE = &H0001 应用程序创建一个窗口 WM_DESTROY = &H0002 一个窗口被销毁 WM_MOVE ...
- NSURLSession下载
ios7引入了NSURLSession,通过它可以支持后台相关的网络操作的新特性: 1. 后台上传下载: 2.不需要通过nsoperation,直接用nsurlsession的api就可以做到网络操作 ...
- 屌丝就爱尝鲜头——java8总结晒一晒
前两节讨论了那么多,这节就是两个议题,讨论了新增的日期的api,再说一说我的Java8的一些心得体会了. 首先,我们必须要搞清楚Java 8 为什么要增加新的日期的api,这是由于老的日期api非常的 ...
- 十个 Laravel 5 程序优化技巧
性能一直是 Laravel 框架为人诟病的一个点,所以调优 Laravel 程序算是一个必学的技能. 接下来分享一些开发的最佳实践,还有调优技巧,大家有别的建议也欢迎留言讨论. 这里是简单的列表: 配 ...
- Thinkphp 获取所有子分类或父分类ID
/** * @Author: HTL * @Email: Huangyuan413026@163.com * @DateTime: 2016-04-22 11:25:02 * @Description ...
- 解决THINKCMF后台文章的相册图集只能上传一个图片的问题
遇到的问题: 最近使用了THINKCMF给客户开发了一个企业网站,客户在使用了一段时间后打电话说后台文章编辑页面有问题 经过沟通过知道,在后台文章编辑和添加页面相册图集每次只能上传一张图片 在跟客 ...
- Mongoose Connection best practice
There is often quite a lot of confusion about how best to set up a database connection with Mongoose ...
- 在java中像js那样处理json数据
工作中经常需要通过ajax向前台返回json数据,都是通过拼字符串拼出来的,很发麻烦不说,还容易出错. 于是想,能不能像js那样操作json呢?或者说更方便的操作呢? Google的gson就是这样的 ...
- ArcGIS读取dem格式数据
DEM是GIS常用的一种数据,用来做各种分析.展示等,十分有用!它实质上就是一个栅格,只不过这个栅格值表示高程,常用的格式是tif,grid等.今天听到了另外一种说法:*.dem是最常见到的DEM的格 ...
- PD 之 连接数据库并导出数据及生成PDM文件
使用PowerDesigner工具,连接数据库并导出数据及生成PDM文件. 1.建立连接 “以管理员身份运行”打开PowerDesigner,右键“Workspace”→“New”→“Physical ...