+3

Swift 2 Control Flow

Swift là ngôn ngữ lập trình mới được phát triển bởi Apple Inc. với mục đích hỗ trợ lập trình viên trong việc phát triển các ứng dụng chạy trên các hệ điều hành như iOS, OSX và watchOS. Swift được xây dựng dựa trên việc kế thừa các tính năng của 2 ngôn ngữ C và Objective-C.

Swift2Apple mới đây đã giới thiệu Swift 2, phiên bản kế tiếp của ngôn ngữ lập trình mà hãng từng ra mắt hồi năm ngoái với nhiều cải tiến về mặt hiệu năng, phát hiện lỗi cũng như tiết kiệm thời gian xây dựng app và thu nhỏ kích thước ứng dụng. Tuy nhiên, thông tin đáng quan tâm nhất có lẽ là việc Apple sẽ mã nguồn mở hóa Swift 2 ra thế giới. Điều này không chỉ giúp các bên thứ ba có thể tạo ra những công cụ lập trình tốt hơn, sáng tạo hơn để việc viết code bằng Swift 2 trở nên dễ dàng hơn, mà nó còn mở ra khả năng xuất hiện những ứng dụng Swift chạy trên những nền tảng không phải của Apple*. Swift 2 sẽ xuất hiện vào cuối năm nay với toàn bộ thư viện chuẩn và bộ compiler dành cho OS X, iOS và Linux.

*Ghi chú: ứng dụng viết bằng Swift 2 trong tương không nhất thiết phải là app viết cho iOS hay OS X, cũng không nhất thiết là phải sử dụng các thành phần hệ thống từ hai hệ điều hành này. Đó có thể là một app cho Android, cho Windows, cho web... Khi đó, Swift 2 chỉ đơn giản là một ngôn ngữ lập trình mà thôi. Cũng như việc chúng có thể xài ngôn ngữ Java, Python, C++ để viết phần mềm cho Windows hay Android vậy.

*** Một số điểm nổi bật của swift2 với swift**

do-while bây giờ là repeat-while

Hãy bắt đầu với điều cơ bản. Vòng lặp do-while bây giờ đổi tên thành repeat-while. Ví dụ:

var i = 0
repeat {
i++
 print(i)
} while i < 10

chúng ta thấy ở phần sau, keyword “do” bây giờ có thể được chấp nhận ở các chức năng khác của ngôn ngữ. Bằng việc đổi “do” thành “repeat”, chúng ta có thể dễ dàng nhận biết khối code đó là vòng lặp hơn.

Mệnh đề for-in where

Một tính năng cơ chúng ta khác là keyword “where” cho câu lệnh “for-in”. Bây giờ chúng ta có thể khai báo điều kiện ở ngay trong “for” bằng cách sử dụng mệnh đề “where”. Ví dụ ở dưới đây khi lặp trong mảng các số tự nhiên, điều kiện là các số lớn hơn 100 thì sẽ in ra console.

let numbers = [20, 18, 39, 49, 68, 230, 499, 238, 239, 723, 332]
for number in numbers where number > 100 {
 print(number)
}

if-case Pattern Matching

Lần đầu tiên Swift ra mắt, câu lệnh “switch” cũng được cập nhật. Ngoài việc có thể sử dụng switch để kiểm tra mọi loại dữ liệu, switch còn hỗ trợ cả “range” lẫn “pattern matching”. Dưới đây là ví dụ về việc sử dụng cho “range matching”

let examResult = 49
 switch examResult {
 case 0...49: print("Fail!")
 case 50...100: print("Pass!")
 default: break
 }

Trong Swift 2, thay vì “switch” chúng ta có thể sử dụng “if case” để thực hiện câu lệnh tương đương:

if case 0...49 = examResult {
 print("Fail!")
 } else if case 50...100 = examResult {
 print("Pass!")
 }

“if case” cũng có thể được dùng cho “pattern matching”. Ở ví dụ dưới đây sử dụng các bộ dữ liệu để so sánh:

let userInfo = (id: "322424", name: "Ngoc tam", age: 25, email: "ngoctam.9k@gmail.com")

 if case (_, _, 0..<18, _) = userInfo {
 print("Bạn chưa đủ tuổi để thực hiện hành động này. Tuổi của bạn dưới 18.")
 } else if case (_, _, _, let email) = userInfo where email == "" {
 print("Email không được để trống.")
 } else {
 print("Thông tin chính xác!")
 }

