Swift 3.0 có gì mới?
Nếu bạn nghĩ những thay đổi từ 1.2 lên 2.0 đã “dễ sợ” lắm rồi; thì bạn sẽ ngạc nhiên với phiên bản 3.0 này đấy. Với Swift 3.0, bạn sẽ thấy sự biến đổi 180o, code của phiên bản trước sẽ không build nếu bạn không thay đổi lại cho phù hợp.
Trong bài viết này, tuy không thể nêu ra toàn bộ các thay đổi của phiên bản mới, nhưng tôi sẽ cố gắng chia sẻ một vài thay đổi quan trọng kèm theo các ví dụ cụ thể.
CẢNH BÁO #1: Swift 3.0 vẫn đang được phát triển. Bài viết sẽ liên tục cập nhật những thay đổi mới nhất.
CẢNH BÁO #2: Có rất nhiều thay đổi lớn nhỏ. Có vẻ như đây là phiên bản “cột mốc” ổn định trong nhiều năm sắp tới, và các phiên bản tiếp theo sẽ thay đổi ít hơn.
CẢNH BÁO #3: Những tính năng bất cập trong phiên bản 2.2 đã được loại bỏ: ++, –, C-style vòng lặp, tuple splat syntax,…
Tất cả tham số hàm đều có nhãn (tên)
Ở Swift 2.0, ta đã chứng kiến sự thay đổi trong cách gọi hàm (function) và phương thức (method). Và với phiên bản 3.0 này, ta sẽ lại lần nữa thấy sự thay đổi này, ở một mức độ cao hơn nữa. Từ Swift 2.x trở về trước, tên phương thức không yêu cầu nhãn ở tham số đầu tiên, nên tên của tham số đầu thường dính luôn vào tên phương thức. Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 | 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) |
Swift 3 sẽ mặc định yêu cầu nhãn cho tham số (bạn có thể điều chỉnh để thay đổi), đồng nghĩa với việc tên phương thức sẽ không còn nêu chi tiết các tham số nữa. Trong thực tế, phần cuối của tên hàm thường sẽ chuyển thành tên của tham số đầu.
Để minh họa rõ hơn, dưới đây là một đoạn code trong Swift 2.2 kèm theo ví dụ tương ứng trong Swift 3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | names.indexOf("Taylor") names.index(of: "Taylor") "Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding) "Taylor".write(toFile: "somefile", atomically: true, encoding: NSUTF8StringEncoding) SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10) SKAction.rotate(byAngle: CGFloat(M_PI_2), duration: 10) UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline) UIFont.preferredFont(forTextStyle: UIFontTextStyleSubheadline) 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) NSTimer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true) |
Đấy là các phương thức bạn đã call, đồng thời cũng gây hiệu ứng domino cho các phương thức called: Khi bạn kết nói đến một số framework như UIKit, các phương thức sẽ tuân theo quy luật “không có tên tham số đầu” giống các phiên bản trước (điều này áp dụng cả với Swift 3).
Dưới đây là một số ví dụ điển hình ở Swift 2.2:
1 2 3 4 5 | 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 |
Với Swift 3, tất cả đều cần underscore (_) trước tham số đầu tiên, để báo hiệu rằng caller (Objective-C code) sẽ không dùng nhãn tham số:
1 2 3 4 5 | 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ỏ từ không cần thiết
Khi Swift chuyển thành mã nguồn mở hồi tháng 12 năm ngoái, API guildlines của Swift đã tiên đoán trước cả thay đổi của phiên bản 3 với ba từ: “omit needless words” (loại bỏ từ không cần thiết). Và giờ đây ta đã thấy sự thay đổi này trong Swift 3, tên phương thức sẽ loại bỏ các từ hiển nhiên.
Trước hết, hãy xem thử một vài ví dụ đơn giản trong Swift 2.2:
1 2 3 4 5 | let blue = UIColor.blueColor() let min = numbers.minElement() attributedString.appendAttributedString(anotherString) names.insert("Jane", atIndex: 0) UIDevice.currentDevice() |
Bạn có xác định được các từ dư thừa không? Khi bạn làm việc với UIColor
, blue hiển nhiên là màu rồi, nên blueColor()
không cần thiết nữa. Khi bạn nối attributed string với nhau, bạn có cần phải xác định rằng bạn đang append một attributed string chứ không phải là elephant?
Sau đây là đoạn code tương ứng trong Swift 3:
1 2 3 4 5 | let blue = UIColor.blue() let min = numbers.min() attributedString.append(anotherString) names.insert("Jane", at: 0) UIDevice.current() |
Như bạn thấy đấy, tên phương thức được rút ngắn đi nhiều!
Thay đổi này đặc biệt ảnh hưởng đến string (có nhiều cấu trúc lặp). Để làm rõ hơn, sau đây ta sẽ so sánh trực tiếp, dòng trên của Swift 2.2 và dòng dưới của Swift 3.0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | " Hello ".stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()) " Hello ".trimmingCharacters(in: .whitespacesAndNewlines()) "Taylor".containsString("ayl") "Taylor".contains("ayl") "1,2,3,4,5".componentsSeparatedByString(",") "1,2,3,4,5".componentsSeparated(by: ",") 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 |
Chú ý: capitalized
vẫn là thuộc tính, nhưng lowercaseString
và uppercaseString
đã chuyển thành phương thức lowercased()
và uppercased()
.
Tôi lựa chọn các ví dụ trên vì bước nhảy đến Swift 3 không quá dài, nhưng vẫn có nhiều thay đổi quá khác biệt làm tôi “nhức hết cả đầu” – thường là vì phương thức cuối cùng quá ngắn, không rõ ràng như trước.
Ví dụ như:
1 | dismiss(animated: true, completion: nil) |
Khi nhìn thấy đoạn code này, tôi nghĩ ngay trong đầu: “dismiss cái gì cơ?” Có lẽ ta đã quá quen thuộc với lối lập trình iOS từ trước đến nay. Nhưng khi bạn đảo ngược thay đổi của nhãn tham số và “thêm lại” các từ không cần thiết, bạn có thể thấy, cũng không khác gì Swift 2.2 cả:
1 | dismissViewControllerAnimated(true, completion: nil) |
prepareForSegue()
cũng có thay đổi tương tự, giờ đây ta có:
1 | override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) |
lowerCamelCase thay cho UpperCamelCase với enum và property
Dù không liên quan (về mặt cú pháp) cho lắm, nhưng cách dùng chữ in hoa trong tên class, struct, property, enum,… của chúng ta vẫn thường theo một quy ước: class, struct và enum dùng UpperCamelCase (MyStruct, WeatherType.Cloudy), property và tên tham số dùng lowerCamelCase (emailAddress, requestString).
Tuy vậy, vẫn có một số ngoại lệ, và các ngoại lệ này sẽ không còn trong Swift 3 nữa: các property và tham số bắt đầu bằng chữ viết tắt giờ đây sẽ dùng lowerCamelCase.
Thay đổi này đôi khi cũng không quá lạ lẫm: Swift 2.2 sử dụng NSURLRequest(URL: someURL)
để tạo object NSURLRequest
– hãy chú ý đoạn “URL” viết hoa. Swift 3 sẽ viết lại thành NSURLRequest(url: someURL)
, và cũng đồng nghĩa bạn sẽ phải dùng các lệnh như webView.request?.url?.absoluteString
để đọc URL của một web view.
Bạn có thấy khó chịu khi tên property biến thành “nửa nạc nửa mỡ”, chỗ thì hoa, chỗ thì thường, như: CGColor
hay CIColor
? Bạn đoán không sai đâu: trong Swift 3, chúng đã biến thành cgColor
và ciColor
, khi đưa vào code:
1 | let red = UIColor.red().cgColor |
Thay đổi này sẽ tăng tính nhất quán hơn: tất cả property và thông số sẽ bắt đầu bằng chữ thường, và sẽ không có ngoại lệ.
Đồng thời, enum case cũng có sự thay đổi, từ UpperCamelCase thành lowerCamelCase. Theo tôi, thay đổi này khá đúng: enum là kiểu dữ liệu (như struct), nhưng enum value lại gần với property hơn. Nói cách khác, tất cả Apple enum bạn sử dụng giờ đây sẽ là chữ thường:
1 2 3 4 5 6 7 8 | UIInterfaceOrientationMask.Portrait // old UIInterfaceOrientationMask.portrait // new NSTextAlignment.Left // old NSTextAlignment.left // new SKBlendMode.Replace // old SKBlendMode.replace // new |
Thay đổi nhỏ trên dẫn đến một sự thay đổi lớn hơn nữa vì optionals của Swift thực tế chính là enum, như:
1 2 3 4 | enum Optional { case None case Some(Wrapped) } |
Có nghĩa nếu bạn dùng .Some
khi làm việc với optionals, bạn cần phải chuyển thành .some
. Tất nhiên, bạn có thể tận dụng cơ hội này để hoàn toàn loại bỏ .some – hai đoạn code dưới đây có chức năng như nhau:
1 2 3 4 5 6 7 | for case let .some(datum) in data { print(datum) } for case let datum? in data { print(datum) } |
Swifty importing of C functions
Swift 3 còn giới thiệu attribute cho hàm trong ngôn ngữ C, cho phép người tạo thư viện chỉ định nhiều cách thức mới để nhập code vào Swift thật nhanh chóng và chuẩn xác. Ví dụ như, tất cả các hàm bắt đầu bằng “CGContext” sẽ được map đến các property và method trên một CGContext object. Nói cách khác, cái “mụn cóc xấu xí” CGContextSetFillColorWithColor()
cuối cùng cũng đã bị cắt bỏ.
Để thấy rõ hơn, sau đây là ví dụ trong Swift 2.2:
1 2 3 4 5 6 7 8 9 10 | 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ể xem là object mà bạn có thể call method được, không cần phải lặp lại CGContext
liên tục. Vậy, ta có thể viết lại đoạn code trên như sau:
1 2 3 4 5 6 7 8 9 10 | 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() } |
Lưu ý: trong Swift 2.2 lẫn 3.0, UIGraphicsGetCurrentContext()
sẽ trả về CGContext
tùy chọn, nhưng vì Swift 3.0 dùng phương thức calls, ta phải unwrap cẩn thận trước khi sử dụng.
Bạn cũng có thể thấy kiểu mapping hàm C này ở nhiều trường hợp khác, ví dụ: giờ đây bạn có thể đọc property numberOfPages
của một CGPDFDocument
. Và CGAffineTransform
cũng đã cải thiện đáng kể. Sau đây là một số ví dụ cũ và mới:
1 2 3 4 5 6 7 8 9 10 11 | 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ừ
Có lẽ đọc đến đây nhiều người sẽ nhảy đến phần kết ngay. Đừng nên nhé, phần này cũng quan trọng không kém đâu.
Dưới đây là một số trích dẫn từ Swift API guildelines:
- “Khi operation được miêu tả bằng động từ, sử dụng thể mệnh lệnh của động từ với mutating method và thêm hậu tố “ed” hoặc “ing” để đặt tên cho các method không mutate tương ứng”
- “Nên sử dụng thể quá khứ phân từ của động từ để đặt tên của biến không mutate”
- “Khi Thêm “ed” không đúng ngữ pháp vì động từ có tân ngữ trực tiếp, dùng hiện tại phân từ của động từ để đặt tên biến không mutate”
- “Khi operation được miêu tả bằng danh từ, dùng danh từ với method không mutate và thêm tiền tố “form” để đặt tên mothod có mutate tương ứng”
Không ngạc nhiên khi các quy luật trong Swift đang dần gắn với thuật ngữ ngôn ngữ học ngày càng nhiều hơn (vì Swift là một loại “ngôn ngữ” mà). Tuy vậy, việc này có thể gây nhiều khó khăn cho các bạn yếu anh ngữ, bởi nhiều tên method sẽ có nhiều sự thay đổi.
Hãy bắt đầu bằng một vài ví dụ:
1 2 3 4 5 | myArray.enumerate() myArray.enumerated() myArray.reverse() myArray.reversed() |
Mỗi khi Swift 3 điều chỉnh method với “d” thêm vào cuối: đây là giá trị được trả về.
Quy luật này đặc biệt làm ta dễ nhầm lẫn khi sắp xếp array. Swift 2.2 dùng sort()
và trả array đã sắp xếp, và sortInPlace()
để sắp xếp array tại chỗ. Trong Swift 3.0, sort()
đổi thành sorted()
như ví dụ trên, và sortInPlace()
đổi thành sort()
.
Bạn phải cẩn thận chỗ này vì sort()
trong Swift 2.2 trả kết quả array được sắp xếp, còn sort()
trong swift 3.0 lại sắp xếp array tại chỗ.
Thay đổi nhiều như vậy để làm gì?
Chỉ với một số thay đổi nhỏ thôi, chúng ta cũng đã khó làm quen lắm rồi. vậy thay đổi thế này thì ích gì? Trong thực tế, ta có lẽ không nhận thấy, nhưng những thay đổi này là nỗ lực của Apple nhằm giúp ngôn ngữ dễ học hơn, dễ dùng hơn, và nhanh hơn nữa (đặc biệt với người mới học).
Những thay đổi trên cũng không phải là ý tưởng “bộc phát” của Apple. Đây đều là những ý kiến của cộng đồng, đã qua thảo luận kỹ lưỡng trước khi được Apple “phê chuẩn” đưa vào Swift 3.0.
Nếu muốn, bạn cũng có thể tham gia đóng góp và thảo luận ý kiến, góp phần quyết định tương lai và “vận mệnh” của ngôn ngữ Swift.
Tecktalk via hackingwithswift