[iOS] RAC入門小教程

這個(gè)topic是很久以來(lái)多想寫的霹娄,但是一直沒有被迫寫,所以這次嘗試寫一下~

RAC 指的就是 RactiveCocoa 鲫骗,是 Github 的一個(gè)開源框架犬耻,能夠通過信號(hào)提供大量方便的事件處理方案,讓我們更簡(jiǎn)單粗暴地去處理事件执泰,現(xiàn)在分為 ReactiveObjC(OC) 和 ReactiveSwift(swift)枕磁。

所以使用的話就只要:

pod 'ReactiveObjC'
RAC組成

RAC可以處理事件的監(jiān)聽,接管了蘋果所有的事件機(jī)制(addTarget术吝,代理计济,通知,KVO)排苍,是一套重量級(jí)響應(yīng)式函數(shù)式編程開源框架沦寂,它可以幫助我們簡(jiǎn)單的處理事件。它具有高聚合低耦合的特性淘衙。但需要注意Block中的循環(huán)引用問題

這里引入了兩個(gè)問題:什么是響應(yīng)式 & 什么是函數(shù)式传藏?


什么是函數(shù)式?

函數(shù)式的一個(gè)典型是swift那種彤守,那么它的定義是什么呢毯侦?

Functional programming is a programming paradigm
1.treats computation as the evaluation of mathematical functions
2.avoids changing-state and mutable data
by wikipedia

從它的定義也可以知道函數(shù)式的主要特點(diǎn):

  • 沒有使用外部變量,只是使用了傳入的參數(shù)具垫,類似數(shù)學(xué)中的函數(shù)叫惊,真正的是函數(shù)的輸入映射到輸出,只要固定輸入做修,那么每次執(zhí)行函數(shù)的結(jié)果都是一樣的。
    (所以它不怕多線程執(zhí)行)
diff between OOP and functional programming

那么追求純函數(shù)的理由是什么呢:

  • 可緩存性
    var squareNumber = memoize(function(x){ return x*x; });
    squareNumber(4);//=> 16
    squareNumber(4); // 從緩存中讀取輸入值為 4 的結(jié)果
  • 可移植性/自文檔化
    純函數(shù)是完全自給自足的抡草,它需要的所有東西都能輕易獲得饰及。自給自足:不使用外部數(shù)據(jù)或者函數(shù),所有函數(shù)內(nèi)部所需要的外部數(shù)據(jù)都以參數(shù)的形式傳入康震。
  • 可測(cè)試性:
    相同輸入得到相同的輸出
  • 合理性(引用透明性):
    使用一種叫做“等式推導(dǎo)”(equational reasoning)的技術(shù)來(lái)分析代碼燎含。所謂“等式推導(dǎo)”就是“一對(duì)一”替換,有點(diǎn)像在不考慮程序性執(zhí)行的怪異行為(quirks of programmatic evaluation)的情況下腿短,手動(dòng)執(zhí)行相關(guān)代碼屏箍。
  • 并行代碼:
    可以并行運(yùn)行任意純函數(shù)绘梦。因?yàn)榧兒瘮?shù)根本不需要訪問共享的內(nèi)存,而且根據(jù)其定義赴魁,純函數(shù)也不會(huì)因副作用而進(jìn)入競(jìng)爭(zhēng)態(tài)

函數(shù)是一等公民(first-class citizens)卸奉。層級(jí)越高,權(quán)力越大颖御。很多時(shí)候高層的人有權(quán)利做的事榄棵,下面的人就不能做。

程序世界里潘拱,有且僅有這么幾種權(quán)力: 創(chuàng)建疹鳄,賦值(賦值給變量),傳遞(當(dāng)作參數(shù)傳遞)芦岂。

不同的編程語(yǔ)言瘪弓,函數(shù)的權(quán)利會(huì)不一樣。

  • 在java語(yǔ)言中禽最,創(chuàng)建/賦值/傳遞腺怯,這些權(quán)利object 都具備,function 都不具備弛随。對(duì)象可以通過參數(shù)傳遞到另一個(gè)對(duì)象里瓢喉,從而兩個(gè)對(duì)象可以互相通信。函數(shù)卻不行舀透,兩個(gè)函數(shù)想要通信栓票,必須以對(duì)象為介質(zhì)。
  • 在js中愕够,函數(shù)可以創(chuàng)建/賦值/傳遞走贪,所以在js中函數(shù)是一等公民。函數(shù)是 javascript 的主要工作單元惑芭。

Programming Paradigm 編程范式是什么坠狡?

編程范式(Programming Paradigm)是某種編程語(yǔ)言典型的編程風(fēng)格或者說是編程方式。一種范式可以在不同的語(yǔ)言中實(shí)現(xiàn)遂跟,一種語(yǔ)言也可以同時(shí)支持多種范式逃沿。例如 JavaScript 就是一種多范式的語(yǔ)言。

(以下非原創(chuàng)借鑒寫的很好的人噠)

  • 幾種常用的編程范式:
  1. 過程化(命令式)編程:過程化編程幻锁,也被稱為命令式編程凯亮,應(yīng)該是最原始的、也是我們最熟悉的一種傳統(tǒng)的編程方式哄尔。從本質(zhì)上講假消,它是“馮.諾伊曼機(jī)“運(yùn)行機(jī)制的抽象,它的編程思維方式源于計(jì)算機(jī)指令的順序排列岭接。我們常常寫的也就是這種命令式編程富拗。

  2. 事件驅(qū)動(dòng)編程:基于事件驅(qū)動(dòng)的程序設(shè)計(jì)在圖形用戶界面(GUI)出現(xiàn)很久前就已經(jīng)被應(yīng)用于程序設(shè)計(jì)中臼予,可是只有當(dāng)圖形用戶界面廣泛流行時(shí),它才逐漸形演變?yōu)橐环N廣泛使用的程序設(shè)計(jì)模式啃沪。

  3. 面向?qū)ο缶幊?/code>:過程化范式要求程序員用按部就班的算法看待每個(gè)問題粘拾。很顯然,并不是每個(gè)問題都適合這種過程化的思維方式谅阿。這也就導(dǎo)致了其它程序設(shè)計(jì)范式出現(xiàn)半哟,包括我們現(xiàn)在介紹的面向?qū)ο蟮某绦蛟O(shè)計(jì)范式。面向?qū)ο蟮某绦蛟O(shè)計(jì)包括了三個(gè)基本概念:封裝性签餐、繼承性寓涨、多態(tài)性。面向?qū)ο蟮某绦蛘Z(yǔ)言通過類氯檐、方法戒良、對(duì)象和消息傳遞,來(lái)支持面向?qū)ο蟮某绦蛟O(shè)計(jì)范式冠摄。

  4. 函數(shù)式編程:比起過程化編程糯崎,函數(shù)式編程更加強(qiáng)調(diào)程序執(zhí)行的結(jié)果而非執(zhí)行的過程,倡導(dǎo)利用若干簡(jiǎn)單的執(zhí)行單元讓計(jì)算結(jié)果不斷漸進(jìn)河泳,逐層推導(dǎo)復(fù)雜的運(yùn)算沃呢,而不是設(shè)計(jì)一個(gè)復(fù)雜的執(zhí)行過程。

