初識RAC

本人有若干成套學(xué)習(xí)視頻, 可試看! 可試看! 可試看, 重要的事情說三遍 包含Java, 數(shù)據(jù)結(jié)構(gòu)與算法, iOS, 安卓, python, flutter等等, 如有需要, 聯(lián)系微信tsaievan.

RAC - ReactiveCocoa, 是github上的一套開源框架, 是一套函數(shù)響應(yīng)式編程框架.

那么這套框架是干什么用的呢?

RAC中很重要的一個類叫做RACsignal, 當(dāng)我們有數(shù)據(jù)產(chǎn)生時, 就創(chuàng)建一個信號, 這么說可能太抽象了

RACSignal在創(chuàng)建對象的時候, 不是用的alloc,init方法, 而是給我們提供了一個類方法:

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe

在這個類方法中, 有一個參數(shù)didSubscribe, 這是一個block, 這個block是一個參數(shù)為id類型, 遵守RACSubscriber協(xié)議的對象, 返回值為RACDisposable *類型的block.

創(chuàng)建信號時報(bào)錯

第28行報(bào)錯, 這是因?yàn)槟愕?code>block是一個帶返回值的block, 而你又什么都沒有返回, 所以不管三七二十一, 先返回一個nil. 然后我在block中做一件事情, 打印一句話, 代碼如下:


- (void)viewDidLoad {
    [super viewDidLoad];
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"signal has been created");
        return nil;
    }];
}
代碼寫到這里, 這個block中的代碼會執(zhí)行嗎? 顯然不會, 那么, 這段代碼什么時候執(zhí)行呢?

這個時候就需要信號訂閱:
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

這段代碼中, 又有一個block參數(shù), 這個block是一個參數(shù)為id類型的對象, 返回值為空的block, 信號訂閱時, 我在這個block中什么也不做, 上面的打印同樣能夠執(zhí)行:

執(zhí)行block

我們來看一下這個內(nèi)部是怎么實(shí)現(xiàn)的:

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

其中signal->_didSubscribe = [didSubscribe copy];這句代碼將didSubscribe這個block保存在對象的屬性中.

那我們來看是不是在- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock這個方法中調(diào)用了block:

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}

subscribe方法中,didSubscribe方法被調(diào)用了

調(diào)用者是`RACDynamicSignal`
`didSubscribe`這個`block`的執(zhí)行

從上圖我們可以看到, didSubscribe這個block在信號訂閱的時候被調(diào)用, 在調(diào)用的同時, 把subscriber這個參數(shù)傳給block, 所以我們在

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"signal has been created");
        [subscriber sendNext:@"subscriber send"];
        return nil;
    }];

這個代碼塊內(nèi)部是可以拿到subscriber這個對象的, 然后我再調(diào)用sendNext這個方法, 會出現(xiàn)什么情況呢?

`sendNext`觸發(fā)block的調(diào)用

我們發(fā)現(xiàn), subscribeNext:方法參數(shù)的block被調(diào)用了

那我們來看看sendNext這個方法內(nèi)部做了什么?

執(zhí)行`nextBlock`

這個方法執(zhí)行了我當(dāng)初保存在subscriber屬性中的block, 并且將value參數(shù)也傳給了這個nextBlock, 所以在nextBlock執(zhí)行的時候, 可以獲得x參數(shù)

`block`代碼塊的執(zhí)行詳情

將`block`代碼塊保存在屬性中

好了, 我們一直沒有關(guān)注+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe這個方法中block參數(shù)的返回值是干嘛的, 那么我們就給這個block一個返回值:

`block`的返回值

可以看到的是: 這個返回值是RACDisposable類型的, 然后我們創(chuàng)建一個RACDisposable對象是用+ (instancetype)disposableWithBlock:(void (^)(void))block這個類方法, 同樣參數(shù)也是一個block, 但是這個block很快就被調(diào)用了, 這是為什么呢?

subscriber生存周期結(jié)束

這個返回值

RACDisposable *disposable = [RACDisposable disposableWithBlock:^{
            NSLog(@"disposable");
        }];
        return disposable;

