9. Trailing closure:

Nếu bạn cần truyền 1 biểu thức closure vào hàm như 1 đối số cuối cùng và biểu thức closure đó hơi dài, bạn có cách hữu ích hơn đó là viết 1 hàm trailing closure. Hàm trailing closure được viết sau dấu ngoặc đơn của hàm mặc dù nó vẫn là 1 đối số của hàm đó. Khi bạn sử dụng cú pháp của trailing closure, bạn không cần viết tên của đối số như 1 phần của việc gọi hàm

Hàm closure sắp xếp các chuỗi trong phần “Cú pháp biểu thức closure” ở trên có thể viết lại bên ngoài dấu ngoặc của phương thức “sorted(by : )” để trở thành 1 hàm trailing closure

Nếu 1 biểu thức closure được khai báo như những hàm hoặc phương thức chỉ có đối số và bạn khai báo chúng như 1 trailing closure, bạn không cần phải viết dấu ngoặc đơn () sau tên của hàm hoặc phương thức khi bạn gọi chúng

Trailing closure rất hữu ích khi hàm closure đủ dài để không thể viết hết trên 1 dòng. Ví dụ như, kiểu mảng trong Swift có phương thức “map(_ :)” có thể lấy 1 hàm closure làm 1 đối số độc lập. Hàm closure này được gọi với mỗi phần tử trong mảng và trả về 1 giá trị luân phiên cho mỗi phần tử (có thể là vài kiểu dữ liệu khác nhau).

Sau khi áp dụng các closure được khai báo cho mỗi phần tử của mảng, phương thức “map(_ : )” trả về 1 mảng mới có chứa tất cả các giá trị ánh xạ có thứ tự săp xếp tương tự như các giá trị tương ứng trong mảng ban đầu.

Ở đây bạn có thể dùng phương thức “map( _ : )” với 1 trailing closure để chuyển đổi mảng các giá trị Int sảng mảng giá trị String. Mảng [16,58,510] sẽ được dùng để tạo ra mảng [“OneSix”,”FiveEight”,”FiveOneZero”]

Đoạn code ở trên tạo ra 1 dictionary ánh xạ giữa các số nguyên và tên gọi tiếng Anh của chúng. Nó cũng định nghĩa 1 mảng số nguyên để chuyên sang dạng chuỗi. Bạn có thể dùng mảng “numbers” để tạo ra 1 mảng các giá trị String bằng cách truyền 1 biểu thức closure dưới dạng trailing closure vào phương thức “map( _ : )’ của mảng

Phương thức “map( _ : )” gọi đến biểu thức closure mỗi lần cho mỗi phần tử trong mảng.Bạn không cần chỉ ra kiểu dữ liệu của tham số đầu vào của hàm closure – là number – vì kiểu dữ liệu có thể được suy ra từ các giá trị trong mảng ánh xạ

Trong ví dụ này, biến number được khởi tạo với giá trị của tham số “number” của hàm closure. Vì thế giá trị có thể bị thay đổi trong thân hàm closure (tham số của hàm và closure luôn là hằng số). Biểu thức closure cũng chỉ định kiểu trả về là “String’ để nói rằng kiểu trả về này sẽ được lưu trữ trong mảng ánh xạ đầu ra

Biểu thức closure tạo ra 1 chuỗi ký tự đầu ra mỗi lần khi nó được gọi tới. Nó sẽ tính toán chữ số cuối cùng của biến “number” bằng cách dùng toán tử lấy phần dư (“number” % 10), và dùng chữ số để tìm ra chuỗi gần đúng trong từ điển “digitNames”. Hàm closure có thể sử dụng để tạo ra 1 chuỗi đại diện cho bất kỳ số nguyên nào lướn hơn 0.

  • Chú ý: Việc gọi đến chỉ số của dictionary “digitnames” đi kèm với dấu cảm thán (!). điều này nghĩa là chỉ số cảu dictionary trả về giá trị optional để chỉ ra rằng dictonary tìm kiếm sự chắc chắn nếu key đó không tồn tại. Trong ví dụ ở trên, ta đảm bảo rằng “number % 10” sẽ luôn là 1 chỉ số thích hợp cho ‘digitNames”, và dấu chấm than được dùng để “force-unwrap’ giá trị String được lưu trữ trong giá trị trả về của chỉ số ở dạng optional

Chuỗi ta nhận được từ từ điển “digitNames” được thêm vào trước chuỗi đầu ra, điều này giúp xây dựng 1 cuỗi đảo ngược của dãy số 1 cách hiệu quả. (Biểu thức “number % 10” cho ra giá trị 6 hoặc 16, 8 hoặc 58, 0 hoặc 510).

Biến “number” sau đó được chia cho 10. Bởi vì đây là số nguyên, nó được làm tròn xuống trong quá trình chia nên 16 sẽ trở thành 1, 58 trở thành 5, 510 trở thành 51.

Quá trình này được lặp lại cho đến khi “number” bằng 0, tại đó chuỗi đầu ra được trả về bởi hàm closure và được thêm vào mảng đầu ra bởi phương thức “map( _ : ) “

Việc sử dụng cú pháp trailing closure trong ví dụ ở trên đã đong gói gọn gàng chức năng tức thời của hàm closure ngay sau hàm mà closure đó hỗ trợ mà không cần phải gộp toàn bộ hàm closure vào trong dấu ngoặc đơn của phương thức “map( _ : )”.

10. Nắm bắt giá trị:

1 closure có thể nắm bắt các biến và các hằng số từ các phạm vi xung quanh nơi mà nó được định nghĩa. Closure sau đó có thể dựa theo và sửa đổi giá trị của các biến và các hằng số trong thân hàm của có, ngay cả khi phạm vi xác định biến và hằng số lúc đầu không còn tồn tại.

Trong Swift, dạng đơn giản nhất của closure có thể nắm bắt được các giá trị là hàm lông nhau, được viết trong thân của 1 hàm khác. Hàm lồng nhau có thể nắm bắt được bất kỳ đối số nào của hàm nằm bên ngoài và bên trong của nó.

Đây là ví dụ về hàm “makeIncrementer” có chứa 1 hàm lồng nhau gọi là “incrementer”. Hàm lồng “incrementer()” nắm bắt 2 giá trị là “runningTotal” và “amount” từ phạm vi xung quanh nó. Sau khi nắm bắt các giá trị này, “makeIncrementer” sẽ trả về “incrementer” như 1 closure, làm tăng “runningTotal” bởi “amount” mỗi lần khi nó được gọi tới.

Kiểu trả về của ‘makeIncrementer” là “() -> Int”. Điều này có nghĩa nó trả về 1 hàm thay vì 1 giá trị đơn giản. Hàm trả về không có tham số và trả về giá trị Int mỗi lần nó được gọi tới. Để hiểu hơn về việc hàm trả về 1 hàm khác, xem phần “Function Types as Return Types”.

Hàm “makeIncrementer(forIncrementer: )” định nghĩa 1 biến số nguyên gọi là “runningTotal” để lưu trữ tổng số lượng hiện tại của giá trị tăng trưởng được trả về. Biến này được khởi tạo với giá trị bằng 0.

Hàm “makeIncrement(forIncrement : )” có tham số truyền vào ở kiểu Int với tên là “forIncrement” và 1 tham số nữa tên là ‘amount”. Giá trị đối số được truyền vao tham số xác định số lượng ‘runningTotal” sẽ được tăng lên theo mỗi lần hàm tăng được gọi đến. Hàm “makeIncrementer” định nghĩa 1 hàm lồng nhau gọi là “incrementer”, hàm này thực hiện số lượng tăng thực tế. Hàm này đơn giản là thêm “amount” vào “runningTotal” và trả về kết quả.

Khi xem xét 1 cách độc lập, hàm lồng nhau “incrementer()” có vẻ không bình thường:

Hàm “incrementer()” không có bất kỳ tham số nào và nó gọi đến “runningTotal” và “amount” từ bên trong thân hàm của nó. Nó hoạt động được là nhờ nắm bắt được một biến tham chiếu đến “runningTotal” và “amount” từ hàm bao quanh nó và sử dụng chúng bên trong thân hàm của nó. Việc nắm bắt các tham chiếu đảm bảo rằng “runningTotal” và “amount” không xuất hiện khi việc gọi hàm “makeIncrementer” kết thúc, và cũng để đảm bảo rằng “runningTotal”  có thể tái ử dụng lại khi gọi lại hàm “incrementer”.

  • Chú ý: Một cách tối ưu hóa thì Swift có thể thay vào đó nắm bắt và lưu trữ 1 bản sao giá trị nếu giá trị đó không bị thay đổi bởi closure, và nếu giá trị đó không bị thay đổi sau khi closure được tạo ra. Swift cũng giải quyết các vấn đề quản lý bộ nhớ liên quan đến việc xử lý các biến khi chúng không còn cần thiết nữa

Đây là ví dụ về cách gọi hàm “incrementer”

Ví dụ này tạo ra 1 hàng số “incrementByTen” để gọi đến hàm incrementer và thêm “10” vào biến “runningTotal” mỗi khi hàm này được gọi đến. Việc gọi hàm nhiều lần sẽ được thể hiện ở dưới:

Nếu bạn tạo ra 1 incrementer thứ hai, nó sẽ lưu trữ tham chiếu của nó vào 1 biến “runningTotal” mới riêng biệt

Việc gọi lại incrementer ban đầu (“incrementerByTen”) sẽ làm tiếp tục tăng lên biến “runningTotal” và nó không ảnh hưởng đến biến được nắm bắt bởi “incrementBySeven”

  • Chú ý: nếu bạn phân công closure là 1 thuộc tính của 1 đối tượng class, và closure đó nắm bắt đối tượng đó bằng việc gọi đến đối tượng đó hoặc thành phần của chúng, bạn sẽ tạo ra 1 vòng tham chiếu mạnh giữa closure và đối tượng. Swift sử dụng “capture lists” để phá vỡ vào tròn tham chiếu này. Để hiểu rõ hơn hãy xem phần “Strong Reference Cycles for Closures

 

(Còn nữa)

Link phần 1: https://techmaster.vn/posts/35246/tim-hieu-ve-closure-trong-swift-phan-1

Link phần 2: https://techmaster.vn/posts/35247/tim-hieu-ve-closure-trong-swift-phan-2

Link phần cuối: https://techmaster.vn/posts/35249/tim-hieu-ve-closure-trong-swift-phan-cuoi