舉個(gè)例子:

// 命令式
function mysteryFn (nums) {
  let squares = []
  let sum = 0                           // 1. 創(chuàng)建中間變量

  for (let i = 0; i < nums.length; i++) {
    squares.push(nums[i] * nums[i])     // 2. 循環(huán)計(jì)算平方
  }

  for (let i = 0; i < squares.length; i++) {
    sum += squares[i]                   // 3. 循環(huán)累加
  }

  return sum
}

// 以上代碼都是 how 而不是 what...
// 函數(shù)式
const mysteryFn = (nums) => nums
  .map(x => x * x)                      // a. 平方
  .reduce((acc, cur) => acc + cur, 0)   // b. 累加

響應(yīng)式

什么是響應(yīng)式拆挥?這個(gè)問題其實(shí)之前有篇里面我也講過薄霜,就是如果正常我們寫一行代碼a = b + c,此時(shí)如果b和c都是1纸兔,那么這個(gè)時(shí)候a得到的就是2惰瓜,然后如果在這行之后b變?yōu)榱?,你需要重新執(zhí)行a = b + c才能讓a更新為3汉矿,但是響應(yīng)式就是當(dāng)b變?yōu)?的時(shí)候c自動(dòng)就可以變成3崎坊。


RAC使用

強(qiáng)推:http://www.reibang.com/p/35a28cf0a22f

終于到了真正的使用啦,每個(gè)類都大概看一下怎么用吧~ RAC有現(xiàn)成的KVO以及UI之類的方法洲拇,可以直接用例如:

[[button1 rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
  NSLog(@"button1 clicked");
}];
1. RACSignal

高階用法歡迎參考:http://www.reibang.com/p/7620edadcf88

- (void)testSignal {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSLog(@"block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)奈揍,就會(huì)調(diào)用block。");
        
        // 2.發(fā)送信號(hào)
//        [subscriber sendNext:@1];
        
        // 如果不在發(fā)送數(shù)據(jù)赋续,最好發(fā)送信號(hào)完成打月,內(nèi)部會(huì)自動(dòng)調(diào)用[RACDisposable disposable]取消訂閱信號(hào)。
        [subscriber sendError:nil];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信號(hào)被銷毀");
        }];
    }];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 1");
    }];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 2");
    }];
}

上面醬紫如果只是設(shè)置了訂閱block蚕捉,外加signal在函數(shù)結(jié)束的時(shí)候沒有引用了就會(huì)自動(dòng)銷毀,于是輸出為:

2020-08-21 23:31:04.311701+0800 Example1[98032:2252660] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)柴淘,就會(huì)調(diào)用block迫淹。
2020-08-21 23:31:04.311879+0800 Example1[98032:2252660] 信號(hào)被銷毀
2020-08-21 23:31:04.312061+0800 Example1[98032:2252660] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)秘通,就會(huì)調(diào)用block。
2020-08-21 23:31:04.312176+0800 Example1[98032:2252660] 信號(hào)被銷毀

這里的信號(hào)被銷毀每次subscribe的時(shí)候都會(huì)調(diào)用敛熬,其實(shí)也就是signal可能對(duì)應(yīng)多個(gè)訂閱者肺稀,默認(rèn)每個(gè)訂閱者加入的時(shí)候給signal一個(gè)機(jī)會(huì)讓它在block里面給訂閱者發(fā)消息,如果它發(fā)了应民,訂閱者的next block就會(huì)執(zhí)行话原,但無(wú)論發(fā)了沒有,默認(rèn)都是給signal處理機(jī)會(huì)以后就會(huì)執(zhí)行RACDisposable dispose解除已經(jīng)有過接手信號(hào)機(jī)會(huì)的訂閱者诲锹。

現(xiàn)在打開[subscriber sendNext:@1];的注釋繁仁,那么由于調(diào)用了sendNext,訂閱者的訂閱block就會(huì)被調(diào)用归园,所以signal會(huì)被touch~

2020-08-21 23:34:32.705363+0800 Example1[98144:2256354] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)黄虱,就會(huì)調(diào)用block。
2020-08-21 23:34:32.705608+0800 Example1[98144:2256354] signal touched 1
2020-08-21 23:34:32.705818+0800 Example1[98144:2256354] 信號(hào)被銷毀
2020-08-21 23:34:32.706005+0800 Example1[98144:2256354] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)庸诱,就會(huì)調(diào)用block捻浦。
2020-08-21 23:34:32.706165+0800 Example1[98144:2256354] signal touched 2
2020-08-21 23:34:32.706355+0800 Example1[98144:2256354] 信號(hào)被銷毀

原理可以參考上面的系列文章里面的第一個(gè),主要是醬紫的:


signal原理

每個(gè)subscriber都遵循RACSubscriber協(xié)議桥爽,其實(shí)有4個(gè)方法:

- (void)sendNext:(nullable id)value;
- (void)sendError:(nullable NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

error和complete其實(shí)對(duì)應(yīng)著我們?cè)谟嗛喌臅r(shí)候傳入的block朱灿,如果我們send error了,那么error的block就會(huì)執(zhí)行钠四,complete也是:

RACDisposable *dispose = [signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"signal touched 1");
} error:^(NSError * _Nullable error) {
    NSLog(@"signal error");
} completed:^{
    NSLog(@"signal completed");
}];

2. RACDisposable

這個(gè)就是聽起來(lái)就知道是做什么系列了盗扒,其實(shí)是訂閱者銷毀的時(shí)候回觸發(fā)的block,RACDisposable就是對(duì)block的一層包裝:

- (instancetype)initWithBlock:(void (^)(void))block {
    NSCParameterAssert(block != nil);

    self = [super init];

    _disposeBlock = (void *)CFBridgingRetain([block copy]); 
    OSMemoryBarrier();

    return self;
}

訂閱者取消訂閱有兩種可能性都會(huì)觸發(fā):

  1. 訂閱者destroy沒有強(qiáng)引用了
  2. 手動(dòng)調(diào)用了[disposable dispose]

第一種其實(shí)就是為什么我們的signal每次被訂閱都會(huì)銷毀一次形导,我們?nèi)绻靡粋€(gè)數(shù)組持有一下訂閱者环疼,那么就不會(huì)觸發(fā)銷毀啦:

- (void)testSignal {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSLog(@"block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào),就會(huì)調(diào)用block朵耕。");
        
        // 2.發(fā)送信號(hào)
        [subscriber sendNext:@1];
        
        [self->subscribers addObject:subscriber];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信號(hào)被銷毀");
        }];
    }];
    
    RACDisposable *dispose = [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 1");
    } error:^(NSError * _Nullable error) {
        NSLog(@"signal error");
    } completed:^{
        NSLog(@"signal completed");
    }];
//    [dispose dispose];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 2");
    }];
}

輸出:
2020-08-22 12:46:15.780694+0800 Example1[3545:41194] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)炫隶,就會(huì)調(diào)用block。
2020-08-22 12:46:15.780933+0800 Example1[3545:41194] signal touched 1
2020-08-22 12:46:15.781115+0800 Example1[3545:41194] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)阎曹,就會(huì)調(diào)用block伪阶。
2020-08-22 12:46:15.781244+0800 Example1[3545:41194] signal touched 2

這個(gè)時(shí)候就發(fā)現(xiàn)dispose沒有再調(diào)用了~ 那么如果我想讓他不通過釋放訂閱者解除訂閱要怎么做呢?打開上面注釋的[dispose dispose];即可手動(dòng)釋放訂閱者处嫌,輸出將變?yōu)椋?/p>

2020-08-22 12:48:55.519454+0800 Example1[3626:43490] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)栅贴,就會(huì)調(diào)用block。
2020-08-22 12:48:55.519770+0800 Example1[3626:43490] signal touched 1
2020-08-22 12:48:55.519971+0800 Example1[3626:43490] 信號(hào)被銷毀
2020-08-22 12:48:55.520135+0800 Example1[3626:43490] block調(diào)用時(shí)刻:每當(dāng)有訂閱者訂閱信號(hào)熏迹,就會(huì)調(diào)用block檐薯。
2020-08-22 12:48:55.520283+0800 Example1[3626:43490] signal touched 2

注意哦,如果我們sendNext以后調(diào)用了sendComplete,那么其實(shí)sendComplete里面會(huì)給你調(diào)用dispose方法解除訂閱噠坛缕。


3. RACSubject

這個(gè)RACSubject其實(shí)是繼承自RACSignal的墓猎,它更像我們以為的信號(hào),就是當(dāng) send next 觸發(fā)的時(shí)候赚楚,所有訂閱者的next block都會(huì)被觸發(fā):

- (void)testSubject {
    RACSubject *subject = [RACSubject subject];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next1 :%@", x);
    }];
    
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next2 :%@", x);
    }];
    
    NSLog(@"testSubject");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subject sendNext:@"x"];
    });
}

輸出:
2020-08-22 12:52:43.338328+0800 Example1[3713:45879] testSubject
2020-08-22 12:52:46.338972+0800 Example1[3713:45879] next1 :x
2020-08-22 12:52:46.339484+0800 Example1[3713:45879] next2 :x

原理其實(shí)就是它循環(huán)了一下所有的訂閱者:

- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}

那么如果我想dispose一下腫么破呢毙沾?那就要看subscriber協(xié)議里面最后一個(gè)方法了:

- (void)testSubject {
    RACSubject *subject = [RACSubject subject];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next1 :%@", x);
    }];
    
    [subject didSubscribeWithDisposable:[RACCompoundDisposable disposableWithBlock:^{
        NSLog(@"disposable block1");
    }]];

    [subject didSubscribeWithDisposable:[RACCompoundDisposable disposableWithBlock:^{
        NSLog(@"disposable block2");
    }]];
    
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next2 :%@", x);
    }];
    
    NSLog(@"testSubject");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subject sendNext:@"x"];
    });
}

輸出:
2020-08-22 12:53:56.902050+0800 Example1[4259:50201] testSubject
2020-08-22 12:53:59.902840+0800 Example1[4259:50201] next1 :x
2020-08-22 12:53:59.903351+0800 Example1[4259:50201] next2 :x
2020-08-22 12:53:59.903802+0800 Example1[4259:50201] disposable block1
2020-08-22 12:53:59.904165+0800 Example1[4259:50201] disposable block2

也就是在signal銷毀的時(shí)候會(huì)觸發(fā)我們加入的disposable們,并且是按照加入的順序觸發(fā)噠宠页。


4. RACReplaySubject

這個(gè)其實(shí)和subject很類似左胞,區(qū)別就是這個(gè)是可以先發(fā)信號(hào)再訂閱,也就是新的訂閱者訂閱的時(shí)候举户,可以拿到之前發(fā)過的所有歷史信息:

- (void)testReplaySubject {
    RACReplaySubject *subject = [RACReplaySubject subject];
    [subject sendNext:@"hhh1"];
    [subject sendNext:@"hhh2"];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"value received: %@", x);
    }];
    NSLog(@"finished");
}

輸出:
2020-08-22 12:57:29.315784+0800 Example1[4367:53504] value received: hhh1
2020-08-22 12:57:29.316010+0800 Example1[4367:53504] value received: hhh2
2020-08-22 12:57:29.316146+0800 Example1[4367:53504] finished

