Swift 5.1 cuối cùng đã được phát hành, mặc dù chưa không hoàn chỉnh tuyệt đối, Tuy nhiên, chúng ta vẫn có được một số tính năng quan trọng khác dưới dạng ổn định, cho phép chúng tôi sử dụng các thư viện của bên thứ ba mà không phải lo lắng phiên bản của trình biên dịch Swift mà họ đã xây dựng.

Bây giờ, chúng ta cũng nhận được một số cải tiến quan trọng của ngôn ngữ swift, và trong bài viết này, cafedevn sẽ đi qua chúng và cung cấp các ví dụ để bạn có thể thấy chúng hoạt động như thế nào. Khi bạn đọc qua bài này, rất có thể bạn sẽ thấy có khá nhiều tính năng mới liên quan đến SwiftUI. Một trong những tính năng tâm đắc nhất mà Apple đang đầu tư phát triển.

Cải tiến lớn trong hàm khởi tạo (init)

SE-0242 giới thiệu những cải tiến lớn cho một trong những tính năng được sử dụng phổ biến nhất của Swift: Đó là hàm khởi tạo cho struct.

Trong các phiên bản trước của Swift, trình khởi tạo được tạo tự động để chấp nhận các tham số khớp với các thuộc tính của struct, như sau:

Trong Swift 5.1, điều này đã được cải tiến để trình khởi tạo có thể sử dụng các giá trị tham số mặc định cho bất kỳ thuộc tính nào trong struct. Trong cấu trúc người User chúng tôi đã cung cấp logoutCount giá trị mặc định là 0, có nghĩa là chúng tôi có thể chỉ định nó hoặc để nó cho trình khởi tạo tự gán giá trị 0 cho nó khi khởi tạo:

Điều này cho phép chúng tôi tránh lặp lại mã.

Return được hiểu ngầm định từ các hàm biểu thức đơn

SE-0255 đã loại bỏ một sự không nhất quán nhỏ nhưng quan trọng trong ngôn ngữ – các hàm biểu thức đơn trả về giá trị giờ có thể xóa từ khóa return và Swift sẽ hiểu nó một cách ngầm là nó sẽ return.

Trong các phiên bản trước của Swift, việc đóng một dòng trả về giá trị mà bạn có thể bỏ qua từ khóa return vì dòng mã duy nhất phải có giá trị trả về giá trị. Vì vậy, hai đoạn mã này giống hệt nhau:

Trong Swift 5.1, hành vi này hiện cũng đã được mở rộng cho các chức năng thông thường – nếu chúng chứa một biểu thức duy nhất – thực sự là một đoạn mã tạo thành một giá trị – thì bạn có thể bỏ từ khóa return , như sau:

Self

SE-0068 mở rộng việc sử dụng Self của Swift và nó được sử dụng bên trong các class, struct và enum. Điều này đặc biệt hữu ích cho các loại dynamic , dùng để biết chính xác loại của một cái gì đó cần được xác định trong lúc runtime.

Ví dụ, xem xét mã này:

Khai báo một maximumActiveRequests tĩnh cho trình quản lý mạng và thêm phương thức printDebugData() để in thuộc tính tĩnh. Ta thấy nó hoạt động tốt, nhưng khi RequestManager được phân lớp, mọi thứ trở nên phức tạp hơn:

Kế thừa maximumActiveRequests từ lớp cha và thay đổi giá trị của maximumActiveRequests ,nhưng nếu chúng ta gọi printDebugData() thì nó sẽ in ra giá trị từ lớp cha của nó:

Cái này sẽ in ra 1 chứ không phải 4 – bây giờ chúng ta có thể viết Self (với chữ S viết hoa) để chỉ loại hiện tại. Vì vậy, chúng ta có thể viết lại printDebugData() thành thế này:

Điều này có nghĩa là Self hoạt động giống như cách nó đã làm cho các protocol trong các phiên bản Swift trước đó.

Các Return kiểu opaque(mờ đục)

SE-0244 giới thiệu khái niệm các loại opaque trong Swift. Một loại opaque là một loại mà chúng ta đã nói về khả năng của một đối tượng mà không biết cụ thể đó là loại đối tượng nào.

Thoạt nhìn nghe có vẻ giống một protocol hơn, nhưng các kiểu trả về opaque đưa khái niệm protocol đi xa hơn vì có thể làm việc với các associated types, chúng yêu cầu cùng loại được sử dụng bên trong mỗi lần dùng và chúng cho phép chúng ta ẩn việc thực hiện chi tiết.

