ReactiveCocoa的使用

ReactiveCocoaGitHub上一個開源的函數(shù)響應式(Functional Reactive Programming)框架漫试,提供Objective-C ReactiveObjCSwift ReactiveSwift版本谤碳。這里只針對 ReactiveObj 的使用進行講解,Swift 項目的話推薦使用 RxSwift肴颊。

為什么要使用 ReactiveCocoa?

MVC 是蘋果官方推薦的框架模式秕重,對于早期的 APP 開發(fā)來說確實能解決不少問題策治。但發(fā)展到現(xiàn)在,APP 的迭代日積月累创夜,功能也越來越復雜杭跪,Controller 就變得十分臃腫,有些老的項目里面三四千行代碼的 Controlle r比比皆是驰吓,極大減弱了代碼可閱讀性和可維護性涧尿,極其復雜的業(yè)務邏輯都揉在 Controller 甚至導致接手項目的童鞋花費大量的時間去改動極少的一部分代碼,還容易搞出問題檬贰。這時候我們就考慮使用 MVVM 來替代 MVC姑廉,遵循高內(nèi)聚、低耦合的原則進行編程翁涤。ReactiveCocoa 對于 MVVM 并不是必需品桥言,但是使用這個框架來實現(xiàn)是為了更好地將 ViewModelController 綁定在一起,以更簡潔葵礼、更優(yōu)雅的方式來實現(xiàn) MVVM 号阿。

ReactiveCocoa簡介

RAC是一個將函數(shù)響應式編程范式帶入iOS的開源庫,其兼具函數(shù)式與響應式的特性鸳粉。它是由Josh Abernathy和Justin Spahr-Summers當初在開發(fā)GitHub for Mac過程中創(chuàng)造的扔涧,靈感來源于Functional Reactive Programming。所以届谈,這么一個神奇?zhèn)ゴ蟮膸炜菀梗谷皇莻€副產(chǎn)物!而這個副產(chǎn)物比孕育它的產(chǎn)品出名的多疼约,不得不說很有意思卤档。

那么問題來了蝙泼,什么是函數(shù)響應式編程-簡稱為FRP 呢程剥?一言以蔽之,F(xiàn)RP是基于異步事件流進行編程的一種編程范式。針對離散事件序列進行有效的封裝织鲸,利用函數(shù)式編程的思想舔腾,滿足響應式編程的需要。

ReactiveCocoa基本使用

RACStream

RACStream 是一個抽象類搂擦,ReactiveCocoa工程中通常使用它的子類 RACSignalRACSequence稳诚。作為抽象類本身不提供方法的實現(xiàn),所有基于流的操作都可以建立在該類之上瀑踢。

其包含5個需要子類重寫的方法:empty扳还、return:bind:橱夭、concat:氨距、zipwith:

RACEvent

上面提到棘劣,響應式編程可以將變化的值通過數(shù)據(jù)流進行傳播俏让。為此,RAC 中定義了一個事件的概念茬暇,即:RACEvent首昔。

事件分三種類型:Next類型,Completed 類型和 Error 類型糙俗。其中 Next 類型和 Error 類型事件內(nèi)部可以承載數(shù)據(jù)勒奇,而 Completed 類型并不。

RACSignal

這是 RAC 中最基本的一個概念巧骚,中文名叫做“信號”撬陵,搞懂了這個類,就可以用 RAC 去玩耍了网缝。

信號代表的是一個隨時間而改變的值流巨税。作為一個流,可以將不斷變化的值(或者說數(shù)據(jù))向外進行傳播粉臊。想獲取一個信號中的數(shù)據(jù)草添,需要訂閱它。什么是訂閱呢扼仲?和訂閱博客远寸,訂報紙,訂牛奶一個意思屠凶。但前提是這個信號是存在的驰后,所以想要訂閱必先創(chuàng)建。反過來說矗愧,創(chuàng)建了一個信號但是并沒有訂閱它灶芝,也獲取不到其內(nèi)部的數(shù)據(jù)。(這種情況下 RACSignal 信號根本就不會向外發(fā)送數(shù)據(jù),下一篇中會詳細介紹夜涕,暫時忽略)犯犁。當一個訂閱過程結(jié)束時,如有必要去做一些清理工作(當然為了回收資源需要將信號銷毀女器,但 RAC 內(nèi)部會自動處理酸役,使用者無需關心)。綜上驾胆,一個信號完整的使用過程應該是創(chuàng)建涣澡,訂閱咙咽,清理次企。

信號被訂閱了之后,可以認為在信號源和訂閱者之間建立起了一座橋梁驾茴,通過它信號源源不斷的向訂閱者發(fā)送最新數(shù)據(jù)锅必,直到橋被銷毀事格。但是要注意,這是一條很窄而且承重很差的橋搞隐,以至于一次只能通過一條數(shù)據(jù)驹愚。如果將一條數(shù)據(jù)理解成一個人,那么通俗的說就是只有一個人通過了另一個人才能繼續(xù)過劣纲,而絕不能同時兩個人走上橋逢捺。

信號向外傳播數(shù)據(jù)的載體就是事件。其中 Next 類型事件可以承載任意類型數(shù)據(jù)-即id 癞季,甚至可以是 nil 劫瞳。但一般不用來承載錯誤類型數(shù)據(jù),因為有 Error 類型事件單獨做這件事绷柒。Completed 類型事件僅作為一個正常完成的標志志于,不承載任何數(shù)據(jù)。

信號被訂閱了之后废睦,可以發(fā)送任意多個 Next 事件(當然可以是0)伺绽,直到發(fā)送了一個 Completed 事件或者一個 Error 事件,這兩個事件都標志著結(jié)束嗜湃,區(qū)別在于 Completed 事件表示正常結(jié)束奈应,而 Error 事件表示因為某種錯誤而結(jié)束。只要兩者之一被發(fā)送了购披,整個訂閱過程就結(jié)束了杖挣。

- (void)racCreateSignalDemo {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSString *jsonString = @"";
        NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
        NSError *error;
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
        if (!error) {
            [subscriber sendNext:dic];
            [subscriber sendCompleted];
        }else {
            [subscriber sendError:error];
        }
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信號被銷毀"); //當信號發(fā)送完成或者發(fā)送錯誤
        }];
    }];
    [signal subscribeNext:^(NSDictionary * _Nullable x) {
        NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"%@", error);
    } completed:^{
        NSLog(@"completed!");
    }];
}
RACSubscriber

訂閱者,這是一個協(xié)議刚陡,只要遵循這個協(xié)議就能成為訂閱者惩妇,用于發(fā)送信號株汉。

@protocol RACSubscriber <NSObject>
@required
- (void)sendNext:(nullable id)value;
- (void)sendError:(nullable NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end
RACDisposable

用于取消訂閱或者清理資源,當信號發(fā)送完成或發(fā)送錯誤時會自動執(zhí)行 disposableWithBlock 屿附。

- (void)racDisposeDemo {
    RACSignal *s = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"hi~"];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"disposable..."); // disposable...
        }];
    }];
    RACDisposable *d = [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // hi~
    }];
    [d dispose]; //可以主動取消訂閱
}
RACScheduler

是對 GCD 的封裝郎逃,并且支持取消操作哥童,取消操作其實是沒有執(zhí)行回調(diào)挺份。

RACObserve

RAC() 可以將 Signal 發(fā)出事件的值賦值給某個對象的某個屬性,其參數(shù)為對象名和屬性名

RACObserve() 參數(shù)為對象名和屬性名贮懈,新建一個 Signal 并對對象的屬性的值進行觀察匀泊,當值變化時 Signal 會發(fā)出事件

比較常見的用法就是 RACObserve(someTarget, someProperty),但是大家了解 RACObserve(target.someTarget, someProperty)RACObserve(target, someTarget.someProperty) 之間的區(qū)別么朵你?具體可以看以下代碼片段以及執(zhí)行的結(jié)果

self.label = [UILabel new];
self.label.text = @"123";

[RACObserve(self.label, text) subscribeNext:^(id x) {
    NSLog(@"RACObserve(self.label, text) 的方式 %@", x);
}];

[RACObserve(self, label.text) subscribeNext:^(id x) {
    NSLog(@"RACObserve(self, label.text) 的方式 %@", x);
}];

self.label.text = @"1234";
self.label = [UILabel new];
self.label.text = @"12345";

// output

RACObserve(self.label, text) 的方式 123
RACObserve(self, label.text) 的方式 123
RACObserve(self, label.text) 的方式 1234
RACObserve(self.label, text) 的方式 1234
RACObserve(self, label.text) 的方式 (null)
RACObserve(self, label.text) 的方式 12345

以上面代碼為例各聘,RACObserve(self.label, text) 其實是監(jiān)聽 self.label 這個對象的 text 屬性,所以當這個對象 text 發(fā)生變化時抡医,第一個是 block 是能夠收到回調(diào)的躲因,但是當 self.label 被重新賦值后,原來的 label 無人持有相當于變成了 nil忌傻,所以第一個 block 將不再生效大脉。而 RACObserve(self, label.text) 監(jiān)聽的是 self,然后 keyPathlabel.text水孩,所以當 label 或者其 text 發(fā)生變化都會觸發(fā)這個回調(diào)镰矿。所以區(qū)別在于 target 以及 keyPath 的設置。

雙向綁定:
假設我們的 UIViewController 里有一個 UITextField 和一個 UILabel俘种, 我們希望在 UITextField 輸入時秤标, UILabel 里面同步顯示一樣的內(nèi)容。我們通過把 UITextFieldUILabel 綁定到同一個 model 宙刘,也就是 UIViewControllername 屬性上 來實現(xiàn)這一點苍姜。

RAC(self.nameTextfield, text) = RACObserve(self, name);
RAC(self.nameLabel, text) = RACObserve(self, name);
    
[self.nameTextfield.rac_textSignal subscribeNext:^(id x) {
    self.name = x;
}];
RACSubject

RACSubjectRACSignal 的子類,自身可以充當信號悬包,也可以發(fā)送信號怖现。