Ở “if case” đầu tiên kiểm tra bộ “age” (tuổi) của dữ liệu xem có lớn hơn 18 không. Dấu gạch dưới (_) nghĩa là “bất cứ giá trị nào cũng được”. Ở đây chỉ quan tâm đến kiểm tra “age”. Dòng “else if case” kiểm trả bộ “email” có để trống không. Cuối cùng là nếu thông tin đều hợp lệ sẽ chạy đến “else”.

Giới thiệu về Guard

Swift 2 giới thiệu keyword guard . Theo tài liệu của Apple thì guard được mô tả như sau:

A guard statement, like an if statement, executes statements depending on the Boolean value of an expression. You use a guard statement to require that a condition must be true in order for the code after the guard statement to be executed.

Đại loại là câu lệnh “guard” giống như câu lệnh “if”. Trước khi giải thích rõ về câu lệnh “guard”, chúng ta hãy đọc và hiểu về ví dụ dưới đây:

struct Article {
 var title:String?
 var description:String?
 var author:String?
 var totalWords:Int?
 }

 func printInfo(article: Article) {
 if let totalWords = article.totalWords where totalWords > 1000 {
   if let title = article.title {
     print("Tiêu đề: \(title)")
   } else {
     print("Lỗi: Không thể in tiêu đề!")
   }
 } else {
   print("Lỗi: Không thể xác định totalWords!")
 }
 }

 let sampleArticle = Article(title: "Swift2", description: "about Swift 2", author: "Ngoctam")
 printlnfo(sampleArticle)

Hàm “printInfo” ở đây có chức năng hiện tiêu đề của bài viết thỏa mãn điều kiện totalWords lớn hơn 1000. Như các biến Optionals, chúng ta sử dụng “if let” để xác định biến đó có chứa giá trị hay không. Nếu giá trị là “nil”, chúng ta sẽ hiện một thông báo lỗi.

Về cơ bản, câu lệnh “if-else” tuân theo cấu trúc:

if <một vài điều kiện thỏa mãn> {
   // do something
   if <một vài điều kiện thỏa mãn>{
   // do something
   } else {
   // Hiện lỗi hoặc làm hoạt động nào đó nếu điều kiện không thỏa mãn
   }
 } else {
  // Hiện lỗi hoặc làm hoạt động nào đó nếu điều kiện không thỏa mãn
 }

chúng ta có thể dễ dàng nhận ra nếu phải kiểm tra nhiều điều kiện hơn thì sẽ có rất nhiều câu lệnh “if-else” lồng vào nhau. Về cơ bản lập trình thì chẳng có gì là sai cả. Tuy nhiên về việc đọc code sẽ rất lộn xộn nếu có nhiều điều kiện lồng nhau như thế.

Đây là lí do câu lệnh guard ra đời. Cú pháp của “guard” trông như dưới đây

guard <điều kiện thỏa mãn> else {
 // làm điều gì đó nếu điều kiện không thỏa mãn
 }
 // tiếp tục thực hiện các hoạt động khác

néu chúng ta viết lại đoạn code ví dụ trên sử dụng “guard”, nó trông gọn gàng và clear hơn:

func printInfo(article: Article) {
guard let totalWords = article.totalWords where totalWords > 1000 else {
  print("Lỗi: totalWords!")
  return
}

guard let title = article.title else {
  print("Error: Couldn't print the title of the article!")
  return
}

print("Title: \(title)")

Với “guard”, tập trung xử lí các điều kiện bạn không muốn. Ngoài ra, nó buộc chúng ta phải xử lí 1 trường hợp tại 1 thời điểm. Do đó, code sẽ clean và dễ đọc hơn

Error Handling

Khi phát triển một ứng dụng hoặc bất cứ chương trình nào, cần phải xử lí mọi tình huống có thể cho dù nó tốt hay xấu. Hiển nhiên, mọi thứ có thể chạy sai. Thử nghĩ xem nếu bạn làm 1 ứng dụng có liên quan tới dữ liệu động từ máy chủ (webserver, cloud,…) thì ứng dụng đó sẽ phải đối mặt với trường hợp thiết bị không có kết nối internet.

Ở phiên bản Swift 1.2 trở về trước thiếu một phương pháp để xử lí lỗi thích hợp. Ví dụ xử lí điều kiện lỗi như dưới đây:

let request = NSURLRequest(URL: NSURL(string: "http://framgia.vn")!)
var response:NSURLResponse?
var error:NSError?

let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)