Ví dụ, nếu chúng ta muốn phóng các loại máy bay chiến đấu khác nhau từ căn cứ Rebel, chúng tôi có thể viết mã như thế này:

Bất cứ ai gọi chức năng đó đều biết nó sẽ trả về một số loại Plane nhưng không biết chính xác là gì. Do đó, chúng tôi có thể thêm struct YPlane: Plane { } hoặc các loại khác.

Nhưng có một vấn đề là điều gì xảy ra nếu chúng ta muốn kiểm tra xem một máy bay chiến đấu cụ thể có phải là Red 5 không? Bạn có thể nghĩ rằng giải pháp là làm cho Plane tuân thủ giao thức Equatable để chúng ta có thể sử dụng == . Tuy nhiên, ngay sau khi bạn làm điều đó, Swift sẽ đưa ra một lỗi đặc biệt đáng sợ cho hàm launchPlane . Protocol ‘Plane’ chỉ có thể được sử dụng như một ràng buộc chung vì nó có các yêu cầu phải là Self or associated type.

Protocol Equatable phải so sánh hai trường hợp của chính nó (Self) để xem chúng có giống nhau không, nhưng Swift không đảm bảo rằng hai thứ tương đương giống nhau – chúng ta có thể so sánh một Plane với một loạt các số nguyên.

Các kiểu opaque đã giải quyết vấn đề này bởi vì mặc dù chúng ta chỉ thấy một protocol đang được sử dụng, nhưng bên trong trình biên dịch Swift biết chính xác giao thức đó thực sự giải quyết – nó biết đó là XPlane , một chuỗi các chuỗi hoặc bất cứ điều gì.

Để trả về một loại opaque, sử dụng từ khóasome trước tên protocol của bạn:

Từ góc nhìn của người gọi vẫn lấy lại được một Plane , có thể là XPlane , YPlane hoặc thứ gì khác phù hợp với protocol Plane . Nhưng từ phối cảnh của trình biên dịch, nó biết chính xác những gì đang được trả về, vì vậy nó có thể đảm bảo chúng ta tuân theo tất cả các quy tắc một cách chính xác.

Ví dụ, hãy xem xét một hàm trả về some Equatable như thế này:

Khi chúng ta gọi điều đó, tất cả những gì chúng ta biết là một loại giá trị tương đương, tuy nhiên nếu gọi nó hai lần thì chúng ta có thể so sánh kết quả của hai lần gọi đó vì Swift biết chắc chắn đó sẽ là cùng một loại cơ bản:

Điều này cũng không đúng nếu chúng ta có hàm thứ hai trả về some Equatable , như thế này:

Mặc dù theo quan điểm của chúng tôi, cả hai đều trả về cho chúng tôi một loại Equatable và chúng tôi có thể so sánh kết quả của hai lần gọi với makeString() hoặc lần gọi với makeInt() , Swift sẽ không cho phép chúng tôi so sánh giá trị trả về của makeString() với giá trị trả về của makeInt() vì nó biết so sánh một chuỗi và một số nguyên không có ý nghĩa gì.

Một điều quan trọng ở đây là các hàm có kiểu trả về opaque phải luôn trả về một kiểu cụ thể. Ví dụ: nếu chúng ta cố gắng sử dụng Bool.random() để khởi chạy ngẫu nhiên XPlane hoặc YPlane thì Swift sẽ từ chối build mã của chúng ta vì trình biên dịch không còn có thể cho biết những gì sẽ được trả về.

Bạn cũng có thể nghĩ rằng nếu chúng ta luôn cần phải trả về cùng loại, tại sao không chỉ viết hàm dưới dạng func launchPlane() -> XPlane ? Mặc dù điều đó đôi khi có thể hoạt động, nhưng nó tạo ra các vấn đề mới như sau:

  • Chúng ta có thể nhận các loại mà chúng ta không thực sự muốn nó trả về. Ví dụ: nếu chúng tôi sử dụng someArray.lazy.drop { … } chúng ta sẽ được trả về LazyDropWhileSequence – một loại dành riêng và đặc biệt từ thư viện chuẩn Swift. Tất cả những gì chúng tôi thực sự quan tâm là một chuỗi, nhưng chúng ta có thể nhận một loại nào khác.
  • Chúng ta mất khả năng thay đổi mới một kiểu trả về mới nào đó của chúng ta sau này. Làm cho launchPlane() chỉ trả lại một XPlane có nghĩa là chúng ta không thể chuyển sang một loại khác trong tương lai. Bằng cách trả lại loại opaque, chúng tôi có thể trả lại X-Plane ngay hôm nay, sau đó chuyển sang B-Plane trong một năm – chúng ta chỉ trả về một loại trong bất kỳ bản build mã nào, nhưng chúng tôi vẫn có thể linh hoạt thay đổi ý định.

