Generics

Generics là một khái niệm rất thú vị trong Swift, nó cho phép chúng ta tái sử dụng code cho nhiều kiểu khác nhau. Ví dụ

func swapInts(_ a: inout Int, _ b: inout Int) {
    let temporaryB = b
    b = a
    a = temporaryB
}

Ở đây hàm swapInts nhận 2 biến Ints và tráo đổi chúng:

var num1 = 10
var num2 = 20
 
swapInts(&num1, &num2)
print(num1)   // 20
print(num2)   // 10

Nhưng nếu như chúng ta muốn hoán đổi 2 biến String thì sao? Bạn sẽ lại phải viết lại function:

func swapStrings(_ a: inout String, _ b: inout String) {
    let temporaryB = b
    b = a
    a = temporaryB

Như bạn thấy thì cả 2 function đều có cùng một tính năng, chỉ khác biệt ở parameter truyền vào. Vậy liệu chúng ta có thể viết một hàm tráo đổi mà hỗ trợ cho mọi kiểu không? Câu trả lời là Generics.

func swapAnything<T>(_ a: inout T, _ b: inout T) {
    let temporaryB = b
    b = a
    a = temporaryB
}

Đầu tiên hãy để tôi giải thích những gì chúng ta vừa viết. Chúng ta khởi tạo một function bình thường, nhưng bạn có để ý tới chữ T không?. Chữ T đó là gì?, nó đóng vài trò ra sao?. Hai dấu ngoặc nhọn là cú pháp của Generic trong Swift. Khi chúng ta nhét chữ T vào trong giữa hai ngoặc nhọn đó, chúng ta đang thông báo với Swift rằng chúng ta đang theo kiểu generic type gọi là T. Bây giờ chúng ta có thể tham chiếu tới kiểu T trong hàm. T ở đây tượng trưng cho bất kỳ kiểu nào mà chúng ta muốn áp dụng trong function.

Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster
var string1 = "Happy"
var string2 = "New Year"
 
swapAnything(&string1, &string2)
print(string1) // New Year
print(string2) // Happy
 
var bool1 = false
var bool2 = true
 
swapAnything(&bool1, &bool2)
print(bool1) // true
print(bool2) // false

Hãy cùng nhau đi tiếp tới một ví dụ nữa, với những bạn nào thông thạo về Objective-C, các bạn có nhớ cách mà phần tử trong Array được trả về dưới dạng id không?

NSArray *myArray = @[1, 2, 3, 4, 5];
int myInt = (int)myArray[2];

Chúng ta luôn phải ép những phần tử từ Array về đúng kiểu của nó một cách thủ công. Và đây không phải là phương pháp an toàn. Nếu như bạn không biết kiểu của các phần tử đó thì sao? Bạn sẽ ép chúng về kiểu gì? Và nếu như bạn quên ép kiểu và cố dùng chúng, app sẽ crash.

Và đây là lúc Generic trở thành vị cứu tinh.

struct Stack {
    private var storage = [Any]()
    
    mutating func push(_ item: Any) {
        storage.append(item)
    }
    
    mutating func pop() -> Any? {
        return storage.removeLast()
    }
}
 
var myStack = Stack()
myStack.push("foo")
myStack.push(5)
myStack.push(4.7)
 
let element = myStack.pop()

Tại đây chúng ta khai báo  stack, cho phép bạn đẩy push bất cứ thứ gì vào trong và lôi chúng ra,pop 

Có một vấn đề ở đây, các phần tử được lưu ở trong stack và các phần tử chúng ta tách ra là thuộc kiểu Any. Làm thế nào để chúng ta biết được kiểu của phần tử là gì?.

switch element {
case let number as Int: print(number)
case let number as Double: print(number)
case let text as String: print(text)
default: print("Unknown type")
}

Đây không phải là phương án tối ưu và cũng không khuyến khích việc lưu phần tử trong một mảng thuộc kiểu Any trong Swift. Cách tốt hơn đó là giới hạn stack thành một kiểu duy nhất. Ở trường hợp này, generic sẽ tạo cho bạn một kiểu chung duy nhất. 

struct GenericStack<Element> {
    private var storage = [Element]()
    
    mutating func push(_ item: Element) {
        storage.append(item)
    }
    
    mutating func pop() -> Element? {
        return storage.removeLast()
    }
}

Lúc này thì stack đã trở nên an toàn hơn, mặc dù nhìn code có vẻ khó hiểu hơn so với phương pháp cũ.

var textStack = GenericStack<String>()
textStack.push("foo")
textStack.push("bar")
textStack.push("baz")
 
let textElement = textStack.pop()  // baz
 
var numStack = GenericStack<Int>()
numStack.push(10)
numStack.push(20)
numStack.push(30)
 
let numElement = numStack.pop()  // 30

Hết phần 3

Nguồn bài viết: http://www.appcoda.com/mastering-swift/