- (void)racSubjectDemo {
    RACSubject *subject = [RACSubject subject];
    [subject subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x);
    }];
    [subject sendNext:@"hi~"];
}

RACSubject 通常情況是用來替代 block 回調(diào)。

@interface ViewController : UIViewController
@property (nonatomic, strong, readonly) RACSubject *successSubject;
@end

@implementation ViewController
@synthesize successSubject = _successSubject;
- (void)viewDidLoad {
    [super viewDidLoad];
    @weakify(self);
    [[RACScheduler mainThreadScheduler] afterDelay:3.0 schedule:^{
        @strongify(self);
        [self.successSubject sendNext:@"success!"];
    }];
}
- (RACSubject *)successSubject {
    if (!_successSubject) {
        _successSubject = [RACSubject subject];
    }
    return _successSubject;
}
@end
ViewController *vc = [ViewController new];
[vc.successSubject subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@", x);
}];
RACReplaySubject

RACSubjectRACReplaySubject 的區(qū)別:

RACSubject 需要先訂閱再發(fā)送數(shù)據(jù)玉罐,這樣才能接收到數(shù)據(jù)屈嗤。 RACReplaySubject在發(fā)送數(shù)據(jù)之前訂閱能接收到數(shù)據(jù),在發(fā)送數(shù)據(jù)之后的對象再訂閱也可以接收到數(shù)據(jù)吊输。

- (void)racReplaySubjectDemo {
    RACReplaySubject<NSString *> *replaySubject = [RACReplaySubject subject];
    [replaySubject subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); //hi~
    }];
    [replaySubject sendNext:@"hi~"];
    [replaySubject subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); //hi~
    }];
}
RACCommand

RACCommand 并不表示數(shù)據(jù)流饶号,它只是一個繼承自 NSObject 的類,但是它卻可以用來創(chuàng)建和訂閱用于響應某些事件的信號季蚂。

@interface RACCommand<__contravariant InputType, __covariant ValueType> : NSObject

@end

它本身并不是一個 RACStream 或者 RACSignal 的子類茫船,而是一個用于管理 RACSignal 的創(chuàng)建與訂閱的類琅束。

ReactiveCocoa 中的 FrameworkOverview 部分對 RACCommand 有這樣的解釋:

A command, represented by the RACCommand class, creates and subscribes to a signal in response to some action. This makes it easy to perform side-effecting work as the user interacts with the app.

在用于與 UIKit 組件進行交互或者執(zhí)行包含副作用的操作時,RACCommand 能夠幫助我們更快的處理并且響應任務算谈,減少編碼以及工程的復雜度涩禀。

具體使用請參考:RACCommand的使用

RACTuple

你一定為當跨方法傳多個值時是選擇每個值作為一個參數(shù)還是選擇傳一個字典而糾結(jié)過。第一種方法可能會令方法名特別長然眼,第二種方法根本不知道字典里面都有哪些 key艾船。為了避免陷入此尷尬,RAC 定義了一個 RACTuple 類高每。它可以包含多個對象屿岂。酷斃了鲸匿!

為了簡化使用爷怀,RAC 額外又定義了兩個宏:RACTuplePack(...)RACTupleUnpack(...),分別用來“裝包”和“拆包”带欢。

NSString *when = @"2021-01-13";
NSString *where = @"Beijing";
NSString *who = @"Fengxiao";
RACTuple *tuple = RACTuplePack(when, where, who);
RACTupleUnpack(NSString *time, NSString *place, NSString *person) = tuple;
NSLog(@"Three element are : %@, %@, %@",time,place,person);

執(zhí)行結(jié)果:


執(zhí)行結(jié)果
RACSequence
  • 數(shù)組
- (void)arrayDemo {
    NSArray *array = @[@"A", @"B", @1, @2];
    [array.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"value:%@", x);
    }];
}
  • 字典
- (void)racDictionaryDemo {
    NSDictionary *dict = @{@"name": @"Persona", @"address": @"Tianfu Software Park"};
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
        RACTupleUnpack(NSString *key, NSString *value) = x;
        NSLog(@"key is: %@, value is: %@",key, value);
    }];
}
RACMulticastConnection

用于當一個信號需要被多次訂閱运授,為了保證創(chuàng)建信號時避免多次調(diào)用創(chuàng)建信號中的block,可以使用 RACMulticastConnection 來解決乔煞。

