Generics swift
GENERICS LÀ GÌ Generics là một trong những tính năng mạnh mẽ nhất của Swift, và phần lớn các thư viện chuẩn Swift được xây dựng với mã generic. Trong thực tế, chúng ta đã sử dụng thường xuyên. Ví dụ, kiểu Array và Dictionary của Swift là cả 2 tập hợp generic. Chúng ta có thể tạo một mảng chứa ...
GENERICS LÀ GÌ
Generics là một trong những tính năng mạnh mẽ nhất của Swift, và phần lớn các thư viện chuẩn Swift được xây dựng với mã generic. Trong thực tế, chúng ta đã sử dụng thường xuyên. Ví dụ, kiểu Array và Dictionary của Swift là cả 2 tập hợp generic. Chúng ta có thể tạo một mảng chứa các giá trị Int, hoặc một mảng chứa các giá trị String, hoặc thực sự là một mảng cho bất kỳ kiểu nào khác có thể được tạo ra trong Swift. Tương tự như vậy, chúng ta có thể tạo ra một từ điển để lưu trữ các giá trị của bất kỳ kiểu quy định.
1- Vấn đề giải quyết Generics
Dưới đây là một mẫu hàm non-generic gọi là oneSwapTwoInts, nó tráo đổi hai giá trị Int:
func oneSwapTwoInts(inout a: Int, inout b: Int) { let temporaryA = a a = b b = temporaryA }
Phương thức này sử dụng của hai tham số in-out để tráo đổi giá trị của a và b, như được mô tả trong In-Out Parameters.
Phương thức oneSwapTwoInts tráo đổi giá trị ban đầu của b vào a, và giá trị ban đầu của a vào b. Bạn có thể gọi Phương thức nay để tráo đổi giá trị của hai biến Int:
ar someInt = 3 var anotherInt = 107 oneSwapTwoInts(&someInt, &anotherInt) println("someInt is now (someInt), and anotherInt is now (anotherInt)") // prints "someInt is now 107, and anotherInt is now 3”
Phương thức oneSwapTwoInts thật hữu ích, nhưng nó chỉ có thể được sử dụng với giá trị Int. Nếu muốn tráo đổi 2 giá trị String, hoặc 2 giá trị Double, chúng ta phải viết nhiều phương thức hơn nữa, như là phương thức oneSwapTwoStrings và hàm swapTwoDoubles được biểu diễn dưới đây:
func oneSwapTwoStrings(inout a: String, inout b: String) { let temporaryA = a a = b b = temporaryA } func swapTwoDoubles(inout a: Double, inout b: Double) { let temporaryA = a a = b b = temporaryA }
thể thấy các phương thức oneSwapTwoInts, oneSwapTwoStrings, và swapTwoDoubles giống hệt nhau. Sự khác biệt duy nhất là kiểu của giá trị mà chúng chấp nhận (Int, String, và Double).
Nó sẽ được hữu ích hơn, và linh hoạt hơn đáng kể, để viết một phương thức duy nhất mà có thể hoán đổi hai giá trị của bất kỳ kiểu nào. Generic code cho phép bạn viết một phương thức như vậy. (Một phiên bản chung của các hàm này được định nghĩa ở bên dưới.)
Chú ý
Trong cả ba phương thức, điều quan trong là các kiểu của a và b được định nghĩa tương tự nhau. Nếu a và b không cùng một kiểu, nó sẽ không thể tráo đổi giá trị của chúng. Swift là một ngôn ngữ an toàn kiểu, và không cho phép (ví dụ) một biến có kiểu là String và một biến có kiểu là Double tráo đổi giá trị cho nhau. Việc cố gắng làm như vậy sẽ được báo cáo như là một lỗi compile-time.
**2- phương thức Generic **
Generic functions có thể làm việc với bất kỳ kiểu nào. Dưới đây là một cách viết generic của phương thức oneSwapTwoInts từ bên trên, gọi là oneSwapTwoValues:
func oneSwapTwoValues<T>(inout a: T, inout b: T) { let temporaryA = a a = b b = temporaryA }
oneSwapTwoValues giống hệt phương thức oneSwapTwoInts. Tuy nhiên, dòng đầu tiên của oneSwapTwoValues hơi khách với oneSwapTwoInts. Dưới đây là sự so sánh các dòng đầu tiên:
func oneSwapTwoInts(inout a: Int, inout b: Int) func oneSwapTwoValues<T>(inout a: T, inout b: T)
generic sử dụng một tên kiểu giữ chỗ – placeholder (gọi là T, trong trường hợp này) thay vì một tên kiểu thực tế (như là Int, String, hay Double). Tên kiểu giữ chỗ không nói bất cứ điều gì về T phải là gì, cả a và b phải cùng kiểu T, để sử dụng tạo chỗ của T sẽ được xác định mỗi lần hàm oneSwapTwoValues được gọi.
Sự khác biệt nữa là tên của phương thức generic (oneSwapTwoValues) được theo sau bởi tên kiểu giữ chỗ (T) bên trong các dấu ngoặc nhọn (<T>). Các dấu ngoặc nói với Swift rằng T là một tên kiểu giữ chỗ trong định nghĩa hàm oneSwapTwoValues. Bởi vì T là một giữ chỗ, Swift tìm kiếm một kiểu thực tế gọi là T.
phương thức oneSwapTwoValues hiện giờ có thể được gọi trong các tương tự như oneSwapTwoInts, ngoại trừ việc nó có thể được truyền vào hai giá trị của kiểu bất kỳ, chỉ cần các giá trị này cùng một kiểu tương tự nhau. Mỗi lần oneSwapTwoValues được gọi, kiểu được sử dụng cho T được suy ra từ kiểu của giá trị truyền vào phương thức.
Trong 2 trường hợp dưới đây, T được suy ra tương ứng là Int và String:
var someInt = 3 var anotherInt = 107 oneSwapTwoValues(&someInt, &anotherInt) // someInt is now 107, and anotherInt is now 3 var someString = "hello" var anotherString = "world" oneSwapTwoValues(&someString, &anotherString) // someString is now "world", and anotherString is now "hello"
Lưu ý
phương thức oneSwapTwoValues định nghĩa bên trên gọi là swap, nó là một phần của thư viện chuẩn Swift, và được tự động làm sẵn cho chúng ta sử dụng trong các ứng dụng. Nếu bạn cần hành vi của hàm oneSwapTwoValues trong đoạn mã riêng của bạn, bạn có thể sử dụng hàm swap hiện có của Swift thay vì cung cấp thực hiện riêng của bạn.
3- Kiểu Tham số
Trong ví dụ oneSwapTwoValues ở bên trên, kiểu giữ chỗ T là một ví dụ của tham số kiểu – type parameter. Tham số kiểu xác định và đặt tên một kiểu giữ chỗ, và được viết trực tiếp ngay sau tên hàm, giữa một cặp ngoặc nhọn (như là <T>).
Một khi bạn chỉ định một tham số kiểu, chúng ta có thể sử dụng nó để định nghĩa kiểu của một tham số của hàm (như là tham số a và tham số b của hàm oneSwapTwoValues), hay như là kiểu trả về của hàm, hay như một chú thích kiểu bên trong thân của hàm. Trong từng trường hợp, kiểu giữ chỗ biểu diễn bởi tham số kiểu được thay thế với một kiểu thực tế mỗi khi hàm được gọi. (Trong ví dụ oneSwapTwoValues ở trên, T được thay thế với Int trong lần đầu tiên phương thức được gọi, và được thay thế với String trong lần thứ hai nó được gọi.)
chúng ta có thể cung cấp nhiều hơn một tham số kiểu bằng cách viết tên nhiều tham số kiểu trong ngoặc nhọn, các nhau bởi dấu phẩy.
4- Đặt tên tham số kiểu Naming
Trong các trường hợp đơn giản, một hàm generic hoặc kiểu generic tham chiếu tới một kiểu giữ chỗ (như là hàm generic oneSwapTwoValues ở bên trên, hoặc một tập hợp generic mà lưu một kiểu duy nhất, như là Array),
trường hợp đang định nghĩa các hàm generic phức tạp hơn, hoặc các kiểu generic với nhiều tham số, để cung cấp nhiều mô tả tên tham số kiểu hơn. Ví dụ, kiểu Dictionary của Swift có 2 tham số kiểu – một cho các key của nó và một cho các value. Nếu đang viết Dictionary chúng ta có thể đặt tên 2 tham số Key và Value.
lưu ý
luôn tạo tên tham số kiểu dạng UpperCamelCase (như là T và Key)
**5- Kiểu Generic **
Ngoài các phương thức generic, Swift cho phép định nghĩa các kiểu generic của riêng mình. Chúng là các lớp, cấu trúc, và liệt kê tuỳ chỉnh cái mà có thể làm việc với bất kỳ kiểu nào, trong một cách tương tự như Array và Dictionary.
Phần này cho thấy làm thế nào để viết một kiểu tập hợp generic đc gọi là Stack. Một ngăn xếp là một tập hợp các giá trị, tương tự như một mảng, nhưng với một tập hợp toán tử hạn chế hơn kiểu Array của Swift. Một mảng cho phép các phần tử mới được chèn vào và lấy ra ở bất kỳ vị trí nào trong mảng. Một ngăn xếp cho phép các phần tử mới được chỉ nối vào cuối của tập hợp.
Lưu ý
Khái niệm của một ngăn xếp được sử dụng bởi lớp UINavigationController để mô hình hoá các view controller trong phân cấp điều hướng của nó cần gọi phương thức pushViewController:animated: của class UINavigationController để thêm (hay là push) một view controller trong xếp điều hướng, và phương thức popViewControllerAnimated: của nó để lấy ra .
dưới đây là minh hoạ dưới đây biểu diễn hành vi push/pop cho một ngăn xếp:
-
Hiện tại có 3 giá trị trong stack.
-
Một giá trị thứ tư được “pushed” lên đến đỉnh của stack.
-
Stack hiện nắm giữ bốn giá trị, với giá trị gần đây nhất ở trên đỉnh.
-
Phần tử trên cùng trong ngăn xếp được lấy ra, hay là “popped”.
-
Sau khi lấy ra một giá trị, ngăn xếp một lần nữa nắm giữ ba giá trị.
-
Dưới đây là cách viết một phiên bản non-generic của một ngăn xếp, trong trường hợp này là một ngăn xếp của các giá trị Int:
struct OneIntStack { var items = [Int]() mutating func push(item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() }
Lưu ý rằng phiên bản generic của Stack cơ bản tương tự như phiên bản non-generic, nhưng với một tham số kiểu giữ chỗ gọi là T thay cho một kiểu thực tế là Int. Tham số kiểu này được viết bên trong cặp ngoặc nhọn (<T>) ngay sau tên của cấu trúc.
– Để tạo một thuộc tính gọi là items, nó được khởi tạo với một mảng rỗng các giá trị kiểu T. – Để chỉ ra rằng phương thức push có một tham số duy nhất gọi là item, nó phải là kiểu T. – Để chỉ ra rằng giá trị trả về bởi phương thức pop sẽ là một giá trị của kiểu T.
việc tạo ra một thể hiện Stack mới bằng cách viết kiểu được lưu trữ trong ngăn xếp bên trong ngoặc nhọn. Ví dụ, để tạo một ngăn xếp mới của các chuỗi, bạn viết Stack<String>():
var oneStackOfStrings = Stack<String>() oneStackOfStrings.push("uno") oneStackOfStrings.push("dos") oneStackOfStrings.push("tres") oneStackOfStrings.push("cuatro") // the stack now contains 4 strings
Dưới đây là cách oneStackOfStrings trông như thế nào sau khi thêm bốn giá trị vào trong ngăn xếp:
Lấy một giá trị từ ngăn xếp trả về và lấy ra giá trị trên cùng, “cuatro”:cuatro
let fromTheTop = oneStackOfStrings.pop() // fromTheTop is equal to "cuatro", and the stack now contains 3 strings
Dưới đây là cách ngăn xếp sau khi lấy ra phần tử trên cùng.
6- Mở rông kiểu Generic
Khi mở rộng một kiểu generic, không cung cấp một danh sách tham số kiểu như một phần của định nghĩa của extension. Thay vào đó, danh sách tham số kiểu từ định nghĩa kiểu ban đầu có sẵn của extension, và tên tham số kiểu được sử dụng để tham chiếu tới các tham số kiểu từ định nghĩa ban đầu.
extension Stack { var topItem: T? { return items.isEmpty ? nil : items[items.count - 1] } }
Thuộc tính topItem trả về một giá trị optioal kiểu T. Nếu ngăn xếp rỗng, topItem trả về nil, nếu ngăn xếp không rỗng, topItem trả về phần tử cuối cùng trong mảng items.
Lưu ý rằng, extension này không định nghĩa một danh sách tham số kiểu. Thay vào đó, tên tham số kiểu hiện có của kiểu Stack, T, được sử dụng trong extension để biểu thị kiểu optional của thuộc tính tính toán topItem.
if let topItem = oneStackOfStrings.topItem { println("The top item on the stack is (topItem).") } // prints "The top item on the stack is tres."
7-Những ràng buộc
phương thức swapTwoValues và kiểu Stack có thể làm việc với bất kỳ kiểu nào. Tuy nhiên, đôi khi nó hữu ích để bắt buộc ràng buộc kiểu – type constraints – đã biết với các kiểu mà có thể được sử dụng với các phương thức generic và các kiểu generic.
Ví dụ, kiểu Dictionary của Swift đặt một giới hạn trên các kiểu mà có thể được sử dụng như là các khoá cho từ điển. Như được mô tả trong Dictionaries, kiểu của các khoá từ điển phải được băm – hashable. Đó là, nó phải cung cấp một cách để xây dựng biểu diễn duy nhất của nó. Dictionary cần các khoá của nó để băm ví thế nó có thể kiểm tra rằng liệu nó từng chứa một giá trị cho một khoá cụ thể. Nếu không có yêu cầu này, Dictionary không thể nói rằng liệu nó nên chèn hoặc thay thế một giá trị cho một khoá cụ thể, và nó cũng sẽ không thể tìm thấy một giá trị cho khoá được cấp cái mà từng ở trong từ điển.
Yêu cầu này được thực thi bởi một ràng buộc kiểu trên kiểu khoá cho Dictionary, nó chỉ ra rằng kiểu khoá phải phù hợp với giao thức Hashable, một giao thức đặc biệt định nghĩa trong thư viện chuẩn Swift. Tất cả các kiểu cơ bản của Swift (như là String, Int, Double, và Bool) được băm theo mặc định.
chúng ta có thể định nghĩa ràng buộc kiểu của riêng mình khi tạo các kiểu generic tuỳ chỉnh, và các ràng buộc này cung cấp nhiều sức mạnh của chương trình generic. Các khái niệm trừu tượng – Abstract concepts – như các kiểu ký tự Hashable trong lớp ký tự khái niệm của chúng, thay vì kiểu ngầm của chúng.
7.1 Cú pháp ràng buộc kiểu
chúng ta viết các ràng buộc kiểu bằng cách đặt một lớp duy nhất hoặc ràng buộc giao thức sau một tên của tham số kiểu, phân cách bởi dấu hai chấm, như một ohanaf của danh sách tham số kiểu. Cú pháp đơn giản cho các ràng buộc kiểu trên một hàm generic được biểu diễn bên dưới
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // function body goes here }
phương thức t ở trên có 2 tham số kiểu. Tham số kiểu đầu tiên, T, có một ràng buộc kiểu mà yêu cầu T là một lớp con của SomeClass. Tham số kiểu thứ hai, U, có một ràng buộc kiểu mà yêu cầu U phù hợp với giao thức SomeProtocol.
**7.2 Ràng buộc kiểu trong Action **
Dưới đây là một hàm non-generic gọi là findStringIndex, nó được đặt vào một giá trị String cho việc tìm kiếm và một mảng của các giá trị String để tìm kiếm bên trong nó. phương thức findStringIndex trả về một giá trị optional Int, nó sẽ là chỉ số của xâu phù hợp đầu tiên nếu nó được tìm thấy, hoặc nil nếu xâu không thể được tìm thấy.
func findStringIndex(array: [String], valueToFind: String) -> Int? { for (index, value) in enumerate(array) { if value == valueToFind { return index } } return nil
phương thức findStringIndex có thể được sử dụng để tìm một giá trị chuỗi trong một mảng các chuỗi:
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] if let foundIndex = findStringIndex(strings, "llama") { println("The index of llama is (foundIndex)") } // prints "The index of llama is 2"
Nguyên tắc của việc tìm kiếm chỉ số của một giá trị trong một mảng chỉ không hữu ích cho chuỗi, tuy nhiên. có thể viết chức năng tương tự như một phương thức generic gọi là findIndex, bằng cách thay thế bất kỳ đề cập nào của các chuỗi với các giá trị của một số kiểu T thay thế.
func findIndex<T>(array: [T], valueToFind: T) -> Int? { for (index, value) in enumerate(array) { if value == valueToFind { return index } } return nil }
phương thức này không biên dịch như được viết ở trên. Vấn đề nằm ở việc kiểm tra phép bằng, “if value == valueToFind”. Không phải mọi kiểu trong Swift có thể được so sánh với toán tử bằng (==). Nếu tạo ra lớp hoặc cấu trúc cho riêng mình để biểu diễn một mô hình dữ liệu phức tạp.
Bất kỳ kiểu nào là Equatable có thể được sử dụng an toán với hàm findIndex, bởi vì nó được đảm bảo để hỗ trợ toán tử bằng với. Để biểu diễn thực tế, bạn viết một ràng buộc kiểu của Equatable như một phần của định nghĩa của tham số kiểu khi bạn định nghĩa hàm:
func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? { for (index, value) in enumerate(array) { if value == valueToFind { return index } } return nil
Tham số kiểu duy nhất cho findIndex được viết là T: Equatable, nó nghĩa là “bất kỳ kiểu T nào mà phù hợp với giao thức Equatable.”
phương thức findIndex hiện giờ biên dịch thành công và có thể được sử dụng với bất kỳ kiểu nào là Equatable, như là Double hoặc String:
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3) // doubleIndex is an optional Int with no value, because 9.3 is not in the array let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea") // stringIndex is an optional Int containing a value of 2
**8- Kiểu liên kết **
Khi định nghĩa một giao thức, đôi khi hữu ích đẻ khai báo một hoặc nhiều kiểu liên kết như một phần của định nghĩa của giao thức. Một kiểu liên kết đặt một tên giữ chỗ cho một kiểu mà được sử dụng như một phần của giao thức. Kiểu thực tế để sử dụng cho kiểu liên kết đó không được chỉ định tận tới khi giao thức được thừa nhận.
Các kiểu liên kết được chỉ định với từ khoá typealias.
** 8.1 Liên kết kiểu trong Action **
Dưới đây là một ví dụ của một giao thức gọi là Container, nó khai báo một kiểu liên kết gọi là ItemType:
protocol Container { typealias ItemType mutating func append(item: ItemType) var count: Int { get } subscript(i: Int) -> ItemType { get } }
Giao thức Container định nghĩa ba phải năng được yêu cầu mà bất kỳ container nào phải cung cấp:
– phải có thể thêm một phần tử mới vào container với phương thức append.
– phải có thể truy cập một bộ đém của các phần tử trong container thông qua thuộc tính count mà trả về một giá trị Int.
– phải có thể lấy mỗi từng phần tử trong container với một subscript mà lấy một giá trị chỉ số Int.
Giao thức này không chỉ ra rằng các phần tử trong container nên được lưu như thể nào, hoặc kiểu gì mà chúng được cho phép.
Bất kỳ kiểu nào phù hợp với giao thức Protocol phải có thể được chỉ định kiểu của các giá trị mà nó lưu trữ.
Đặc biệt, nó phải chắc chắn rằng chỉ các phần tử của kiểu chính xác được thêm vào container, và mó phải rõ ràng về kiểu của các giá trị được trả về bởi subscript của nó.
Để định nghĩa các yêu cầu này, giao thức Protocol cần một cách để tham chiếu tới kiểu của các phần từ mà container sẽ giữ, mà không biết kiểu gì cho một container cụ thể.
Để đạt được điều này, giao thức Container khai báo một kiểu liên kết gọi là ItemType, được viết là typealias ItemType. Giao thức không định nghĩa ItemType.Tuy nhiên ItemType cung cấp một cách để tham chiếu tới kiểu của các phần tử trong một Container.
Dưới đây là một vd của kiểu non-generic OneIntStack từ trước đó với giao thức Container:
struct OneIntStack: Container { // original OneIntStack implementation var items = [Int]() mutating func push(item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } // conformance to the Container protocol typealias ItemType = Int mutating func append(item: Int) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Int { return items[i] } }
Kiểu OneIntStack thực hiện cả 3 yêu cầu của giao thức Container, và trong từng trường hợp kết thúc phần của chúng năng hiện có của kiểu OneIntStack để đáp ứng các yêu cầu này.
OneIntStack quy định rằng đối với việc thực hiện này của Container, ItemType phù hợp được sử dụng là một kiểu của Int.
Cảm ơn suy luận kiểu của Swift, bạn không thức sự cần khai báo một ItemType cụ thể của Int như phần định nghĩa của OneIntStack. Bởi vì OneIntStack phù hợp với tất cả các yêu cầu của giao thức Container, Swift có thể suy ra ItemType thích hợp để sử dụng, đơn giản bằng cách nhìn vào kiểu của tham số item của phương thức append và kiểu trả về của subscript.
chúng ta cũng có thể mà cho kiểu generic Stack phù hợp với giao thức Container:
struct OneStack<T>: Container { // original OneStack<T> implementation var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } // conformance to the Container protocol mutating func append(item: T) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> T { return items[i] } }
tham số kiểu giữ chỗ T được sử dụng như là kiểu của tham số item trong phương thức append và kiểu trả về của subscript. Do đó, Swift có thể suy ra rằng T là kiểu thích hợp để sử dụng như là ItemType cho container cụ thể.
**8.2 Mở rông một kiểu hiện có xác định kiểu liên kết **
Chúng ta có thể mở rộng một kiểu hiện có để bổ sung sự phù hợp cho một giao thức. Điều này bao gồm một giao thức với một kiểu liên kết.
Kiểu Array của Swift cung cấp một phương thức append, một thuộc tính count, và một subscript với một chỉ số Int để lấy các phần tử của nó Ba khả năng này phù hợp các yêu cầu của giao thức Container. Có thể mở rộng Array để phù hợp với giao thức Container chỉ đơn giản bằng cách khai báo Array với extension rỗng.
extension Array: Container {}
phương thức append hiện của có mảng và subscipt cho phép Swift suy ra kiểu thích hợp để sử dụng cho ItemType, giống hệt như kiểu generic Stack ở trên. Sau khi định nghĩa extension này, bạn có thể sử dụng Array như là một Container.
9- Mệnh đề Where
Các ràng buộc kiểu, như đã mô tả tỏng Type Constraints, cho phép định nghĩa các yêu cầu trên tham số kiểu liên kết với một hàm generic hoặc kiểu generic.
Nó cũng có thể hữu ích để định nghĩa các yêu cầu cho các kiểu liên kết. làm việc này bằng cách định nghĩa mệnh đề where – where clauses – như là phần của một danh sách tham số kiểu. Một mệnh đề where cho phép bạn yêu cầu rằng một kiểu liên kết phù hợp với một giao thức thực tế, và/hoặc các tham số kiểu thực tế và các kiểu liên kết là tương tự.
Ví dụ dưới đây định nghĩa một hàm generic gọi là allItemsMatch, kiểm tra xem nếu 2 thể hiện Container chứa các phần tử tương tự trong cùng thứ tự. Hàm trả về một giá trị Boolean là true nếu tất cả phần tử phù hợp và một giá trị là false nếu chúng không phù hợp.
func allItemsMatch< C1: Container, C2: Container where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> (someContainer: C1, anotherContainer: C2) -> Bool { // check that both containers contain the same number of items if someContainer.count != anotherContainer.count { return false } // check each pair of items to see if they are equivalent for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // all items match, so return true return true }
Hàm này có 2 đối số gọi là someContainer và anotherContainer. Đối số someContainer có kiểu là C1, và đối số anotherContainer có kiểu là C2. Cả hai C1 và C2 là các tham số kiểu giữ chỗ cho hai kiểu container được xác định khi hàm được gọi.
Danh sách tham số kiểu của hàm đặt các yêu cầu sau trên hai tham số kiểu:
– C1 phải phù hợp với giao thức Container ( viết là C1: Container). – C2 cũng phải phù hợp với giao thức Container (viết là C2: Container). – ItemType cho C1 phải tương tự như ItemType cho C2 ( viết là C1.ItemType == C2.ItemType). – ItemType cho C1 phải phù hợp với giao thức Equatable (viết là C1.ItemType: Equatable)
Yêu cầu thứ ba và thứ tư được định nghĩa như một phần của một mệnh đề where, và được viết sau từ khoá where như một phần của danh sách tham số kiểu của hàm.
Các yêu cầu này nghĩa là:
– someContainer là một container của kiểu C1. – anotherContainer là một container của kiểu C2. – someContainer và anotherContainer chứa kiểu tương tự của các phần tử. – Các phần tử trong someContainer có thể được kiểm tra với toán tử không bằng với (!=) để xem chúng có khác với các phần tử khác.
Các yêu cầu này cho phép hàm allItemsMatch so sánh hai container, thậm chí chúng là một kiểu container khác.
phương thức allItemsMatch bắt đầu bằng cách kiểm tra xem các container chứa cùng số lượng của các phần tử, không có cách nào mà chúng có thể phù hợp, và hàm trả về false.
Sau khi thực hiện kiểm tra, hàm duyệt qua tất cả các phần tử trong someContainer với vòng lặp for-in và bán phạm vi (..<). Đối với từng phần tử, hàm kiểm tra xem liệu phần tử từ someContainer có không bằng với phần tử tương ứng trong anotherContainer. Nếu hai phần tử là không bằng, thì hai container không phù hợp, và hàm trả về false.
Nếu vòng lặp kết thúc mà không tìm thấy một điểm không phù hợp, 2 container phù hợp, và hàm trả về true.
Dưới đây là cách mà hàm allItemsMatch thực hiện:
var oneStackOfStrings = Stack<String>() oneStackOfStrings.push("uno") oneStackOfStrings.push("dos") oneStackOfStrings.push("tres") var arrayOfStrings = ["uno", "dos", "tres"] if allItemsMatch(oneStackOfStrings, arrayOfStrings) { println("All items match.") } else { println("Not all items match.") } // prints "All items match.”
Ví dụ trên tạo ra một thể hiện Stack được để lưu các giá trị String, và thêm ba chuỗi vào trong Stack. Ví dụ cũng tạo ra một thể hiện Array khởi tạo với một mảng chữ chứa ba chuỗi tương tự như ngăn xếp. Mặc dù ngăn xếp và mảng là một kiểu khác nhau, chúng cùng phù hợp với giao thức Container, và cùng chứa các giá trị của kiểu tương tự. Có thể gọi hàm allItemsMatch với hai container này với các đối số của nó. Trong ví dụ trên, hàm allItemsMatch báo cáo chính xác rằng tất cả phần tử trong hai container phù hợp