使用ReactiveCocoa實現iOS平臺響應式編程

使用ReactiveCocoa實現iOS平臺響應式編程

ReactiveCocoa和響應式編程

在說ReactiveCocoa以前,先要介紹一下FRP(Functional Reactive Programming,響應式編程),在維基百科中有這樣一個例子介紹:html

在命令式編程環境中,a = b + c 表示將表達式的結果賦給a,而以後改變b或c的值不會影響a。但在響應式編程中,a的值會隨着b或c的更新而更新。react

Excel就是響應式編程的一個例子。單元格能夠包含字面值或相似」=B1+C1″的公式,而包含公式的單元格的值會依據其餘單元格的值的變化而變化 。ios

而ReactiveCocoa簡稱RAC,就是基於響應式編程思想的Objective-C實踐,它是Github的一個開源項目,你能夠在這裏找到它。git

關於FRP和ReactiveCocoa能夠去看leezhong的這篇blog,圖文並茂,講的很好。github

ReactiveCocoa框架概覽

先來看一下leezhong再博文中提到的比喻,讓你對有個ReactiveCocoa很好的理解:編程

能夠把信號想象成水龍頭,只不過裏面不是水,而是玻璃球(value),直徑跟水管的內徑同樣,這樣就能保證玻璃球是依次排列,不會出現並排的狀況(數據都是線性處理的,不會出現併發狀況)。水龍頭的開關默認是關的,除非有了接收方(subscriber),纔會打開。這樣只要有新的玻璃球進來,就會自動傳送給接收方。能夠在水龍頭上加一個過濾嘴(filter),不符合的不讓經過,也能夠加一個改動裝置,把球改變成符合本身的需求(map)。也能夠把多個水龍頭合併成一個新的水龍頭(combineLatest:reduce:),這樣只要其中的一個水龍頭有玻璃球出來,這個新合併的水龍頭就會獲得這個球。vim

下面我來逐一介紹ReactiveCocoa框架的每一個組件網絡

Streams

Streams 表現爲RACStream類,能夠看作是水管裏面流動的一系列玻璃球,它們有順序的依次經過,在第一個玻璃球沒有到達以前,你無法得到第二個玻璃球。
RACStream描述的就是這種線性流動玻璃球的形態,比較抽象,它自己的使用意義並不很大,通常會以signals或者sequences等這些更高層次的表現形態代替。併發

Signals

Signals 表現爲RACSignal類,就是前面提到水龍頭,ReactiveCocoa的核心概念就是Signal,它通常表示將來要到達的值,想象玻璃球一個個從水龍頭裏出來,只有了接收方(subscriber)才能獲取到這些玻璃球(value)。app

Signal會發送下面三種事件給它的接受方(subscriber),想象成水龍頭有個指示燈來彙報它的工做狀態,接受方經過-subscribeNext:error:completed:對不一樣事件做出相應反應

  • next 從水龍頭裏流出的新玻璃球(value)

  • error 獲取新的玻璃球發生了錯誤,通常要發送一個NSError對象,代表哪裏錯了

  • completed 所有玻璃球已經順利抵達,沒有更多的玻璃球加入了

一個生命週期的Signal能夠發送任意多個「next」事件,和一個「error」或者「completed」事件(固然「error」和「completed」只可能出現一種)

Subjects

subjects 表現爲RACSubject類,能夠認爲是「可變的(mutable)」信號/自定義信號,它是嫁接非RAC代碼到Signals世界的橋樑,頗有用。嗯。。。 這樣講仍是很抽象,舉個例子吧:

1

2

3

RACSubject *letters = [RACSubject subject];

RACSignal *signal = [letters sendNext:@"a"];

 

能夠看到@"a"只是一個NSString對象,要想在水管裏順利流動,就要借RACSubject的力。

Commands

command 表現爲RACCommand類,偷個懶直接舉個例子吧,好比一個簡單的註冊界面:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

    RACSignal *formValid = [RACSignal

        combineLatest:@[

            self.userNameField.rac_textSignal,

            self.emailField.rac_textSignal,

        ]

        reduce:^(NSString *userName, NSString *email) {

            return @(userName.length > 0

                    && email.length > 0);

        }];

 

   RACCommand *createAccountCommand = [RACCommand commandWithCanExecuteSignal:formValid];

   RACSignal *networkResults = [[[createAccountCommand

       addSignalBlock:^RACSignal *(id value) {

           //... 網絡交互代碼

       }]

       switchToLatest]

       deliverOn:[RACScheduler mainThreadScheduler]];

 

   // 綁定建立按鈕的 UI state 和點擊事件

    [[self.createButton rac_signalForControlEvents:UIControlEventTouchUpInside] executeCommand:createAccountCommand];

 

Sequences

sequence 表現爲RACSequence類,能夠簡單看作是RAC世界的NSArray,RAC增長了-rac_sequence方法,可使諸如NSArray這些集合類(collection classes)直接轉換爲RACSequence來使用。

Schedulers

scheduler 表現爲RACScheduler類,相似於GCD,but schedulers support cancellationbut schedulers support cancellation, and always execute serially.

ReactiveCocoa的簡單使用

實踐出真知,下面就舉一些簡單的例子,一塊兒看看RAC的使用

Subscription

接收 -subscribeNext: -subscribeError: -subscribeCompleted:

1

2

3

4

5

6

7

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;

 

// 依次輸出 A B C D…

[letters subscribeNext:^(NSString *x) {

    NSLog(@"%@", x);

}];

 

Injecting effects

注入效果 -doNext: -doError: -doCompleted:,看下面註釋應該就明白了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

__block unsigned subscriptions = 0;

 

RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {

    subscriptions++;

    [subscriber sendCompleted];

    return nil;

}];

 

// 不會輸出任何東西

loggingSignal = [loggingSignal doCompleted:^{

    NSLog(@"about to complete subscription %u", subscriptions);

}];

 

// 輸出:

// about to complete subscription 1

// subscription 1

[loggingSignal subscribeCompleted:^{

    NSLog(@"subscription %u", subscriptions);

}];

 

Mapping

-map: 映射,能夠看作對玻璃球的變換、從新組裝

1

2

3

4

5

6

7

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;

 

// Contains: AA BB CC DD EE FF GG HH II

RACSequence *mapped = [letters map:^(NSString *value) {

    return [value stringByAppendingString:value];

}];

 

Filtering

-filter: 過濾,不符合要求的玻璃球不容許經過

1

2

3

4

5

6

7

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

 

// Contains: 2 4 6 8

RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {

    return (value.intValue % 2) == 0;

}];

 

Concatenating

-concat: 把一個水管拼接到另外一個水管以後

1

2

3

4

5

6

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

 

// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9

RACSequence *concatenated = [letters concat:numbers];

 

Flattening

-flatten:

Sequences are concatenated

1

2

3

4

5

6

7

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

RACSequence *sequenceOfSequences = @[ letters, numbers ].rac_sequence;

 

// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9

RACSequence *flattened = [sequenceOfSequences flatten];

 

Signals are merged (merge能夠理解成把幾個水管的龍頭合併成一個,哪一個水管中的玻璃球哪一個先到先吐哪一個玻璃球)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

RACSubject *letters = [RACSubject subject];

RACSubject *numbers = [RACSubject subject];

RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {

    [subscriber sendNext:letters];

    [subscriber sendNext:numbers];

    [subscriber sendCompleted];

    return nil;

}];

 

RACSignal *flattened = [signalOfSignals flatten];

 

// Outputs: A 1 B C 2

[flattened subscribeNext:^(NSString *x) {

    NSLog(@"%@", x);

}];

 

[letters sendNext:@"A"];

[numbers sendNext:@"1"];

[letters sendNext:@"B"];

[letters sendNext:@"C"];

[numbers sendNext:@"2"];

 

Mapping and flattening

-flattenMap: 先 map 再 flatten

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

 

// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

RACSequence *extended = [numbers flattenMap:^(NSString *num) {

    return @[ num, num ].rac_sequence;

}];

 

// Contains: 1_ 3_ 5_ 7_ 9_

RACSequence *edited = [numbers flattenMap:^(NSString *num) {

    if (num.intValue % 2 == 0) {

        return [RACSequence empty];

    } else {

        NSString *newNum = [num stringByAppendingString:@"_"];

        return [RACSequence return:newNum];

    }

}];

 

 

 

 

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;

 