這個(gè)原理其實(shí)就是在sendNext的時(shí)候把數(shù)據(jù)存到了一個(gè)數(shù)組里面烤宙,然后訂閱者訂閱的時(shí)候會(huì)先把這個(gè)數(shù)組遍歷一次:

- (void)sendNext:(id)value {
    @synchronized (self) {
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
        
        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
        
        [super sendNext:value];
    }
}

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            for (id value in self.valuesReceived) {
                if (compoundDisposable.disposed) return;

                [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
            }

            ……
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}

5. RACScheduler

Scheduler其實(shí)是對(duì)GCD的一個(gè)封裝,主要是做調(diào)度的:

- (void)testScheduler {
    NSLog(@"started");
    [[RACScheduler scheduler] schedule:^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    }];
    NSLog(@"finished");
}

輸出:
2020-08-22 13:02:05.910901+0800 Example1[4478:57139] started
2020-08-22 13:02:05.911190+0800 Example1[4478:57139] finished
2020-08-22 13:02:05.911413+0800 Example1[4478:57289] 當(dāng)前線程:<NSThread: 0x600001200d80>{number = 3, name = (null)}

RACScheduler是一個(gè)父類敛摘,他有很多子類:


RACScheduler的子類

我們用到的直接其實(shí)是:

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
    return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}

@interface RACTargetQueueScheduler : RACQueueScheduler

看RACQueueScheduler的名字也可以知道這個(gè)scheduler其實(shí)是一個(gè)依賴于queue執(zhí)行的:

- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);

    RACDisposable *disposable = [[RACDisposable alloc] init];

    dispatch_async(self.queue, ^{
        if (disposable.disposed) return;
        [self performAsCurrentScheduler:block];
    });

    return disposable;
}

也就是說门烂,它的執(zhí)行順序依賴于我們傳入的是并行還是串行queue。

我們通過[RACScheduler scheduler]創(chuàng)建的其實(shí)傳入的是global queue兄淫,也就是一個(gè)并行的隊(duì)列屯远,下面來(lái)嘗試一下:

- (void)testScheduler {
    NSLog(@"started");
    [[RACScheduler scheduler] schedule:^{
        NSLog(@"當(dāng)前線程1:%@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"當(dāng)前線程1:%@",[NSThread currentThread]);
    }];
    
    [[RACScheduler scheduler] schedule:^{
        NSLog(@"當(dāng)前線程2:%@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"當(dāng)前線程2:%@",[NSThread currentThread]);
    }];
    
    NSLog(@"finished");
}

輸出:
2020-08-22 13:09:57.205527+0800 Example1[4663:63879] started
2020-08-22 13:09:57.205864+0800 Example1[4663:63879] finished
2020-08-22 13:09:57.206289+0800 Example1[4663:63989] 當(dāng)前線程1:<NSThread: 0x6000035a4640>{number = 4, name = (null)}
2020-08-22 13:09:57.206331+0800 Example1[4663:63988] 當(dāng)前線程2:<NSThread: 0x6000035f07c0>{number = 5, name = (null)}
2020-08-22 13:10:00.207777+0800 Example1[4663:63989] 當(dāng)前線程1:<NSThread: 0x6000035a4640>{number = 4, name = (null)}
2020-08-22 13:10:00.207825+0800 Example1[4663:63988] 當(dāng)前線程2:<NSThread: 0x6000035f07c0>{number = 5, name = (null)}

RACScheduler還有其他的獲取方式:

  • immediateScheduler,立即執(zhí)行的線程捕虽,其實(shí)就是在當(dāng)前線程執(zhí)行的
  • mainThreadScheduler慨丐,獲取主線程調(diào)度器
  • currentScheduler,就是獲取當(dāng)前線程調(diào)度器泄私,但不會(huì)立即執(zhí)行
- (void)testScheduler {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
        
        NSLog(@"started");
        [[RACScheduler immediateScheduler] schedule:^{
            NSLog(@"當(dāng)前線程2:%@",[NSThread currentThread]);
            sleep(3);
            NSLog(@"當(dāng)前線程2:%@",[NSThread currentThread]);
        }];
        
        NSLog(@"finished");
    });
}

輸出:
2020-08-22 21:26:46.562790+0800 Example1[7651:88680] started
2020-08-22 21:26:46.563918+0800 Example1[7651:88742] 當(dāng)前線程:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:46.564143+0800 Example1[7651:88742] started
2020-08-22 21:26:46.564534+0800 Example1[7651:88742] 當(dāng)前線程2:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:49.569298+0800 Example1[7651:88742] 當(dāng)前線程2:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:49.569719+0800 Example1[7651:88742] finished

如果換成mainThreadScheduler輸出就會(huì)是醬紫房揭,不再是同步調(diào)用啦:

2020-08-22 21:29:35.556247+0800 Example1[7754:91485] 當(dāng)前線程:<NSThread: 0x600000b249c0>{number = 4, name = (null)}
2020-08-22 21:29:35.556581+0800 Example1[7754:91485] started
2020-08-22 21:29:35.556826+0800 Example1[7754:91485] finished
2020-08-22 21:29:35.579004+0800 Example1[7754:91405] 當(dāng)前線程2:<NSThread: 0x600000b61080>{number = 1, name = main}
2020-08-22 21:29:38.579679+0800 Example1[7754:91405] 當(dāng)前線程2:<NSThread: 0x600000b61080>{number = 1, name = main}

如果換成currentScheduler是醬紫的,按理說也是異步的晌端,但實(shí)際輸出是醬紫的:

2020-08-22 21:31:23.592051+0800 Example1[7841:93828] 當(dāng)前線程:<NSThread: 0x60000340dfc0>{number = 5, name = (null)}
2020-08-22 21:31:23.592302+0800 Example1[7841:93828] started
2020-08-22 21:31:23.592497+0800 Example1[7841:93828] finished

為啥沒執(zhí)行schedule的內(nèi)容呢捅暴?

+ (RACScheduler *)currentScheduler {
    RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
    if (scheduler != nil) return scheduler;
    if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;

    return nil;
}

如果當(dāng)前線程之前沒有創(chuàng)建scheduler并且又不是主線程,那么就會(huì)return一個(gè)nil咧纠,自然就不會(huì)執(zhí)行啦蓬痒。

  • scheduler還可以通過優(yōu)先級(jí)來(lái)創(chuàng)建,例如醬紫:
- (void)testScheduler {
    [[RACScheduler schedulerWithPriority:RACSchedulerPriorityLow] schedule:^{
        NSLog(@"scheduler:%@",[RACScheduler currentScheduler]);
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    }];
}

輸出:
2020-08-22 21:35:24.544379+0800 Example1[8014:98226] scheduler:<RACTargetQueueScheduler: 0x600001476a20> org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler
2020-08-22 21:35:24.544993+0800 Example1[8014:98226] 當(dāng)前線程:<NSThread: 0x600000167900>{number = 5, name = (null)}

RAC優(yōu)先級(jí)其實(shí)是對(duì)應(yīng)GCD的優(yōu)先級(jí)的:

typedef enum : long {
    RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH,
    RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT,
    RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW,
    RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND,
} RACSchedulerPriority;

不同優(yōu)先級(jí)其實(shí)就是對(duì)應(yīng)了gcd不同的queue漆羔,注意這里默認(rèn)是global的queue哈:

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
    return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}

Finally梧奢,為什么RAC要封裝GCD呢?這個(gè)其實(shí)我感覺是為了更好用和好看演痒,dispatch很多縮進(jìn)亲轨,但是RAC如果可以直接delivery就會(huì)好看很多~ 希望有大佬來(lái)教學(xué)一下這個(gè)問題~


6. RACMulticastConnection

如果我們通過signal做網(wǎng)絡(luò)請(qǐng)求可以醬紫:

- (void)testConn {
    RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"發(fā)送網(wǎng)絡(luò)請(qǐng)求");
        sleep(3);
        [subscriber sendNext:@"得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)"];
        
        return nil;
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"1 - %@",x);
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"2 - %@",x);
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"3 - %@",x);
    }];
}

輸出:
2020-08-22 21:46:45.095715+0800 Example1[8973:108249] 發(fā)送網(wǎng)絡(luò)請(qǐng)求
2020-08-22 21:46:48.097432+0800 Example1[8973:108249] 1 - 得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
2020-08-22 21:46:48.098200+0800 Example1[8973:108249] 發(fā)送網(wǎng)絡(luò)請(qǐng)求
2020-08-22 21:46:51.099029+0800 Example1[8973:108249] 2 - 得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
2020-08-22 21:46:51.099650+0800 Example1[8973:108249] 發(fā)送網(wǎng)絡(luò)請(qǐng)求
2020-08-22 21:46:54.101352+0800 Example1[8973:108249] 3 - 得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)

就可能會(huì)重復(fù)觸發(fā),RACMulticastConnection就是用于避免這個(gè)事兒的:

- (void)testConn {
    RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"發(fā)送網(wǎng)絡(luò)請(qǐng)求");
        sleep(3);
        
        [subscriber sendNext:@"得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)"];
        
        return nil;
    }];
    
    RACMulticastConnection *connect = [signal publish];
    
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"1 - %@",x);
    }];
    
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"2 - %@",x);
    }];
    
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"3 - %@",x);
    }];
    
    [connect connect];
}

輸出:

2020-08-22 21:57:56.231520+0800 Example1[9697:117688] 發(fā)送網(wǎng)絡(luò)請(qǐng)求
2020-08-22 21:57:59.232046+0800 Example1[9697:117688] 1 - 得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
2020-08-22 21:57:59.232525+0800 Example1[9697:117688] 2 - 得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
2020-08-22 21:57:59.232854+0800 Example1[9697:117688] 3 - 得到網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)

它的大概原理是醬紫的鸟顺,感興趣的可以看之前的參考:


conn原理

其實(shí)就是conn的signal其實(shí)是一個(gè)subject惦蚊,而非之前的RACSignal,所以可以訂閱好幾個(gè)。注意哦蹦锋,signal訂閱的時(shí)候是順序執(zhí)行的曾撤,需要先執(zhí)行創(chuàng)建時(shí)候每次訂閱觸發(fā)的block,之后才能走下一個(gè)訂閱晕粪;而subject比較正常,是可以先訂閱的渐裸,然后一起等待sendNext的觸發(fā)


7. RACCommand

這個(gè)東西其實(shí)我也一直很迷茫是干啥用的~ 參考了:http://www.reibang.com/p/dc472c644e7b

一般情況下巫湘,RACCommand主要用來(lái)封裝一些請(qǐng)求、事件等昏鹃,舉個(gè)例子尚氛,我們的tableView在下拉滾動(dòng)時(shí)若想刷新數(shù)據(jù)需要向接口提供頁(yè)碼或者最后一個(gè)數(shù)據(jù)的ID,我們可以把請(qǐng)求封裝進(jìn)RACCommand里洞渤,想要獲取數(shù)據(jù)的時(shí)候只要將頁(yè)碼或者ID傳入RACCommand里就可以了阅嘶,同時(shí)監(jiān)控RACCommand何時(shí)完成,若完成后將數(shù)據(jù)加入到tableview的數(shù)組就可以了,這是一個(gè)平常用的比較多的場(chǎng)景载迄。使用是主要有三個(gè)注意點(diǎn):

  1. RACCommand必須返回信號(hào),信號(hào)可以為空
  2. RACCommand必須強(qiáng)引用
  3. RACCommand發(fā)送完數(shù)據(jù)必須發(fā)送完成信號(hào)

例如醬紫:

-(void)loadInfo{

      //input就是控制器中,viewmodel執(zhí)行command時(shí)excute傳入的參數(shù)
      RACCommand * command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {

          //command必須有信號(hào)返回值,如果沒有的話可以為[RACSignal empty]
          return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
                  {
                      NSMutableDictionary * params = [NSMutableDictionary dictionary];

                      params[@"build"] = @"3360";
                      params[@"channel"] = @"appstore";
                      params[@"plat"] = @"2";

                      [FYRequestTool GET:@"http://app.bilibili.com/x/banner" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

                          [subscriber sendNext:responseObject];

                          //發(fā)送完信號(hào)必須發(fā)送完成信號(hào),否則無(wú)法執(zhí)行
                          [subscriber sendCompleted];

                      } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

                          [subscriber sendError:error];

                      }];

                  return [RACDisposable disposableWithBlock:^{

                          [FYRequestTool cancel];

                          NSLog(@"這里面可以寫取消請(qǐng)求,完成信號(hào)后請(qǐng)求會(huì)取消");

                          }];
                  }];
          }];

      //必須強(qiáng)引用這個(gè)command,否則無(wú)法執(zhí)行
      self.command = command;
  }

