Bài viết
Reactive programming là gì? Tại sao tôi nên dùng nó?
Trong bài viết này, tôi sẽ giải thích lý do tại sao reactive programming lại là 1 trong những design patterns quan trọng nhất khi lập trình ứng dụng thông qua 3 tình huống quen thuộc trong lập trình – những tình huống ảnh hưởng đến thời gian lập trình, thường tạo ra bug và khiến quá trình design và refactoring gặp khó khăn. Chính lúc này, reactive programming sẽ chỉ ra những chỗ rườm rà, loại bỏ những yếu tố không an toàn và restructure code để tăng khả năng maintain.
Những tình huống chuyên biệt mà tôi sẽ giải thích gồm:
- Một button có status
isEnabled
phụ thuộc vào 2 state value khác nhau - Một type storage thread-safe
- Một task bất đồng bộ với 1 timeout
Về reactive programming
Reactive programming được định nghĩa theo nguyên tắc sau:
Bất kì “Getter” nào dành cho mutable state cũng gây ra nhiều vấn đề. Thay vì sử dụng getters, các state values đã calculate, đã generate, đã tải hoặc đã nhận về nên được gửi ngay lập tức vào 1 channel và bất cứ phần nào của program muốn tiếp cận những values đó phải đăng kí channel
Ý tưởng ở đây là chúng ta phải remove state ra khỏi những phần đã exposed của chương trình, thay vào đó sẽ đóng gói (encapsulate) vào trong các channels. Các channels sẽ hiển thị rõ các data dependencies và effects, giúp bạn hiểu rõ hơn các thay đổi, maintain dễ hơn, cũng như đơn giản hơn cách chúng ta modify state của ứng dụng.
React programming hỗ trợ nguyên tắc cơ bản này với cách tiếp cận tập trung vào các compositions nối tiếp và song song của channels để chuyển đổi các dòng dữ liệu khi chúng được tải đi và hợp nhất các thay đổi có thể xảy ra đồng thời hoặc trong các patterns giao nhau.
Nói 1 cách đơn giản, reactive programming quản lý các dòng dữ liệu bất đồng bộ giữa các nguồn dữ liệu và components cần phải react với dữ liệu.
Một button phụ thuộc vào 2 state values
Hãy bắt đầu 1 task cơ bản trong lập trình ứng dụng: set trạng thái isEnabled
của 1 button.
Giả dụ bạn chỉ có thể kích hoạt 1 button “Add to favorites” trong giao diện của mình nếu (và chỉ nếu) 2 điều kiện sao được thỏa mãn:
1/ User đã đăng nhập
2/ Buộc chọn ít nhất một file
Nếu thêm 1 yếu tố phức tạp: tình trạng đăng nhập được cập nhật thông thường trong 1 thread background:
Giả như cập nhật của các values có liên quan được gửi đi sử dụng Key-Value-Observing thì view controller của chúng ta có thể chứa đoạn code sau:
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
|
< span class = "kr" > override < / span > < span class = "kd" > func < / span > < span class = "nf" > viewDidLoad < / span > < span class = "p" > ( ) < / span > < span class = "p" > { < / span >
< span class = "kc" > super < / span > < span class = "p" > . < / span > < span class = "n" > viewDidLoad < / span > < span class = "p" > ( ) < / span >
< span class = "n" > addToFavoritesButton < / span > < span class = "p" > . < / span > < span class = "n" > isEnabled < / span > < span class = "p" >= < / span > < span class = "n" > loginStatus < / span > < span class = "p" > . < / span > < span class = "n" > isLoggedIn < / span > < span class = "o" > & amp ; & amp ; < / span > < span class = "o" > ! < / span > < span class = "n" > folderView < / span > < span class = "p" > . < / span > < span class = "n" > selection < / span > < span class = "p" > . < / span > < span class = "bp" > isEmpty < / span >
< span class = "n" > loginStatus < / span > < span class = "p" > . < / span > < span class = "n" > addObserver < / span > < span class = "p" > ( < / span > < span class = "kc" > self < / span > < span class = "p" > , < / span > < span class = "n" > forKeyPath < / span > < span class = "p" > : < / span > < span class = "p" > #</span><span class="n">keyPath</span><span class="p">(</span><span class="n">LoginStatus</span><span class="p">.</span><span class="n">isLoggedIn</span><span class="p">),</span>
< span class = "n" > options < / span > < span class = "p" > : < / span > < span class = "n" > NSKeyValueObservingOptions < / span > < span class = "p" > . < / span > < span class = "n" > new < / span > < span class = "p" > , < / span > < span class = "n" > context < / span > < span class = "p" > : < / span > < span class = "kc" > nil < / span > < span class = "p" > ) < / span >
< span class = "n" > folderView < / span > < span class = "p" > . < / span > < span class = "n" > addObserver < / span > < span class = "p" > ( < / span > < span class = "kc" > self < / span > < span class = "p" > , < / span > < span class = "n" > forKeyPath < / span > < span class = "p" > : < / span > < span class = "p" > #</span><span class="n">keyPath</span><span class="p">(</span><span class="n">FolderView</span><span class="p">.</span><span class="n">selection</span><span class="p">),</span>
< span class = "n" > options < / span > < span class = "p" > : < / span > < span class = "n" > NSKeyValueObservingOptions < / span > < span class = "p" > . < / span > < span class = "n" > new < / span > < span class = "p" > , < / span > < span class = "n" > context < / span > < span class = "p" > : < / span > < span class = "kc" > nil < / span > < span class = "p" > ) < / span >
< span class = "p" > } < / span >
< span class = "kr" > override < / span > < span class = "kd" > func < / span > < span class = "nf" > observeValue < / span > < span class = "p" > ( < / span > < span class = "n" > forKeyPath < / span > < span class = "n" > keyPath < / span > < span class = "p" > : < / span > < span class = "nb" > String < / span > < span class = "p" > ? , < / span > < span class = "n" > of < / span > < span class = "n" > object < / span > < span class = "p" > : < / span > < span class = "nb" > Any < / span > < span class = "p" > ? , < / span >
< span class = "n" > change < / span > < span class = "p" > : < / span > < span class = "p" > [ < / span > < span class = "n" > NSKeyValueChangeKey < / span > < span class = "p" > : < / span > < span class = "nb" > Any < / span > < span class = "p" > ] ? , < / span > < span class = "n" > context < / span > < span class = "p" > : < / span > < span class = "n" > UnsafeMutableRawPointer < / span > < span class = "p" > ? ) < / span > < span class = "p" > { < / span >
< span class = "k" > switch < / span > < span class = "p" > ( < / span > < span class = "n" > keyPath < / span > < span class = "p" > , < / span > < span class = "n" > change < / span > < span class = "p" > ? [ < / span > < span class = "n" > NSKeyValueChangeKey < / span > < span class = "p" > . < / span > < span class = "n" > newKey < / span > < span class = "p" > ] ) < / span > < span class = "p" > { < / span >
< span class = "k" > case < / span > < span class = "p" > ( . < / span > < span class = "n" > some < / span > < span class = "p" > ( #</span><span class="n">keyPath</span><span class="p">(</span><span class="n">LoginStatus</span><span class="p">.</span><span class="n">isLoggedIn</span><span class="p">)),</span> <span class="p">.</span><span class="n">some</span><span class="p">(</span><span class="kd">let</span> <span class="nv">isLoggedIn</span> <span class="kc">as</span> <span class="nb">Bool</span><span class="p">)):</span>
< span class = "n" > addToFavoritesButton < / span > < span class = "p" > . < / span > < span class = "n" > isEnabled < / span > < span class = "p" >= < / span > < span class = "n" > isLoggedIn < / span > < span class = "o" > & amp ; & amp ; < / span > < span class = "o" > ! < / span > < span class = "n" > folderView < / span > < span class = "p" > . < / span > < span class = "n" > selection < / span > < span class = "p" > . < / span > < span class = "bp" > isEmpty < / span >
< span class = "k" > case < / span > < span class = "p" > ( . < / span > < span class = "n" > some < / span > < span class = "p" > ( #</span><span class="n">keyPath</span><span class="p">(</span><span class="n">FolderView</span><span class="p">.</span><span class="n">selection</span><span class="p">)),</span> <span class="p">.</span><span class="n">some</span><span class="p">(</span><span class="kd">let</span> <span class="nv">selection</span> <span class="kc">as</span> <span class="nb">Array</span><span class="p"><</span><span class="n">FileView</span><span class="p">>)):</span>
< span class = "n" > addToFavoritesButton < / span > < span class = "p" > . < / span > < span class = "n" > isEnabled < / span > < span class = "p" >= < / span > < span class = "n" > loginStatus < / span > < span class = "p" > . < / span > < span class = "n" > isLoggedIn < / span > < span class = "o" > & amp ; & amp ; < / span > < span class = "o" > ! < / span > < span class = "n" > selection < / span > < span class = "p" > . < / span > < span class = "bp" > isEmpty < / span >
< span class = "k" > default < / span > < span class = "p" > : < / span >
< span class = "kc" > super < / span > < span class = "p" > . < / span > < span class = "n" > observeValue < / span > < span class = "p" > ( < / span > < span class = "n" > forKeyPath < / span > < span class = "p" > : < / span > < span class = "n" > keyPath < / span > < span class = "p" > , < / span > < span class = "n" > of < / span > < span class = "p" > : < / span > < span class = "n" > object < / span > < span class = "p" > , < / span > < span class = "n" > change < / span > < span class = "p" > : < / span > < span class = "n" > change < / span > < span class = "p" > , < / span > < span class = "n" > context < / span > < span class = "p" > : < / span > < span class = "n" > context < / span > < span class = "p" > ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
< span class = "kd" > deinit < / span > < span class = "p" > { < / span >
< span class = "n" > loginStatus < / span > < span class = "p" > . < / span > < span class = "n" > removeObserver < / span > < span class = "p" > ( < / span > < span class = "kc" > self < / span > < span class = "p" > , < / span > < span class = "n" > forKeyPath < / span > < span class = "p" > : < / span > < span class = "p" > #</span><span class="n">keyPath</span><span class="p">(</span><span class="n">LoginStatus</span><span class="p">.</span><span class="n">isLoggedIn</span><span class="p">))</span>
< span class = "n" > folderView < / span > < span class = "p" > . < / span > < span class = "n" > removeObserver < / span > < span class = "p" > ( < / span > < span class = "kc" > self < / span > < span class = "p" > , < / span > < span class = "n" > forKeyPath < / span > < span class = "p" > : < / span > < span class = "p" > #</span><span class="n">keyPath</span><span class="p">(</span><span class="n">FolderView</span><span class="p">.</span><span class="n">selection</span><span class="p">))</span>
< span class = "p" > } < / span >
|
Như bạn thấy, cập nhật trạng thái isEnabled
cho 1 button đơn tốn đến 20 dòng code – thậm chí chừng này còn chưa đủ. Một vài lỗi nhỏ có thể sẽ xảy ra, không phải vì chúng ta là những lập trình viên tồi mà vì chúng ta code để ít resistance nhất. Chúng ta sẽ fix lỗi nếu các lỗi đó quá rõ ràng, nhưng nếu code vẫn hoạt động được – như đoạn code ở trên – thì nhiều khả năng nó sẽ vượt qua được vòng testing.
Vậy vấn đề nào trong đoạn code này sẽ khiến bạn phải đau đầu sau đó?
- Thread unsafe: với tình trạng đăng nhập được cập nhật trong thread background,
observeValue
dành cho#keyPath(LoginStatus.isLoggedIn)
là memory không an toàn khi nó tiếp cận giá trịfolderView.selection.isEmpty
ở sai thread. Tình huống này cũng cập nhậtaddToFavoritesButton.isEnabled
sai trong thread background. - Không thể hòa hợp getters và
observeValue
s: Một thay đổi đăng nhập có thể xảy ra giữa call đếnloginStatus.isLoggedIn
và các calladdObserver
tương ứng trong hàmviewDidLoad
. Có thể thay đổi thứ tự của chúng nhưng sau đó, chúng ta có thể lấy được những cập nhật trước khi khởi tạo. Tương tự, vìloginStatus.isLoggedIn
được cập nhật trên nhiều thread khác nhau, nên có thể getter sẽ trả về 1 giá trị từ điểm khác kịp thời, tương ứng vớiobserveValue
vừa được cập nhật cho giá trị đó – điều này dẫn đến tình trạng các updates bị thừa và có khả năng gây ra những thay đổi state không cần thiết (gồm cả state có thể bỏ qua các transitions hoặc tiến ngược). Giải pháp duy nhất chính là sử dụngNSKeyValueObservingOptions.initial
cẩn thận và tự caching các giá trị đã biết cuối cùng – tránh hoàn toàn các getters. - Sử dụng KVO sai: Sử dụng method
observeValue
(bằng cách chuyển vềkeyPath
và/hoặcobject
) là 1 giải pháp không đáng tin cậy. Bất kể những collisions trên keypath hoặc object, hầu hết các observations đều phải unique, vì thế 1 đối tượngcontext
sẽ là giải pháp tốt hơn – nhưng nó đòi hỏi bạn phải chỉ định và lưu trữ 1 context trên mỗi observation và sau đó release khi hoàn thành. Vì vậy, chúng tôi phải tránh dùng nó. - Refactor khó: Chúng tôi cập nhật giá trị
addToFavoritesButton.isEnabled
ở 3 nơi khác nhau. Nếu dependency khác được thêm vào giá trị này, chúng tôi phải nhớ cập nhật cả 3 nơi. - Không có lifecycle notifications: nếu các đối tượng
loginStatus
hoặcfolderView
bất ngờ release, chúng tôi sẽ không nhận được notification và button vẫn bị kích hoạt không đúng.
Những vấn đề này có thể sửa được nhưng chúng tôi phải viết code nhiều hơn.
Trong reactive programming, giả sử tất cả các state values gửi những thay đổi của chúng thông qua các reactive programming channels, thay vì Key-Value-Observing, bạn sẽ chỉ cần đoạn code sau:
1
2
3
4
5
6
7
8
9
10
11
12
|
< span class = "kr" > override < / span > < span class = "kd" > func < / span > < span class = "nf" > viewDidLoad < / span > < span class = "p" > ( ) < / span > < span class = "p" > { < / span >
< span class = "kc" > super < / span > < span class = "p" > . < / span > < span class = "n" > viewDidLoad < / span > < span class = "p" > ( ) < / span >
< span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > endpoints < / span > < span class = "o" > += < / span > < span class = "n" > loginStatus < / span > < span class = "p" > . < / span > < span class = "n" > loggedInSignal < / span >
< span class = "p" > . < / span > < span class = "n" > combineLatest < / span > < span class = "p" > ( < / span > < span class = "n" > second < / span > < span class = "p" > : < / span > < span class = "n" > folderView < / span > < span class = "p" > . < / span > < span class = "n" > selectionSignal < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span > < span class = "nv" > $ 0 < / span > < span class = "o" > & amp ; & amp ; < / span > < span class = "o" > ! < / span > < span class = "nv" > $ 1 < / span > < span class = "p" > . < / span > < span class = "bp" > isEmpty < / span > < span class = "p" > } < / span >
< span class = "p" > . < / span > < span class = "n" > subscribe < / span > < span class = "p" > ( < / span > < span class = "n" > context < / span > < span class = "p" > : < / span > < span class = "p" > . < / span > < span class = "n" > main < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span > < span class = "n" > result < / span > < span class = "k" > in < / span >
< span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > addToFavoritesButton < / span > < span class = "p" > . < / span > < span class = "n" > isEnabled < / span > < span class = "p" >= < / span > < span class = "n" > result < / span > < span class = "p" > . < / span > < span class = "n" > value < / span > < span class = "p" > ? ? < / span > < span class = "kc" > false < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
|
“App scenario – dynamic view properties” xuất hiện trong CwlSignal.playground. Những vấn đề đề cập ở trên đã được giải quyết: đoạn code này hoàn toàn threadsafe, không có các notifications thừa, không phụ thuộc vào @objc
để observe key value và dễ dàng refactor lẫn maintain.
Maintain 1 dictionary values thread-safe
Bạn có thể sử dụng 1 dictionary như “model” trong 1 ứng dụng nhỏ. Dù yêu cầu 1 bộ nhớ hiện đại hơn dictionary, pattern cập nhật và notify được hiển thị ở đây sẽ tương tự như bất kì app nào được viết tốt.
Giải quyết vấn đề với các classes Cocoa chuẩn DispatchQueue
và NotificationCenter
như sau:
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
31
32
33
34
35
36
37
38
39
40
41
42
|
< span class = "kd" > class < / span > < span class = "nc" > DocumentValues < / span > < span class = "p" > { < / span >
< span class = "kd" > typealias < / span > < span class = "n" > Dict < / span > < span class = "p" >= < / span > < span class = "nb" > Dictionary < / span > < span class = "p" > & lt ; < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > , < / span > < span class = "nb" > Any < / span > < span class = "p" > & gt ; < / span >
< span class = "kd" > typealias < / span > < span class = "n" > Tuple < / span > < span class = "p" >= < / span > < span class = "p" > ( < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > , < / span > < span class = "nb" > Any < / span > < span class = "p" > ? ) < / span >
< span class = "kd" > static < / span > < span class = "kd" > let < / span > < span class = "nv" > changed < / span > < span class = "p" >= < / span > < span class = "n" > Notification < / span > < span class = "p" > . < / span > < span class = "n" > Name < / span > < span class = "p" > ( < / span > < span class = "s" > "com.mycompany.mymodule.documentvalues.changed" < / span > < span class = "p" > ) < / span >
< span class = "c1" > // Underlying storage protected by a `DispatchQueue` mutex</span>
< span class = "kd" > private < / span > < span class = "kd" > var < / span > < span class = "nv" > storage < / span > < span class = "p" >= < / span > < span class = "n" > Dict < / span > < span class = "p" > ( ) < / span >
< span class = "kd" > private < / span > < span class = "kd" > let < / span > < span class = "nv" > mutex < / span > < span class = "p" >= < / span > < span class = "n" > DispatchQueue < / span > < span class = "p" > ( < / span > < span class = "n" > label < / span > < span class = "p" > : < / span > < span class = "s" > "" < / span > < span class = "p" > ) < / span >
< span class = "kd" > init < / span > < span class = "p" > ( ) < / span > < span class = "p" > { } < / span >
< span class = "c1" > // Access to the storage involves copying out of the mutex</span>
< span class = "kd" > var < / span > < span class = "nv" > values < / span > < span class = "p" > : < / span > < span class = "n" > Dict < / span > < span class = "p" > { < / span >
< span class = "k" > return < / span > < span class = "n" > mutex < / span > < span class = "p" > . < / span > < span class = "n" > sync < / span > < span class = "p" > { < / span >
< span class = "k" > return < / span > < span class = "n" > storage < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
< span class = "c1" > // Remove a value and send a change notification</span>
< span class = "kd" > func < / span > < span class = "nf" > removeValue < / span > < span class = "p" > ( < / span > < span class = "n" > forKey < / span > < span class = "n" > key < / span > < span class = "p" > : < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "kd" > let < / span > < span class = "nv" > latest < / span > < span class = "p" >= < / span > < span class = "n" > mutex < / span > < span class = "p" > . < / span > < span class = "n" > sync < / span > < span class = "p" > { < / span > < span class = "p" > ( ) < / span > < span class = "p" > - & gt ; < / span > < span class = "n" > Dict < / span > < span class = "k" > in < / span >
< span class = "n" > storage < / span > < span class = "p" > . < / span > < span class = "n" > removeValue < / span > < span class = "p" > ( < / span > < span class = "n" > forKey < / span > < span class = "p" > : < / span > < span class = "n" > key < / span > < span class = "p" > ) < / span >
< span class = "k" > return < / span > < span class = "n" > storage < / span >
< span class = "p" > } < / span >
< span class = "n" > NotificationCenter < / span > < span class = "p" > . < / span > < span class = "k" > default < / span > < span class = "p" > . < / span > < span class = "n" > post < / span > < span class = "p" > ( < / span > < span class = "n" > name < / span > < span class = "p" > : < / span > < span class = "n" > DocumentValues < / span > < span class = "p" > . < / span > < span class = "n" > changed < / span > < span class = "p" > , < / span > < span class = "n" > object < / span > < span class = "p" > : < / span > < span class = "kc" > self < / span > < span class = "p" > , < / span >
< span class = "n" > userInfo < / span > < span class = "p" > : < / span > < span class = "n" > latest < / span > < span class = "p" > ) < / span >
< span class = "p" > } < / span >
< span class = "c1" > // Create/change a value and send a change notification</span>
< span class = "kd" > func < / span > < span class = "nf" > setValue < / span > < span class = "p" > ( < / span > < span class = "kc" > _ < / span > < span class = "n" > value < / span > < span class = "p" > : < / span > < span class = "nb" > Any < / span > < span class = "p" > , < / span > < span class = "n" > forKey < / span > < span class = "n" > key < / span > < span class = "p" > : < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "kd" > let < / span > < span class = "nv" > latest < / span > < span class = "p" >= < / span > < span class = "n" > mutex < / span > < span class = "p" > . < / span > < span class = "n" > sync < / span > < span class = "p" > { < / span > < span class = "p" > ( ) < / span > < span class = "p" > - & gt ; < / span > < span class = "n" > Dict < / span > < span class = "k" > in < / span >
< span class = "n" > storage < / span > < span class = "p" > [ < / span > < span class = "n" > key < / span > < span class = "p" > ] < / span > < span class = "p" >= < / span > < span class = "n" > value < / span >
< span class = "k" > return < / span > < span class = "n" > storage < / span >
< span class = "p" > } < / span >
< span class = "n" > NotificationCenter < / span > < span class = "p" > . < / span > < span class = "k" > default < / span > < span class = "p" > . < / span > < span class = "n" > post < / span > < span class = "p" > ( < / span > < span class = "n" > name < / span > < span class = "p" > : < / span > < span class = "n" > DocumentValues < / span > < span class = "p" > . < / span > < span class = "n" > changed < / span > < span class = "p" > , < / span > < span class = "n" > object < / span > < span class = "p" > : < / span > < span class = "kc" > self < / span > < span class = "p" > , < / span >
< span class = "n" > userInfo < / span > < span class = "p" > : < / span > < span class = "n" > latest < / span > < span class = "p" > ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
|
Tôi đã cẩn thận sao chép các values trong và ngoài mutext và nó giúp bộ nhớ an toàn trong mọi trường hợp. Class này sử dụng notifications để các interface khác có thể nhận được các updates.
Vậy vấn đề ở đây là gì?
- Khuyến khích thói quen xấu: Interface khác sẽ dễ dàng tiếp cận property
values
hiện tại nhưng để observe notificationDocumentValues.changed
chính xác đòi hỏi nhiều thứ hơn nữa, vì vậy bạn phải khuyến khích các dependent interfaces quên đi để observe những thay đổi 1 cách chính xác và không đồng bộ nữa. - Không có cách nào để khởi tạo và subscribe an toàn: nếu bạn lấy
values
sau đó bắt đầu observe các notifications, nhiều khả năng 1 thay đổi có thể xảy ra giữa 2 actions đó (khiến bạn bỏ lỡ 1 notification). Nếu bạn observe các notifications trước, sau đó lấyvalues
, thì bạn có thể nhận 1 notification đầu tiên trước khi khởi tạo.NSKeyValueObservingOptions.initial
có thể fix vấn đề đó cho KVO nhưng vớiNotification
s, bạn sẽ cần vài dòng code rõ ràng để giải quyết vấn đề này. - Dễ gặp deadlocks: Hàm
removeValue
trongstorage
xóa 1 arbitrary value trong 1 mutex. Nếu có 1deinit
trong giá trị đã xóa này vàdeinit
cố gắng thay đổiDocumentValues
(re-enter mutex), bạn đã tạo 1 deadlock rồi. - Khó refactor: Tất cả những thay đổi liên quan đến
storage
rất vô nghĩa. Nếu bạn cần thêm functionality trong tương lai – như writeDocumentValues
vào 1 file trong mỗi thay đổi – bạn sẽ phải cẩn thận tích hợp thay đổi này vào nhiều nơi. - Không có lifecycle notifications: Nếu đối tượng
DocumentValues
bị xóa, theo mặc định bạn sẽ không nhận được notifications
Những vấn đề này khá tương tự những vấn đề trong ví dụ trước. Cũng tương tự, mỗi giải pháp sẽ cần bạn bổ sung code, kéo theo nhiều thứ khác phức tạp. Hệ quả là ban đầu bạn có nhận ra các vấn đề đó, nhưng do mức độ tinh xảo của chúng mà bạn có thể không nhận ra trong quá trình testing.
Một implementation sử dụng reactive programming sẽ thay thế propertyvalues
với 1 channel truyền đi value hiện tại, theo sau là các cập nhật tương lai, như 1 stream.
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
31
32
33
34
35
36
37
|
< span class = "kd" > class < / span > < span class = "nc" > DocumentValues < / span > < span class = "p" > { < / span >
< span class = "kd" > typealias < / span > < span class = "n" > Dict < / span > < span class = "p" >= < / span > < span class = "nb" > Dictionary < / span > < span class = "p" > & lt ; < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > , < / span > < span class = "nb" > Any < / span > < span class = "p" > & gt ; < / span >
< span class = "kd" > typealias < / span > < span class = "n" > Tuple < / span > < span class = "p" >= < / span > < span class = "p" > ( < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > , < / span > < span class = "nb" > Any < / span > < span class = "p" > ? ) < / span >
< span class = "kd" > private < / span > < span class = "kd" > let < / span > < span class = "nv" > input < / span > < span class = "p" > : < / span > < span class = "n" > SignalInput < / span > < span class = "p" > & lt ; < / span > < span class = "n" > Tuple < / span > < span class = "p" > & gt ; < / span >
< span class = "c1" > // Access to the data is via the signal.</span>
< span class = "kd" > public < / span > < span class = "kd" > let < / span > < span class = "nv" > signal < / span > < span class = "p" > : < / span > < span class = "n" > SignalMulti < / span > < span class = "p" > & lt ; < / span > < span class = "n" > Dict < / span > < span class = "p" > & gt ; < / span >
< span class = "kd" > init < / span > < span class = "p" > ( ) < / span > < span class = "p" > { < / span >
< span class = "c1" > // Actual values storage is encapsulated within the signal</span>
< span class = "p" > ( < / span > < span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > input < / span > < span class = "p" > , < / span > < span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > signal < / span > < span class = "p" > ) < / span > < span class = "p" >= < / span > < span class = "n" > Signal < / span > < span class = "p" > & lt ; < / span > < span class = "n" > Tuple < / span > < span class = "p" > & gt ; . < / span > < span class = "n" > create < / span > < span class = "p" > { < / span >
< span class = "nv" > $ 0 < / span > < span class = "p" > . < / span > < span class = "bp" > map < / span > < span class = "p" > ( < / span > < span class = "n" > withState < / span > < span class = "p" > : < / span > < span class = "p" > [ : ] ) < / span > < span class = "p" > { < / span > < span class = "p" > ( < / span > < span class = "n" > state < / span > < span class = "p" > : < / span > < span class = "kr" > inout < / span > < span class = "n" > Dict < / span > < span class = "p" > , < / span > < span class = "n" > update < / span > < span class = "p" > : < / span > < span class = "n" > Tuple < / span > < span class = "p" > ) < / span > < span class = "k" > in < / span >
< span class = "c1" > // All updates pass through this single, common function.</span>
< span class = "k" > switch < / span > < span class = "n" > update < / span > < span class = "p" > { < / span >
< span class = "k" > case < / span > < span class = "p" > ( < / span > < span class = "kd" > let < / span > < span class = "nv" > key < / span > < span class = "p" > , < / span > < span class = "p" > . < / span > < span class = "n" > some < / span > < span class = "p" > ( < / span > < span class = "kd" > let < / span > < span class = "nv" > value < / span > < span class = "p" > ) ) : < / span > < span class = "n" > state < / span > < span class = "p" > [ < / span > < span class = "n" > key < / span > < span class = "p" > ] < / span > < span class = "p" >= < / span > < span class = "n" > value < / span >
< span class = "k" > case < / span > < span class = "p" > ( < / span > < span class = "kd" > let < / span > < span class = "nv" > key < / span > < span class = "p" > , < / span > < span class = "p" > . < / span > < span class = "kr" > none < / span > < span class = "p" > ) : < / span > < span class = "n" > state < / span > < span class = "p" > . < / span > < span class = "n" > removeValue < / span > < span class = "p" > ( < / span > < span class = "n" > forKey < / span > < span class = "p" > : < / span > < span class = "n" > key < / span > < span class = "p" > ) < / span >
< span class = "p" > } < / span >
< span class = "k" > return < / span > < span class = "n" > state < / span >
< span class = "c1" > // Convert single `Signal` into multi-subscribable `SignalMulti` with `continuous`</span>
< span class = "p" > } . < / span > < span class = "n" > continuous < / span > < span class = "p" > ( < / span > < span class = "n" > initial < / span > < span class = "p" > : < / span > < span class = "p" > [ : ] ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
< span class = "kd" > func < / span > < span class = "nf" > removeValue < / span > < span class = "p" > ( < / span > < span class = "n" > forKey < / span > < span class = "n" > key < / span > < span class = "p" > : < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "n" > input < / span > < span class = "p" > . < / span > < span class = "n" > send < / span > < span class = "p" > ( < / span > < span class = "n" > value < / span > < span class = "p" > : < / span > < span class = "p" > ( < / span > < span class = "n" > key < / span > < span class = "p" > , < / span > < span class = "kc" > nil < / span > < span class = "p" > ) ) < / span >
< span class = "p" > } < / span >
< span class = "kd" > func < / span > < span class = "nf" > setValue < / span > < span class = "p" > ( < / span > < span class = "kc" > _ < / span > < span class = "n" > value < / span > < span class = "p" > : < / span > < span class = "nb" > Any < / span > < span class = "p" > , < / span > < span class = "n" > forKey < / span > < span class = "n" > key < / span > < span class = "p" > : < / span > < span class = "n" > AnyHashable < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "n" > input < / span > < span class = "p" > . < / span > < span class = "n" > send < / span > < span class = "p" > ( < / span > < span class = "n" > value < / span > < span class = "p" > : < / span > < span class = "p" > ( < / span > < span class = "n" > key < / span > < span class = "p" > , < / span > < span class = "n" > value < / span > < span class = "p" > ) ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
|
“App scenario – threadsafe key-value storage” xuất hiện trong CwlSignal.playground. Kích cỡ code không có nhiều khác biệt lớn (27 dòng không-trống, không-comment so với 23 dòng sau đó) nhưng trong trường hợp này, mỗi vấn đề đề cập ở trên đã được giải quyết gọn gàng.
- Khối lượng công việc để tiếp cận 1 giá trị 1 lần hoặc subscribe chính xác vẫn giữ nguyên
- Nếu cần phải tách việc xử lý giá trị ban đầu và các giá trị kế tiếp (như sử dụng 1 trình tự
capture
vàsubscribe
), stream sẽ được dừng chính xác để bạn không bỏ lỡ notification. - Mọi thứ đều threadsafe (closure
map
sẽ không bao giờ bị gọi đồng thời và re-entrancy sẽ không xảy ra) - Tất cả những thay đổi xảy ra trong hàm
map
và có thể được coordinated ở đó - Một message
SignalError.cancelled
được tự động gửi đến các subscribers nếuinput
được release
Bên cạnh threadsafe, lưu ý rằng sẽ không còn mutable variables trong class; state được đóng gói (encapsulated) trong class; state được đóng gói trong Signal
.
Ngoài ra, bạn sẽ phạm ít lỗi implementation hơn; code sẽ declarative và được contained nhiều hơn và bạn không cần phải quản trị và giải quyết mutex nào nữa.
Một task bất đồng bộ với 1 timeout
Trước đây, tôi đã từng đề cập đoạn code bên dưới trong bài Testing Actions over Time. Đoạn code gồm 1 class với 1 hàm connect
thực hiện 2 việc sau:
- Gọi 1 hàm
underlyingConnect
cần callback và invoke nó trong completion - Nếu code hỏng trước khi hàm
underlyingConnect
gọi completion handler, hãy khởi động 1 timer để hủy hàmunderlyingConnect
đó.
Tôi đã tạo class khá phức tạp bằng cách cho phép người dùng gọi connect
nhiều lần trong class Service
– tuy lần call connect
trước đó vẫn có những tasks bất đồng bộ cần phải giải quyết
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
< span class = "kd" > public < / span > < span class = "kd" > protocol < / span > < span class = "nc" > Cancellable < / span > < span class = "p" > : < / span > < span class = "kd" > class < / span > < span class = "p" > { < / span > < span class = "kd" > func < / span > < span class = "nf" > cancel < / span > < span class = "p" > ( ) < / span > < span class = "p" > } < / span >
< span class = "kd" > enum < / span > < span class = "nc" > ServiceError < / span > < span class = "p" > : < / span > < span class = "n" > Error < / span > < span class = "p" > { < / span > < span class = "k" > case < / span > < span class = "n" > timeout < / span > < span class = "p" > } < / span >
< span class = "kd" > class < / span > < span class = "nc" > Service < / span > < span class = "p" > { < / span >
< span class = "kd" > typealias < / span > < span class = "n" > ConnectionFunction < / span > < span class = "p" >= < / span >
< span class = "p" > ( < / span > < span class = "n" > DispatchQueue < / span > < span class = "p" > , < / span > < span class = "p" > @ < / span > < span class = "n" > escaping < / span > < span class = "p" > ( < / span > < span class = "n" > Result < / span > < span class = "p" > & lt ; < / span > < span class = "nb" > String < / span > < span class = "p" > & gt ; ) < / span > < span class = "p" > - & gt ; < / span > < span class = "p" > ( ) ) < / span > < span class = "p" > - & gt ; < / span > < span class = "n" > Cancellable < / span >
< span class = "kd" > let < / span > < span class = "nv" > underlyingConnect < / span > < span class = "p" > : < / span > < span class = "n" > ConnectionFunction < / span >
< span class = "kd" > var < / span > < span class = "nv" > currentAction < / span > < span class = "p" > : < / span > < span class = "n" > Cancellable < / span > < span class = "p" > ? < / span > < span class = "p" >= < / span > < span class = "kc" > nil < / span >
< span class = "kd" > let < / span > < span class = "nv" > queue < / span > < span class = "p" >= < / span > < span class = "n" > DispatchQueue < / span > < span class = "p" > ( < / span > < span class = "n" > label < / span > < span class = "p" > : < / span > < span class = "s" > "</span><span class=" si ">\(</span><span class="n">Service</span><span class="p">.</span><span class="kc">self</span><span class="si">)</span><span class=" s ">" < / span > < span class = "p" > ) < / span >
< span class = "kd" > let < / span > < span class = "nv" > handler < / span > < span class = "p" > : < / span > < span class = "p" > ( < / span > < span class = "n" > Result < / span > < span class = "p" > & lt ; < / span > < span class = "nb" > String < / span > < span class = "p" > & gt ; ) < / span > < span class = "p" > - & gt ; < / span > < span class = "p" > ( ) < / span >
< span class = "c1" > // Construction of the Service lets us specify the underlying "connect" service</span>
< span class = "kd" > init < / span > < span class = "p" > ( < / span > < span class = "n" > connect < / span > < span class = "p" > : < / span > < span class = "p" > @ < / span > < span class = "n" > escaping < / span > < span class = "n" > ConnectionFunction < / span > < span class = "p" >= < / span > < span class = "n" > NetworkService < / span > < span class = "p" > . < / span > < span class = "kd" > init < / span > < span class = "p" > , < / span >
< span class = "n" > handler < / span > < span class = "p" > : < / span > < span class = "p" > @ < / span > < span class = "n" > escaping < / span > < span class = "p" > ( < / span > < span class = "n" > Result < / span > < span class = "p" > & lt ; < / span > < span class = "nb" > String < / span > < span class = "p" > & gt ; ) < / span > < span class = "p" > - & gt ; < / span > < span class = "p" > ( ) ) < / span > < span class = "p" > { < / span >
< span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > underlyingConnect < / span > < span class = "p" >= < / span > < span class = "n" > connect < / span >
< span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > handler < / span > < span class = "p" >= < / span > < span class = "n" > handler < / span >
< span class = "p" > } < / span >
< span class = "c1" > // The connect function that we want to test</span>
< span class = "kd" > func < / span > < span class = "nf" > connect < / span > < span class = "p" > ( < / span > < span class = "n" > timeout < / span > < span class = "n" > seconds < / span > < span class = "p" > : < / span > < span class = "nb" > Double < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "kd" > var < / span > < span class = "nv" > previousAction < / span > < span class = "p" > : < / span > < span class = "n" > Cancellable < / span > < span class = "p" > ? < / span > < span class = "p" >= < / span > < span class = "kc" > nil < / span >
< span class = "n" > queue < / span > < span class = "p" > . < / span > < span class = "n" > sync < / span > < span class = "p" > { < / span >
< span class = "n" > previousAction < / span > < span class = "p" >= < / span > < span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > currentAction < / span >
< span class = "c1" > // Tie the timer and underlying action together with a single lifetime object for</span>
< span class = "c1" > // this `connect` action</span>
< span class = "kd" > let < / span > < span class = "nv" > timerAndAction < / span > < span class = "p" >= < / span > < span class = "n" > CancellableTimerAndAction < / span > < span class = "p" > ( ) < / span >
< span class = "c1" > // Run the underlying connection</span>
< span class = "kd" > let < / span > < span class = "nv" > underlyingAction < / span > < span class = "p" >= < / span > < span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > underlyingConnect < / span > < span class = "p" > ( < / span > < span class = "n" > queue < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "p" > [ < / span > < span class = "kr" > weak < / span > < span class = "n" > timerAndAction < / span > < span class = "p" > ] < / span > < span class = "n" > result < / span > < span class = "k" > in < / span >
< span class = "c1" > // Cancel the action so no futher callbacks are invoked</span>
< span class = "n" > timerAndAction < / span > < span class = "p" > ? . < / span > < span class = "n" > cancel < / span > < span class = "p" > ( ) < / span >
< span class = "c1" > // Send the succes to the handler</span>
< span class = "n" > handler < / span > < span class = "p" > ( < / span > < span class = "n" > result < / span > < span class = "p" > ) < / span >
< span class = "p" > } < / span >
< span class = "c1" > // Run the timeout timer</span>
< span class = "kd" > let < / span > < span class = "nv" > timer < / span > < span class = "p" >= < / span > < span class = "n" > DispatchSource < / span > < span class = "p" > . < / span > < span class = "n" > singleTimer < / span > < span class = "p" > ( < / span > < span class = "n" > interval < / span > < span class = "p" > : < / span >
< span class = "n" > DispatchTimeInterval < / span > < span class = "p" > . < / span > < span class = "n" > fromSeconds < / span > < span class = "p" > ( < / span > < span class = "n" > seconds < / span > < span class = "p" > ) , < / span > < span class = "n" > queue < / span > < span class = "p" > : < / span > < span class = "n" > queue < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "p" > [ < / span > < span class = "kr" > weak < / span > < span class = "n" > timerAndAction < / span > < span class = "p" > ] < / span > < span class = "k" > in < / span >
< span class = "c1" > // Cancel the action so no futher callbacks are invoked</span>
< span class = "n" > timerAndAction < / span > < span class = "p" > ? . < / span > < span class = "n" > cancel < / span > < span class = "p" > ( ) < / span >
< span class = "c1" > // Send the timeout to the handler</span>
< span class = "n" > handler < / span > < span class = "p" > ( . < / span > < span class = "n" > failure < / span > < span class = "p" > ( < / span > < span class = "n" > ServiceError < / span > < span class = "p" > . < / span > < span class = "n" > timeout < / span > < span class = "p" > ) ) < / span >
< span class = "p" > } < / span > < span class = "kc" > as < / span > < span class = "p" > ? < / span > < span class = "n" > DispatchSource < / span >
< span class = "c1" > // Store everything in the lifetime object for this action and then store that</span>
< span class = "c1" > // in the parent</span>
< span class = "n" > timerAndAction < / span > < span class = "p" > . < / span > < span class = "n" > timer < / span > < span class = "p" >= < / span > < span class = "n" > timer < / span >
< span class = "n" > timerAndAction < / span > < span class = "p" > . < / span > < span class = "n" > action < / span > < span class = "p" >= < / span > < span class = "n" > underlyingAction < / span >
< span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > currentAction < / span > < span class = "p" >= < / span > < span class = "n" > timerAndAction < / span >
< span class = "p" > } < / span >
< span class = "c1" > // A good rule of thumb: never release lifetime objects inside a mutex – you might</span>
< span class = "c1" > // trigger a re-entrancy deadlock</span>
< span class = "bp" > withExtendedLifetime < / span > < span class = "p" > ( < / span > < span class = "n" > previousAction < / span > < span class = "p" > ) < / span > < span class = "p" > { } < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
< span class = "kd" > class < / span > < span class = "nc" > CancellableTimerAndAction < / span > < span class = "p" > : < / span > < span class = "n" > Cancellable < / span > < span class = "p" > { < / span >
< span class = "kd" > var < / span > < span class = "nv" > timer < / span > < span class = "p" > : < / span > < span class = "n" > Cancellable < / span > < span class = "p" > ? < / span > < span class = "p" >= < / span > < span class = "kc" > nil < / span >
< span class = "kd" > var < / span > < span class = "nv" > action < / span > < span class = "p" > : < / span > < span class = "n" > Cancellable < / span > < span class = "p" > ? < / span > < span class = "p" >= < / span > < span class = "kc" > nil < / span >
< span class = "kd" > func < / span > < span class = "nf" > cancel < / span > < span class = "p" > ( ) < / span > < span class = "p" > { < / span >
< span class = "n" > timer < / span > < span class = "p" > ? . < / span > < span class = "n" > cancel < / span > < span class = "p" > ( ) < / span >
< span class = "n" > action < / span > < span class = "p" > ? . < / span > < span class = "n" > cancel < / span > < span class = "p" > ( ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
|
Bây giờ, đoạn code trên đã hoạt động được và như tôi thấy, không hề có bugs. Nhưng đây là 1 đoạn code quá lớn khi mà nó chỉ đưa 1 timeout vào 1 hàm cơ bản.
Hầu hết size code phụ thuộc vào việc phải coding cẩn thận để tránh gây ra lỗi. Sau khi hàmconnect
bắt đầu underlyingAction
và timer
, cần phải lưu trữ underlyingAction
và timer
trong timerAndAction
tùy chỉnh (để ràng buộc các lifetimes với nhau). Có vài cách để xử lý cẩn thận previousAction
khi nó được release bên ngoài queue.sync
(để ngăn các vấn đề về re-entrancy). Cả handler closure của underlyingAction
và handler closure của timer
cần phải tiếp cận hand closure khác (để hủy mọi thứ 1 cách chính xác), vì thế sẽ có vài reference yếu xảy ra.
Chúng ta không cần phải code cẩn thận quanh quá nhiều vấn đề. Chúng ta cần đoạn code nhỏ hơn. Chúng ta cần nó đơn giản hơn.
Reactive programming đã xuất hiện.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
< span class = "kd" > class < / span > < span class = "nc" > Service < / span > < span class = "p" > { < / span >
< span class = "kd" > private < / span > < span class = "kd" > let < / span > < span class = "nv" > input < / span > < span class = "p" > : < / span > < span class = "n" > SignalInput < / span > < span class = "p" > & lt ; < / span > < span class = "n" > DispatchTimeInterval < / span > < span class = "p" > & gt ; < / span >
< span class = "c1" > // Instead of "handler" callbacks, output is now via this signal</span>
< span class = "kd" > let < / span > < span class = "nv" > signal < / span > < span class = "p" > : < / span > < span class = "n" > SignalMulti < / span > < span class = "p" > & lt ; < / span > < span class = "n" > Result < / span > < span class = "p" > & lt ; < / span > < span class = "nb" > String < / span > < span class = "p" > & gt ; < / span > < span class = "o" > & gt ; < / span >
< span class = "kd" > init < / span > < span class = "p" > ( < / span > < span class = "n" > connect < / span > < span class = "p" > : < / span > < span class = "p" > @ < / span > < span class = "n" > escaping < / span > < span class = "p" > ( ) < / span > < span class = "p" > - & gt ; < / span > < span class = "n" > Signal < / span > < span class = "p" > & lt ; < / span > < span class = "nb" > String < / span > < span class = "p" > & gt ; ) < / span > < span class = "p" > { < / span >
< span class = "p" > ( < / span > < span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > input < / span > < span class = "p" > , < / span > < span class = "kc" > self < / span > < span class = "p" > . < / span > < span class = "n" > signal < / span > < span class = "p" > ) < / span > < span class = "p" >= < / span > < span class = "n" > Signal < / span > < span class = "p" > & lt ; < / span > < span class = "n" > DispatchTimeInterval < / span > < span class = "p" > & gt ; . < / span > < span class = "n" > create < / span > < span class = "p" > { < / span > < span class = "n" > s < / span > < span class = "k" > in < / span >
< span class = "c1" > // Return results only from the latest connection attempt</span>
< span class = "n" > Signal < / span > < span class = "p" > & lt ; < / span > < span class = "n" > Result < / span > < span class = "p" > & lt ; < / span > < span class = "nb" > String < / span > < span class = "p" > & gt ; < / span > < span class = "o" > & gt ; < / span > < span class = "p" > . < / span > < span class = "n" > switchLatest < / span > < span class = "p" > ( < / span >
< span class = "c1" > // Each time we receive a timeout duration, convert it into a connection attempt</span>
< span class = "n" > s < / span > < span class = "p" > . < / span > < span class = "bp" > map < / span > < span class = "p" > { < / span > < span class = "n" > i < / span > < span class = "k" > in < / span > < span class = "n" > connect < / span > < span class = "p" > ( ) . < / span > < span class = "n" > timeout < / span > < span class = "p" > ( < / span > < span class = "n" > interval < / span > < span class = "p" > : < / span > < span class = "n" > i < / span > < span class = "p" > , < / span > < span class = "n" > resetOnValue < / span > < span class = "p" > : < / span > < span class = "kc" > false < / span > < span class = "p" > ) . < / span > < span class = "n" > materialize < / span > < span class = "p" > ( ) < / span > < span class = "p" > } < / span >
< span class = "p" > ) . < / span > < span class = "n" > multicast < / span > < span class = "p" > ( ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
< span class = "kd" > func < / span > < span class = "nf" > connect < / span > < span class = "p" > ( < / span > < span class = "n" > seconds < / span > < span class = "p" > : < / span > < span class = "nb" > Double < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span >
< span class = "n" > input < / span > < span class = "p" > . < / span > < span class = "n" > send < / span > < span class = "p" > ( < / span > < span class = "n" > value < / span > < span class = "p" > : < / span > < span class = "p" > . < / span > < span class = "n" > fromSeconds < / span > < span class = "p" > ( < / span > < span class = "n" > seconds < / span > < span class = "p" > ) ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } < / span >
|
“Parallel composition – operators” xuất hiện trong CwlSignal.playground. Sự khác biệt quả thực rất đáng kinh ngạc, nó thậm chí còn không giống class gốc. Tuy nhiên, class này lại hoạt động như nhau, chỉ là thông qua các reactive programming channels, hay vì qua các callbacks với state và mutexes. Với reactive programming, tất cả các threading và lifetime management gây rất nhiều vấn đề cho implementation trước đều đã được giải quyết – chúng vẫn ở đó nhưng chúng đã được quản lý tự động.
Để có thể làm quen với các reactive programming operators như switchLatest
và materialize
có thể sẽ tốn của bạn kha khá thời gian. Tất cả các reactive programming operators sẽ được chỉ dẫn trong CwlSignal via Xcode quick help nhưng vì cũng tương tự như ReactiveX implementations, nên bạn cũng có thể tham khảo tài liệu đó để hiểu thêm về insight.
Trong trường hợp bạn nghĩ sử dụng hàm built-in cho timeout
là 1 hành vi “gian lận” thì bạn có thể đơn giản thay đổi 1 dòng đó với:
1
2
3
4
5
6
7
8
9
10
|
< span class = "kd" > let < / span > < span class = "nv" > timer < / span > < span class = "p" >= < / span > < span class = "n" > Signal < / span > < span class = "o" > & lt ; < / span > < span class = "p" > ( ) < / span > < span class = "o" > & gt ; < / span > < span class = "p" > . < / span > < span class = "n" > timer < / span > < span class = "p" > ( < / span > < span class = "n" > interval < / span > < span class = "p" > : < / span > < span class = "p" > . < / span > < span class = "n" > seconds < / span > < span class = "p" > ( < / span > < span class = "mi" > 10 < / span > < span class = "p" > ) ) < / span >
< span class = "k" > return < / span > < span class = "n" > connect < / span > < span class = "p" > ( ) . < / span > < span class = "n" > combine < / span > < span class = "p" > ( < / span > < span class = "n" > second < / span > < span class = "p" > : < / span > < span class = "n" > timer < / span > < span class = "p" > ) < / span > < span class = "p" > { < / span > < span class = "n" > eitherSignal < / span > < span class = "p" > , < / span > < span class = "n" > next < / span > < span class = "k" > in < / span >
< span class = "k" > switch < / span > < span class = "n" > eitherSignal < / span > < span class = "p" > { < / span >
< span class = "k" > case < / span > < span class = "p" > . < / span > < span class = "n" > result1 < / span > < span class = "p" > ( < / span > < span class = "kd" > let < / span > < span class = "nv" > r < / span > < span class = "p" > ) : < / span > < span class = "n" > next < / span > < span class = "p" > . < / span > < span class = "n" > send < / span > < span class = "p" > ( < / span > < span class = "n" > result < / span > < span class = "p" > : < / span > < span class = "n" > r < / span > < span class = "p" > ) < / span >
< span class = "k" > case < / span > < span class = "p" > . < / span > < span class = "n" > result2 < / span > < span class = "p" > : < / span > < span class = "n" > next < / span > < span class = "p" > . < / span > < span class = "n" > send < / span > < span class = "p" > ( < / span > < span class = "n" > error < / span > < span class = "p" > : < / span > < span class = "n" > MyErrors < / span > < span class = "p" > . < / span > < span class = "n" > timeout < / span > < span class = "p" > ) < / span >
< span class = "p" > } < / span >
< span class = "p" > } . < / span > < span class = "n" > materialize < / span > < span class = "p" > ( ) < / span >
|
Nhiều tiểu tiết hơn 1 chút nhưng vẫn không quá phức tạp.
Kết luận
Reactive programming đã thay đổi cách lưu trữ dữ liệu, cách dữ liệu “chảy” trong program của bạn và cách mà các yếu tố trong program của bạn được kết nối. Kết quả nhận được là rất nhiều cải tiến sau:
- Thread safety
- Coordinating các tasks bất đồng bộ đồng thời
- Loose coupling của các components
- Data dependencies
Lợi ích lớn nhất đến từ việc bạn nhận ra rằng khi áp dụng 1 giải pháp vào chỉ 1 trong các vấn đề đó, bạn sẽ thu hoạch được 1 giải pháp miễn phí cho 3 vấn đề còn lại.
Đối với các tình huống mà code đã giải quyết hợp lý các vấn đề, thì reactive programming có thể giúp bạn tiết kiệm được kha khá các dòng code.
Kết quả cuối chính là viết code dễ hơn và dễ maintain hơn.
Nguồn: cocoawithlove
- Phá mã cổ điển
- Tài liệu hướng dẫn qui trình kết nối Ngân Lượng với Magento, flow with NganLuong Magento
- Dán mạch với keo dẫn diện tự chế đơn giản
- Tất cả về AI - Trí tuệ nhân tạo - Artificial Intelligence
- Tôi code vì tiền?
- Xóa mật khẩu file excel chỉ với 6 bước đơn giản
- Di động đã cứu Nintendo (Pokemon GO) và giết chết Yahoo như thế nào?
- Hệ thống định vị Beidou Trung Quốc
- Top 10 ngôn ngữ lập trình
- Hướng dẫn quản trị Magento, admin Magento user guide
- Giảm chi phí logistics, phải ứng dụng công nghệ
- Tạo một ứng dụng với Rails API backend và VueJs frontend
DVMS chuyên:
- Tư vấn, xây dựng, chuyển giao công nghệ Blockchain, mạng xã hội,...
- Tư vấn ứng dụng cho smartphone và máy tính bảng, tư vấn ứng dụng vận tải thông minh, thực tế ảo, game mobile,...
- Tư vấn các hệ thống theo mô hình kinh tế chia sẻ như Uber, Grab, ứng dụng giúp việc,...
- Xây dựng các giải pháp quản lý vận tải, quản lý xe công vụ, quản lý xe doanh nghiệp, phần mềm và ứng dụng logistics, kho vận, vé xe điện tử,...
- Tư vấn và xây dựng mạng xã hội, tư vấn giải pháp CNTT cho doanh nghiệp, startup,...
Vì sao chọn DVMS?
- DVMS nắm vững nhiều công nghệ phần mềm, mạng và viễn thông. Như Payment gateway, SMS gateway, GIS, VOIP, iOS, Android, Blackberry, Windows Phone, cloud computing,…
- DVMS có kinh nghiệm triển khai các hệ thống trên các nền tảng điện toán đám mây nổi tiếng như Google, Amazon, Microsoft,…
- DVMS có kinh nghiệm thực tế tư vấn, xây dựng, triển khai, chuyển giao, gia công các giải pháp phần mềm cho khách hàng Việt Nam, USA, Singapore, Germany, France, các tập đoàn của nước ngoài tại Việt Nam,…
Quý khách xem Hồ sơ năng lực của DVMS tại đây >>
Quý khách gửi yêu cầu tư vấn và báo giá tại đây >>