Swift 4 so với Swift 3 - Sự khác biệt và những tính năng mới
Bài đăng này đã không được cập nhật trong 7 năm
Từ khi apple ra ngôn ngữ swift mới, quá trình phát triển của nó cũng đã trải qua rất nhiều phiên bản, cùng với sự thay đổi rất lớn ở các bản 1.x, 2.x, 3.x, cho đến thời điểm 3.x thì swift đã dần đi vào ổn định cho nên tới bản 4.0 này cũng có sự thay đổi nhưng không nhiều như các lần cập nhật trước, trong bài viết này tôi sẽ liệt kê đầy đủ các sự khác biệt chính cùng với những tính năng mới cập nhật trên phiên bản mới.
Hãy cùng điểm qua 3 nền tảng của swift: Là một ngôn ngữ tuyệt vời để code bởi Swift có những lợi thế riêng và nổi bật, cũng được cho là sẽ "sống lâu" hơn Objective-C Swift giúp chúng ta code nhanh, an toàn, và cấu trúc ngắn gọn dễ hiểu, tính ứng dụng rất cao, code swift có thể được build chạy trên rất nhiều thiết bị khác nhau, từ các thiết bị của apple cho đến cả dùng Swift để code server, thậm chí apple còn tạo ra swift playground trong công cụ xcode để cho bạn code đến đâu nó thể hiện ra ngay lập tức, mới mỗi 1 phiên bản của nó thì cũng dc cải thiệt và trở nên nhanh hơn, điều này đúng với swift 4, đây được đánh giá là phiên bản khá là ổn định, sẵn sàng để làm những dự án lớn.
1 tính năng rất tốt mà apple đã trang bị cho xcode 9, khiến bạn sẽ không phải lo lắng chuyện tương thích với swift 4 từ swift 3, bạn sẽ hiểu vì sao khi đọc xong bài viết này.
1. Bắt đầu
Bản thân ngôn ngữ sẽ không thể gọi là hữu dụng nếu thiếu đi 1 IDE với các tính năng tốt đi kèm, với cộng đồng lập trình của apple thì bạn có thể download phiên bản mới nhất của Xcode 9 từ Apple develop, đây công cụ khá là tốt mà apple hỗ trợ cho cộng đồng developer, bạn hoàn toàn có thể cài đặt nhiều bản xcode khác nhau trên máy tính cuả bạn.
Nếu bạn bắt đầu với 1 dự án mới hoàn toàn thì đó là điều rất tốt cho bạn, nhưng nếu bạn đang phát triển 1 dự án với swift 3.0 thì quá trình chuyển đổi lên Swift 4 là yêu cầu phải thực hiện.
Để bắt đầu tốt nhất ta nên thực hiện với playground để hiểu rõ những sự khác biệt khi dùng với phiên bản swift 4
2. Chuyển đổi sang phiên bản Swift 4
Thông thường để chuyển đổi sang phiên bản swift mới hơn sẽ tốn khá nhiều effort đặc biệt là swift 2.x sang 3.x, có thể mất từ 2 tới 3 ngày, hoặc lâu hơn nếu dự án lớn hơn, nhưng từ 3 sang 4 lại khá dễ dàng, bạn có thể giảm thiểu thời gian hơn kha khá so với các phiên bản trước.
3. Chuẩn bị trước khi chuyển đổi
Xcode 9 không phải là chỉ hỗ trợ mỗi Swift 4, nên việc biên dịch dự án swift cũ hơn như 3.2 sẽ không phải là vấn đề quá to tát, mọi thứ luôn diễn ra 1 cách thuận lợi và dễ dàng. Điều này có thể bởi vì trình biên dịch Swift 4 hỗ trợ luôn cả việc biên dịch swift 3.2, thậm chí bạn có thể tìm được phần config sử dụng phiên bản swift nào trong cài đặt project, điều này sẽ rất hữu ích khi bạn dùng các thư viện bên thứ 3 mà thư viện đó chưa kịp cập nhật lên swift 4, ta hoàn toàn có thể giữ nó ở bản swift cũ hơn. Tuy nhiên, không chỉ có ngôn ngữ là thay đổi mà ngay cả SDKs cũng có 1 số sự đổi thay, cho nên việc cập nhật các thư viện lên swift mới cũng là điều bắt buộc cho dự án phát triển lâu dài.
4. Công cụ chuyển đổi Swift
Sau mỗi lần cập nhật apple cũng cung cấp cho chúng ta 1 công cụ chuyển đổi đi kèm, bạn có thể thực hiện bằng cách vào Edit -> Convert -> To Current Swift Syntax… sau đó chọn vào target mà bạn cần chuyển đổi
Sau khi bạn chọn thay đổi thì công cụ chuyển đổi sẽ ra soát toàn bộ target của bạn vào tạo 1 xem trước để bạn có thể xem sự thay đổi, nó có thể thay đổi hầu hêt code cho bạn nhưng không phải tất cả, có thể bạn sẽ phải tự tay thao tác ở 1 số nơi.
5. CocoaPods
Hầu hết các nhà phát triển apple đều sử dụng cocoapods cho việc quản lý các open source từ bên thứ 3. như đã nói ở trên, không phải tất cả các thư viện bên thứ 3 đều nhanh chóng hỗ trợ swift 4, vì vậy bạn sẽ thấy lỗi trong khi biên dịch các thư viện này. Giải pháp đặt ra là bạn thêm 1 vài lệnh để nó có thể build trên phiên bản swift 3.2 trước, mở podfile mà cập nhật lại dạng như thế này:
old_swift_3_pods = [
'PodName1',
'PodName2',
]
post_install do |installer|
installer.pods_project.targets.each do |target|
if old_swift_3_pods.include? target.name
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '3.2'
end
end
end
end
sau đó chạy lệnh "pod install" là ok sau đó bạn có thể biên dịch mà không gặp lỗi như trước nữa.
Bây giờ hãy cùng điểm qua 1 số thay đổi và bổ sung trong API
6. Strings
Bạn có thể duyệt toàn bộ 1 chuỗi String như sau:
let string = "Hello, Mind Studios!"
for character in string {
print(character)
}
Điều này cũng có nghĩa là bạn có thể dùng tất cả các Phương thức của collection với string như: count, isEmpty, map(), filter(), index(of:) và nhiều hơn nữa:
string.count // No more `string.characters.count` 🙃
string.isEmpty // false
let index = string.index(of: " ") // 6
let reversedCollection = "abc".reversed()
let reversedString = String(reversedCollection) // "cba"
// String filtering
let string = "ni123n456iniASijasod! 78a9-kasd aosd0"
let numbersString = string.filter { Int(String($0)) != nil } // "1234567890"
7. Kiểu Substring mới:
Hãy cùng xem kiểu substring mới nó thể hiện như thế nào?
// Split string into substrings
let string = "Hello, Mind Studios!"
let parts = string.split(separator: " ") // ["Hello,", "Mind", "Studios!"]
type(of: parts.first!) // Substring.Type
Cả 2 kiểu String và Substring đều hỗ trợ StringProtocol nên làm cho chúng gần như nhau và tương thích tốt với nhau:
var hello = parts.first!
// Concatenate a String onto a Substring
hello += " 🐶!" // "Hello, 🐶!"
// Create a String from a Substring
let helloDog = String(hello) // "Hello, 🐶!"
8. Có 1 ghi chú quan trọng với Substring:
Việc sử dụng Substring thì không khuyến khích lưu trữ nó lâu trong khi sử dụng, bởi substring sẽ sử dụng 1 tham chiếu đến bộ nhớ lớn hơn so với string thông thường, tham chiếu này thậm chí còn tồn tại khi quá trình sử dụng nó kết thúc, việc giữ 1 tham chiếu lâu như vậy sẽ làm Leak bộ nhớ.
Điều này có nghĩa là Substring nên được sử dụng tạm thời để phục vụ cho String, nên sử dụng theo kiểu như sau:
let substring: Substring = ... // Substring
let string = String(substring) // String
someMethod(string)
Dù thế nào đi nữa hãy tận dụng thế mạnh này để cho ra kết quả phù hợp trong từng trường hợp cụ thể, hãy nhớ ghi chú quan trọng mà được nhắc đến bên trên.
9. Thể hiện 1 chuỗi string nhiều dòng trong code:
Sử dụng 3 dấu nháy đôi cho bắt đầu và kết thúc:
let multilineString = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nam mattis lorem et leo laoreet fermentum.
Mauris pretium enim ac mi tempor viverra et fermentum nisl.
Sed diam nibh, posuere non lectus at, ornare bibendum erat.
Fusce mattis sem ac feugiat vulputate. Morbi at nunc maximus, vestibulum orci et, dictum neque.
Vestibulum vulputate augue ac libero vulputate vestibulum.
Nullam blandit et sapien non fermentum.
Proin mollis nisl at vulputate euismod.
"""
10. Bỏ qua xuống dòng trong 1 chuỗi string nhiều dòng, hãy xem đoạn code và kết quả để hiểu vấn đề này:
let escapedNewline = """
Line 1,
Line 2 \
next part of line 2,
Line 3
"""
print(escapedNewline)
Và đây là kết quả:
Line 1,
Line 2 next part of line 2,
Line 3
11. Cải thiện việc hỗ trợ Unicode:
Swift 4 đã chính thức hỗ trợ Unicode 9, điều đó có nghĩa các vấn đề khi đếm số ký tự trong chuỗi sẽ không còn xuất hiện nữa:
"👩💻".count // 1, in Swift 3: 2
"👍🏽".count // 1, in Swift 3: 2
"👧🏽".count // 1, in Swift 3: 2 - person + skin tone
"👨👩👧👦".count // 1, in Swift 3: 4
"🇺🇦🇺🇸🇮🇪".count // 3, in Swift 3: 1
12. Kiểm soát truy cập các biến, phương thức
Swift 3 đã đưa vào khái niệm fileprivate để điều chỉnh việc truy cập, gâp kha khá nhầm lẫn.
Trước đây việc dùng "private" sẽ giới hạn quyền truy cập các biến, phương thức trong 1 class dẫn tới việc nếu sử dụng trong 1 extension trong file đó sẽ không được, nên swift mới sinh ra thêm khái niệm fileprivate để mở rộng thêm việc truy cập các biến, phương thức trong cùng 1 file. Nhưng việc sử dụng như vậy cũng phát sinh vấn đè là các thành 1 trong cùng 1 file có thể truy cập tới các biến fileprivate mà có khi nó cũng chẳng liên quan đến nhau.
Swift 4 thiết lập lại trật tự bằng cách cho phép truy cập vào các biến private từ 1 extension, xem code bên dưới:
struct User {
private let firstName: String
private let lastName: String
}
extension User: CustomStringConvertible {
var description: String {
return "User: \(firstName) \(lastName)"
}
}
13. Dictionary and Set
Khởi tạo Dictionary với Sequence
Giờ đây Dictionary đã có thể được khởi tạo với Sequence, nhưng không phải tất cả các sequence được cho phép cách khởi tạo này, chỉ có dạng có chứa Tuples (key, value) tương ứng với key và value trong kiểu Dictionary.
et stocksIdentifiers = ["AAPL", "GOOGL", "NKE"]
let stocksValues = [158.28, 940.13, 53.73]
let pairs = zip(stocksIdentifiers, stocksValues)
let stocksValuesDict = Dictionary(uniqueKeysWithValues: pairs) // ["GOOGL": 940.13, "NKE": 53.73, "AAPL": 158.28]
Ta thấy function zip có tác dụng kết hợp key và value theo cặp để cho chúng ta kết quả mong muốn, thật tiện lợi phải không nào?
14. Merging các Dictionary với nhau:
Với swift 4 bạn có thể merge các dictionary với nhau và nó sẽ merged các key trùng lặp, bạn có thể sử dụng thông quá closure "uniquingKeysWith", như dưới đây:
let duplicates = [("a", 1), ("b", 5), ("a", 3), ("b", 3)]
let dictionary = Dictionary(duplicates, uniquingKeysWith: { (first, _) in
return first
}) // ["b": 5, "a": 1]
Với code trên là nếu gặp giá trị trùng nhau thì lấy về giá trị đầu tiên.
Thêm 1 ví dụ nữa cho rõ ràng, đó là việc đếm số lần xuất hiện các ký tự trong 1 chuỗi:
let string = "Hello!"
let pairs = Array(zip(string, repeatElement(1, count: string.count)))
let counts = Dictionary(pairs, uniquingKeysWith: +) // ["H": 1, "e": 1, "o": 1, "l": 2, "!": 1]
Việc chồng toán tử trong swift cho phép chúng ta có thể merged theo cách sau:
let values = ["a": 1, "b": 5]
var additionalValues = ["b": 3, "c": 2, "a": 3]
additionalValues.merge(values, uniquingKeysWith: +) // ["b": 8, "c": 2, "a": 4]
thật tuyệt phải không nào?
15. Subscript với giá trị mặc định
Với swift 3 ta viết như sau:
let dict = ["a": 1, "b": 5]
dict["c"] ?? 0 // 0
mục đích là nếu dict["c"] = nil thì lấy 0 làm giá trị mặc định, qua swift 4 ta có thể viết:
let dict = ["a": 1, "b": 5]
dict["c", default: 0] // 0, equals to `dict["c"] ?? 0` in Swift 3
kiểu này có vẻ hay hơn nhưng trông thì dài hơn
16. Bạn cũng có thể thay đổi Dictionary trong khi sử dụng với giá trị mặc định:
let string = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nam mattis lorem et leo laoreet fermentum.
Mauris pretium enim ac mi tempor viverra et fermentum nisl.
Sed diam nibh, posuere non lectus at, ornare bibendum erat.
Fusce mattis sem ac feugiat vulputate. Morbi at nunc maximus, vestibulum orci et, dictum neque.
Vestibulum vulputate augue ac libero vulputate vestibulum.
Nullam blandit et sapien non fermentum.
Proin mollis nisl at vulputate euismod.
"""
var wordsCountByLine = [Int: Int]()
let lines = string.split(separator: "\n")
for (index, line) in lines.enumerated() {
let lineWordsCount = line.split(separator: " ").count
wordsCountByLine[index, default: 0] += lineWordsCount
}
print(wordsCountByLine) // [2: 10, 4: 15, 5: 7, 6: 6, 7: 6, 0: 8, 1: 7, 3: 10]
17. Hạn chế các liên kết kiểu trong Protocols
extension Sequence where Element: Numeric {
var sum: Element {
var result: Element = 0
for element in self {
result += element
}
return result
}
}
[1,2,3,4].sum
việc sử dụng từ khoá where sẽ giới hạn phương thức sum chỉ sử dụng với kiểu số.
18. Lưu trữ và triển khai (Encoding/Decoding)
Trước đây để Serialize 1 kiểu custom nào đó bạn sẽ sữ dụng giao thức NSCoding. nhưng vấn đề là không sử dụng nó được khi ta sử dụng các kiểu non-class như struct, enum. Với swift 4 có 1 cách dễ dàng và hiệu quả đó là giao thức Codable, implement nó như sau:
struct Employee: Codable {
let name: String
let age: Int
let role: Role
enum Role: String, Codable {
case manager
case developer
case admin
}
}
struct Company {
let name: String
let officeLocation: Location?
let employees: [Employee]
}
struct Location : Codable {
let latitude: Double
let longitude: Double
}
Trường hợp đơn giản nhất bạn chỉ cần thêm Coable vào các thành phần mà bạn muốn tạo kiểu custom, sau đó trình biên dịch sẽ làm nó cho bạn 1 cách kỳ diệu
Codable là 1 typealias của 2 giao thức Decodable & Encodable
19. Encoding
Nếu bạn muốn serialize hoặc deserialize một giá trị codable - bạn phải sử dụng và encoder hoặc decoder object. Swift 4 đã có một bộ Encoding / Decoding cho danh sách thuộc tính JSON, cũng như CacaoError Throw trong quá trình Encoding / Decoding. NSKeyedArchiver & NSKeyedUnarchiver cũng hỗ trợ các loại Codable.
let employee = Employee(name: "Peter", age: 27, role: .manager)
let company = Company(name: "Awesome Company", officeLocation: nil, employees: [employee])
let encoder = JSONEncoder()
let companyData = try encoder.encode(company)
let string = String(data: companyData, encoding: .utf8)!
print(string)
>>>
{
"name" : "Awesome Company",
"employees" : [
{
"name" : "Peter",
"age" : 27,
"role" : "manager"
}
]
}
Vậy với JSONEncoder kết hợp với codable ta có thể dễ dàng chuyển đổi 1 object sang json
20. Decoding
Ngược lại với encoding, decoding sẽ làm thao tác ngược lại, ta xem ví dụ thực tế:
let decoder = JSONDecoder()
let jsonData = """
[
{
"name" : "Peter",
"age" : 27,
"role" : "manager"
},
{
"name" : "Alex",
"age" : 26,
"role" : "developer"
},
{
"name" : "Eugene",
"age" : 30,
"role" : "admin"
}
]
""".data(using: .utf8)!
let employees = try decoder.decode([Employee].self, from: jsonData)
Trường hợp này nếu có 1 giá trị Codable failed khi decode thì lập tức cả collection sẽ bị fail điều này có lẽ sẽ gây bất tiện chút
21. Custom Key names
Trong hầu hết các trường hợp sử dụng thì chúng ta điều phải custom lại key để mapping với object, ta có thể sử dụng nested enum named CodingKeys:
struct Country: Decodable {
let id: String
let name: String
let phoneCode: String
private enum CodingKeys: String, CodingKey {
case id = "alpha3"
case name
case phoneCode = "phone_code"
}
}
22. Custom Decoding
Tuỳ tình tình huống cụ thể nếu việc khởi tạo của bạn khá là phức tạp bạn có thể custom lại nó từ Decodable:
struct Transaction {
let id: Int
let action: String
let source: String
let amount: Int
let state: TransactionState
let createdAt: Date
let authorName: String
enum TransactionState: String, Decodable {
case done
case canceled
case processed
}
}
extension Transaction: Decodable {
private enum CodingKeys: String, CodingKey {
case id
case action = "action_name"
case source = "source_name"
case amount
case state
case createdAt = "created_at"
case author
}
private enum AuthorKeys: String, CodingKey {
case fullName = "full_name"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
actionName = try container.decode(String.self, forKey: .action)
sourceName = try container.decode(String.self, forKey: .source)
let createdAtValue = try container.decode(Double.self, forKey: .createdAt)
createdAt = Date(timeIntervalSince1970: createdAtValue)
state = try container.decode(TransactionState.self, forKey: .state)
amount = try container.decodeIfPresent(Int.self, forKey: .amount) ?? 0
do {
let authorContainer = try container.nestedContainer(keyedBy: AuthorKeys.self, forKey: .author)
authorName = try authorContainer.decode(String.self, forKey: .fullName)
} catch {
authorName = ""
}
}
}
Khá là hay ho phải không nào?
23. Key Value Coding
Một trong những tính năng tiện dụng mà swift 4 mang lại đó là SmartKeyPath. Không giống như swift 3 #keyPath(), nó không phải là kiểu strong, và không chỉ hoạt động với các member của objective-c, Swift 4 keypath là 1 kiểu generic, có nghĩa là keypath bây giờ nó là kiểu strong, hãy xem ví dụ để hiểu hơn:
struct User {
var username: String
}
Ta có thể truy cập vào 1 giá trị trong đối tượng thông qua keypath dạng như sau:
let user = User(username: "max")
let username = user[keyPath: \User.username] // "max"
Và còn có thể gán ngược trở lại:
var user = User(username: "max")
user[keyPath: \User.username] = "alex" // "alex"
Và keypath cũng không bị giới hạn bởi các thứ bậc khác nhau trong khai báo biến:
struct Comment {
let content: String
var author: User
}
let max = User(username: "max")
let comment = Comment(content: "Nice post!", author: max)
let authorUsername = comment[keyPath: \Comment.author.username] // "max"
Còn có thể khai báo như 1 biến, như sau:
let authorKeyPath = \Comment.author
let usernameKeyPath = authorKeyPath.appending(path: \.username)
let authorUsername = comment[keyPath: usernameKeyPath] // "max"
Có thể dùng cho cả biến option nữa :
let max = User(username: "max")
let alex = User(username: "alex")
var post = Post(title: "What's new in Swift 4", comments: [])
let topCommentAuthorUsernameKeyPath = \Post.topComment?.author.username
post[keyPath: topCommentAuthorUsernameKeyPath] // nil
let comment = Comment(content: "🚀", author: alex)
let anotherComment = Comment(content: "Nice post!", author: max)
post.comments = [comment, anotherComment]
post[keyPath: topCommentAuthorUsernameKeyPath] // "alex"
24. # KVO
Các API KVO mới phụ thuộc vào Objective-C Runtime và chỉ hoạt động với các class kế thừa NSObject, vì vậy nó không thể được sử dụng cho các cấu trúc Swift và các lớp mà không kế thừa NSObject. Và phải được đánh dấu bằng cách thêm như sau @objc dynamic var.
class User: NSObject {
@objc dynamic var name: String
var username: String
init(name: String, username: String) {
self.name = name
self.userName = userName
super.init()
}
}
let user = User(name: "Max", username: "max")
let nameObservation = user.observe(\.name, options: [.new, .old]) { user, change in // NSKeyValueObservation
if let oldValue = change.oldValue, let newValue = change.newValue {
print("fullName has changed from \(oldValue) to \(newValue)")
} else {
print("fullName is now \(user.name)")
}
}
user.name = "Alex" // name has changed from Max to Alex
Gọi invalidate() nếu muốn ngưng theo dõi sự thay đổi trên object name
nameObservation.invalidate()
user.name = "Elina" // observer isn't get called
Việc theo dõi này cũng sẽ ngưng khi deinit, vậy bạn cần phải lưu trữ biến phù hợp để ngăn chặn nến vẫn muốn sử dụng KVO
25. Swift 4 còn bổ sung thêm cách sử dụng Range đó là One-Sided Ranges, thường được sử dụng khi bạn muốn khởi tạo Infinite Sequences như sau:
let letters = ["a", "b", "c", "d"]
let numberedLetters = Array(zip(1..., letters)) // [(1, "a"), (2, "b"), (3, "c"), (4, "d")]
let string = "Hello, Mind Studios!"
let index = string.index(of: ",")!
string[..<index] // "Hello"
string[...index] // "Hello,"
Hoặc sử dụng trong cấu switch:
let value = 5
switch value {
case 1...:
print("greater than zero")
case 0:
print("zero")
case ..<0:
print("less than zero")
default:
break
}
26. Generic Subscripts
Subscript giờ đây đã có thể sử dụng biến generic và return về generic
Ví dụ 1:
struct JSON {
let data: [String: Any]
subscript<T>(key: String) -> T? {
return data[key] as? T
}
}
let jsonDictionary: [String: Any] = [
"name": "Ukraine",
"flag": "🇺🇦",
"population": 42_500_000
]
Ví dụ 2:
let json = JSON(data: jsonDictionary)
let population: Int? = json["population"] // 42500600
extension Dictionary where Value == String {
subscript<T: RawRepresentable>(key: Key) -> T? where T.RawValue == Value {
guard let string = self[key] else {
return nil
}
return T(rawValue: string)
}
}
enum Color: String {
case red
case green
case blue
}
27. Tối thiểu hoá việc sử dụng các giao thức của objective-c
Việc này với swift mà nói sẽ làm giảm thiểu kích thước của ứng dụng đi kha khá bởi sẽ không cần phải bên dịch các mã objective-C Trong nhiều trường hợp bạn sẽ không cần phải khai báo @objc nữa nhưng đến swift 4 vẫn chưa hết, vẫn có trường hợp bạn cần phải sử dụng như:
Khai báo thuộc tính có @objc Khai báo để gọi đến @objc Các khai báo có tiền tố @IBAction, @IBInspectable, @IBOutlet, @NSManaged, @GKInspectable
Tạo ra các class và protocol có nhiều chức năng khác nhau bằng cách merge chúng lại theo cách:
User & Codable & CustomStringConvertible
typealias MyType = User & Codable & CustomStringConvertible
28. Lợi ích của Swift 4
Khi phiên bản mới của swift được tạo ra nó đã cải thiện hơn rất nhiều cả về tốc độ và tính ổn định, nó nhanh hơn, ở swift 4 thì việc giảm sự phụ thuộc vào objective-c đã khiến cho kích thước file build ra đã giảm hơn ví dụ ở bản swift mới nhất ta có file build khoảng 17Mb thì ở bản cũ nó là khoảng 20Mb, việc sửa các lỗi từ trước đã khiến cho swift ổn định và trông nhanh hơn trước. Việc sử dụng ngôn ngữ mới đối với developer thì chỉ mất 1 khoảng thời gian ngắn để chuyển đổi, nên hãy chuyển đổi để nhận được những lợi thế tốt nhất mà ngôn ngữ hỗ trợ. Thanks for reading
Bài viết được tổng hợp từ https://themindstudios.com/blog/swift-4-vs-swift-3-differences/ nó sẽ rất hữu ích với các bạn.
All rights reserved