Skip to main content

Swift: Closure và những thuật ngữ liên quan


Ở bài viết lần này mình sẽ cùng các bạn đi ôn lại về một kiến thức rất nền tảng và khá là đặc biệt của Swift, đó chính là Closure. Đầu tiên, hãy đi đến định nghĩa của nó trước nhé.

Định nghĩa

"Closure là một khối chức năng độc lập có thể truyền đi và được sử dụng trong code của bạn." Nghe vẫn khá là mơ hồ nhỉ? Closure trong Swift khá là giống blocks trong C hay Objective-C, là một đoạn mã được bao hàm trong block thực hiện một chức năng cụ thể nào đó như là Function vậy. Nói đến Function thì nó có tên (để gọi), có kiểu trả về (hoặc không với Void) và đoạn mã trong block để thực hiện chức năng, vậy Function có phải là Closure không? Câu trả lời là có với Global hay Nested function thực chất là trường hợp đặc biệt của closure. Closure sẽ thuộc một trong 3 dạng dưới đây:
  • Global function là một closure có tên và không capturing value
  • Nested function là một closure có tên và có khả năng capturing value từ function bao quanh chúng
  • Closure expression là một closure không có tên được viết dưới dạng giản lược và có khả năng capturing value
Thuật ngữ capturing value ở đây là để chỉ khả năng bắt lấy, sử dụng những hằng số hay biến số từ những nội dung xung quanh nơi mà nó được định nghĩa.

Closure Expressions

Closure expressions là một cách để viết closure ngắn gọn hơn, tập trung vào cú pháp. Nó cung cấp một số những sự tối ưu hoá cho cú pháp để có thể rút ngắn nhưng vẫn mang lại sự rõ ràng và ý nghĩa. Những sự tối ưu hoá về cú pháp này gồm có:
  • Tự suy diễn kiểu của tham số và kiểu trả về từ bối cảnh xung quanh
  • Ngầm return đối với các single-expression closures
  • Các viết tắt tên các arguments
  • Cú pháp Trailling closure
Nếu các bạn thắc mắc Closure expressions có thể rút gọn tới cỡ nào, các bạn có thể đọc bài viết Funtion tiến hoá trở thành Closure và cái kết bất ngờ! của anh Trương Minh Thắng để thấy rõ hơn về sự rút gọn của Closure expressions.

Trailling Closure

Trailling closure là một trong những cách rút gọn hữu hiệu khi bạn truyền một closure vào một function như là argument cuối cùng của function, và cái thể hiện của closure này khá dài. Khi sử dụng trailling closure thì chúng ta không cần phải viết nhãn của closure trong function đó nữa. Hãy nhìn qua một ví dụ.
func someFunctionThatTakesAClosure(closure: () -> Void) {
    // function body
}

// gọi function không dùng tới trailling closure

someFunctionThatTakesAClosure(closure: {
    // closure's body 
})

// khi sử dụng trailling closure 

someFunctionThatTakesAClosure() {
    // trailing closure's body
}
Hãy tới một ví dụ cụ thể hơn, lần này chúng ta sẽ sử dụng trailling closure để chuyển đổi một mảng giá trị Int sang một mảng String. Đây sẽ là đầu vào
let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
Và giờ chúng ta sẽ sử dụng toán tử map và trailling closure để thực hiện
let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// KQ: ["OneSix", "FiveEight", "FiveOneZero"]
Toán tử map thực hiện đoạn code trong closure đối với mỗi một phần tử ở trong mảng numbers. Và number trong closure cũng không cần định nghĩa rõ ràng kiểu vì đặc điểm tự suy diễn kiểu dữ liệu của tham số trong closure.

Escaping Closure

Escaping closure là để chỉ việc khi closure được truyền vào như một argument của function nhưng lại được gọi sau đó, khi mà function này đã kết thúc. Điều này rất hữu ích khi chúng ta làm việc với các tác vụ bất đồng bộ hay thao tác với dữ liệu từ server/API ... Để khai báo một closure là một escaping thì chỉ cần thêm @escaping vào trước kiểu của tham số. Hãy xem qua một ví dụ
var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

Closures are reference type

Hãy lưu ý rằng một closure là một kiểu tham chiếu vì vậy khi gán một hằng hay biến vào closure nó sẽ làm tăng reference counting của biến đó. Và khi sự dụng self trong closure cũng nên sử dụng weak hoặc unowned để tránh reference retain cycles.

Comments

Popular posts from this blog

MVVM và VIPER: Con đường trở thành Senior

Trong bài viết trước chúng ta đã tìm hiểu về MVC và MVP để ứng dụng cho một iOS App đơn giản. Bài này chúng ta sẽ tiếp tục ứng dụng 2 mô hình MVVM và VIPER . Nhắc lại là ứng dụng của chúng ta cụ thể khi chạy sẽ như sau: Source code đầy đủ cho tất cả mô hình MVC, MVP, MVVM và VIPER các bạn có thể download tại đây . MVVM MVVM có thể nói là mô hình kiến trúc được rất nhiều các cư dân trong cộng đồng ưa chuộng. Điểm tinh hoa của kiến trúc này là ở ViewModel , mặc dù rất giống với Presenter trong MVP tuy nhiên có 2 điều làm nên tên tuổi của kiến trúc này đó là: ViewModel không hề biết gì về View , một ViewModel có thể được sử dụng cho nhiều View (one-to-many). ViewModel sử dụng Observer design pattern để liên lạc với View (thường được gọi là binding data , có thể là 1 chiều hoặc 2 chiều tùy nhu cầu ứng dụng). Chính đặc điểm này MVVM thường được phối hợp với các thư viện hỗ trợ Reactive Programming hay Event/Data Stream , đây là triết lý lập trình hiện đại và hiệu

Alamofire vs URLSession

Alamofire vs URLSession: a comparison for networking in Swift Alamofire and URLSession both help you to make network requests in Swift. The URLSession API is part of the foundation framework, whereas Alamofire needs to be added as an external dependency. Many  developers  doubt  whether it’s needed to include an extra dependency on something basic like networking in Swift. In the end, it’s perfectly doable to implement a networking layer with the great URLSession API’s which are available nowadays. This blog post is here to compare both frameworks and to find out when to add Alamofire as an external dependency. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! This shows the real power of Alamofire as the framework makes a lot of things easier. What is Alamofire? Where URLSession can be found within the s

Fileprivate vs private: Giải thích sự khác biệt

Fileprivate vs private in Swift: The differences explained Fileprivate and private are part of the access control modifiers in Swift. These keywords, together with internal, public, and open, make it possible to restrict access to parts of your code from code in other source files and modules. The private access level is the lowest and most restrictive level whereas open access is the highest and least restrictive. The documentation of Swift will explain all access levels in detail to you, but in this blog post, I’m going to explain the differences between two close friends: fileprivate and private. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! Open access is the highest (least restrictive) access level and private access is the lowest (most restrictive) access level. This will improve readability and mak