- (void)racRACSignalDemo {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"發(fā)送數(shù)據(jù)");
        [subscriber sendNext:@"hello"];
        return nil;
    }];
    [signal subscribeNext:^(id x) {
        NSLog(@"接受數(shù)據(jù):%@", x);
    }];
    [signal subscribeNext:^(id x) {
        NSLog(@"接受數(shù)據(jù):%@", x);
    }];
}
2021-01-13 14:46:32.438670+0800 001---RACCommand[12454:276098] 發(fā)送數(shù)據(jù)
2021-01-13 14:46:32.438768+0800 001---RACCommand[12454:276098] 接受數(shù)據(jù):hello
2021-01-13 14:46:32.439045+0800 001---RACCommand[12454:276098] 發(fā)送數(shù)據(jù)
2021-01-13 14:46:32.439124+0800 001---RACCommand[12454:276098] 接受數(shù)據(jù):hello
- (void)racRACMulticastConnectionDemo {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"發(fā)送數(shù)據(jù)");
        [subscriber sendNext:@"hello"];
        return nil;
    }];
    RACMulticastConnection *connect = [signal publish];
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"訂閱者1:%@", x);
    }];
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"訂閱者2 %@", x);
    }];
    [connect connect];
}
2021-01-13 14:47:08.712054+0800 001---RACCommand[12487:277535] 發(fā)送數(shù)據(jù)
2021-01-13 14:47:08.712182+0800 001---RACCommand[12487:277535] 訂閱者1:hello
2021-01-13 14:47:08.712280+0800 001---RACCommand[12487:277535] 訂閱者2 hello
數(shù)據(jù)綁定
RAC()

用于和某個對象的屬性綁定

- (void)racTextFieldDemo {
    RAC(self, name) = self.textField.rac_textSignal;
    [self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", self.name);
    }];
}
RACChannelTo()

雙向綁定

- (void)racRACChannelToDemo {
    RACChannelTo(self, name) = RACChannelTo(self.textField, text);
    self.textField.text = @"1";
    NSLog(@"%@", self.name); // 1
    self.name = @"2";
    NSLog(@"%@", self.textField.text); // 2
}
定時
interval: 定時器
- (void)racTimerDemo {
    __block NSInteger second = 0.f;
    NSInteger duration = 30.f;
    [[[[RACSignal interval:1.0 onScheduler:RACScheduler.mainThreadScheduler] take:duration] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(NSDate * _Nullable x) {
        second++;
        NSLog(@"%lds", second);
    } completed:^{
        NSLog(@"completed!");
    }];
}
delay: 延遲接收
- (void)racDelayDemo {
    RACSignal *s = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"delay"];
        [subscriber sendCompleted];
        return nil;
    }] delay:5.0];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
}
timeout: 超時
- (void)racTimeOutDemo {
    RACSignal *s = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
          //TODO
        return nil;
    }] timeout:5.0 onScheduler:RACScheduler.currentScheduler];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"result: %@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"error: %@", error); //error: Error Domain=RACSignalErrorDomain Code=1 "(null)"
    }];
}
基本使用
UIGestureRecognizer
- (void)racTapGestureDemo {
    [self.tapGesture.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
        NSLog(@"%@", x); // <UITapGestureRecognizer: 0x282c29600; state = Ended; view = <UIView 0x100c11030>; target= <(action=sendNext:, target=<RACPassthroughSubscriber 0x281030180>)>>
    }];
}
delegate
 #pragma mark -遵循<UITextFieldDelegate>
- (void)racDelegateDemo {
    self.nameTextField.delegate = self;
    [[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(RACTuple * _Nullable x) {
        NSLog(@“%@“, x);
    }];
}
NSNotificationCenter
- (void)racNotificationDemo {
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"%@", x); //NSConcreteNotification 0x280891230 {name = UIApplicationDidEnterBackgroundNotification; object = <UIApplication: 0x1039034b0>}
    }];
}
KVO
- (void)racRACObserveDemo {
    [RACObserve(self, nam
e) subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UI交互
UIButton
- (void)racButtonDemo {
    [[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"%@", x); //<UIButton: 0x101207950; frame = (137.5 100; 100 30); opaque = NO; layer = <CALayer: 0x28191b160>>
    }];
}
UIDatePicker
- (void)racDatePickerDemo {
    [[self.datePicker rac_newDateChannelWithNilValue:nil] subscribeNext:^(NSDate * _Nullable x) {
        NSLog(@"%@", x); //Tue Nov 19 16:25:00 2020
    }];
}
UIImagePickerController
- (void)racImagePickerGestureDemo {
    [[self.imagePicker rac_imageSelectedSignal] subscribeNext:^(NSDictionary * _Nullable x) {
        NSLog(@"%@", x);
        /*
         UIImagePickerControllerCropRect = "NSRect: {{0, 0}, {1024, 1024}}";
         UIImagePickerControllerEditedImage = "<UIImage:0x2822b9830 anonymous {750, 750}>";
         UIImagePickerControllerImageURL = "file:///private/var/mobile/Containers/Data/Application/BF742343-BEF3-4BE2-BE73-4C6103E07B68/tmp/55783D0E-CE19-4FF6-B1F7-EA754C6C09A4.jpeg";
         UIImagePickerControllerMediaType = "public.image";
         UIImagePickerControllerOriginalImage = "<UIImage:0x2822b94d0 anonymous {1024, 1024}>";
         UIImagePickerControllerReferenceURL = "assets-library://asset/asset.JPG?id=C33A82F5-AFB0-4521-8448-9ADD718601B3&ext=JPG";
         */
    }];
}
UISegmentedControl
- (void)racSegmentDemo {
    [[self.segment rac_newSelectedSegmentIndexChannelWithNilValue:nil] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UISlider
- (void)racSliderDemo {
    [[self.slider rac_newValueChannelWithNilValue:0] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UISwitch
- (void)racSwitchDemo {
    [[self.aswitch rac_newOnChannel] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UIStepper
- (void)racStepperDemo {
    [[self.stepper rac_newValueChannelWithNilValue:nil] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UITextField(UITextView跟它類似)
- (void)racTextFieldDemo {
    // 監(jiān)聽textField文本的變化
    [self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x);
    }];
    // 雙向綁定
    RACChannelTo(self.label, text) = self.textField.rac_newTextChannel;
}

ReactiveCocoa組合操作符

映射
flattenMap:

flattenMap 作用:把原信號的內(nèi)容映射成一個新信號吁朦,并 return 返回給一個 RACStream 類型數(shù)據(jù)。實際上是根據(jù)前一個信號傳遞進來的參數(shù)重新建立了一個信號瘤缩,這個參數(shù)喇完,可能會在創(chuàng)建信號的時候用到,也有可能用不到剥啤。

- (void)racFlattenMapDemo {
    [[self.accountTF.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
        return [RACReturnSignal return:[NSString stringWithFormat:@"自定義了返回信號:%@",value]];
    }]
     subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
}

依次輸入5個1打印如下:


返回結(jié)果
map:

map 作用:是將原信號的值自定義為新的值锦溪,不需要再返回 RACStream 類型,value 為源信號的內(nèi)容府怯,將 value 處理好的內(nèi)容直接返回即可刻诊。map 方法將會創(chuàng)建一個一模一樣的信號,只修改其 value牺丙。

- (void)racMapDemo {
    [[self.accountTF.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
        return [NSString stringWithFormat:@"自定義了返回信號:%@",value];
    }]subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
}

依次輸入12345打印如下:

返回結(jié)果

總結(jié)一下则涯,同樣作為映射命令,在實際開發(fā)過程中冲簿,如果使用 map 命令粟判,則 block 代碼塊中 return 的是對象類型;而 flattenMap 命令 block 代碼塊中 return 的是一個新的信號峦剔。

組合
concat:

作用:按 concat: 的順序來拼接多個信號档礁,并按照這個順序來接收信號源的值。多個任務串行的方式來執(zhí)行吝沫,并依次返回結(jié)果呻澜。
場景: 有兩個接口請求递礼,第二個接口的請求參數(shù)依賴第一個接口請求返回的數(shù)據(jù)。
注意:前一個信號必須要完成了羹幸,也就是要調(diào)用 sendCompleted 脊髓,那么后一個信號才能執(zhí)行;如果前一個信號 sendError: 的話栅受,后面的信號都無法執(zhí)行将硝。

- (void)racConcatDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACReplaySubject subject];
    NSMutableArray *array = [NSMutableArray array];
    [[subjectA concat:subjectB] subscribeNext:^(id  _Nullable x) {
        [array addObject:x]; // 即使subjectB先sendNext值,但是根據(jù)concat的順序窘疮,這里的訂閱也是先收到subjectB的值
    }];
    [subjectB sendNext:@"b"];
    [subjectA sendNext:@"a"];
    [subjectA sendCompleted];
    NSLog(@"%@", array); // (a, b)
}
- (void)racConcatDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:5.0 schedule:^{ // → ①
            [subscriber sendNext:@"A"]; // → ②
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{ // // → ③
            [subscriber sendNext:@"B"]; // → ④
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"C"]; // → ⑤
        [subscriber sendCompleted];
        return nil;
    }];
    //第一個信號必須發(fā)送完成袋哼,第二個信號才會被激活. signalA發(fā)送完成后冀墨,也就是說signalA的block內(nèi)必須執(zhí)行[subscriber sendCompleted]闸衫,才會執(zhí)行signalB的sendNext發(fā)送數(shù)據(jù)
    [[[signalA concat:signalB] concat:signalC] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
}
then:

作用:用于連接兩個信號(或者是多個信號),只有前一個信號完成了诽嘉,才會連接到后一個信號蔚出。按順序來監(jiān)聽信號,而且只能獲得最后一個信號的值虫腋。
場景:與 concat 很相似骄酗。
注意:前一個信號必須要完成了,也就是要調(diào)用 sendCompleted悦冀,那么后一個信號才能執(zhí)行趋翻;如果前一個信號 sendError: 的話,后面的信號都無法執(zhí)行盒蟆。

- (void)racThenDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@1]; // → ①
        [subscriber sendCompleted]; // → ②
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@2]; // → ④
        [subscriber sendCompleted]; // → ⑥
        return nil;
    }];
    [[signalA then:^RACSignal * _Nonnull{
        return signalB; // → ③
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // → ⑤
    }];
}
merge:

作用:把多個信號合并為一個信號踏烙,任何一個信號發(fā)送新的數(shù)據(jù)就會觸發(fā) subscribeNext。接收的順序是信號的發(fā)送順序历等。
場景:多個并發(fā)請求讨惩,對順序沒有要求,誰先返回結(jié)果寒屯,訂閱者就先收到誰發(fā)送的值荐捻。
注意:一旦某一個信號觸發(fā) sendError,那么就會終止接收其他信號源再發(fā)送的數(shù)據(jù)寡夹。比如 A-B-C-D 处面,B 觸發(fā)了錯誤,將不再接收 B-C-D 發(fā)送的值菩掏。

- (void)racMergeDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    RACSubject *subjectC = [RACSubject subject];
    RACSignal *single = [[subjectA merge:subjectB] merge:subjectC];
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 第一次接收到:A魂角,第二次接收到:B,第三次接收到:C
    }];
    [subjectA sendNext:@"A"];
    [subjectC sendNext:@"C"];
    [subjectB sendNext:@"B"];
}
// A C B
- (void)racMergeDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:5.0 schedule:^{ // → ①
            [subscriber sendNext:@"A"]; // → ④
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{ // → ②
            [subscriber sendNext:@"B"]; // → ③
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    // 合并信號, 任何一個信號發(fā)送數(shù)據(jù)患蹂,都能監(jiān)聽到
    [[signalA merge:signalB] subscribeNext:^(id x) {
        NSLog(@"%@", x); // 第一次接收到B或颊,第二次接收到A
    }];
}
zip:

作用:將兩個 signal 壓縮成一個 signal砸紊,并且兩個 signal 都要發(fā)送 value,訂閱者才會接收到數(shù)據(jù)囱挑,接收到的數(shù)據(jù)是一個元祖對象醉顽。
場景:兩個任務并行執(zhí)行,訂閱者拿到最終的執(zhí)行結(jié)果平挑。 zipWith: 的使用最好不要搞太復雜游添,通常壓縮兩個異步任務即可。
注意:?這里有個特殊的現(xiàn)象:無論是 [signalA zipWith:signalB] 還是 [signalB zipWith: signalA]通熄,根據(jù)先發(fā)送數(shù)據(jù)的信號源的 sendNext 次數(shù)唆涝,去匹配后一個信號源 sendNext。假設 signalA 先發(fā)送了 2 次唇辨,即使 signalB 再發(fā)送了 3 次廊酣,最終訂閱者也只會收到 2 次,多出來的數(shù)據(jù)將會被過濾掉赏枚,執(zhí)行順序是A1A2B1subscribeNextB2subscribeNextB3 →無法匹配 signalA 數(shù)據(jù)→終止亡驰。

- (void)racZipWithDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:2.0 schedule:^{
            [subscriber sendNext:@"A-1"]; // → ②
        }];
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{
            [subscriber sendNext:@"A-2"]; // → ④
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:1.0 schedule:^{
            [subscriber sendNext:@"B-1"]; // → ①
        }];
        [[RACScheduler scheduler] afterDelay:4.0 schedule:^{
            [subscriber sendNext:@"B-2"]; // → ⑤
        }];
        return nil;
    }];
    [[RACSignal zip:@[signalA, signalB]] subscribeNext:^(id x) {
        NSLog(@"%@", x); // → ③ <RACTwoTuple: 0x282fd84f0> ("A-1","B-1")   // → ⑥<RACTwoTuple: 0x282fd9070> ("A-2","B-2")
    }];
}
zipWith:
- (void)racZipWithDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    RACSignal *single = [subjectA zipWith:subjectB];
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    [subjectA sendNext:@"A"];
    [subjectB sendNext:@"B"];
    // (A, B)
}
- (void)racZipWithDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:2.0 schedule:^{ // → ①
            [subscriber sendNext:@"A-1"]; // → ③
            [subscriber sendNext:@"A-2"]; // → ④
            [subscriber sendNext:@"A-3"]; // → ⑤
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{ // → ②
            [subscriber sendNext:@"B-1"]; // → ⑥
            [subscriber sendNext:@"B-2"]; // → ⑧
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    [[signalA zipWith:signalB] subscribeNext:^(id x) {
        NSLog(@"%@", x); // → ⑦ <RACTwoTuple: 0x282fd84f0> ("A-1","B-1")   // → ⑨<RACTwoTuple: 0x282fd9070> ("A-2","B-2")
    }];
}
combineLatest:

作用:將數(shù)組中的多個 signal 打包成一個 signal,并且每個 signal 都要發(fā)送數(shù)據(jù)饿幅,才會接收到數(shù)據(jù)凡辱。
場景:監(jiān)聽手機號 textField 和驗證碼 textField 的輸入情況
注意
subjectA 發(fā)送了新的值,這時候訂閱者是不會接收到數(shù)據(jù)的栗恩,必須 subjectB 也發(fā)送了數(shù)據(jù)透乾,那么訂閱者才會接收到數(shù)據(jù)。
subjectA發(fā)送1磕秤,subjectB 發(fā)送2乳乌,這時候訂閱者接收到的最新數(shù)據(jù)是(1, 2);然后subjectA 發(fā)送 3亲澡,那么 subjectA 更新了數(shù)據(jù)钦扭,這時候訂閱者接收到的數(shù)據(jù)就是(3, 2)

- (void)racCombineLatestDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:2 schedule:^{
            [subscriber sendNext:@"A1"]; // -> ②
        }];
        [[RACScheduler scheduler] afterDelay:4 schedule:^{
            [subscriber sendNext:@"A2"]; // -> ④
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:1 schedule:^{
            [subscriber sendNext:@"B1"]; // -> ①
        }];
        [[RACScheduler scheduler] afterDelay:3 schedule:^{
            [subscriber sendNext:@"B2"]; // -> ③
        }];
        [[RACScheduler scheduler] afterDelay:5 schedule:^{
            [subscriber sendNext:@"B3"]; // -> ⑤
        }];
        return nil;
    }];
    RACSignal *combineSianal = [RACSignal combineLatest:@[signalA, signalB]];
    [combineSianal subscribeNext:^(id x) {
        NSLog(@"combineLatest:%@", x); //(A1, B1)床绪,(A1, B2)客情,(A2, B2),(A2, B3)
    }];
}
combineLatestWith:
- (void)racCombineLatestWithDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:2 schedule:^{
            [subscriber sendNext:@"A1"]; // -> ②
        }];
        [[RACScheduler scheduler] afterDelay:4 schedule:^{
            [subscriber sendNext:@"A2"]; // -> ④
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler scheduler] afterDelay:1 schedule:^{
            [subscriber sendNext:@"B1"]; // -> ①
        }];
        [[RACScheduler scheduler] afterDelay:3 schedule:^{
            [subscriber sendNext:@"B2"]; // -> ③
        }];
        [[RACScheduler scheduler] afterDelay:5 schedule:^{
            [subscriber sendNext:@"B3"]; // -> ⑤
        }];
        return nil;
    }];
    RACSignal *combineSianal = [signalA combineLatestWith:signalB];
    [combineSianal subscribeNext:^(id x) {
        NSLog(@"combineLatest:%@", x); //(A1, B1)癞己,(A1, B2)膀斋,(A2, B2),(A2, B3)
    }];
}
reduce:

combineLatest: reduce:
還有 zip: reduce: 以及其他的痹雅,這里以 combineLatest: reduce: 為例:
將打包的多個 signalvalue仰担,重新聚合為一個新的 value

- (void)racCombineLatestReduceDemo {
    RACSignal *s1 = RACObserve(self.phoneTextfield, text);
    RACSignal *s2 = RACObserve(self.codeTextfield, text);
    RACSignal *validSignal = [RACSignal combineLatest:@[s1, s2] reduce:^(NSString *phone, NSString *code){
        return @(phone.length == 11 && code.length == 6);
    }];
}
過濾
filter:

過濾掉無法滿足條件的绩社。

- (void)racFilterDemo {
    [[[self.textField rac_textSignal] filter:^BOOL(NSString * _Nullable value) {
        return value.length < 10;
    }] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); // 只能接收到字符串長度小于10的value
    }];
}
ignore:

忽略掉特定的值摔蓝。

- (void)racIgnoreDemo {
    [[[self.textField rac_textSignal] ignore:@"."] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); // 這里文本輸入框里的內(nèi)容和條件進行比較
    }];
}
distinctUntilChanged:

當前的 value 與上次相比有變化才會收到 value赂苗,否則會被忽略掉。
應用場景:針對刷新 UI贮尉,如果數(shù)據(jù)沒有發(fā)生改變的話拌滋,就沒必要浪費資源去刷新UI

- (void)racDistinctUntilChangedDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject distinctUntilChanged] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 1, 2
    }];
    [subject sendNext:@1];
    [subject sendNext:@1]; // 第二次發(fā)送的數(shù)據(jù)對比上次猜谚,是沒有發(fā)生改變的败砂,這種就會被忽略掉
    [subject sendNext:@2];
}
take:

一共取 N 次信號發(fā)送的 value

- (void)racTakeDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject take:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 1, 2
    }];
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3]; // 因為只會取兩次魏铅,所以第三次發(fā)送的數(shù)據(jù)舊被忽略掉了
    [subject sendCompleted];
}
takeLast:

取結(jié)束前的 N 次信號發(fā)送的 value昌犹,必須要調(diào)用 sendCompleted

- (void)racTakeLastDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject takeLast:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 2, 3
    }];
    [subject sendNext:@1]; // 從第二次開始取览芳,所以第一次發(fā)送的數(shù)據(jù)被忽略掉了
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendCompleted];
}
takeUntil:

直到 subjectB 發(fā)送后斜姥,subjectA 便無法再接收到數(shù)據(jù)。

- (void)racTakeUntilDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    [[subjectA takeUntil:subjectB] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 1
    }];
    [subjectA sendNext:@1];
    [subjectB sendNext:@2];
    [subjectA sendNext:@3];
}
switchToLatest:

signalOfSignals路操,取信號中的信號疾渴。

- (void)racSwitchToLatestDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    [[subjectA switchToLatest] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    [subjectA sendNext:subjectB];
    [subjectB sendNext:@"signal in signal"];
}

也可以用于 RACCommand 的訂閱

