Các cách thay thế vòng lặp for C-style trong Swift

Hôm qua 31/5 Swift đã cho download bản Swift 3.0 Preview 1, vậy là việc swift 3.0 chuẩn bị ra mắt đã gần hơn bao giờ hết. Với hàng loạt thay đổi mới và đặc biệt là việc remove các vòng lặp for C-style thì đã đến lúc chúng ta cần phải refactor dần code trong project để chuẩn bị cho tương lai rồi. Các bạn chú ý là ở đây, khi lên bản 3.0, các bạn bắt buộc phải thay đổi cú pháp trong code của mình không sử dụng vòng lặp C-Style nữa và đây là các gợi ý mình dành cho bạn

Update Jun 5 2016: Apple đã gỡ xuống bản preview của 3.0 và nói rằng nó chưa phải bản preview final. Tuy nhiên bản 3.0 vẫn được kỳ vọng sẽ ra mắt vào Late 2016

Update Jun 14 2016: Các bạn có thể thử Swift 3.0 bằng cách download xcode 8 beta từ Apple rồi nhé

Ở bài viết này, mình sẽ gợi ý ra một vài cách để các bạn có thể refactor lại code project của mình cho phù hợp với bản 3.0 tới.

Lặp n lần

/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 */

// thay vì sử dụng
for var i = 0; i < 10; i++ {
    print(i)
}

// hãy sử dụng
for i in 0..<10 {
    print(i)
}

Lặp n lần theo chiều ngược lại

/* 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 */

// thay vì sử dụng
for var i = 10; i > 0; i-- {
    print(i)
}

// hãy dùng
for i in (1...10).reverse() {
    print(i)
}

Lặp với Stride

/* 0, 2, 4, 6, 8 */

// thay vì dùng
for var i = 0; i < 10; i += 2 {
    print(i)
}

// hãy dùng
for i in 0.stride(to: 10, by: 2) {
    print(i)
}

Lặp qua các giá trị của một mảng

let someNumbers = [2, 3, 45, 6, 8, 83, 100]

/* 2, 3, 45, 6, 8, 83, 100 */

// thay vì dùng
for var i = 0; i < someNumbers.count; i++ {
    print(someNumbers[i])
}

// hãy dùng
for number in someNumbers {
    print(number)
}

// hoặc
someNumbers.forEach { number in
    print(number)
}

Lặp ngược lại các giá trị của một mảng

let someNumbers = [2, 3, 45, 6, 8, 83, 100]

/* 100, 83, 8, 6, 45, 3, 2 */

// thay vì dùng
for var i = someNumbers.count - 1; i >= 0; i-- {
    print(someNumbers[i])
}

// hãy dùng
for number in someNumbers.reverse() {
    print(number)
}

Lặp qua mảng cùng với index

let someNumbers = [2, 3, 45, 6, 8, 83, 100]

/*
 1: 2
 2: 3
 3: 45
 4: 6
 5: 8
 6: 83
 7: 100
 */

// thay vì dùng
for var i = 0; i < someNumbers.count; i++ {
    print("\(i + 1): \(someNumbers[i])")
}

// hãy dùng
for (index, number) in someNumbers.enumerate() {
    print("\(index + 1): \(number)")
}

// hoặc dùng
someNumbers.enumerate().forEach { (index, number) in
    print("\(index + 1): \(number)")
}

Lặp qua Indices của mảng

let someNumbers = [2, 3, 45, 6, 8, 83, 100]

/* 0, 1, 2, 3, 4, 5, 6 */

// thay vì dùng
for var i = 0; i < someNumbers.count; i++ {
    print(i)
}

// hãy dùng
for index in someNumbers.indices {
    print(index)
}

Ứng dụng thực tiễn

Có lẽ cần phải nói thêm 1 chút về hàm Stride vì nó khá mới mẻ đối với các developer bằng một trường hợp cụ thể thì sẽ dễ hiểu hơn nhỉ. Giả sử bây giờ bạn cần làm một ứng dụng đọc sách (xem tranh...) và để tối ưu bộ nhớ, bạn chỉ muốn giữ lại nội dung của 2 trang liền kề với trang hiện tại. Lúc này, một phần đoạn code của bạn sẽ có dạng như sau:

let currentPage = page
let prevPage = currentPage - 1
let nextPage = currentPage + 1

// Code xoá toàn bộ nội dung của các trang trước
for var p = 0; p < prevPage; ++p {
  removeMemoryOfPage(p)
}

Để thay thế cho vòng lặp này, có thể các bạn sẽ nghĩ tới:

for p in 0..<prevPage {
  removeMemoryOfPage(p)
}

Nhưng nếu trang hiện tại là trang đầu tiên (currentPage = 0) thì sao? Lúc này prevPage sẽ có giá trị là -1, bạn thấy rồi chứ, lúc này bạn sẽ gặp lỗi fatal error: Can't form Range with end < start với cách viết mới trong khi cách viết cũ vẫn hoạt động bình thường. Tuy rằng chúng ta có thể dùng một lệnh if để bỏ qua nếu là trường hợp này nhưng nó sẽ không được hay cho lắm nếu có thể code xử lý logic ở trong vòng lặp đúng không. Vậy giải pháp lúc này là gì? Bạn đoán đúng rồi đấy, chúng ta sẽ dùng Stride ở đây. Như trong tài liệu thì function stride được mô tả như sau:

Returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression that is less than end.

Điều quan trọng cần chú ý ở đây đó là stride không quan tâm tới việc giá trị to: phải lớn hơn giá trị start. Như thế 0.stride(to: -1, by: 1) sẽ trả về một dãy trống. Ok, thế thì bạn đã biết phải thay thế đoạn code trong ví dụ cụ thể này như nào rồi đúng không?

for p in 0.stride(to: prevPage, by: 1) {
  removeMemoryOfPage(p)
}

Nhìn tuyệt vời hơn đúng không khi không cần phải có logic if bên trong vòng lặp. Chúng ta cũng có thể làm cho đoạn code "functional" hơn bằng cách sử dụng forEach:

0.stride(to: prevPage, by: 1).forEach { (p) in
  removeMemoryOfPage(p)
}

Như vậy là đã xử lý xong đối với các trang ở trước trang liên kề gần nhất rồi, còn xử lý với trang sau trang liền kề gần nhất (> nextPage) nữa, các bạn thử suy nghĩ và ứng dụng 1 chút cả indices nữa nhé. Hãy thử comment giải pháp của bạn xem sao và chúng ta cùng trao đổi. Ngoài ra thì nếu bạn có thêm những phương pháp khác cho việc thay thế vòng lặp C-Style thì cùng chia sẻ nhé.

Chú ý khi dùng forEach rằng bạn sẽ không sử dụng được breakcontinue nữa nhé.

Đừng quên sử dụng Higher Order Functions

Thay vì sử dụng vòng lặp, hãy thử suy nghĩ liệu bạn có thể dùng các higher order functions của Swift như map, flatMap, filter, reduce ... Nếu được, hãy ưu tiên sử dụng chúng nhé.

Nguồn tham khảo:

https://www.natashatherobot.com/swift-alternatives-to-c-style-for-loops/ và một vài nguồn khác trong quá trình tìm hiểu các thay đổi và ứng phó đối với swift 3.0 😃


All Rights Reserved