Trong một số khía cạnh, tất cả điều này nghe có vẻ tương tự như thuốc generic, cũng giải quyết vấn đề về Self or associated type. Generics cho phép chúng ta viết mã như thế này:

Ta tạo một protocol mới và yêu cầu phải có thể khởi tạo mà không có tham số, sau đó tạo hai cấu struct implement với protocol đó, sau đó tạo một hàm chung để sử dụng nó. Tuy nhiên, sự khác biệt ở đây là bây giờ những người gọi của launchImperialPlane() là những người chọn loại máy bay chiến đấu mà họ cần, như thế này:

Nếu bạn muốn người gọi có thể chọn kiểu dữ liệu của họ thì generic sẽ hoạt động tốt, nhưng nếu bạn muốn hàm quyết định kiểu trả về thì opaque sẽ làm được.

Vì vậy, các loại kết quả opaque cho phép chúng tôi làm một số điều:

  • Các function của chúng tôi quyết định loại dữ liệu nào được trả về, chứ không phải người gọi các function đó.
  • Chúng ta không cần phải lo lắng về các yêu cầu loại Self hoặc associated type, bởi vì trình biên dịch biết chính xác loại được dùng bên trong.
  • Chúng ta có thể thay đổi kiểu trả về một cách dễ dàng trong tương lai, bất cứ khi nào chúng ta cần.
  • Chúng tôi không đưa các kiểu dữ liệu nội bộ bên trong ra bên ngoài biết.

Static and class subscripts

SE-0254 thêm khả năng static subscript, có nghĩa là chúng áp dụng cho các kiểu dữ liệu nào đó thay vì các thể hiện của một kiểu dữ liệu đó.

Các thuộc tính và phương thức tĩnh được sử dụng khi một bộ giá trị được chia sẻ giữa tất cả các phiên bản của kiểu đó. Ví dụ: nếu bạn có một kiểu để lưu trữ cài đặt ứng dụng của mình, bạn có thể viết mã như thế này:

Chúng ta sẽ dùng 1 dictionary bên trong một enum như trên thì chúng ta có thể kiểm soát truy cập cẩn thận hơn – chúng ta không nên thể tạo ra nhiều phiên bản Settings khác nhau.

Thay vào đó, với Swift 5.1, giờ đây chúng ta có thể sử dụng một static subscript, cho phép chúng ta viết lại mã của mình vào đây:

Cải tiến này cho các static subscript hoặc class subscript.

Cảnh báo cho trường hợp none

Các tùy chọn của Swift được triển khai như một eunm của hai trường hợp: somenone .Điều này đã dẫn đến khả năng nhầm lẫn nếu chúng ta tạo ra các enum riêng trường hợp none , sau đó bọc nó bên trong một Optional.

Ví dụ:

Được sử dụng như một Optional không phải lúc nào cũng rõ ràng:

Điều đó sẽ in ra “none”. Nhưng nếu chúng ta sử dụng một Optional cho enum đó – nếu chúng ta không biết nên sử dụng kiểu border nào – thì chúng ta sẽ gặp vấn đề:

Điều đó in ra .none , vì Swift giả định .none có nghĩa là tùy chọn trống, thay vì tùy chọn có giá trị BorderStyle.none .

Trong Swift 5.1, sự nhầm lẫn này hiện in một cảnh báo:“Assuming you mean ‘Optional.none’; did you mean ‘BorderStyle.none’ instead?” Nó sẽ thông báo cho các developer biết rằng mã của họ có thể không hoàn toàn có nghĩa như họ nghĩ.

Kết hợp các enum optional với các non-optional

Swift luôn đủ thông minh để xử lý switch / case khớp giữa các optional và non-optional cho chuỗi và số nguyên, nhưng trước Swift 5.1 không được mở rộng cho enum.