[[letters

    flattenMap:^(NSString *letter) {

        return [database saveEntriesForLetter:letter];

    }]

    subscribeCompleted:^{

        NSLog(@"All database entries saved successfully.");

    }];

 

Sequencing

-then:

1

2

3

4

5

6

7

8

9

10

11

12

13

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;

 

// 新水龍頭只包含: 1 2 3 4 5 6 7 8 9

//

// 但當有接收時,仍會執行舊水龍頭doNext的內容,因此也會輸出 A B C D E F G H I

RACSignal *sequenced = [[letters

    doNext:^(NSString *letter) {

        NSLog(@"%@", letter);

    }]

    then:^{

        return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal;

    }];

 

Merging

+merge: 前面在flatten中提到的水龍頭的合併

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

RACSubject *letters = [RACSubject subject];

RACSubject *numbers = [RACSubject subject];

RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];

 

// Outputs: A 1 B C 2

[merged subscribeNext:^(NSString *x) {

    NSLog(@"%@", x);

}];

 

[letters sendNext:@"A"];

[numbers sendNext:@"1"];

[letters sendNext:@"B"];

[letters sendNext:@"C"];

[numbers sendNext:@"2"];

 

Combining latest values

+combineLatest: 任什麼時候刻取每一個水龍頭吐出的最新的那個玻璃球

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

RACSubject *letters = [RACSubject subject];

RACSubject *numbers = [RACSubject subject];

RACSignal *combined = [RACSignal

    combineLatest:@[ letters, numbers ]

    reduce:^(NSString *letter, NSString *number) {

        return [letter stringByAppendingString:number];

    }];

 

// Outputs: B1 B2 C2 C3

[combined subscribeNext:^(id x) {

    NSLog(@"%@", x);

}];

 

[letters sendNext:@"A"];

[letters sendNext:@"B"];

[numbers sendNext:@"1"];

[numbers sendNext:@"2"];

[letters sendNext:@"C"];

[numbers sendNext:@"3"];

 

Switching

-switchToLatest: 取指定的那個水龍頭的吐出的最新玻璃球

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

RACSubject *letters = [RACSubject subject];

RACSubject *numbers = [RACSubject subject];

RACSubject *signalOfSignals = [RACSubject subject];

 

RACSignal *switched = [signalOfSignals switchToLatest];

 

// Outputs: A B 1 D

[switched subscribeNext:^(NSString *x) {

    NSLog(@"%@", x);

}];

 

[signalOfSignals sendNext:letters];

[letters sendNext:@"A"];

[letters sendNext:@"B"];

 

[signalOfSignals sendNext:numbers];

[letters sendNext:@"C"];

[numbers sendNext:@"1"];

 

[signalOfSignals sendNext:letters];

[numbers sendNext:@"2"];

[letters sendNext:@"D"];

 

經常使用宏

RAC 能夠看做某個屬性的值與一些信號的聯動

1

2

3

4

RAC(self.submitButton.enabled) = [RACSignal combineLatest:@[self.usernameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id(NSString *userName, NSString *password) {

    return @(userName.length >= 6 && password.length >= 6);

}];

 

RACObserve 監聽屬性的改變,使用block的KVO

1

2

3

4

[RACObserve(self.textField, text) subscribeNext:^(NSString *newName) {

     NSLog(@"%@", newName);

}];

 

UI Event

RAC爲系統UI提供了不少category,很是棒,好比UITextView、UITextField文本框的改動rac_textSignal,UIButton的的按下rac_command等等。

最後

有了RAC,能夠不用去操心值何時到達何時改變,只須要簡單的進行數據來了以後的步驟就能夠了。

說了這麼多,在回過頭去看leezhong的比喻該文最後總結的關係圖,再好好梳理一下吧。我也是初學者,坐臥不安的呈上這篇博文,歡迎討論,若有不正之處歡迎批評指正。

參考

https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.mdhttps://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md
http://vimeo.com/65637501
http://iiiyu.com/2013/09/11/learning-ios-notes-twenty-eight/
http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.html http://nshipster.com/reactivecocoa/

相關文章
相關標籤/搜索