Swift Generics (Part 1)

by Hoang Anh Tuan
649 views

Generic cho phép bạn viết function và type 1 cách linh hoạt, dễ dàng tái sử dụng, có thể hoạt động với bất kì loại nào tùy theo các yêu cầu mà bạn xác định. Với generic, bạn có thể tránh sự trùng lặp của code mà vẫn thể hiện í định của nó 1 cách rõ ràng.

Ở phần 1 của bài viết, bạn sẽ học được:

  • Generics là gì ?
  • Tại sao chúng hữu dụng ?
  • Cách sử dụng generic đơn giản.

Getting Started

Giả sử bạn được yêu cầu viết 1 phương thức tính tổng 2 số với kiểu dữ liệu bất kì. Thông thường, bạn sẽ viết các func riêng cho các kiểu dữ liệu riêng.

func addInt(num1: Int, num2: Int) -> Int {
    return num1 + num2
}

func addDouble(num1: Double, num2: Double) -> Double {
    return num1 + num2
}

Dễ dàng nhận thấy, func addInt(::) và func addDouble(::) là 2 func khác nhau nhưng có thân hàm giống nhau. Bạn không chỉ có 2 func, mà code của bạn bị lặp lại.
-> Generic có thể được sử dụng để giảm 2 hàm này thành 1 và loại bỏ sự trùng lặp code.

Note

Trong các func ở trên, kiểu dữ liệu của num1num2 là giống nhau. Nếu kiểu dữ liệu của chúng khác nhau thì không thể cộng 2 số.

Generic Function

Generic func có thể hoạt động với bất kì kiểu dữ liệu nào. Dưới đây là code generic của addInt(::) ở trên, tên là addTwoNumbers:

func addTwoNumbers<T>(num1: T, num2: T) -> T {
   return num1 + num2
}

Tưởng tượng bạn có 1 UITextfield, UITextfield đó có 1 trường gọi là placeholder. placeholder sẽ được thay thế bằng text khi bạn nhập text vào UITextfield.
Tương tự như vậy, func addTwoNumbers(::) sử dụng kiểu dữ liệu T như là 1 placeholder. Kiểu dữ liệu T sẽ được thay thế bằng 1 kiểu dữ liệu xác định do bạn quyết định như Int, Double, … mỗi khi func addTwoNumbers(::) dược gọi. Ta có thể thấy trong func addTwoNumbers(::) ở trên, num1 và num2 có cùng kiểu dữ liệu là T.

Note:

  • Sự khác biệt giữa generic func và non-generic func là: Ở tên của generic func thì có 1 kiểu dữ liệu placeholder (T) được khai báo trong cặp ngoặc nhọn (<T>). Cặp ngoặc nhọn nói với Swift rằng T là kiểu dữ liệu placeholder.
  • Bạn có thể thay T bằng các các tên khác như A, B, C, Element, … tùy bạn muốn. Việc thay đổi tên chỉ có tác dụng thay đổi tên của kiểu dữ liệu placeholder, chứ không thay đổi gì về tác dụng. Nên tránh đặt tên trùng với các kiểu dữ liệu xác định như Int, String, Double, … để tránh gây nhầm lẫn.
  • Và luôn đặt tên bắt đầu bằng chữ cái in hoa để người đọc hiểu rằng đó là 1 placeholder cho 1 kiểu dữ liệu chứ không phải cho 1 giá trị.

Tuy nhiên, khi khai báo func addTwoNumbers(::) như trên, bạn sẽ gặp lỗi như sau:

Lỗi này có nghĩa là, bạn không thể áp dụng toán tử + cho kiểu dữ liệu T vì không phải mọi kiểu dữ liệu trong Swift đều có thể cộng với nhau bằng toán tử (+). Để áp dụng được toán tử (+) cho kiểu dữ liệu T, bạn làm như sau:

protocol Summable {
    static func +(num1: Self, num2: Self) -> Self
}

func addTwoNumbers<T: Summable>(num1: T, num2: T) -> T {
    return num1 + num2
}

Ở đây, bạn khai báo 1 protocol Summable có chứa toán tử (+) và cho T extends Summable. Khi đó có nghĩa là, kiểu dữ liệu xác định mà bạn muốn đưa vào để thay T thì kiểu dữ liệu đó phải extends Summable, và hiển nhiên là chúng sẽ phải khai báo toán tử (+).

Sử dụng generic:

Khi bạn gọi hàm addTwoNumbers, bạn sẽ thay T bằng 1 kiểu dữ liệu xác định. Tuy nhiên, vì T là kiểu Summable, nên kiểu dữ liệu xác định mà bạn muốn truyền vào cũng phải extends Summable và khai báo toán tử + cho kiểu dữ liệu đó.

Example 1: Gọi hàm addTwoNumbers cho 2 số kiểu Int:

  • Trước hết, phải cho Int extend Summable.
extension Int: Summable {}

Note: Vì bản thân các kiểu dữ liệu như Int, Double, String, … đã có chứa sẵn toán tử (+), nên không cần khai báo toán tử (+) cho chúng nữa.

let number1: Int = 5
let number2: Int = 10
let total = addTwoNumbers<Int>(num1: number1, num2: number2) // 15

Example 2: Khởi tạo và khai báo hàm addTwoNumbers(::) cho 2 số kiểu String :v

Note: Bạn có thể gọi hàm theo cách: addTwoNumbers<String>(num1: string1, num2: string2), hoặc bỏ <String> đi, vì Swift sẽ dựa vào kiểu dữ liệu bạn truyền vào num1, num2 để tự hiểu kiểu dữ liệu xác định bạn muốn thay cho T là String.

Ở phần tiếp theo, mình sẽ giới thiệu về:

  • Extending a generic type.
  • Subclass a generic type.
  • Cách sử dụng generic nâng cao.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.