if error == nil {
print(response)

// Xử lí dữ liệu

} else {
// Xử lí lỗi
}

Khi gọi một hàm mà hàm đó có thể phát sinh lỗi, thông thường chúng ta sẽ truyền lỗi đó vào 1 đối tượng là NSError (như 1 con trỏ). Nếu có lỗi, đối tượng này sẽ thiết lập các lỗi tương ứng. Sau đó chúng ta sẽ kiểm tra nếu đối tượng này có phải là nil hay không và xử lí với các lỗi tương ứng. Đó là cách chúng ta xử lí các lỗi trong Swift 1.2

try / throw / catch

Ở Swift 2, với mỗi lỗi sẽ đi kèm với 1 ngoại lệ bằng cách sử dụng các keyword try/catch/throw (tương tự Java và 1 số ngôn ngữ khác). Đoạn code trên có thể được viết lại như sau:

let request = NSURLRequest(URL: NSURL(string: "http://framgia.vn")!)
var response:NSURLResponse?
var error:NSError?
do {
        let data = try NSURLConnection.sendSynchronousRequest(request, 	returningResponse: &response)
        print(response)
        // Xử lí dữ liệu
    } catch {
        // Xử lí lỗi
        print(error)
}

Bây giờ chúng ta sử dụng mệnh đề “do-catch” để bắt các lỗi và xử lí chúng một cách thích hợp. Như chúng ta có thể thấy, ở đây từ khóa “try” đã được đặt trước method (hàm). Với việc giới thiệu việc xử lí lỗi trong Swift 2.0, một số methods có thể ném ra các lỗi trong trường hợp method đó chạy lỗi. Khi chúng ta cần gọi một method có thể có lỗi, chúng ta sẽ cần đặt từ khóa “try” phía trước method đó.

Làm thế nào để biết method đó có hỗ trợ bắt lỗi qua throws hay không? Đơn giản chúng ta chỉ cần gõ method đó trong editor, editor sẽ gợi ý.

Screen Shot 2015-10-28 at 01.51.04.png

một ví dụ về mô hình shopping cart nhỏ. Khách hàng lưu tạm giỏ hàng và kiểm tra giỏ hàng, tuy nhiên chương trình sẽ ném ra một lỗi nếu gặp các trường hợp:

• Giỏ hàng chỉ chứa tối đa 5 item, nếu không nó sẽ ném ra một lỗi “cartIsFull”.

• Phải có ít nhất 1 sản phẩm trong giỏ hàng khi kiểm tra. Nếu không sẽ ném ra một lỗi “cartIsEmpty”.

Trong Swift các lỗi được đại diện bởi các loại giá trị phù hợp với giao thức ErrorType. Thông thường, sử dụng một “enumeration” để liệt kê các điều kiện lỗi. Trong trường hợp này chúng ta có thể liệt kê như sau:

enum ShoppingCartError: ErrorType {
        case cartIsFull
        case emptyCart
}

Về giỏ hàng, chúng ta tạo một class “LiteShoppingCart” để xây dựng các hàm. đoạn code dưới đây:

struct Item {
    var price:Double
    var name:String
}
class LiteShoppingCart {
    var items:[Item] = []
    func addItem(item:Item) throws {
        guard items.count < 5 else {
             throw ShoppingCartError.cartIsFull
         }
         items.append(item)
     }
     func checkout() throws {
         guard items.count > 0 else {
            throw ShoppingCartError.emptyCart
	}
    // Tiếp tục kiểm tra
}

Ở method “addItem” có thêm keyword “throws” để chỉ ra rằng method này có thể ném ra một lỗi. Bên trong, chúng ta sử dụng “guard” để đảm bảo tổng số sản phẩm là ít hơn 5. Nếu không chúng ta sẽ ném ra lỗi “ShoppingCartError.cartIsFull”. Để ném ra một lỗi, chúng ta chỉ cần dùng keyword “throw” sau đó là lỗi chúng ta muốn.

Tương tự với method “checkout”. Nếu giỏ hàng không chứ sản phẩm nào thì chúng ta sẽ ném ra lỗi “ShoppingCartError.emptyCart”. Bây giờ hãy xem khi chúng ta chạy method “checkout” với giỏ hàng trống thì có vấn đề gì. Sử dụng đoạn code dưới đây để test.

let shoppingCart = LiteShoppingCart()
do {
    try shoppingCart.checkout()
    print("Kiểm tra giỏ hàng thành công!")
} catch ShoppingCartError.cartIsFull {
    print("Không thể thêm sản phẩm vì giỏ hàng đã đầy")
} catch ShoppingCartError.emptyCart {
    print("Giỏ hàng trống!")
} catch {
    print(error)
}

Method “checkout” có khả năng ném ra một lỗi nên chúng ta sử dụng “do-catch” để bắt lỗi. Nếu chúng ta chạy đoạn code trên bằng Playgrounds nó sẽ bắt được lỗi “ShoppingCartError.emptyCart” và in ra tin nhắn tương ứng bởi vì chúng ta chưa thêm bất cứ sản phẩm nào vào giỏ hàng.

Bây giờ chèn đoạn code sau bên trong mệnh đề “do”, và phải trước dòng lệnh gọi method “checkout”:

try shoppingCart.addItem(Item(price: 100.0, name: "Product #1"))
try shoppingCart.addItem(Item(price: 100.0, name: "Product #2"))
try shoppingCart.addItem(Item(price: 100.0, name: "Product #3"))
try shoppingCart.addItem(Item(price: 100.0, name: "Product #4"))
try shoppingCart.addItem(Item(price: 100.0, name: "Product #5"))
try shoppingCart.addItem(Item(price: 100.0, name: "Product #6"))

Ở đây chúng ta đã thêm 6 sản phẩm vào mảng “shoppingCart”. Nó sẽ ném ra một lỗi bởi vì giỏ hàng chúng ta quy định chỉ chứa tối đa 5 sản phẩm.

Khi bắt các lỗi, chúng ta có thể chỉ ra chính xác loại lỗi để xử lí cụ thể cho từng loại lỗi (ví dụ ở đây là ShoppingCartError.cartIsFull). Ngoài ra nếu chúng ta không chỉ rõ một loại định dạng cụ thể cho mệnh đề “catch” Swift sẽ tự động đưa ra lỗi chung.

defer

Nếuđã biết qua về Java, trong vấn đề xử lí lỗi/ngoại lệ có câu lệnh “try-catch-finally”. Trong đó đoạn code nằm trong “finally” sẽ chạy bất kể có lỗi hay không trước đó.

Swift 2 cung cấp một chức năng tương tự bằng cách sử dụng keyword “defer”. Tiếp tục với ví dụ trên để hiểu hơn về “defer”, chúng ta có thể chèn câu lệnh “defer” vào method “checkout” như sau:

func checkout() throws {
    defer {
        print("Tổng số sản phẩm trong giỏ hàng = \	(shoppingCart.items.count)")
    }
    guard items.count > 0 else {
        throw ShoppingCartError.emptyCart
    }
    // Tiếp tục kiểm tra
}

Bây giờ chúng ta có thể gọi method “checkout” nó sẽ in chuỗi “Tổng số sản phẩm trong giỏ hàng” bất kể method hoàn thành bình thường hay có lỗi. Nếu test method “checkout” bằng đoạn code dưới :

    let shoppingCart = LiteShoppingCart()
do {
    try shoppingCart.checkout()
    print("Kiểm tra giỏ hàng thành công!")
} catch ShoppingCartError.cartIsFull {
    print("Không thể thêm sản phẩm vì giỏ hàng đã đầy")
} catch ShoppingCartError.emptyCart {
    print("Giỏ hàng trống!")
} catch {
    print(error)
}

chúng ta sẽ thấy 2 chuỗi message trong console: “Tổng số sản phẩm trong giỏ hàng = 0 Giỏ hàng trống!” Câu lệnh “defer” khá hữu ích trong việc dọn dẹp(xóa mảng, object, clear cache,…) để tối ưu, tăng hiệu suất ứng dụng:

func aMethod() throws {
        //Ví dụ:  Mở 1 vài tài nguyên trong máy (mở file)
        defer {
            // Giải phóng tài nguyên và dọn dẹp…
        }
        // Xử lí lỗi (Ví dụ: lỗi không load được file)
        // Làm gì đó với file đã mở
    }

All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.