signal如果我們?cè)谟嗛喌腷lock里面去做網(wǎng)絡(luò)請(qǐng)求讯柔,是無(wú)法帶入一些參數(shù)給它的,但是command可以通過execute的參數(shù)做區(qū)分护昧,然后你可以返回一個(gè)signal魂迄,在signal內(nèi)部通過不同input做不同的事情,最后sendNext一下觸發(fā)訂閱者的block告知訂閱者并返回拿到的數(shù)據(jù):

- (void)testCommand {
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        NSLog(@"create command 1 with input:%@", input);
        sleep(3);
        NSLog(@"create command 2");
        
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            // 這里可以拿input去做不同的事情
            NSLog(@"signal subscribed 1 with input:%@", input);
            sleep(3);
            [subscriber sendNext:@"a"];
            NSLog(@"signal subscribed 2");
            
            return [RACDisposable disposableWithBlock:^{
                NSLog(@"RACDisposable disposable block");
            }];
        }];
    }];
    
    NSLog(@"testCommand");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSString *input = @"執(zhí)行";
        
        [[command.executionSignals switchToLatest] subscribeNext:^(id  _Nullable x) {
            NSLog(@"switchToLatest-->%@",x);
        }];
        
        [command execute:input];
    });
}

輸出:
2020-08-22 22:25:33.442545+0800 Example1[10798:139387] testCommand
2020-08-22 22:25:36.443640+0800 Example1[10798:139387] create command 1 with input:執(zhí)行
2020-08-22 22:25:39.445241+0800 Example1[10798:139387] create command 2
2020-08-22 22:25:39.447601+0800 Example1[10798:139387] executing-->0
2020-08-22 22:25:39.448927+0800 Example1[10798:139387] executing-->1
2020-08-22 22:25:39.449554+0800 Example1[10798:139387] signal subscribed 1 with input:執(zhí)行
2020-08-22 22:25:42.450867+0800 Example1[10798:139387] switchToLatest-->a
2020-08-22 22:25:42.451391+0800 Example1[10798:139387] signal subscribed 2
2020-08-22 22:25:42.451810+0800 Example1[10798:139387] RACDisposable disposable block

8. bind

bind其實(shí)是RACStream的方法惋耙,但是很多類都復(fù)寫了它捣炬,例如signal的:

- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);

    /*
     * -bind: should:
     * 
     * 1. Subscribe to the original signal of values.
     * 2. Any time the original signal sends a value, transform it using the binding block.
     * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
     * 4. If the binding block asks the bind to terminate, complete the _original_ signal.
     * 5. When _all_ signals complete, send completed to the subscriber.
     * 
     * If any signal sends an error at any point, send that to the subscriber.
     */

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACSignalBindBlock bindingBlock = block();

        __block volatile int32_t signalCount = 1;   // indicates self

        RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

        void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) {
            if (OSAtomicDecrement32Barrier(&signalCount) == 0) {
                [subscriber sendCompleted];
                [compoundDisposable dispose];
            } else {
                [compoundDisposable removeDisposable:finishedDisposable];
            }
        };

        void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
            OSAtomicIncrement32Barrier(&signalCount);

            RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
            [compoundDisposable addDisposable:selfDisposable];

            RACDisposable *disposable = [signal subscribeNext:^(id x) {
                [subscriber sendNext:x];
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(selfDisposable);
                }
            }];

            selfDisposable.disposable = disposable;
        };

        @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(selfDisposable);
                    }
                }
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(selfDisposable);
                }
            }];

            selfDisposable.disposable = bindingDisposable;
        }

        return compoundDisposable;
    }] setNameWithFormat:@"[%@] -bind:", self.name];
}

其實(shí)就是創(chuàng)建了一個(gè)新的signal黔龟,然后在這個(gè)信號(hào)被訂閱的時(shí)候會(huì)執(zhí)行我們bind時(shí)候提供的block芍阎,根據(jù)我們?cè)赽ind里面返回的signal是不是空來(lái)決定是要addSignal還是addComplete至非。

注意哦之所以后面signal被觸發(fā)的時(shí)候习劫,可以給subscriber send next主要是因?yàn)樵?code>addSignal里面有引用subscriber哦到踏。

所以其實(shí)bind主要做的是:

正常來(lái)說我們發(fā)送信號(hào)只有信號(hào)的訂閱者才會(huì)接收到消息拦宣,而bind會(huì)將信號(hào)攔截過濾后發(fā)送到新的信號(hào)訂閱者中擒权。

它的流程是醬紫的:

  • 原信號(hào)->bind(生成新信號(hào))
  • 原信號(hào)發(fā)送消息->bind Block 進(jìn)行過濾乏沸,然后發(fā)送消息到內(nèi)部一個(gè)信號(hào)中-> 轉(zhuǎn)發(fā)到bind時(shí)生成的新信息號(hào)中

舉個(gè)例子:

- (void)testBind {
    RACSubject *subject = [RACSubject subject];
    RACSignal * signal = [subject bind:^RACSignalBindBlock _Nonnull{
        NSLog(@"bind block");
        
        return ^RACSignal *(id _Nullable value, BOOL *stop){
            NSLog(@"signal block");
            return [RACReturnSignal return:value];
        };
    }];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"收到的數(shù)據(jù) - %@",x);
    }];
    
    [subject sendNext:@"啟動(dòng)自毀程序"];
}

輸出:
2020-08-23 08:45:52.755609+0800 Example1[2023:32254] bind block
2020-08-23 08:45:52.756066+0800 Example1[2023:32254] signal block
2020-08-23 08:45:52.756573+0800 Example1[2023:32254] 收到的數(shù)據(jù) - 啟動(dòng)自毀程序