- (void)racSwitchToLatestDemo {
    [self.command.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        //TODO
        NSLog(@"%@", x);
    }];
    [self.command.errors subscribeNext:^(NSError * _Nullable x) {
        //TODO
    }];
    [self.command execute:nil];
}
skip:

跳過 N 次接收 value千贯。

- (void)racSkipDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject skip:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 3, 4
    }];
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendNext:@4];
}
重復操作
retry:

只要失敗sendError:屯仗,就會循環(huán)執(zhí)行 block 內(nèi)的代碼,直到成功sendNext:為止搔谴;

- (void)racRetryDemo {
    __block int i = 0;
    RACSignal *s = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        if (i > 3) {
            [subscriber sendNext:[NSString stringWithFormat:@"i【%d】> 3", i]];
        }else {
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:1 userInfo:@{NSLocalizedDescriptionKey: @"變量小于等于3"}];
            [subscriber sendError:error];
        }
        i++;
        return nil;
    }];
    [[s retry] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"%@", error);
    }];
}
replay:

如果不使用 replay 的話魁袜,多次訂閱會多次執(zhí)行 signal 內(nèi)的代碼;如果使用了 replay 的話敦第,多次訂閱也只會執(zhí)行一次 signal 內(nèi)的代碼峰弹。

- (void)racRelayDemo {
    RACSignal *s = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"hi~"];
        [subscriber sendCompleted];
        return nil;
    }] replay];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"第一次:%@", x);
    }];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"第二次:%@", x);
    }];
}
throttle:

當某個 signal 發(fā)送比較頻繁時,可以使用 throttle 達到節(jié)流的目的芜果,throttle 代表多少秒之后才會訂閱鞠呈。

- (void)racThrottleDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject throttle:3] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    int i = 1000;
    while (i) {
        [subject sendNext:@(i)];
        i--;
    }
    dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(delayTime, dispatch_get_main_queue(), ^{
        [subject sendNext:@"hi~"];
    });
}
其他
doNext:和doError:

它們將會在訂閱收到數(shù)據(jù)之前被調(diào)用,通常在訂閱接收到數(shù)據(jù)之前右钾,doNext:doError: 可以先拿到數(shù)據(jù)做一些操作蚁吝,但是并不會影響訂閱原本要接收的數(shù)據(jù)。

- (RACCommand *)problemTypeCommand {
    if (!_problemTypeCommand) {
        @weakify(self);
        _problemTypeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSNumber *input) {
            @strongify(self);
            self.problemTypeTemp = nil;
            return [[[[Request fetchProblemTypesSecondStepWithCourseId:self.courseId parentId:input] collect] doNext:^(NSArray *x) {
                  @strongify(self);
                self.problemTypeTemp = [NSArray arrayWithArray:x];
            }] doError:^(NSError * _Nonnull error) {
                  @strongify(self);
                self.problemTypeTemp = nil;
            }];
        }];
    }
    return _problemTypeCommand;
}
collect

將信號發(fā)送的數(shù)據(jù)收集起來舀射,訂閱將會收到一個裝著這些數(shù)據(jù)的數(shù)組

NSArray *stuInfoArray = @[@{@"name": @"John", @"sex": @"男", @"age": @"18", @"Height": @"180"},
                          @{@"name": @"Tony", @"sex": @"男", @"age": @"16", @"Height": @"172"},
                          @{@"name": @"Linda", @"sex": @"女", @"age": @"13", @"Height": @"138"}];
[[[stuInfoArray.rac_sequence.signal map:^id _Nullable(NSDictionary *value) {
    return [MTLJSONAdapter modelOfClass:Student.class fromJSONDictionary:value error:nil];
}] collect] subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@", x);
}];
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窘茁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脆烟,更是在濱河造成了極大的恐慌山林,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邢羔,死亡現(xiàn)場離奇詭異驼抹,居然都是意外死亡桑孩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門框冀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洼怔,“玉大人,你說我怎么就攤上這事左驾×土ィ” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵诡右,是天一觀的道長安岂。 經(jīng)常有香客問我,道長帆吻,這世上最難降的妖魔是什么域那? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮猜煮,結(jié)果婚禮上次员,老公的妹妹穿的比我還像新娘。我一直安慰自己王带,他們只是感情好淑蔚,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愕撰,像睡著了一般刹衫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搞挣,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天带迟,我揣著相機與錄音,去河邊找鬼囱桨。 笑死仓犬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的舍肠。 我是一名探鬼主播搀继,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼貌夕!你這毒婦竟也來了律歼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤啡专,失蹤者是張志新(化名)和其女友劉穎险毁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡畔况,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年鲸鹦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跷跪。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡馋嗜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吵瞻,到底是詐尸還是另有隱情葛菇,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布橡羞,位于F島的核電站眯停,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏卿泽。R本人自食惡果不足惜莺债,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望签夭。 院中可真熱鬧齐邦,春花似錦、人聲如沸第租。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煌妈。三九已至儡羔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間璧诵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工仇冯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留之宿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓苛坚,卻偏偏與公主長得像比被,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泼舱,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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