Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on October 9, 2019

Closure trong 10 phút

iOS & Swift

Contents

    • “Closures are self-contained blocks of functionality that can be passed around and used in your code.”
  • 1. Closure là gì?
  • 2. Phân loại
  • 3. Sử dụng
    • 3.1. Sử dụng trực tiếp
    • 3.2. Các closure đã được định nghĩa ra sẵn
    • 3.3. Tự định nghĩa và sử dụng
  • 4. Type alias
  • 5. Mục đích sử dụng closure
    • 5.1. Sử dụng trong tham số của function
    • 5.2. Sử dụng như là Delegate & Datasource
  • 6. Capture
    • 6.1. Vấn đề
    • 5.2. @nonescaping closure
    • 5.3. @escaping closure
  • 6. Các ứng dụng của Closure nên biết
  • Tổng kết

Chào bạn đến với Fx Studio,

“Closures are self-contained blocks of functionality that can be passed around and used in your code.”

— Swift.org —

1. Closure là gì?

Closure là một block code, có thể tách ra để tái sử dụng. Hiểu đơn giản hơn thì Closure là function, nhưng khuyết danh. Ta có thể gán Closure vào biến và sử dụng như các kiểu value khác.

  • Các điểm cần chú ý:
    • block code
    • function
    • khuyết danh
    • biến
  • Ví dụ đơn giản nhất
let simpleClosure = { print("Hello closure") }

simpleClosure()
  • Giải thích:
    • block code được đặt vào giữa 2 dấu { }
    • simpleClosure là tên biến (bình thường thôi)
    • simpleClosure() thì thực thi closure, dấu ( ) dùng để gọi closure thực thi
  • Closure Syntax

2. Phân loại

  • Có thể phân được ra 3 loại chính sau:
    • Global functions: là closures có tên và không “capture” các giá trị.
    • Nested functions: là closures có tên và có thể “capture” các giá trị từ function chứa nó.
    • Closure expressions: là closures không có tên được viết dưới dạng giản lược syntax và có thể “capture” các giá trị từ các bối cảnh xung quanh.

Đọc xong đảm bảo không hiểu chi hết.

3. Sử dụng

3.1. Sử dụng trực tiếp

  • Ít khi làm loại này
  • Viết và chạy trực tiếp
  • Từ khoá là do

 

3.2. Các closure đã được định nghĩa ra sẵn

  • Các function của các class có sẵn
  • Sử dụng là chủ yếu
  • Hiện tại được nhiều thư viện hay class sử dụng
  • Ví du:
    • Animate của UIView

    • Code
UIView.animate(withDuration: 3.0
    , animations: {
        //code here
}) { (done) in
    //completed
}

 

3.3. Tự định nghĩa và sử dụng

  • Tham khảo 1 số ví dụ sau:
    • Không tham số & Không giá trị trả về
let closrue1 = { () -> Void in
    // code please!
}
    • Có tham số & không giá trị trả về
let sayHiClosure = { (name: String) in
    print("Hi, \(name)")
}

sayHiClosure("Joe")
    • Có tham số và có giá trị trả về
let addClosure = { (a: Int, b: Int) -> Int in
    return a + b
}

print("1 + 1 = \(addClosure(1,1))")
    • Closure trong 1 closure
let closureInClosure = { (name: String) in
    simpleClosure()
    print("I am \(name).")
}

closureInClosure("Nick")

Suy nghĩ một chút chỗ này.

var myVar: Int

var myVar1: () -> ()
var myVar2: () -> (String)
var myVar3: (Int, Int) -> (Int)
  • Bạn có tìm ra điểm tương đồng nào không?
    • Tất cả đều là khai báo biến
    • Tụi nó khác nhau bởi tên (điều hiển nhiên)
    • Tụi nó khác nhau kiểu dữ liệu

Vậy để phân biệt các closure với nhau thì ta dựa vào 2 yếu tố được dùng để phân biệt các function. Đó là:

  • Parameters : danh sách tham số
  • Return type : kiểu giá trị trả về

Chúng ta không thể nào nhớ tụi nó để phân biệt các kiểu function với nhau. Vì vậy tiếp theo là cách định danh để phân biệt tụi nó.

  • Tiếp tục qua ví dụ sau:
    • Khai báo 1 closure
var add: (Int, Int) -> (Int)
    • Gán giá trị và thực thi
add = { (a: Int, b: Int) -> (Int) in
    return a + b
}

print("1 + 1 = \(add(1,1))")
    • Gán giá trị khác và tiếp tục thực thi
add = { (a: Int, b: Int) -> (Int) in
    return a + 2 * b
}

print("1 + 1 = \(add(1,1))")
  • 2 đoạn code cùng 1 kiểu closure nhưng được giá 2 giá trị khác nhau thì sẽ cho kết quả thực thi khác nhau

 

4. Type alias

  • Dùng để định danh một kiểu dữ liệu hiện có.
  • Có thể định danh cho các kiểu function.
  • Mục đích tạo ra 1 cái tên mới cho kiểu dữ liệu để sử dụng được dễ dàng và tái sử dụng nhiều lần.
  • Cú pháp:
typealias <name_alias> = <existing type>
  • Ví dụ
//1
typealias AddClosure = (Int, Int) -> Int
typealias SubClosure = (Int, Int) -> Int

//2
var add: AddClosure = { (a: Int, b: Int) -> (Int) in
    return a + b
}
print("1 + 3 = \(add(1,3))")

//3
add = { (a: Int, b: Int) -> (Int) in
    return a + 2 * b
}
print("1 + 3 = \(add(1,3))")

//4
var sub: SubClosure

//5
sub = { (a: Int, b: Int) -> (Int) in
    return a - b
}
print("5 - 2 = \(sub(5,2))")
  • Giải thích:
    • 1 : định nghĩa 2 kiểu closure
    • 2 : khai báo 1 closure và gán giá trị vào. Sau đó thực thi
    • 3 : gán giá trị mới cho closure trên và thực thi
    • 4 : khai báo 1 closure khác, nhưng không gán giá trị nào hết
    • 5 : gán giá trị cho closure và thực thi
  • Xoắn não thêm một chút nữa
var sub1: SubClosure
var sub2: SubClosure?
var sub3: SubClosure!

var result1 = sub1(1,1)
var result2 = sub2!(2,3)
var result3 = sub3(3,3)

 

Tiếp theo, bạn cần suy nghĩ về câu hỏi dưới đây một chút nha. Vậy,

“Mục đích to lớn nhất của closure là để làm gì?”

5. Mục đích sử dụng closure

“Mục đích quan trọng của sử dụng closure là sử dụng nó như một biến/hằng.”

  • Có thể liệt kê như sau:
    • Sử dụng chúng như là biến/hằng
    • Sử dụng như các tham số trong khai báo 1 function.
    • Sử dụng làm đối số để truyền vào các lời gọi hàm.
    • Sử dụng như là các property trong class, struct, enum.
    • Tái sử dụng hay kế thừa lại.

5.1. Sử dụng trong tham số của function

  • Tham khảo đoạn code sau
//1
typealias Completed = (Int, Bool) -> Void

//2
func add(a: Int, b: Int, completed: Completed) {
    let result = a + b
    completed(result, true)
}

//3
let myComplete = { (result: Int, done: Bool) in
    if done {
        print("Result: \(result)")
    } else {
        print("Failed")
    }
}

//4
add(a: 10, b: 25, completed: myComplete)
  • Giải thích:
    • 1 : khai báo 1 kiểu closure
    • 2 : khai báo 1 function
    • 3 : tạo 1 closure
    • 4 : gọi hàm với các đối số (có đối số là closure)
  •  Hoặc có thể lồng closure vào trực tiếp khi gọi function
add(a: 17, b: 84) { (result, done) in
    if done {
        print("Result: \(result)")
    } else {
        print("Failed")
    }
}

5.2. Sử dụng như là Delegate & Datasource

  • Đây cũng làm mục đích quan trọng nhất trong việc thành tạo sử dụng closure.
  • Sau này sẽ được áp dụng vào nhiều kiểu lập trình nâng cao khác như:
    • Functional programming
    • Reactive programming
    • …
  • Giúp cho việc code rõ ràng dễ hiểu hơn so với cách dùng protocol.
  • Khó khăn thì cần phải có tư duy bất đồng bộ trong lập trình một chút.

 

  • Xem qua ví dụ sau
    • Class tính toán
protocol AddOperatorDelegate {
    func calculate(first: Int, second: Int, result: Int)
}

protocol AddOperatorDatasource {
    func getFirstNumber() -> Int
    func getSecondNumber() -> Int
}

class MyCalculator {
    var delegate: AddOperatorDelegate?
    var dataSource: AddOperatorDatasource?
        
    init() {
    }
    
    func add() -> Void {
        guard let dataSource = dataSource else {
            print("error")
            return
        }
        
        let first = dataSource.getFirstNumber()
        let second = dataSource.getSecondNumber()
        
        let result = first + second
        
        if let delegate = delegate {
            delegate.calculate(first: first, second: second, result: result)
        }
    }
}
    • Định nghĩa 2 protocol
      • Delegate : trả kết quả về class khác
      • DataSource : lấy giá trị từ class khác

 

    • Class khác (nơi handle kết quả và cung cấp dữ liệu)
class MyClass: AddOperatorDatasource, AddOperatorDelegate {
    
    var first: Int
    var second: Int
    
    init(first: Int, second: Int) {
        self.first = first
        self.second = second
    }
    
    func handle() {
        let myCalculator = MyCalculator()
        myCalculator.delegate = self
        myCalculator.dataSource = self
        
        myCalculator.add()
    }
    
    // Delegate & DataSource Protocol implement
    func getFirstNumber() -> Int {
        return first
    }
    
    func getSecondNumber() -> Int {
        return second
    }
    
    func calculate(first: Int, second: Int, result: Int) {
        print("\(first) + \(second) = \(result)")
    }
    

}

//Handle
var myClass = MyClass(first: 28, second: 15)
myClass.handle()
  • Class MyClass
    • Không trực tiếp tình toán
    • Chỉ cung cấp dữ liệu
    • Nhận kết quả trả về

Chuyển sang mục đích chính thôi.

  • Thêm closure vào class MyCalculator
typealias AddOperatorComplete = (Int, Int, Int) -> Void
typealias FirstNumber = () -> Int
typealias SecondNumber = () -> Int
  • Thêm 1 function với closure làm tham số cho class MyCalculator
func add(first: FirstNumber, second: SecondNumber, completion: AddOperatorComplete) {
        let first = first()
        let second = second()
        
        let result = first + second
        
        completion(first, second, result)
    }

 

  • Viết lại hàm handle bên class MyClass
    • Các đối số là function cho các tham số closure
    • Xử lý kết quả trả về từ completion
func handleClosure() {
        let myCalculator = MyCalculator()
        
        myCalculator.add(first: { () -> Int in
            return first
        }, second: { () -> Int in
            return second
        }) { (first, second, result) in
            print("\(first) + \(second) = \(result)")
        }
    }
  • Gọi hàm chạy
//Handle
var myClass = MyClass(first: 28, second: 15)
myClass.handleClosure()

Đơn giản phải không nào. EZ game!

6. Capture

6.1. Vấn đề

“Điều gì sẽ xảy ra khi sử dụng closure trong trường hợp bất đồng bộ?”

Khi thực thi 1 function với các closure thì nó sẽ trải qua một số bước sau:

  • Bước 1: Các closure được định nghĩa
    • Sử dụng closure là các đối số của function được gọi
    • Các giá trị biến được gắn vào closure
  • Bước 2: Thực hiện function
    • Giá trị của các tham số trong hàm được truyền vào từ closure
    • Hàm được gọi và sau khi kết thúc hàm thì các đối số sẽ được giải phóng
    • Các closure truyền vào cũng được giải phóng theo
  • Bước 3: Nhận giá trị trả về
    • Function return về thì các closure mà đã bị giải phóng rồi nên việc thực hiện tiếp sẽ không thể được

Làm thế nào để các closure đó không bị giải phóng?

5.2. @nonescaping closure

  • Với swift 1 và 2 thì mặc địch sẽ là không phải @nonescaping. Nên khi khai báo thì chúng ta cần phải thêm từ khoá @nonescaping vào.
  • Với swift 3 và 3 thì ngược lại.
  • Life cycle của một @nonescaping closure rất đơn giản:
    • Closure được pass vào function argument.
    • Function excute closure. Closure được giải phóng khỏi bộ nhớ.
    • Function return.
  • Khi sử dụng @nonescaping closure, compiler cũng không cần reference tới self, không xảy ra trường hợp reference cycle. Vì vậy không cần sử dụng [weak self] hoặc [unowned self].

 

  • Ví dụ:
func getSumOf(numbers: [Int], completion: (Int) -> Void) {
    var sum = 0
    
    for aNumber in numbers {
        sum += aNumber
    }
    
    completion(sum)
}

func doSomeThing() {
    getSumOf(numbers: [24, 5, 29, 18, 97, 43]) { (result) in
        print("Sum is \(result)")
    }
}

doSomeThing()

 

5.3. @escaping closure

  • Với swift 3 và 4 thì phải sử dụng từ khoá @escaping.
  • Khi cần giữ lại closure thành property của class để chờ excute sau.
    • Để closure có thể được giữ lại trong bộ nhớ sau khi function excute xong và return compiler.
      • Ví dụ: chờ response API…
  • Khi excute closure trên một thread khác, một asynchronous dispatch queue.
    • Queue này sẽ giữ closure trong bộ nhớ, nhằm excute sau. Trường hợp này, chúng ta sẽ không biết được closure sẽ được excute chính xác vào thời điểm nào.

 

  • Ví dụ viết lại ví dụ trên với delay 5 giây mới trả kết quả về
    • Nó tương tự như request API thì diễn ra bất đồng bộ
    • Thời gian nhận giá trị trả về thì không xác định được
func getSumOf(numbers: [Int],  completion: @escaping (Int) -> Void) {
    var sum = 0
    
    for aNumber in numbers {
        sum += aNumber
    }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
        print("After 5 secs")
        completion(sum)
    }
}

func doSomeThing() {
    getSumOf(numbers: [24, 5, 29, 18, 97, 43]) { (result) in
        print("Sum is \(result)")
    }
}

doSomeThing()

 

6. Các ứng dụng của Closure nên biết

  • sorted(): dùng để thay đổi điều kiện sort cho array/collection…

  • filter(): dùng dể lọc các phần tử của collection/array với điều kiện nhất định, ví dụ như lọc tuổi người dùng để kiểm tra nhưng ai đủ tuổi xem 18+ chẳng hạn

  • map(): dùng để áp dụng điều kiện nào đó cho tất cả các item trong array/collection, ví dụ tính tiền của sản phẩm sau khi áp dụng thuế tiêu thụ chẳng hạn

  • reduce(): dùng để tính tổng của array…Xem ví dụ bên dưới, bài toán là cần tính tổng tất cả các sách có trong kho, mỗi record sách được lưu dưới dạng: tên sách, số lượng, và giá.

 

Hết rồi.

Tổng kết

  • Cú pháp closure
  • Phân loại các closure
  • Cách dùng các loại closure
  • Định danh closure
  • Sử dụng vào function như tham số
  • Sử dụng như Delegate & DataSource
  • Capture closure
  • Một số closure hay dùng
FacebookTweetPinYummlyLinkedInPrintEmailShares80

Related Posts:

  • feature_bg_3
    Clean Architecture trong iOS
  • Dart Defines
    Dart Defines trong Flutter và sức mạnh của nó
  • Nimble
    Nimble trong 10 phút
  • Semaphore
    Dispatch Semaphore trong 10 phút
Tags: basic ios tutorial, closure, iOS
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift AI api AppDistribution autolayout basic ios tutorial blog ci/cd closure collectionview combine concurrency crashlytics dart dart basic dart tour Declarative delegate deploy design pattern fabric fastlane firebase flavor flutter GCD gradients iOS MVVM optional Prompt engineering protocol Python rxswift safearea Swift Swift 5.5 SwiftData SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Role-playing vs. Persona-based Prompting
  • [Swift 6.2] Raw Identifiers – Đặt tên hàm có dấu cách, tại sao không?
  • Vibe Coding là gì?
  • Cách Đọc Sách Lập Trình Nhanh và Hiệu Quả Bằng GEN AI
  • Nỗ Lực – Hành Trình Kiến Tạo Ý Nghĩa Cuộc Sống
  • Ai Sẽ Là Người Fix Bug Khi AI Thống Trị Lập Trình?
  • Thời Đại Của “Dev Tay To” Đã Qua Chưa?
  • Prompt Engineering – Con Đường Để Trở Thành Một Nghề Nghiệp
  • Vấn đề Ảo Giác (hallucination) khi tương tác với Gen AI và cách khắc phục nó qua Prompt
  • Điều Gì Xảy Ra Nếu… Những Người Dệt Mã Trở Thành Những Người Bảo Vệ Cuối Cùng Của Sự Sáng Tạo?

You may also like:

  • Observation Framework trong 10 phút
    feature_bg_swiftui_4
  • Swift Optional trong 10 phút
    feature_bg_swift_10
  • Dependency Injection trong 10 phút
    feature_bg_swift_04
  • Quick trong 10 phút
    Quick
  • Dispatch Semaphore trong 10 phút
    Semaphore

Archives

  • May 2025 (2)
  • April 2025 (1)
  • March 2025 (8)
  • January 2025 (7)
  • December 2024 (4)
  • September 2024 (1)
  • July 2024 (1)
  • June 2024 (1)
  • May 2024 (4)
  • April 2024 (2)
  • March 2024 (5)
  • January 2024 (4)
  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (44)
  • Code (11)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (102)
  • No Category (1)
  • RxSwift (37)
  • SwiftUI (80)
  • Tutorials (87)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2025 Fx Studio - All rights reserved.