- Một trong những đặc điểm nổi bật của Swift là có nhiều tính năng gốc được implement bằng chính Swift thay vì phải tuỳ chỉnh cứng trong complier. Về lý thuyết thì điều đó rất tiện lợi và cho chúng ta linh hoạt trong tuỳ chỉnh cách hoạt động có hiệu quả cao cho công việc.
- Ở bài viết này chúng ta sẽ cùng nghiên cứu sâu hơn về việc lựa chọn và tuỳ chỉnh pattern phù hợp trong Swift để thấy chúng ta có thể xây dựng các custom pattern hoàn chỉnh và một số kỹ thuật thú vị chúng ta có thể sử dụng sau này.
1/ Ví dụ đơn giản:
Pattern matching
là công việc khớp giá trị với cácpattern
được xác định trước đó thường để xác định ra nhánh code nào sẽ thực hiện công việc. VD: mỗi khi chúng taswitch
dựa trên các giá trị có sẵn:
1 2 3 4 5 6 7 8 9 |
<span class="token keyword">func</span> <span class="token function">items</span><span class="token punctuation">(</span><span class="token keyword">in</span> section<span class="token punctuation">:</span> <span class="token builtin">Section</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">[</span><span class="token builtin">Item</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> section <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span>featured<span class="token punctuation">:</span> <span class="token keyword">return</span> dataSource<span class="token punctuation">.</span>featuredItems <span class="token keyword">case</span> <span class="token punctuation">.</span>recent<span class="token punctuation">:</span> <span class="token keyword">return</span> dataSource<span class="token punctuation">.</span>recentItems <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Bên trên chúng ta sử dụng enum
Section
để xác định 2pattern
được tạo trước đó (featured
vàrecent
), đây là một cách sử dụng thông thường về việcpattern matching
trong swift, nó tí khi gây ra những ảnh hưởng không đáng có trong khi code. - Để nói rõ hơn, chúng ta định nghĩa
Pattern
struct, chúng ta sẽ sử dụng nó để định nghĩaclosure
của chúng ta.Closure
này sẽ lấy giá trị phù hợp và trả về kết quảBool
:
1 2 3 4 |
<span class="token keyword">struct</span> <span class="token builtin">Pattern</span><span class="token operator"><</span><span class="token builtin">Value</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> closure<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token builtin">Value</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">}</span> |
struct
bên trên có vẻ đơn giản nhưng nó hoàn toàn cho phép chúng ta có thể định nghĩa các sắp xếp tuỳ chỉnhpattern
bằng cách sử dụnggeneric type constaints
để thêm cácstatic factory methods
để tạo cácpattern
của chúng ta.
1 2 3 4 5 6 |
<span class="token keyword">extension</span> <span class="token builtin">Pattern</span> <span class="token keyword">where</span> <span class="token builtin">Value</span><span class="token punctuation">:</span> <span class="token builtin">Hashable</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">any</span><span class="token punctuation">(</span>of candidates<span class="token punctuation">:</span> <span class="token builtin">Set</span><span class="token operator"><</span><span class="token builtin">Value</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> candidates<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>$<span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Tất cả các
pattern matching
trongSwift
đều rất mạnh mẽ, với việc sử dụng operator~=
để dùng làm đối số bên trái và các giá trị phù hợp bên phải.
1 2 3 4 |
<span class="token keyword">func</span> <span class="token operator">~</span><span class="token operator">=</span><span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">(</span>lhs<span class="token punctuation">:</span> <span class="token builtin">Pattern</span><span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">,</span> rhs<span class="token punctuation">:</span> T<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">{</span> lhs<span class="token punctuation">.</span><span class="token function">closure</span><span class="token punctuation">(</span>rhs<span class="token punctuation">)</span> <span class="token punctuation">}</span> |
2/ Pha trộn và tuỳ chỉnh phù hợp:
- Nếu chúng ta đang làm việc trong app như mạng xã hội, khi sử dụng
LoggedInUser
struct để theo dõi date của user đã log in như id, friendId:
1 2 3 4 5 6 |
<span class="token keyword">struct</span> <span class="token builtin">LoggedInUser</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> id<span class="token punctuation">:</span> <span class="token builtin">Identifier</span><span class="token operator"><</span><span class="token builtin">User</span><span class="token operator">></span> <span class="token keyword">var</span> friendIDs<span class="token punctuation">:</span> <span class="token builtin">Set</span><span class="token operator"><</span><span class="token builtin">Identifier</span><span class="token operator"><</span><span class="token builtin">User</span><span class="token operator">></span><span class="token operator">></span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> |
- Bây giờ chúng ta muốn build 1 view controller để hiển thị bất kỳ số người sử dụng như một danh sách mà chúng ta muốn render các icon khác nhau phụ thuộc vào user thuộc loại nào. Quyết định đó được đưa ra bởi
switch statement
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="token keyword">private</span> <span class="token keyword">extension</span> <span class="token builtin">UserListViewController</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">resolveIcon</span><span class="token punctuation">(</span><span class="token keyword">for</span> userID<span class="token punctuation">:</span> <span class="token builtin">Identifier</span><span class="token operator"><</span><span class="token builtin">User</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Icon</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> userID <span class="token punctuation">{</span> <span class="token keyword">case</span> loggedInUser<span class="token punctuation">.</span>id<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span>currentUser <span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token function">any</span><span class="token punctuation">(</span>of<span class="token punctuation">:</span> loggedInUser<span class="token punctuation">.</span>friendIDs<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span>friend <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span>anyUser <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
3/ Pattern so sánh:
- Tiếp tục mở rộng
Pattern
bằng cách thêm vào các chức năng. Chúng ta sẽ viết các extension sử dụng protocolComparable
bao gồm 2method
:
1 2 3 4 5 6 7 8 9 10 |
<span class="token keyword">extension</span> <span class="token builtin">Pattern</span> <span class="token keyword">where</span> <span class="token builtin">Value</span><span class="token punctuation">:</span> <span class="token builtin">Comparable</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">lessThan</span><span class="token punctuation">(</span><span class="token number">_</span> value<span class="token punctuation">:</span> <span class="token builtin">Value</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> $<span class="token number">0</span> <span class="token operator"><</span> value <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">greaterThan</span><span class="token punctuation">(</span><span class="token number">_</span> value<span class="token punctuation">:</span> <span class="token builtin">Value</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> $<span class="token number">0</span> <span class="token operator">></span> value <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Đoạn code trên sẽ trở nên hữu dụng khi chúng ta muốn so sánh các giá trị hơn hoặc kém. Ở ví dụ này chúng ta chúng có thể kiểm tra user có vượt ngưỡng điểm của trò chơi không bằng cách sử dụng
switch
:
1 2 3 4 5 6 7 8 9 10 11 |
func levelFinished(withScore score: Int) { switch score { case .lessThan(50): showGameOverScreen() case .greaterThan(highscore): showNewHighscore(score) default: goToNextLevel() } } |
4/ Covert key path trong các pattern:
- Một các sử dụng khác của việc hình thành các mẫu cực kì hữu dụng là sử dụng
key-path
:
1 2 3 4 |
<span class="token keyword">func</span> <span class="token operator">~</span><span class="token operator">=</span><span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">(</span>lhs<span class="token punctuation">:</span> <span class="token builtin">KeyPath</span><span class="token operator"><</span>T<span class="token punctuation">,</span> <span class="token builtin">Bool</span><span class="token operator">></span><span class="token punctuation">,</span> rhs<span class="token punctuation">:</span> T<span class="token operator">?</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">{</span> rhs<span class="token operator">?</span><span class="token punctuation">[</span>keyPath<span class="token punctuation">:</span> lhs<span class="token punctuation">]</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> |
- Với đoạn code trên chúng ta có thể pha trộn
key-path
với các kiểupattern
cho phép chúng ta có thể giải quyết các đoạn code logic phức tạp chỉ với việc sử dụngswitch statement
. - Ởđây chúng ta đã quyết định cách phân tích một dòng văn bản trong một danh sách phụ thuộc vào first character bằng cách sử dụng
Character type
để tạo mẫu key-path, kết hợp với với các pattern phù hợp với enumKind
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 |
<span class="token keyword">struct</span> <span class="token builtin">ListItemParser</span> <span class="token punctuation">{</span> <span class="token keyword">enum</span> <span class="token builtin">Kind</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> numbered <span class="token keyword">case</span> unordered <span class="token punctuation">}</span> <span class="token keyword">let</span> kind<span class="token punctuation">:</span> <span class="token builtin">Kind</span> <span class="token keyword">func</span> <span class="token function">parseLine</span><span class="token punctuation">(</span><span class="token number">_</span> line<span class="token punctuation">:</span> <span class="token builtin">String</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">ListItem</span> <span class="token punctuation">{</span> <span class="token comment">// Here we're switching on an optional Character, which is</span> <span class="token comment">// the type of values that Swift strings are made up of:</span> <span class="token keyword">switch</span> line<span class="token punctuation">.</span><span class="token builtin">first</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token keyword">none</span><span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token builtin">Error</span><span class="token punctuation">.</span>emptyLine <span class="token keyword">case</span> <span class="token punctuation">.</span>isNewline<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span>empty <span class="token keyword">case</span> <span class="token punctuation">.</span>isNumber <span class="token keyword">where</span> kind <span class="token operator">==</span> <span class="token punctuation">.</span>numbered<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token function">parseLineAsNumberedItem</span><span class="token punctuation">(</span>line<span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token string">"-"</span> <span class="token keyword">where</span> kind <span class="token operator">==</span> <span class="token punctuation">.</span>unordered<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token function">parseLineAsUnorderedItem</span><span class="token punctuation">(</span>line<span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token keyword">let</span> character<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token builtin">Error</span><span class="token punctuation">.</span><span class="token function">invalidFirstCharacter</span><span class="token punctuation">(</span>character<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Để đoạn code trên hoạt động trơn tru, hãy định nghĩa 1 operator khác
==
sẽ trả vềPattern
kết hợpKeyPath
và một hằng số như sau:
1 2 3 4 |
<span class="token keyword">func</span> <span class="token operator">==</span><span class="token operator"><</span>T<span class="token punctuation">,</span> V<span class="token punctuation">:</span> <span class="token builtin">Equatable</span><span class="token operator">></span><span class="token punctuation">(</span>lhs<span class="token punctuation">:</span> <span class="token builtin">KeyPath</span><span class="token operator"><</span>T<span class="token punctuation">,</span> V<span class="token operator">></span><span class="token punctuation">,</span> rhs<span class="token punctuation">:</span> V<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Pattern</span><span class="token operator"><</span>T<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> $<span class="token number">0</span><span class="token punctuation">[</span>keyPath<span class="token punctuation">:</span> lhs<span class="token punctuation">]</span> <span class="token operator">==</span> rhs <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Chúng ta đã kết hợp
key-path
với giá trị được tạo từpattern
chúng ta chỉ cần đơn giản implement cách chúng ta tính toán level của giá trị giao hàng vớiDestination
như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span class="token keyword">struct</span> <span class="token builtin">Destination</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> address<span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> city<span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> country<span class="token punctuation">:</span> <span class="token builtin">Country</span> <span class="token punctuation">}</span> <span class="token keyword">extension</span> <span class="token builtin">Destination</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> shippingCost<span class="token punctuation">:</span> <span class="token builtin">ShippingCost</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token keyword">self</span> <span class="token punctuation">{</span> <span class="token comment">// Combining a key path with a constant value:</span> <span class="token keyword">case</span> <span class="token punctuation">.</span>city <span class="token operator">==</span> <span class="token string">"Paris"</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span>free <span class="token comment">// Using a nested key path as a pattern:</span> <span class="token keyword">case</span> <span class="token punctuation">.</span>country<span class="token punctuation">.</span>isInEurope<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span>reduced <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span>normal <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |