Swift 3.0 thay đổi rất nhiều. Code của bạn chắc chắn không build được cho tới khi bạn thực hiện các thay đổi cần thiết. Thay đổi từ Swift 1.2 tới 2.0 đã là lớn, với Swift 3 chưa thấm vào đâu.

Bài viết này, tôi giải thích các thay đổi quan trọng với code mẫu minh họa.

Cảnh báo 1: Có rất nhiều thay đổi, một số thì khá lặt vặt. Tuy nhiên hy vọng là những thay đổi này chỉ xảy ra lần này, và sẽ khiến ngôn ngữ tốt hơn trong những năm tới, và các thay đổi trong các bản sau nên nhỏ hơn.

Cảnh báo 2: Những thay đổi trong những điểm mới trong Swift 2.2 mà tôi nêu ra đã lỗi thời, có nhiều thứ bị bỏ đi, bao gồm ++, --, vòng lặp kiểu C, cú pháp tuple splat …

Mọi tham số hàm đều có nhãn trừ khi bạn quy định khác

Cách chúng ta gọi hàm và phương thức đã thay đổi kể từ Swift 2.0, lần này nó lại thay đổi lớn nữa. Trong Swift 2.x và các phiên bản trước, tên phương thức không yêu cầu nhãn cho tham số đầu tiên, do đó tên của tham số đầu tiên thường được xây dựng vào tên phương thức. Ví dụ:

names.indexOf("Taylor")
"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

Trong Swift 3, mọi nhãn đều phải có, trừ khi bạn chỉ định khác đi, nghĩa là tên phương thức sẽ không chứa thông tin về các tham số nữa. Phần cuối của tên phương thức sẽ được tách ra và làm tên cho tham số đầu tiên.

Dưới đây là code trong Swift 2.2 và tương đương trong Swift 3:

names.indexOf("Taylor")
names.index(of: "Taylor")

"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
"Taylor".write(toFile: "somefile", atomically: true, encoding: String.Encoding.utf8)

SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
SKAction.rotate(byAngle: CGFloat(M_PI_2), duration: 10)

UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
UIFont.preferredFont(forTextStyle: UIFontTextStyle.subheadline)

override func numberOfSectionsInTableView(tableView: UITableView) -> Int
override func numberOfSections(in tableView: UITableView) -> Int

func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
func viewForZooming(in scrollView: UIScrollView) -> UIView?

NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

Trong lần gọi hàm ở dòng cuối ví dụ trên,  để ý cách NSTimer được gọi chỉ là Timer. Một số loại cơ bản khác cũng bỏ đi tiền tố “NS”, do đó giờ chỉ còn: UserDefaults, FileManager, Data, Date, URL URLRequest, UUID, NotificationCenter, …

Khi bạn kết nối đến các frameworks ví dụ như UIKit, chúng tuân theo quy tắc cũ là “không có tên tham số đầu tiên”, thậm chí là trong Swift 3.

Sau đây là một số ví dụ từ Swift 2.2:

override func viewWillAppear(animated: Bool)
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(view: SKView)
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(textField: UITextField) -> Bool

Tuy nhiên trong Swift 3, chúng đều cần dấu gạch dưới trước tham số đầu tiên, để ám hiệu là hàm gọi (code Objective-C) sẽ không sử dụng nhãn tham số:

override func viewWillAppear(_ animated: Bool)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(_ view: SKView)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(_ textField: UITextField) -> Bool

Loại bỏ những từ thừa

Khi Swift trở thành mã nguồn mở vào tháng 12/2015, hướng dẫn API mới của nó bao gồm 3 từ tiên đoán: “omit needless words” (loại bỏ những từ không cần thiết). Đây là một thay đổi lớn khác trong Swift 3, tên các phương thức mà chứa các từ hiển nhiên thì sẽ bỏ các từ này đi.

Một số ví dụ đơn giản. Đầu tiên, trong Swift 2.2:

let blue = UIColor.blueColor()
let min = numbers.minElement()
attributedString.appendAttributedString(anotherString)
names.insert("Jane", atIndex: 0)
UIDevice.currentDevice()

Bạn có thể chỉ ra các từ thừa không? Khi bạn làm việc với UIColor, dĩ nhiên blue là màu, do đó nói blueColor() là không cần thiết. Khi bạn nối attributed string với một cái khác (tương tự), bạn có cần chỉ ra nó là một attributed string đang thêm vào không?     

Sau đây là code tương tự trong Swift 3:

let blue = UIColor.blue
let min = numbers.min()
attributedString.append(anotherString)
names.insert("Jane", at: 0)
UIDevice.current

Bạn có thể thấy rằng, điều này khiến tên phương thức ngắn đi nhiều!

Thay đổi này đặc biệt ảnh hưởng tới chuỗi, bởi nó bị lặp lại ở mọi nơi. Cách tốt nhất để hiểu là so sánh từng code với nhau, trong ví dụ dưới, cứ dòng trên là Swift 2.2, dòng dưới là Swift 3.0:

"  Hello  ".stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet())
"  Hello  ".trimmingCharacters(in: .whitespacesAndNewlines)

"Taylor".containsString("ayl")
"Taylor".contains("ayl")

"1,2,3,4,5".componentsSeparatedByString(",")
"1,2,3,4,5".components(separatedBy: ",")

myPath.stringByAppendingPathComponent("file.txt")
myPath.appendingPathComponent("file.txt")

"Hello, world".stringByReplacingOccurrencesOfString("Hello", withString: "Goodbye")
"Hello, world".replacingOccurrences(of: "Hello", with: "Goodbye")

"Hello, world".substringFromIndex(7)
"Hello, world".substring(from: 7)

"Hello, world".capitalizedString
"Hello, world".capitalized

Cảnh báo: capitalized vẫn là một thuộc tính, nhưng lowercaseString và uppercaseString đã biến thành các phương thức lowercased() và uppercased().

Tôi chọn các ví dụ trên bởi vì chuyển sang Swift 3 không quá phức tạp, nhưng có nhiều thay đổi lớn đủ để làm tôi nhức đầu – thường là bởi vì phương thức mới này ngắn đến nỗi nó không rõ ràng như lúc trước nữa.

Ví dụ nhìn vào code sau:

dismiss(animated: true, completion: nil)

Lần đầu tiên thấy nó, tôi đã phải tự hỏi: “dismiss cái gì?” Một phần là vì hội chứng Stockholm mắc phải do ta đã quen thuộc với lối lập trình iOS cũ trong thời gian dài, nhưng sau khi học cách đảo ngược thay đổi nhãn tham số và thêm vào các từ thừa, nó sẽ tương tự như dòng code sau trong Swift 2.2:

dismissViewControllerAnimated(true, completion: nil)

Thực tế, phần completion: nil bây giờ là optional, do đó có thể viết như sau:

dismiss(animated: true)

Hàm prepareForSegue() cũng thay đổi tương tự:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)

UpperCamelCase bị thay thế bởi lowerCamelCase đối với enum và các thuộc tính

Mặc dù không liên quan về mặt cú pháp, các chữ viết hoa sử dụng để đặt tên class, và struct, thuộc tính và enum… vẫn luôn tuân theo quy ước khá chặt chẽ: các class, struct và enum sử dụng UpperCamelCase (MyStruct, WeatherType.Cloudy), các thuộc tính và tên tham số sử dụng lowerCamelCase (emailAddress, requestString).

Tuy nhiên vẫn có những ngoại lệ và chúng sẽ bị bỏ trong Swift 3: các thuộc tính và tham số bắt đầu bằng các chữ cái viết tắt trong Swift 2.2 bây giờ sẽ sử dụng lowerCamelCase trong Swift 3.

Swift 2.2 tạo ra các đối tượng NSURLRequest sử dụng NSURLRequest(URL: someURL) – chú ý “URL” được viết hoa. Swift 3 thay đổi thành URLRequest(url: someURL), và có nghĩa, bạn sẽ sử dụng những thứ kiểu như webView.request?.url?.absoluteString để đọc URL cho web view.

Thực sự là không hay khi chỉ một phần của tên thuộc tính viết hoa vd như: CGColor  hoặc CIColor. Bây giờ, nó trở thành cgColor và ciColor trong Swift 3, như kiểu này:

let red = UIColor.red.cgColor

Thay đổi này nhằm thực hiện tính nhất quán: tất cả thuộc tính và tham số đều bắt đầu bằng chữ cái thường, không có ngoại lệ.

Đồng thời, enum case cũng thay đổi, chuyển từ UpperCamelCase về lowerCamelCase. Điều này khá đúng: enum là một kiểu dữ liệu (như struct), nhưng các giá trị của enum thì gần với các thuộc tính hơn. Bây giờ, bất cứ lúc nào bạn sử dụng kiểu enum của Apple, nó sẽ là viết thường. Ví dụ:

UIInterfaceOrientationMask.Portrait // old
UIInterfaceOrientationMask.portrait // new

NSTextAlignment.Left // old
NSTextAlignment.left // new

SKBlendMode.Replace // old
SKBlendMode.replace // new

Tuy nhiên thay đổi nhỏ này dẫn đến thay đổi lớn hơn nữa, do các kiểu optional của Swift thực tế chính là enum:

enum Optional {
    case None
    case Some(Wrapped)
}

Điều này có nghĩa nếu bạn sử dụng .Some để làm việc với các optional, bạn sẽ cần chuyển sang .some. Dĩ nhiên, bạn có thể bỏ .some đi. Hai đoạn code sau là giống nhau:

for case let .some(datum) in data {
    print(datum)
}

for case let datum? in data {
    print(datum)
}

Import các hàm C trong Swift

Swift 3 giới thiệu các attribute cho các hàm C, cho phép người tạo thư viện xác định các cách mới và tốt hơn để import code vào Swift. Ví dụ, tất cả hàm bắt đầu bằng “CGContext” giờ được ánh xạ tới các phương thức thuộc tính trên đối tượng CGContext, điều này khiến cho Swift đặc trưng hơn. Kiểu hàm như CGContextSetFillColorWithColor() đã bị cắt bỏ.

Để minh họa, đây là code trong Swift 2.2:

let ctx = UIGraphicsGetCurrentContext()

let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
CGContextSetFillColorWithColor(ctx, UIColor.redColor().CGColor)
CGContextSetStrokeColorWithColor(ctx, UIColor.blackColor().CGColor)
CGContextSetLineWidth(ctx, 10)
CGContextAddRect(ctx, rectangle)
CGContextDrawPath(ctx, .FillStroke)

UIGraphicsEndImageContext()

Trong Swift 3, CGContext có thể được coi như là 1 đối tượng mà bạn có thể gọi ra các phương thức trên nó, chứ không cần lặp lại CGContext liên tục. Code trong Swift 3:

if let ctx = UIGraphicsGetCurrentContext() {
    let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
    ctx.setFillColor(UIColor.red.cgColor)
    ctx.setStrokeColor(UIColor.black.cgColor)
    ctx.setLineWidth(10)
    ctx.addRect(rectangle)
    ctx.drawPath(using: .fillStroke)

    UIGraphicsEndImageContext()
}

Chú ý: trong cả Swift 2.2 và Swift 3.0 UIGraphicsGetCurrentContext() đều trả về kiểu optional CGContext, nhưng bởi Swift 3 gọi phương thức từ đối tượng, nên để cẩn thận ta cần unwrap trước khi sử dụng.

Kiểu mapping các hàm C như thế này cũng tồn tại ở những nơi khác, ví dụ, giờ bạn có thể đọc thuộc tính numberOfPages của CGPDFDocument, và CGAffineTransform cũng được cải thiện đáng kể. Ví dụ code Swift cũ và mới:

CGAffineTransformIdentity
CGAffineTransform.identity

CGAffineTransformMakeScale(2, 2)
CGAffineTransform(scaleX: 2, y: 2)

CGAffineTransformMakeTranslation(128, 128)
CGAffineTransform(translationX: 128, y: 128)

CGAffineTransformMakeRotation(CGFloat(M_PI))
CGAffineTransform(rotationAngle: CGFloat(M_PI))

Động từ và danh từ

Một số trích dẫn từ guideline của Swift API:

  • “Khi một operation được mô tả tự nhiên bằng một động từ, sử dụng động từ mệnh lệnh đối với các phương thức biến đổi (mutating method) và áp dụng hậu tố “ed” hoặc “ing” đối với tên các phương thức không biến đổi (nonmutating)”
  • “Nên dùng phân từ quá khứ của động từ để đặt tên biến không biến đổi (nonmutating variant)”
  • “Nếu việc thêm “ed” không đúng ngữ pháp do động từ có tân ngữ trực tiếp, đặt tên biến không biến đổi bằng phân từ hiện tại của động từ”
  • “Khi một operation được mô tả tự nhiên bằng một danh từ, sử dụng danh từ cho phương thức không biến đổi và sử dụng tiền tố “form” đặt tên cho phương thức biến đổi”

Ví dụ:

myArray.enumerate()
myArray.enumerated()

myArray.reverse()
myArray.reversed()

Mỗi khi Swift 3 điều chỉnh phương thức bằng cách thêm “d” vào cuối: ở đấy có giá trị trả về.

Những quy tắc này phần lớn dễ hiểu, nhưng có thể gây ra sự nhầm lẫn khi dùng cho sắp xếp mảng. Swift 2.2 sử dụng sort() để trả về mảng đã sắp xếp, và sortInPlace() để sắp xếp mảng tại chỗ. Trong Swift 3.0, sort() được đổi tên thành sorted() (theo như các ví dụ bên trên), và sortInPlace() được đổi tên thành sort().

Chú ý: Điều này có nghĩa là bạn cần phải cẩn thận bời vì trong Swift 2.2 sort() trả lại mảng đã sắp xếp, nhưng trong Swift 3.0, sort() sắp xếp mảng tại chỗ.

Tại sao lại có các thay đổi này

Đọc những thay đổi này là dễ dàng, một số trong đó nhỏ thôi nhưng lại dẫn đến thay đổi hàng loạt, và có thể bạn nghĩ rằng các kỹ sư Swift của Apple đang làm khó chúng ta. Nhưng sự thật là họ đang làm việc chăm chỉ để đảm bảo Swift là dễ học, dễ sử dụng và nhanh nhất có thể, đây là ba ưu tiên rất khác nhau.

Đặc biệt, tôi đã bị choáng bởi sự tận tâm của team Apple nhằm đảm bảo những thay đổi của họ được thảo luận và nhất trí trong cộng đồng mở, đây là một phần nỗ lực của cộng đồng Phát triển Swift. Mỗi thay đổi trên đều đã trải qua thảo luận cộng đồng rộng lớn trước khi áp vào Swift 3.0.

Bạn cũng có thể tham gia và định hình những thay đổi sắp tới: họ luôn mong muốn được nghe ý tưởng từ nhiều người dùng, điều đó cũng nghĩa, tương lai của Swift cũng nằm trong tay bạn.

Nguồn bài viết.