在rac里面也有很多地方用到了bind美莫,比如flattenMap方法,其實(shí)就是bind的封裝厢呵。內(nèi)部就是bind方法的調(diào)用,然后可以傳入一個(gè)block 作為過濾的規(guī)則赐劣。


9. RACStream

這個(gè)part可以參考:http://www.reibang.com/p/0e4de0eeaff7 & http://www.reibang.com/p/64ab974445dd

RACStream中的許多定義都是抽象的哩都,沒有具體實(shí)現(xiàn)漠嵌,需要由其子類進(jìn)行實(shí)現(xiàn)咐汞。

例如signal其實(shí)是繼承自stream的:

@interface RACSignal<__covariant ValueType> : RACStream
  • empty
+ (__kindof RACStream *)empty;
因?yàn)镽AC中nil會(huì)導(dǎo)致crash,所以很多時(shí)候需要定義一個(gè)空對(duì)象來(lái)替代nil儒鹿,一般empty都被創(chuàng)建為一個(gè)單例來(lái)使用化撕。
  • bind 適用于過濾
- (__kindof RACStream *)bind:(RACStreamBindBlock (^)(void))block;
懶綁定,將block返回的RACStream綁定到自身约炎,在調(diào)用時(shí)才執(zhí)行內(nèi)部操作植阴。很多操作都是基于bind的,比如flattenMap圾浅,大多時(shí)候我們都調(diào)用bind的上層方法掠手。

例如,創(chuàng)建一個(gè)signal對(duì)象發(fā)送十次值,并且在其中隨機(jī)發(fā)送@" "贱傀。我們要做的是過濾掉@" "并且在接收到@"5"時(shí)終止后續(xù)發(fā)送返回一個(gè)新的signal并且發(fā)送@"a"惨撇,@"b":

RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    //發(fā)送10次值
    for (int i = 0; i < 10; i ++) {
        //隨機(jī)發(fā)送 @" "
        BOOL empty = arc4random_uniform(2);
        if (!empty || i == 5) {
            [subscriber sendNext:[NSString stringWithFormat:@"%zd",i]];
        }
        else {
            [subscriber sendNext:@" "];
        }
    }
    [subscriber sendCompleted];
    return nil;
}] bind:^RACStreamBindBlock{
    return ^id (NSString * value, BOOL * stop) {
        if ([value isEqualToString:@" "]) { //過濾@" "
            return [RACSignal empty];
        }
        else if ([value isEqualToString:@"5"]){ //接收到 @"5" 終止
            * stop = YES;
            RACSignal * result = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                [subscriber sendNext:@"a"];
                [subscriber sendNext:@"b"];
                [subscriber sendCompleted];
                return nil;
            }];
            return result;
        }
        else {
            return [RACSignal return:value];
        }
        
    };
}];

[signal subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];

[signal subscribeCompleted:^{
    NSLog(@"complete");
}];
/* 某次執(zhí)行結(jié)果
2018-03-27 15:14:02.334078+0800 ReactCocoaTest[91664:2403921] 1
2018-03-27 15:14:02.334390+0800 ReactCocoaTest[91664:2403921] 3
2018-03-27 15:14:02.334562+0800 ReactCocoaTest[91664:2403921] 4
2018-03-27 15:14:02.334836+0800 ReactCocoaTest[91664:2403921] a
2018-03-27 15:14:02.334957+0800 ReactCocoaTest[91664:2403921] b
2018-03-27 15:14:02.335673+0800 ReactCocoaTest[91664:2403921] complete
*/
  • return
+ (__kindof RACStream *)return:(id)value;
把一個(gè)值包裝成對(duì)應(yīng)的RACStream的子類型。
  • concat 適用于連續(xù)的異步任務(wù)
- (__kindof RACStream *)concat:(RACStream *)stream;
連接兩個(gè)信號(hào)府寒,子類實(shí)現(xiàn)具體如何連接魁衙。

例如醬紫:

RACSignal * signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"1"];
    [[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
        [subscriber sendCompleted];
    }];
    return nil;
}];
RACSignal * signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"2"];
    [[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
        [subscriber sendCompleted];
    }];
    return nil;
}];
RACSignal * concat = [signal1 concat:signal2];
[concat subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
[concat subscribeCompleted:^{
    NSLog(@"complete");
}];
[concat subscribeError:^(NSError *error) {
    NSLog(@"error");
}];
/*
2018-03-27 16:37:18.546755+0800 ReactCocoaTest[94470:2467226] 1
2018-03-27 16:37:24.040041+0800 ReactCocoaTest[94470:2467226] 2
2018-03-27 16:37:29.513247+0800 ReactCocoaTest[94470:2467226] complete
*/
  • zipWith 適用于連接兩個(gè)信號(hào)的結(jié)果
- (__kindof RACStream *)zipWith:(RACStream *)stream;
壓縮兩個(gè)信號(hào),子類實(shí)現(xiàn)具體如何壓縮株搔。

例如醬紫:

RACSignal * signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"1"];
    [subscriber sendNext:@"2"];
    [subscriber sendNext:@"3"];
    [subscriber sendNext:@"4"];
    [subscriber sendNext:@"5"];
    [subscriber sendNext:@"6"];
    [[RACScheduler mainThreadScheduler]afterDelay:7 schedule:^{
        [subscriber sendError:nil];
    }];
    return nil;
}];
RACSignal * signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"a"];
    [subscriber sendNext:@"b"];
    [[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
        [subscriber sendNext:@"d"];
        [subscriber sendCompleted];
    }];
    return nil;
}];
RACSignal * zip = [signal1 zipWith:signal2];
[zip subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
[zip subscribeCompleted:^{
    NSLog(@"complete");
}];
[zip subscribeError:^(NSError *error) {
    NSLog(@"error");
}];
/*
2018-03-27 17:20:15.922457+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000206030> (
    1,
    a
)
2018-03-27 17:20:15.922787+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000206010> (
    2,
    b
)
2018-03-27 17:20:21.414965+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000207110> (
    3,
    d
)
2018-03-27 17:20:21.415268+0800 ReactCocoaTest[96661:2499724] complete
*/   

10. RACTuple

RACTuple就和swift里面的元組一樣的剖淀,隨便放數(shù)據(jù),它的內(nèi)部主要就是一個(gè)可以放 id 類型的數(shù)組:

- (instancetype)initWithBackingArray:(NSArray *)backingArray {
    self = [super init];
    
    _backingArray = [backingArray copy];
    
    return self;
}

使用就很簡(jiǎn)單纤房,可以用init纵隔,也可以用提供的快捷方式:

@class RACTwoTuple<__covariant First, __covariant Second>;
@class RACThreeTuple<__covariant First, __covariant Second, __covariant Third>;
@class RACFourTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth>;
@class RACFiveTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth, __covariant Fifth>;

11. RACSequence

它也是繼承RACStream的,所以也有各種zip炮姨、concat捌刮、bind的操作~

RAC對(duì)OC的集合和RACTuple進(jìn)行Category擴(kuò)充,因此可用集合.rac_sequence舒岸,把集合快速轉(zhuǎn)換成RACSequence對(duì)象
訂閱RACSequence的signal绅作,可遍歷所有元素,但因?yàn)閮?nèi)部實(shí)現(xiàn)是異步執(zhí)行的(for in是在當(dāng)前線程)蛾派,所以使用時(shí)候需要注意時(shí)間順序俄认。