subscribeNext:方法調(diào)用時, 賦值給了subscriberdisposable屬性(可以先這么理解), 當(dāng)subscriber生存周期結(jié)束時, 就會調(diào)

- (void)dealloc {
    [self.disposable dispose];
}

這時候

RACDisposable *disposable = [RACDisposable disposableWithBlock:^{
            NSLog(@"disposable");
        }];
        return disposable;

這段代碼中的打印就會執(zhí)行了.

但當(dāng)subscriber被強(qiáng)引用時, dealloc方法不被調(diào)用, disposable也不會被調(diào)用.

強(qiáng)引用`subscriber`時, `disposable`不會被調(diào)用

如果subscriber被強(qiáng)引用時, 我又想取消訂閱, 應(yīng)該怎么做呢?
subscribeNext:這個方法是有返回值的, 這個返回值其實(shí)就是上面didSubscribeblock的返回值(可以先這么理解)
我用一個變量接收一樣, 然后手動disposable即可:

手動`disposable`

那么RACDisposable是做什么用的呢?

這個類可以幫助我們?nèi)∠嗛? 當(dāng)信號發(fā)送完畢了或者失敗了之后取消訂閱, 并調(diào)用指定的代碼.
在RAC中, 還有一個類非常重要, 也很特殊, 叫做RACSubject, 這個類即可以創(chuàng)建信號, 也可以發(fā)送信號. 代碼如下:
    RACSubject *subject = [RACSubject subject];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"subject has been subscribed %@", x);
    }];
    [subject sendNext:@"subject has been sended"];
  • 在創(chuàng)建信號的時候: 這個框架重寫了init方法:
+ (instancetype)subject {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (self == nil) return nil;

    _disposable = [RACCompoundDisposable compoundDisposable];
    _subscribers = [[NSMutableArray alloc] initWithCapacity:1];
    
    return self;
}
  • 他創(chuàng)建了一個可變數(shù)組. 這個可變數(shù)組是用來添加subscriber
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        [subscribers addObject:subscriber];
    }
    
    [disposable addDisposable:[RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            // Since newer subscribers are generally shorter-lived, search
            // starting from the end of the list.
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
        }
    }]];

    return disposable;
}

  • 然后在發(fā)送信號的時候, 會遍歷這個數(shù)組, 把里面的subscriber一一取出來, 分別發(fā)送信號:
`subject`發(fā)送信號
所以, 用RACSubject的好處就是: 可以訂閱多個信號, 最后發(fā)送信號的時候一起調(diào)用.
訂閱多個信號
現(xiàn)在總結(jié)一下RACSignal和RACSubject
RACSignal

RACSubject的本質(zhì)其實(shí)和RACSignal是一樣的, 那為什么它又可以創(chuàng)建信號, 又可以訂閱信號呢?

在OC中, 是沒有多繼承的概念的, 要想實(shí)現(xiàn)多繼承的效果, 可以先繼承一個類, 然后再實(shí)現(xiàn)一個或者多個協(xié)議, 這樣就實(shí)現(xiàn)了多繼承的效果
"多繼承"

那么, 說了這么多, 這個RAC有什么用呢?

用處大了, 因?yàn)樗梢詫?shí)現(xiàn)KVO , 代理, 通知, block的功能, 集多種功能于一身, 當(dāng)你用了RAC之后, 你可以利用RAC實(shí)現(xiàn)以上設(shè)計(jì)模式能實(shí)現(xiàn)的功能.

比如現(xiàn)在有一個簡單的需求:
一個自定義的view上面有一個button, 點(diǎn)擊button之后把button的背景顏色傳給控制器的view. 這是典型的逆向傳值, 通知, 代理, block都能用. 那用RAC怎么做呢?

  • 首先是創(chuàng)建信號, 信號的創(chuàng)建需要在什么地方呢, 值從哪里傳, 信號創(chuàng)建就在哪里, 用一個懶加載創(chuàng)建subject對象:
- (RACSubject *)subject {
    if (!_subject) {
        _subject = [RACSubject subject];
    }
    return _subject;
}
  • 然后在button點(diǎn)擊的時候?qū)㈩伾盘杺鞒鋈?