Trong Swift 5.1 bây giờ chúng ta có thể sử dụng switch / case để khớp các enum optional và non-optional, như thế này:

Swift có thể so sánh trực tiếp enum optional với các trường hợp non-optional, do đó mã sẽ in ra “Build is starting…”

Sự khác biết thứ tự trong Collection

SE-0240 giới thiệu khả năng tính toán và áp dụng sự khác biệt giữa các collection được sắp xếp. Điều này có thể đặc biệt thú vị đối với các developer có collection phức tạp trong chế độ xem bảng, nơi họ muốn thêm và xóa rất nhiều mục một cách trơn tru bằng hình ảnh động.

Rất đơn giản: Swift 5.1 mang đến cho chúng ta một difference(from:). Phương pháp tính toán sự khác biệt giữa hai collection được sắp xếp – những mục cần loại bỏ và những mục cần chèn. Điều này có thể được sử dụng với bất kỳ collection nào có thứ tự có chứa các Equatable.

Lưu ý: Vì Swift hiện vận hành bên trong các hệ điều hành của Apple, các tính năng mới như thế này phải được sử dụng với kiểm tra #available để đảm bảo mã đang được chạy trên HĐH có chức năng mới. Đối với các tính năng sẽ released trong một hệ điều hành không xác định, không báo trước tại một thời điểm nào đó trong tương lai, một phiên bản đặc biệt là 9999 được sử dụng để chúng ta không biết số thực tế là gì.

Đây là mã:

Thay vì áp dụng các thay đổi bằng tay, bạn có thể áp dụng toàn bộ collection bằng phương thức applying() , như sau:

Tạo các mảng chưa được khởi tạo

SE-0245 giới thiệu một trình khởi tạo mới cho các mảng không điền trước các giá trị với mặc định. Điều này trước đây có sẵn dưới dạng API private, có nghĩa là Xcode sẽ không liệt kê nó trong quá trình hoàn tất mã của họ nhưng bạn vẫn có thể sử dụng nó nếu muốn – và nếu bạn vui lòng chấp nhận rủi ro rằng nó sẽ không bị loại bỏ trong tương lai!

Ví dụ: chúng ta có thể tạo một mảng gồm 10 số nguyên ngẫu nhiên như thế này:

Có một số quy tắc ở đây:

  1. Bạn không cần phải sử dụng tất cả khả năng mà bạn yêu cầu, nhưng bạn không thể vượt quá khả năng. Vì vậy, nếu bạn yêu cầu dung lượng 10, bạn có thể đặt initializedCount từ 0 đến 10, nhưng không phải 11.
  2. Nếu bạn không khởi tạo các phần tử cuối cùng nằm trong mảng của mình – ví dụ: nếu bạn đặt initializedCount thành 5 nhưng thực tế không cung cấp giá trị cho các phần tử từ 0 đến 4 – thì chúng có khả năng chứa đầy dữ liệu ngẫu nhiên. Đây là một ý tưởng tồi.
  3. Nếu bạn không đặt initializedCount thì nó sẽ là 0, vì vậy mọi dữ liệu bạn đã gán sẽ bị mất.

Bây giờ, chúng ta có thể viết lại đoạn mã trên bằng map() , như thế này:

Điều đó chắc chắn dễ đọc hơn, nhưng nó kém hiệu quả hơn: nó tạo ra một phạm vi, tạo một mảng trống mới, kích thước nó đến đúng số lượng, lặp lại trong phạm vi.

Ngoài ra

Swift 5.1 vẫn đang được phát triển và mặc dù việc phân nhánh cuối cùng cho chính Swift đã trôi qua

Một lần nữa, tính năng lớn ở đây là sự ổn định của mô-đun và tôi biết nhóm đang làm việc chăm chỉ để có được điều đó . Họ không thông báo ngày release tiếp, mặc dù họ đã nói rằng 5.1 có sự phát triển ngắn hơn đáng kể do Swift 5.0 yêu cầu một số lượng tập trung và sự chú ý khác thường – Tôi đoán chúng ta sẽ thấy bản beta tại WWDC19 , nhưng rõ ràng đây không phải là thứ gì đó vội vã cho một ngày cụ thể.

Cảm ơn các bạn đã đọc và tham khảo bài này, nếu bạn thấy hay và hữu ích, xin vui lòng like fanpage cafedev để ủng hộ, Cảm ơn các bạn nhiều.

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!