NSArray *array = @[@1, @2, @3];
    NSDictionary *dict = @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"};
    NSString *str = @"ABC";
    NSSet *set = [NSSet setWithArray:array];
    RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:array];

    //NSArray 會(huì)返回元素
    [array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"array rac_sequence : %@", x);
    }];

    //NSDictionary 會(huì)返回打包成Tuple的key个少、value
    [dict.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"dict rac_sequence : %@", x);
    }];

    //NSString 會(huì)返回單個(gè)字符
    [str.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"str rac_sequence : %@", x);
    }];

    //NSSet 會(huì)返回元素
    [set.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"set rac_sequence : %@", x);
    }];

    //RACTuple 會(huì)返回內(nèi)置數(shù)組的元素
    [tuple.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"tuple rac_sequence : %@", x);
    }];

    //以下是輸出結(jié)果,從結(jié)果可以看出眯杏,結(jié)果是亂序的
    //如果再打印當(dāng)前線程夜焦,會(huì)發(fā)現(xiàn)每種集合都在各自的一條線程上輸出,但非主線程
    //array rac_sequence : 1
    //array rac_sequence : 2
    //array rac_sequence : 3
    //str rac_sequence : A
    //set rac_sequence : 3
    //str rac_sequence : B
    //tuple rac_sequence : 1
    //set rac_sequence : 2
    //dict rac_sequence : <RACTuple: 0x610000004cf0> (key1,value1)
    //tuple rac_sequence : 2
    //str rac_sequence : C
    //set rac_sequence : 1
    //dict rac_sequence : <RACTuple: 0x610000004ce0> (key3,value3)
    //tuple rac_sequence : 3
    //dict rac_sequence : <RACTuple: 0x608000004bb0> (key2,value2)

How?

RAC的信號(hào)的用法主要有什么呢岂贩?我們?cè)贛VVM或者M(jìn)VC框架中都會(huì)有綁定V和 VM或者M(jìn) 的操作茫经,這個(gè)時(shí)候可以用signal來(lái)實(shí)現(xiàn),比如view來(lái)訂閱signal萎津,然后VM在獲取完數(shù)據(jù)以后可以sendNext把數(shù)據(jù)傳給view即可科平。


為什么 RAC 是 響應(yīng)式 & 函數(shù)式?

首先響應(yīng)體現(xiàn)在 signal 上面姜性,比如當(dāng)我們點(diǎn)擊了一個(gè)button,就會(huì)發(fā)給我們一個(gè) signal 髓考,我們可以在接受到 signal 以后做一些處理部念,這個(gè)就類似于我們?cè)O(shè)置在接受到 b 改變的 signal 里面設(shè)置a = b + c,那么每次 b 變化氨菇,a 都會(huì)自然的變化儡炼,不需要再手動(dòng)改啦。

函數(shù)式比較隱密查蓉,因?yàn)镺C里面函數(shù)是不能作為參數(shù)傳遞的乌询,函數(shù)式只能體現(xiàn)在block上面,所以RAC里面通過block響應(yīng)next就是函數(shù)式的體現(xiàn)了豌研,和swift的promise里面的.then().next()之類的很類似妹田。

References:
http://www.reibang.com/p/5fc71f541b1c
http://www.reibang.com/p/d90f6715f2cc
https://medium.com/@shaistha24/functional-programming-vs-object-oriented-programming-oop-which-is-better-82172e53a526
http://www.reibang.com/p/9eee5a0b42da

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鹃共,隨后出現(xiàn)的幾起案子鬼佣,更是在濱河造成了極大的恐慌,老刑警劉巖霜浴,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晶衷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡阴孟,警方通過查閱死者的電腦和手機(jī)晌纫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)永丝,“玉大人锹漱,你說我怎么就攤上這事±嘁纾” “怎么了凌蔬?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵露懒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我砂心,道長(zhǎng)懈词,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任辩诞,我火速辦了婚禮坎弯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘译暂。我一直安慰自己抠忘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布外永。 她就那樣靜靜地躺著崎脉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伯顶。 梳的紋絲不亂的頭發(fā)上囚灼,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音祭衩,去河邊找鬼灶体。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掐暮,可吹牛的內(nèi)容都是我干的蝎抽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼路克,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼樟结!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起精算,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狭吼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后殖妇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刁笙,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年谦趣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疲吸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡前鹅,死狀恐怖摘悴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舰绘,我是刑警寧澤蹂喻,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布葱椭,位于F島的核電站,受9級(jí)特大地震影響口四,放射性物質(zhì)發(fā)生泄漏孵运。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一蔓彩、第九天 我趴在偏房一處隱蔽的房頂上張望治笨。 院中可真熱鬧,春花似錦赤嚼、人聲如沸旷赖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)等孵。三九已至,卻和暖如春蹂空,著一層夾襖步出監(jiān)牢的瞬間流济,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工腌闯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雕憔。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓姿骏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親斤彼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子分瘦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345