- (IBAction)buttonClick:(UIButton *)sender {
    
    [self.subject sendNext:sender.backgroundColor];
}
  • 最后, 值往哪里傳, 哪里就訂閱信號:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.yfView.subject subscribeNext:^(id  _Nullable x) {
        self.view.backgroundColor = x;
    }];
    
}

就這么簡單的幾步, 就完成了需求, 非常簡單方便, 效果如下:

實(shí)現(xiàn)效果
好城丧, 現(xiàn)在看另外一個重要的類测暗, 叫做RACReplaySubject
`RACReplaySubject`是繼承自`RACSubject`的

RACReplaySubject是繼承自RACSubject

有效代碼

所以訂閱信號里的block是可以執(zhí)行的. 那么這個類和RACSubject有什么不同之處呢?

同樣有效的代碼

當(dāng)我們將訂閱信號和發(fā)送信號交換一下位置, 發(fā)現(xiàn)訂閱的block依然可以執(zhí)行, 這是跟RACSubject不同的地方. 但這是為什么呢?

這個只是初始RAC, 我也在從0開始學(xué)習(xí), 有錯誤的地方, 還請大家批評指正,共同提高.

首先來看創(chuàng)建RACReplaySubject對象的時候, 有什么不一樣

創(chuàng)建可變數(shù)組

可以看出的是, 這個類重寫了init方法, 創(chuàng)建了一個名為valuesRecieved的數(shù)組.

然后, 在發(fā)送信號的過程中:

數(shù)組保存發(fā)送的信號

在發(fā)送信號的時候, 剛才創(chuàng)建的那個valuesReceived數(shù)組就把需要發(fā)送的信號保存在數(shù)組里. 保存在數(shù)組中干嘛呢? 且看:

真正地訂閱信號
訂閱信號的具體實(shí)現(xiàn)

從上圖可以看出, 在訂閱信號的時候, 會先將valuesReceived數(shù)組中保存的信號來一波遍歷, 然后取出來, 自己發(fā)送掉. 這樣, 就算先發(fā)送信號, 在訂閱信號, 也可以完成, 因?yàn)橛嗛喰盘柕膬?nèi)部自己也發(fā)送了信號.

PS. 本人有若干成套學(xué)習(xí)視頻, 包含Java, 數(shù)據(jù)結(jié)構(gòu)與算法, iOS, 安卓, python, flutter等等, 如有需要, 聯(lián)系微信tsaievan.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夕冲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伟恶,更是在濱河造成了極大的恐慌咖耘,老刑警劉巖迅箩,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馋劈,死亡現(xiàn)場離奇詭異,居然都是意外死亡偶洋,警方通過查閱死者的電腦和手機(jī)熟吏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玄窝,“玉大人牵寺,你說我怎么就攤上這事《吡希” “怎么了缸剪?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵吗铐,是天一觀的道長东亦。 經(jīng)常有香客問我,道長唬渗,這世上最難降的妖魔是什么典阵? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮镊逝,結(jié)果婚禮上壮啊,老公的妹妹穿的比我還像新娘。我一直安慰自己撑蒜,他們只是感情好歹啼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布玄渗。 她就那樣靜靜地躺著,像睡著了一般狸眼。 火紅的嫁衣襯著肌膚如雪藤树。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天拓萌,我揣著相機(jī)與錄音岁钓,去河邊找鬼。 笑死微王,一個胖子當(dāng)著我的面吹牛屡限,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炕倘,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钧大,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了罩旋?” 一聲冷哼從身側(cè)響起拓型,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘸恼,沒想到半個月后劣挫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡东帅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年压固,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靠闭。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡帐我,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出愧膀,到底是詐尸還是另有隱情拦键,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布檩淋,位于F島的核電站芬为,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蟀悦。R本人自食惡果不足惜媚朦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望日戈。 院中可真熱鬧询张,春花似錦、人聲如沸浙炼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜗帜,卻和暖如春越妈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钮糖。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工梅掠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人店归。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓阎抒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親消痛。 傳聞我的和親對象是個殘疾皇子且叁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容