Tag: #Swift #iOS #Apple

  • Tìm hiểu về Copy on Write trong Swift

    Tìm hiểu về Copy on Write trong Swift

    Copy on Write (CoW) là 1 khái niệm ko hề mới trong Swift. Nó đã được Apple giới thiệu trong WWDC 2015 và được áp dụng từ iOS 7.0. Tuy nhiên thực chất CoW là gì và chúng có tác dụng gì? Hãy cùng tìm hiểu trong bài viết này nhé.

    Table of contents

    • Copy on Write là gì
    • Kết luận
    • References

    Copy on Write là gì?

    • Trong Swift, chúng ta có các kiểu Reference type và Value type. Nếu bạn gán một value type cho một biến hoặc pass nó như một parameter của function (không phải parameter kiểu inout) thì dữ liệu của value type này sẽ được copy. Lúc này, ta sẽ có hai value type có nội dung giống nhau nhưng trỏ đến hai địa chỉ bộ nhớ riêng biệt. Hôm nay ta sẽ bàn về Copy on Write – một cơ chế quan trọng trong việc tối ưu bộ nhớ của Swift.

    • Trong Swift, khi bạn có một khối lượng lớn các value type và muốn gán hoặc truyền chúng qua các function, nếu bạn copy tất cả dữ liệu sang một vị trí khác trong bộ nhớ thì sẽ gây ra hiện tượng lãng phí hiệu năng. Để giảm thiểu tình trạng này, Swift đã triển khai cơ chế Copy on Write cho một số kiểu dữ liệu là value type như array, dictionary,…

    • Hiểu 1 cách đơn giản, nếu bạn có 1 array có 1000 phần tử và bạn muốn copy mảng đó vào 1 biến khác, Swift sẽ không sao chép ngay lập tức cả 1000 phần tử này mà sẽ sử dụng đến cơ chế Copy on Write: Khi bạn trỏ 2 biến vào cùng 1 mảng, chúng đều trỏ vào cùng 1 địa chỉ ô nhớ, và chỉ đến khi bạn sửa đổi 1 trong 2 biến đó, swift mới tạo ra 1 bản copy mới để sửa và chỉ sửa trên bản copy đó và vẫn giữ nguyên biến còn lại. Bằng cách trì hoãn việc sao chép dữ liệu cho đến khi thực sự cần thiết, Swift đã đảm bảo được việc tối ưu được performance của hệ thống

    • Copy on Write ko phải là cơ chế mặc định cho tất cả các kiểu value type, mà chỉ được áp dụng cho 1 số kiểu như Aray, Collections,… Ngoài ra, với những kiểu value type mà bạn tự custom thì cũng ko có sẵn cơ chế này mà phải tự implement thêm.

    • Ví dụ về cách hoạt động của Copy on Write

    import Foundation
    
    func print(address o: UnsafeRawPointer ) {
        print(String(format: "%p", Int(bitPattern: o)))
    }
    
    var array1: [Int] = [0, 1, 2, 3]
    var array2 = array1
    
    //Print with just assign
    print(address: array1) //0x600000078de0
    print(address: array2) //0x600000078de0
    //Let's mutate array2 to see what's
    array2.append(4)
    
    print(address: array2) //0x6000000aa100
    
    //Output
    //0x600000078de0 array1 address
    //0x600000078de0 array2 address before mutation
    //0x6000000aa100 array2 address after mutation
    

    Đây là 1 ví dụ đơn giản để chỉ cách hoạt động của Copy on Write. Trước hết, tạo biến array1 rồi sau đó gán aray2 bằng với array1. Khi chưa thực hiện thay đổi giá trị thì array2 vẫn trỏ vào cùng 1 địa chỉ ô nhớ với array1. Chỉ khi ta thay đổi giá trị của array2 thì nó mới được copy sang 1 địa chỉ ô nhớ khác, và giá trị mới sẽ trỏ vào địa chỉ ô nhớ này, còn array1 sẽ không có sự thay đổi gì.

    Implement cơ chế Copy on Write cho các dạng value type tự tạo

    • Bạn có thể tự mình implement cơ chế Copy on Write cho các kiểu dữ liệu mà bạn tự custom. Đây là ví dụ trên OptimizationTips.rst trong repo chính của Swift
    final class Ref<T> {
      var val : T
      init(_ v : T) {val = v}
    }
    
    struct Box<T> {
        var ref : Ref<T>
        init(_ x : T) { ref = Ref(x) }
    
        var value: T {
            get { return ref.val }
            set {
              if (!isUniquelyReferencedNonObjC(&ref)) {
                ref = Ref(newValue)
                return
              }
              ref.val = newValue
            }
        }
    }
    // This code was an example taken from the swift repo doc file OptimizationTips 
    // Link: https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst#advice-use-copy-on-write-semantics-for-large-values
    
    

    Đoạn code trên sử dụng loại reference type để triển khai cho kiểu giá trị dạng generics. Về cơ bản, đây là 1 warrper quản lý loại reference type và chỉ trả về 1 instance mới nếu giá trị được tham chiếu không là duy nhất. Nếu không, nó chỉ thay đổi giá trị của kiểu tham chiếu.

    Kết luận:

    Copy on Write là 1 cơ chế rất thông minh để tối ưu hoá việc copy giá trị của các kiểu value type. Đây là 1 cơ chế được sử dụng rất nhiều Swift, dù hầu như chúng ta ko nhìn thấy nó 1 cách rõ ràng vì chúng đã được thực hiện trên các thư viện chuẩn của Swift. Nhưng chúng ta nên biết để có thể tận dụng tối đa lợi ích mà Copy on Write mang lại.

    Refer:

  • Localization: Tối ưu hoá quá trình khi làm ứng hỗ trợ đa ngôn ngữ

    Localization: Tối ưu hoá quá trình khi làm ứng hỗ trợ đa ngôn ngữ

    Như mọi người đã biết, hầu hết các ứng dụng ngày nay đều hỗ trợ tính năng đa ngôn ngữ, nhằm mục đích tiếp cận được nhiều người dùng hơn, cho mọi người sử dụng ứng dụng dễ dàng hơn. Tuy nhiên hiện nay hầu hết các dự án đều làm một cách tự phát, chưa có một quy trình chuẩn. Điều này làm cho quá trình phát triển sinh ra nhiều bug UI sai Message, sai chính tả, nó làm tốn thời gian không đáng có của các bên.

    Trong quá trình làm việc ở các dự án, khi tài liệu được cập nhật đồng nghĩa với việc các Dev phải quay lại kiểm tra các thay đổi message, rồi bắt đầu loay hoay tìm kiếm trong source code để thực hiện thay đổi.

    Hay mỗi khi code một màn hình mới việc phải định nghĩa một đống key, định nghĩa enum/constant và copy/paste nó sang các file localization thật nhàm chán và tiềm tàng nhiều rủi ro về lỗi sai chính tả, copy nhầm. Trước đây, có một member trong dự án của mình chỉ vì copy sai text làm thừa một dấu “;” làm lỗi cả file đa ngôn ngữ, việc này làm cho toàn bộ text trên ứng dụng khi release cho bị sai. Do file đa ngôn ngữ là string nên những lỗi sai chính tả như này mất rất nhiều thời gian để điều tra lỗi.

    Sau nhiều năm làm việc với các dự án khác nhau, cùng các anh em, cộng sự hoàn thành các ứng dụng to nhỏ khác nhau. Mình đã đúc kết được một quy trình giúp cải thiện năng suất làm việc của mọi người, giúp giảm các bug UI về sai message, sai chính tả, giảm thời gian thực hiện tính năng đa ngôn ngữ, giúp quá trình bảo trì, thay đổi message trở nên dễ dàng hơn.

    Quy trình

    Brainstoming

    Trước tiên, để đạt được hiệu quả Leader của các bên gồm BA, Mobile, Web … ngồi lại với nhau để thống nhất về cách làm việc theo quy trình trên. Sau đó phổ biến lại cho member và đảm bảo việc member thực hiện đúng quy trình làm việc.

    Tạo file excel chung lưu các message cần làm đa ngôn ngữ

    File message template các bạn tải ở đây nhé:

    Trong file này mình có định nghĩa sẵn các trường cần nhập và có sẵn công thức để gen ra code của swift và android. Nếu dự án của các bạn sử dụng ngôn ngữ khác thì có thể chỉnh lại công thức cho phù hợp với dự án của mình.

    Nhập nội dung

    Thêm/sửa message

    Screen/Feature: Điền vào tiên màn hình hoặc tính năng.

    MessageKey: Điền vào tên item trên màn hình, nếu SRS có mô tả thì ưu tiên sử dụng key trong SRS, nếu không hãy đặt tên sao cho đúng ý nghĩa của item.

    Tiếng việt: Text được hiển thị khi ngôn ngữ là Tiếng Việt

    Tiếng anh: Text được hiển thị khi ngôn ngữ là Tiếng Anh

    Note:
    TH1. Tài liệu đã định nghĩa sẵn các message cho các ngôn ngữ: thì BA có thể cử 1 bạn ra điền hết vào file này trước khi bên dev thực hiện code. Trường hợp này thì việc làm đa ngôn ngữ khá nhàn, mình sẽ giải thích chi tiết ở các phần phía dưới.
    TH2: Tài liệu chưa định nghĩa message cho các ngôn ngữ(các dự án thường rơi vào trường hợp này): Khi này khi các Dev thực hiện code sẽ tự insert thêm message vào file trên teams, để file tự tạo ra code tương ứng với các ngôn ngữ.

    Công thức tạo code tự động

    Công thức tạo code từ excel

    Các Developers sẽ không phải làm các thao tác lặp đi lặp lại nhàm chán. Ngoài ra nó giúp giảm lỗi đánh máy, lỗi copy paste, lỗi sai chính tả và giúp việc làm đa ngôn ngữ nhanh hơn. Trên file excel mình có tạo ra công thức để tự tạo ra code của swift/android. Khi bạn điền thông tin vào sheet MessageList thì code sẽ được gen tự động, việc của mọi người là chỉ cần copy code vào project và sử dụng.

    Đưa ra các luật lệ

    Tạo ra các luật yêu cầu member phải tuân thủ như sau:

    1. Đặt tên màn hình theo quy định của dự án, cách đặt tên cần thống nhất giữa tài liệu SRS và các class trong code của Dev.
    2. Dev của các bên(mobile, web) và BA khi thực hiện thêm mới, hay chỉnh sửa sẽ thực hiện trực tiếp trên files Localize chung của dự án, khi chỉnh sửa cần tìm đúng tên màn hình, message để sửa, nếu thêm mới thì thêm xuống cuối cùng của của danh sách list message của màn hình đó.
    3. Để tránh việc conflict hoặc giẫm chân nhau khi các bên chạy song song, thì mỗi màn hình những member liên quan tới màn hình đó sẽ cử ra một bạn điển vào file message trước.

    Cách thực hiện code

    TH1: đối với dự án không cho sử dụng thư viện mã nguồn mở

    Khi thực hiện chúng ta chỉ cần vào file excel và copy code vào project của mình rồi sử dụng bình thường.

    TH2: Dự án cho phép sử dụng thư viện mã nguồn mở

    Chúng ta sẽ sử dụng thư viện để tự tạo ra enum nhanh gọn lẹ hơn, ví dụ bên iOS Swift thì chúng ta có thể sử dụng Swiftgen để thực hiện. Link hướng dẫn sử dụng Swiftgen mình cũng có viết môt bài rồi các bạn có thể tham khảo link dưới đây:

    Ưu điểm

    • Giảm thời gian làm việc, tăng năng suất làm việc của team
    • Giảm khả năng bị lỗi UI/Message hay các lỗi về copy/paste khi thực hiện
    • Giảm thời gian bảo trì ứng dụng khi thay đổi message, khi có thay đổi BA sẽ update file message list và báo lại cho Devs, lúc này Dev không cần phải tìm xem thay đổi ở đâu, mà chỉ cần copy code từ file message list là xong.
    • Giảm thời gian implement khi có yêu cầu hỗ trợ thêm ngôn ngữ khác. Vì khi này chúng ta chỉ cần thêm 1 cột nữa ở file excel rồi copy code sang project là xong.
    • Giúp tối ưu được effort khi các team không start cùng một thời điểm. Vì team start sau sẽ sử dụng lại được phần đã làm của team start trước.

    Nhược điểm

    • Cần quản lí, phân chia công việc tốt, member có khả năng làm việc nhóm.
    • Thường chỉ hiệu quả cao đối với các dự án mới

    Tổng kết

    Phía trên là nội dung chia sẻ về cách tối ưu khi làm ứng dụng hỗ trợ đa ngôn ngữ, mình hi vọng bài viết có thể giúp mọi người phần nào trong quá trình thực hiện các ứng dụng có hỗ trợ đa ngôn ngữ. Cảm ơn mọi người đã dành thời gian đọc bài viết này.

  • Hướng dẫn sử dụng SwiftGen cho iOS

    Hướng dẫn sử dụng SwiftGen cho iOS

    Tìm hiểu cách mà SwiftGen giúp dễ dàng loại bỏ các chuỗi ma thuật(magic strings) trong các dự án iOS của bạn.

    Là một nhà phát triển ứng dụng trên thiết bị di động, bạn có thể cảm thấy như bạn gặp phép thuật hàng ngày. Các dòng mã bạn viết sẽ được chuyển đổi thành các ứng dụng mà mọi người trên khắp thế giới có thể sử dụng. Các công cụ mà Apple cung cấp giúp biến điều kỳ diệu đó thành hiện thực và làm cho cuộc sống của bạn dễ dàng hơn. Khi tiến xa hơn vào lĩnh vực phát triển phần mềm, bạn có thể nhận ra có một thứ ma thuật mà bạn không thích: Chuỗi thần kỳ(magic strings).

    Loại an toàn, khái niệm rằng các biến chỉ có thể thuộc một loại cụ thể, cung cấp cho các nhà phát triển các rào chắn(guardrails) để giữ cho các chương trình của họ an toàn. Tuy nhiên, chuỗi ma thuật đưa mã không an toàn vào các ứng dụng đó. Chuỗi ma thuật là gì? Trong quá trình phát triển iOS, bạn đã gặp phải những điều này nhiều lần. Một ví dụ trông giống như sau:

    let color = UIColor(named: "green-apple")
    self.title = "Welcome!"

    Ví dụ này hiển thị “green-apple” và “Welcome!” được viết dưới dạng chuỗi trực tiếp trong mã của bạn. Không quá khi nói rằng tất cả các nhà phát triển đôi khi nhận thấy mình có lỗi với hành vi này.

    Trên thực tế, trong quá trình phát triển iOS, bạn không có nhiều lựa chọn. Ngoài ra, Xcode không cung cấp cách nào để tránh việc này.

    Những người đã làm việc trong Android có thể thấy mình run sợ trước những đoạn mã như thế này. Môi trường phát triển Android có cơ chế chuyển đổi tài nguyên ứng dụng, chẳng hạn như chuỗi, màu sắc, hình ảnh và phông chữ, thành các kiểu biến an toàn. Có rất nhiều lợi ích từ việc này, và đó là:

    • Giảm lỗi sai chính tả
    • Ngăn chặn sự trùng lặp tài nguyên không cần thiết
    • Cung cấp kiểm tra tài nguyên tại thời điểm biên dịch
    • Giúp dọn dẹp tài nguyên cũ
    • Và nhiều hơn thế nữa

    Như đã nêu, các nhà phát triển iOS và macOS không có quyền truy cập vào hệ thống cung cấp sự an toàn cho loại tài nguyên này.

    May mắn thay, có SwiftGen, một trình tạo mã để loại bỏ các chuỗi ma thuật trong ứng dụng của bạn. Có sẵn dưới dạng thư viện mã nguồn mở trên GitHub, bạn có thể thêm tính năng này vào các dự án iOS và macOS của mình để mang lại sự an toàn về loại và kiểm tra thời gian biên dịch của tất cả các Assets của bạn.

    Trong hướng dẫn này bạn sẽ học được cách:

    • Thiết lập dự án của bạn với SwiftGen
    • Xác định các nội dung mà bạn muốn chuyển đổi
    • Xác định nơi mà code được generated
    • Tạo các templates cho phép SwiftGen generate khi dùng SwiftUI cho Fonts và màu sắc.

    Getting Started

    Để bắt đầu, hãy nhấp vào nút Tải xuống tài liệu dưới đây:

    Có một số cách bạn có thể cài đặt SwiftGen để hoạt động với môi trường của mình như sau:

    • CocoaPods
    • Homebrew
    • Mint
    • Directly download a zipped release(Nên dùng cách này)

    Link hướng dẫn cài ở đây: https://github.com/SwiftGen/SwiftGen

    Trong hướng dẫn này, bạn sẽ sử dụng CocoaPods để quản lý SwiftGen.

    Lưu ý: Nếu bạn không có CocoaPods, đừng lo lắng – dự án khởi động và dự án cuối cùng đã được tải xuống phần phụ thuộc. :]

    Mở workspace, có tên là DrinksUp! .Xcworkspace. Vì dự án này sử dụng CocoaPods nên bạn sẽ không thể làm việc trực tiếp với DrinksUp! .Xcodeproj.

    Hãy dành một chút thời gian để xem xét xung quanh trong Xcode. Dự án đã ở trạng thái hoàn thành nhưng sử dụng chuỗi để tham chiếu phông chữ, màu sắc, hình ảnh và chuỗi. Bạn sẽ chuyển đổi tất cả những thứ này vào cuối hướng dẫn.

    Xây dựng và chạy và làm quen với ứng dụng.

    Ứng dụng, DrinksUp !, là một cách để theo dõi các loại đồ uống thú vị mà bạn và gia đình đã thử khi đến nhà hàng hoặc ở nhà.

    Thiết lập SwiftGen

    Bắt đầu bằng cách mở Terminal và điều hướng đến thư mục gốc của dự án khởi động của bạn. Tiếp theo, nhập lệnh sau vào Terminal:

    ./Pods/SwiftGen/bin/swiftgen config init

    Thao tác này sẽ tạo một tệp cấu hình, có tên là swiftgen.yml, tại thư mục gốc dự án của bạn. Nếu tệp này tự động mở trong Xcode, hãy tiếp tục và tắt nó đi.

    Tiếp theo, trong workspace dự án của bạn, đi tới Files ▸ Add new file vào “DrinksUp!”…. Tìm swiftgen.yml. Đảm bảo bỏ chọn Sao chép các mục nếu cần và chọn Tạo tham chiếu thư mục.

    Add SwiftGen.yml without copying file

    Nhấp vào nút Thêm. Khi hoàn tất, bạn sẽ thấy swiftgen.yml ở đầu trình điều hướng Dự án, như bên dưới:

    SwiftGen yml file in Project navigator

    Lưu ý: Bạn có thể di chuyển tệp này đến vị trí giống như hình minh họa, nếu Xcode không thêm nó theo cách tương tự.

    Tệp này là nơi bạn sẽ đặt các hướng dẫn cho SwiftGen biết tệp nào bạn muốn chuyển đổi thành mã được tạo. Loại tệp, YML , cho biết nó đang sử dụng YAML cho cú pháp của nó. Nếu bạn chưa sử dụng YAML trước đây, đây chỉ đơn giản là một cách dễ đọc hơn để xem dữ liệu được tuần tự hóa. Bạn có thể nghĩ về nó như là JSON , được đơn giản hóa.

    Bây giờ, thay thế toàn bộ nội dung của swiftgen.yml bằng nội dung sau:

    # 1 
    input_dir:  DrinksUp! / 
    # 2 
    output_dir:  DrinksUp! / Generated /
    

    Đây là những gì bạn đã thêm:

    1. Bạn đã khai báo một biến input_dir thư mục đầu vào. Điều này cho SwiftGen biết thư mục gốc để điều hướng đến tất cả các đường dẫn tệp mà bạn sẽ sớm thêm vào.
    2. Một biến khác xác định thư mục đầu ra của các tệp Swift được tạo. Bằng cách này, bạn sẽ dễ dàng theo dõi tất cả các tệp SwiftGen hơn.

    Thêm Build Phase

    Để chạy SwiftGen, bạn sẽ cần thêm một giai đoạn xây dựng mới vào dự án của mình. Để thực hiện việc này, hãy chọn dự án của bạn trong trình điều hướng Dự án, chọn Build Phases . Chọn + và chọn New Run Script Phase.

    Add new run script to project

    Đổi tên script thành SwiftGen bằng cách nhấp đúp vào tên hiện tại, Run Script . Tiếp theo, thêm phần sau vào trường văn bản của tập lệnh:

    if [[ -f "${PODS_ROOT}/SwiftGen/bin/swiftgen" ]]; then
      "${PODS_ROOT}/SwiftGen/bin/swiftgen"
    else
      echo "warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it."
    fi 
    

    Cuối cùng, sắp xếp lại thứ tự tập lệnh để ngồi ngay sau tên tập lệnh [CP] Check Pods Manifest.lock . Các Build Phases của bạn bây giờ sẽ giống như sau:

    SwiftGen script added and ordered

    Build và Run. Nếu mọi thứ được thiết lập đúng cách, bạn sẽ không có bất kỳ lỗi nào. Bạn sẽ không có bất kỳ thứ gì trong thư mục Đã tạo của mình. Được rồi chúng ta đến bước tiếp theo.

    Chuyển đổi XCAssets

    Bây giờ, bạn đã sẵn sàng để bắt đầu xóa các chuỗi khỏi dự án của mình! Bước đầu tiên sẽ là SwiftGen tạo mã cho các tệp XCAsset trong dự án. Mở swiftgen.yml và thêm phần sau vào cuối tệp:

    ## XCAssets
    # 1
    xcassets:
      # 2
      inputs:
        - Assets.xcassets
        - Colors.xcassets
      # 3
      outputs:
        # 4
        templateName: swift5
        # 5
        output: XCAssets+Generated.swift
    

    Đây là ý nghĩa của mỗi dòng trong số những dòng này:

    1. Mỗi loại tệp hoặc mẫu, bạn muốn chuyển đổi bằng SwiftGen yêu cầu một mục nhập ở cấp gốc là swiftgen.yml . Ở đây, điều này cho biết bạn muốn SwiftGen chuyển đổi các tệp là XCAsset .
    2. Danh sách này cho biết những tệp SwiftGen nên giới hạn chuyển đổi của nó.
    3. Bạn cần cho SwiftGen biết cách tạo đầu ra.
    4. Bạn phải cung cấp tên mẫu. Ở đây, swift5 là một mẫu mặc định được cung cấp bởi nhóm SwiftGen. Bạn sẽ học cách sử dụng các mẫu của riêng mình sau này.
    5. Cuối cùng, bạn cung cấp tên tệp mà bạn muốn mã Swift mới của mình tạo ra. Hãy nhớ rằng, bạn đã xác định output_dir ở đầu tệp, có nghĩa là nó sẽ xuất thành Generated / XCAssets + Generated.swift .

    Build và Run. Nếu bạn không gặp bất kỳ lỗi nào, thì quá trình tạo mã của bạn đã hoạt động!

    Thêm tệp

    Mở rộng folder Generated trong trình Project navigator. Hiện tại, bạn vẫn sẽ không tìm thấy tệp mới của mình. Để thêm nó, nhấp chuột phải vào Generated và chọn Add Files to “DrinksUp!”… .

    Add generated file to project

    Chọn XCAssets + Generated.swift . Đảm bảo Copy items if needed không được chọn, sau đó nhấp vào Add . Bây giờ, hãy mở XCAssets + Generate.swift và quan sát. Bạn sẽ thấy enum Asset. Trong enum, bạn sẽ tìm thấy các bảng liệt kê khác được xác định phù hợp với các danh mục XCAsset mà bạn đã xác định. Ví dụ:

    • Assets: Mỗi hình ảnh trong dự án bây giờ có một thuộc tính tĩnh được xác định.
    • Colors: Tất cả các màu của ứng dụng cũng có các thuộc tính tĩnh để tham khảo.

    Mở Assets.xcassets . Lưu ý rằng có một nhóm hình ảnh có tên là nội dung khởi chạy . Nhưng Assets đã khai báo tất cả các thuộc tính ảnh tĩnh ở cùng một mức. SwiftGen có thể duy trì tổ chức này cho bạn nhưng không làm như vậy theo mặc định. Mở swiftgen.yml và thay thế toàn bộ mục nhập xcassets bằng mục sau:

    ## XCAssets
    xcassets:
      inputs:
        - Assets.xcassets
        - Colors.xcassets
      outputs:
        templateName: swift5
        # 1
        params:
           # 2
           forceProvidesNamespaces: true
           # 3
           forceFileNameEnum: true
        output: XCAssets+Generated.swift

    Tại đây, bạn tận dụng khả năng của SwiftGen để tùy chỉnh đầu ra mã của bạn bằng cách thực hiện như sau:

    1. Xác định params trên outputs của bạn.
    2. forceProvidesNamespaces: Điều này sẽ duy trì không gian tên của bạn được tìm thấy trong danh mục nội dung.
    3. Tham số bổ sung này đảm bảo cho dù bạn đã cung cấp bao nhiêu tên tệp inputs, SwiftGen sẽ duy trì các bảng liệt kê riêng biệt để đại diện cho từng danh mục nội dung.

    Xây dựng dự án, sau đó quay lại XCAssets + Generated.swift . Bây giờ bạn sẽ thấy Assets có một enum tên mới LaunchAssets để đại diện cho cấu trúc thư mục của bạn.

    Bây giờ, đã đến lúc sử dụng mã mới được tạo này để xóa bất kỳ tham chiếu chuỗi nào đến hình ảnh. Mở DrinksListView.swift . Bạn sẽ thấy Image("milkshake")bên trong các mục trên thanh công cụ được thêm vào chế độ xem. Thay thế dòng bằng dòng sau:

    Image(Asset.Assets.milkshake.name)

    Ở đây, bạn đã tham chiếu tên hình ảnh cho milkshake. Hiện tại, SwiftGen không hỗ trợ làm việc trực tiếp với SwiftUI. Bạn sẽ học cách tự thêm cái này sau. Hiện tại, bạn vẫn có thể sử dụng những gì có sẵn để tải nội dung hình ảnh mà không cần tham chiếu trực tiếp đến chuỗi.

    Sử dụng các mẫu cơ bản bổ sung

    Có một số mẫu bổ sung mà bạn có thể tận dụng mà không cần tùy chỉnh.

    Làm việc với trình tạo giao diện

    Ứng dụng bạn đang làm việc đang sử dụng SwiftUI. Tuy nhiên, để giới thiệu khả năng của SwiftGen để làm việc với Trình tạo giao diện, dự án mẫu bao gồm một bảng phân cảnh và một số bộ điều khiển chế độ xem. Bắt đầu bằng cách tạo mã để hỗ trợ Trình tạo giao diện hoặc Bảng phân cảnh bằng cách thêm phần sau vào swiftgen.yml :

    ## Interface Builder
    ib:
      inputs:
        # 1
        - .
      outputs:
        # 2
        - templateName: scenes-swift5
          output: IB-Scenes+Generated.swift
        # 3
        - templateName: segues-swift5
          output: IB-Segues+Generated.swift
    

    Tại đây, bạn đã làm như sau:

    1. Cho SwiftGen biết rằng bạn muốn nó tìm kiếm bất kỳ tệp nào được hỗ trợ trình tạo giao diện trong thư mục gốc của dự án của bạn.
    2. Một điều tuyệt vời về SwiftGen là nó tách biệt khái niệm Cảnh khỏi Segues . Điều này cho biết tệp mà cảnh của bạn sẽ xuất ra.
    3. Cuối cùng, điều này cho biết tất cả thông tin giả mạo sẽ được xuất ra ở đâu.

    Xây dựng dự án của bạn. Thêm IB-Scenes + Generate.swift và IB-Segues + Generated.swift vào nhóm Generated , giống như bạn đã làm đối với XCAssets + Generated.swift .

    Giờ đây, bạn có thể thay thế thông tin cảnh hoặc thông tin xác thực trong ứng dụng.

    Bắt đầu bằng cách mở InformationViewController.swift . Trong InformationViewController, thay thế việc triển khai showAbout()bằng những điều sau:

    performSegue(
      withIdentifier: StoryboardSegue.Main.showAbout.rawValue, 
      sender: self)

    Tất cả các segues sẽ tạo ra dưới dạng các enum trường hợp thực tế, điều này giúp mọi thứ dễ dàng hơn khi bạn kiểm tra xem segue nào được kích hoạt. Vì đơn giản, mã bạn đã thêm chỉ đang kích hoạt segue.

    Tiếp theo, bên trong InformationView, thay thế makeUIViewController(context:)bằng những thứ sau:

    func makeUIViewController(context: Context) -> some UIViewController {
      StoryboardScene.Main.initialScene.instantiate()
    }
    

    Ở đây, bạn đã đơn giản hóa mã cần thiết để khởi tạo bộ điều khiển chế độ xem ban đầu của bảng phân cảnh. SwiftGen cung cấp một phương thức trợ giúp để nhanh chóng truy cập cảnh ban đầu của bạn.

    Xây dựng và chạy. Nhấn vào Tìm hiểu thêm ở trên cùng bên phải. Bạn sẽ thấy một phương thức xuất hiện, hiển thị cho bạn một số thông tin về ứng dụng. Điều này được gọi vào makeUIViewController(context:)để biết chế độ xem nào cần tải.

    Hiển thị màn hình Tìm hiểu thêm

    Bây giờ, hãy nhấn vào nút About . Bạn sẽ kích hoạt segue mà bạn đã sửa đổi.

    Đồ uốngUp!  Giới thiệu về màn hình

    Làm việc với JSON

    Cuối cùng chúng ta sẽ thêm phần hỗ trợ cho JSON. Thêm phần sau vào cuối swiftgen.yml :

    ## JSON
    json:
      inputs:
        - Resources/
      outputs:
        templateName: runtime-swift5
        output: JSON+Generated.swift
    

    Điều này hiện cung cấp một cách để tham chiếu đến bất kỳ tệp JSON nào bạn có trong tài nguyên của dự án. Bạn sẽ sử dụng điều này để chuyển đổi dữ liệu giả đi kèm với ứng dụng.

    Xây dựng dự án, sau đó thêm JSON + Generated.swift vào nhóm Đã tạo của bạn .

    Bây giờ, hãy mở Drink.swift . Loại bỏ hoàn toàn các struct tên được đặt tên MockData. Sau đó, thay thế phần mở rộng cho DrinkStore, được tìm thấy ở dưới cùng, bằng phần mở rộng sau:

    extension DrinkStore {
      static var mockData: [Drink] {
        do {
          let data = try JSONSerialization.data(
            withJSONObject: JSONFiles.starterDrinks,
            options: [])
          let mockDrinks = try JSONDecoder().decode([Drink].self, from: data)
          return mockDrinks
        } catch {
          print(error.localizedDescription)
          return []
        }
      }
    }
    

    Tại đây, bạn sẽ thấy các tham chiếu mã của mình JSONFiles.starterDrinks. Mở MockData.json và nhận thấy khóa đầu tiên trong tệp, có tên là starterDrinks . SwiftGen đã lấy đối tượng cấp cao nhất này và cung cấp nó dưới dạng thuộc tính tĩnh trên JSONFiles để bạn tham khảo khi cần.

    Xây dựng và chạy. Bạn sẽ không nhận thấy bất cứ điều gì khác so với trước đây – chỉ là đồ uống hiển thị trong danh sách.

    Thức uống

    Làm việc với chuỗi

    Có lẽ một trong những tiện ích lớn nhất mà SwiftGen cung cấp là khả năng sử dụng các biến để tham chiếu các chuỗi được bản địa hóa(Localized Strings). Đó là một thực tiễn tuyệt vời để đặt bất kỳ văn bản nào bạn sẽ trình bày cho người dùng ứng dụng của mình bên trong các tệp strings hoặc tệp stringsdict được bản địa hóa . Nhưng nếu bạn đã làm điều này, bạn biết rằng một khi bạn vượt quá một số chuỗi, sẽ trở nên khó khăn để nhớ những chuỗi nào có sẵn. Cũng có cảm giác thừa rằng bạn có một chuỗi trong tệp chuỗi và… một chuỗi trong mã của bạn.

    Dự án này chứa các tệp chuỗi sau:

    • Localizable.strings : Tệp chuỗi run of the-mill của bạn, được tạo bằng các khóa và giá trị.
    • Localizable.stringsdict : Bạn nên sử dụng các tệp stringdict bất cứ khi nào bạn cần lo lắng về các chuỗi đa nguyên. Loại tệp này không chỉ hỗ trợ dịch các chuỗi mà còn hỗ trợ cách đa hóa các từ cho bất kỳ biến thể nào mà một ngôn ngữ yêu cầu.

    Để chuyển đổi tất cả các tệp chuỗi của bạn, hãy thêm phần sau vào swiftgen.yml :

    ## Strings
    strings:
      inputs:
        # 1
        - en.lproj
      outputs:
        - templateName: structured-swift5
          # 2
          params:
            publicAccess: true
          output: Strings+Generated.swift
    

    Bạn nên biết một số điều quan trọng về những gì bạn đã thêm ở đây:

    1. Khi bạn chuyển đổi các tệp chuỗi của mình, bạn chỉ nên sử dụng một trong các thư mục được bản địa hóa. Đối với mỗi ngôn ngữ được thêm vào, một thư mục bản địa hóa mới sẽ được tạo. Trong dự án này, bản địa hóa duy nhất là tiếng Anh. Nếu bạn thêm nhiều ngôn ngữ hơn, không cần phải sửa đổi mục nhập này để nhận các bản dịch bổ sung đó. Bởi vì tệp chuỗi phải có một tập hợp các khóa và giá trị phù hợp, bạn sẽ tham chiếu các bản dịch giống như bạn sẽ làm nếu bạn không sử dụng SwiftGen.
    2. Bạn đã thêm một tham số mới, được đặt tên publicAccess. Nếu bạn nhìn xung quanh bất kỳ tệp nào đã tạo mà bạn đã thêm, bạn sẽ thấy tất cả các loại đều có công cụ sửa đổi quyền truy cập internal. Bằng cách sử dụng tham số này, bạn có thể thay đổi công cụ sửa đổi quyền truy cập của các bảng kê đã tạo thành công khai.

    Build dự án, sau đó thêm Strings + Generated.swift vào nhóm Generated . Trước khi bạn chuyển đổi tất cả các chuỗi trong ứng dụng, điều quan trọng là phải hiểu tệp này hơi khác một chút như thế nào.

    Hiểu tệp

    Mở Strings + Generated.swift . Bạn sẽ thấy kiểu cha được tạo, được đặt tên L10n. Trong phần này enum, bạn sẽ thấy một số kiểu con được tạo bên trong nó DrinkDetailNavigationvà DrinkList. Chúng tương ứng với các chuỗi được khai báo trong Localizable.strings .

    Mở Localizable.strings và xem cách nó khai báo mục nhập đầu tiên:

    "DrinkList.Navigation.Title" = "Drinks";

    Lưu ý cách khai báo khóa bằng ký hiệu không gian tên bằng dấu chấm:

    • DrinkList : Điều này cho biết chuỗi này thuộc về màn hình Danh sách đồ uống.
    • Navigation : Cho biết chuỗi này sẽ được sử dụng trong thanh điều hướng.
    • Title : Cuối cùng, điều này cho biết đó là tiêu đề trong thanh điều hướng.

    Bây giờ, bạn có thể chọn tổ chức và đặt tên cho các chuỗi của mình theo cách khác. Không có gì sai với điều đó – kiểu đặt tên này được sử dụng để hiển thị cách SwiftGen sẽ chuyển đổi và tổ chức mã của bạn. Đối với mỗi khoảng thời gian bạn đặt trong chuỗi của mình, SwiftGen sẽ tạo thêm một kiểu con.

    Quay lại Strings + Generated.swift , bạn sẽ thấy hàm tĩnh drinksCount. SwiftGen giúp bạn dễ dàng làm việc với các chuỗi đa nguyên. Thay vì phải tạo tham chiếu đến các chuỗi được bản địa hóa và sử dụng trình định dạng chuỗi, các hàm được tạo này giúp bạn dễ dàng sử dụng một hàm lấy các giá trị của chuỗi đa nguyên của bạn.

    Bây giờ, chuyển đổi tất cả các chuỗi bản địa hóa được sử dụng trong ứng dụng để trỏ đến các loại được tạo. Bắt đầu bằng cách mở DrinksListView.swift . Tiếp theo, tìm dòng mã:

    Text("DrinkList.Navigation.Title")

    Đổi nó thành

    Text(L10n.DrinkList.Navigation.title)

    Chờ một chút… L10n là gì? Đây là một phím tắt cho “bản địa hóa”. Bạn cũng có thể thấy “quốc tế hóa” được viết tắt là i18n . Nếu bạn đếm các chữ cái giữa chữ cái đầu tiên và chữ “n” cuối cùng trong một trong hai từ, bạn sẽ tìm thấy 10 hoặc 18 chữ cái tương ứng. Mặc dù điều này có ý nghĩa, sẽ không tốt nếu sử dụng một tên khác cho loại chuỗi cấp cao nhất của bạn phải không?

    Mở swiftgen.yml và thêm một thuộc tính vào mục nhập chuỗi của bạn, ngay sau publicAccessđó, nó trông giống như sau:

    strings:
      inputs:
        - en.lproj
      outputs:
        - templateName: structured-swift5
          params:
            publicAccess: true
            enumName: Strings
          output: Strings+Generated.swift

    Ở đây, bạn đã thêm tham số enumName. Điều này cho phép bạn thay đổi loại từ “L10n” thành “Strings”.

    Xây dựng và chạy. Lần này, bạn sẽ có một lỗi biên dịch. Điều này là do loại L10n không còn nữa. Truy cập DrinksListView.swift và tìm:

    Text(L10n.DrinkList.Navigation.title)

    Thay thế nó bằng

    Text(Strings.DrinkList.Navigation.title)

    Bây giờ, ứng dụng của bạn đang sử dụng tên loại mới mà bạn đã cung cấp ở bước trước.

    Build ứng dụng của bạn. Bạn sẽ không còn gặp bất kỳ lỗi biên dịch nào nữa.

    Lưu ý : Nếu bạn vẫn gặp lỗi, bạn có thể cần phải làm sạch dự án của mình. Chọn Product ▸ Clean and build folders , sau đó tạo lại.

    Tiếp theo tìm tài sản drinkCountString. Đây là mã sử dụng tệp stringdict để xử lý cách hiển thị số lượng đồ uống trong danh sách. Thay thế nó bằng những thứ sau:

    private  var drinkCountString: String {
       Strings .drinksCount (drinkStore.drinks.count)
    }

    Nếu bạn so sánh nó với mã ở đó trước đây, bạn có thể thấy đây là cách nhanh hơn nhiều để tham chiếu các chuỗi đa nguyên.

    Bạn nên chuyển đổi tất cả các chuỗi trong dự án khỏi sử dụng chuỗi. Mở Localizable.strings và xem tất cả các khóa chuỗi. Bạn nên tìm từng cách sử dụng các khóa này trong một tệp Swift và hoán đổi nó cho các biến được tạo bởi SwiftGen.

    Khi bạn hoán đổi văn bản xếp hạng trong DrinkDetailView.swift , nó sẽ sử dụng một hàm để cung cấp chuỗi, giống như cách bạn xử lý số lượng đồ uống.

    Tạo mẫu tùy chỉnh

    Cho đến thời điểm này, tệp swiftgen.yml của bạn đã sử dụng các mẫu mặc định do SwiftGen cung cấp. Tất cả những thứ này được lưu trữ trong SwiftGen pod. Nếu các mẫu này không cung cấp đầy đủ chức năng bạn muốn, bạn có thể tạo các mẫu của riêng mình để tạo mã theo cách bạn muốn. Các mẫu được xây dựng bằng Stencil , một dự án mã nguồn mở cung cấp ngôn ngữ tạo mẫu cho Swift. Trong phần này, bạn sẽ học cách sửa đổi các mẫu hiện có và sử dụng chúng để tạo mã của bạn.

    Nếu bạn nhìn trong trình điều hướng Dự án, bạn sẽ thấy có một thư mục có tên là Mẫu . Trong đó, có hai thư mục con: Fonts và xcassets . Với những điều này, bạn sẽ được SwiftGen cung cấp hỗ trợ để sử dụng màu sắc và phông chữ trực tiếp trong SwiftUI.

    Hỗ trợ màu SwiftUI

    Để thêm Colorhỗ trợ SwiftUI, hãy mở asset_swift5_swiftui.stencil . Lúc đầu, mọi thứ có thể hơi choáng ngợp vì tệp không có bất kỳ hỗ trợ cú pháp mã nào.

    Trên dòng 13, thêm dòng mã sau:

    import SwiftUI

    Tiếp theo, quay lại swiftgen.yml . Trong mục nhập đầu tiên của bạn, đối với xcassets , hãy tìm dòng nơi bạn xác định tên mẫu:

    templateName:  swift5

    Bây giờ, hãy thay thế nó như sau:

    templatePath:  Templates / xcassets / asset_swift5_swiftui.stencil

    Tại đây, bạn đã thay đổi từ sử dụng templateNamesang templatePath. Điều này yêu cầu SwiftGen sử dụng mẫu tùy chỉnh của bạn thay vì mẫu tích hợp sẵn.

    Xây dựng dự án, sau đó truy cập XCAssets + Generated.swift . Ở trên cùng, bây giờ bạn sẽ thấy:

    import SwiftUI

    Vì bạn đã thêm nhập vào tệp Stencil nên khi mã được tạo, mã sẽ chọn thay đổi này và thêm nhập mới. Khá tuyệt, phải không?

    Mở asset_swift5_swiftui.stencil và thay thế:

    // Add Support For SwiftUI Here

    Như dưới đây:

    {{accessModifier}} private(set) lazy var color: Color = {
      Color(systemColor)
    }()

    Trong đoạn mã này, bạn có thể xem cách sử dụng Stencil nhiều hơn một chút. Đây là những gì bạn đã thêm:

    • {{accessModifier}}: Đây là cách bạn nói với Stencil cách thay thế bằng thứ gì đó được cung cấp trong quá trình tạo mã. Nếu bạn nhìn vào dòng 11 của tệp này, bạn sẽ thấy accessModifier được định nghĩa là một biến. Theo mặc định, công cụ sửa đổi quyền truy cập là internal. Nếu bạn nhớ từ trước, bạn đã thấy cách bạn có thể thay đổi điều này thànhpublic
    • Phần còn lại của điều này thực sự chỉ là mã tiêu chuẩn. Nó tạo ra màu SwiftUI từ màu UIKit.

    Lưu ý : Có một sửa đổi khác được thực hiện đối với mẫu như một phần của vật liệu khởi động. Nó thay đổi loại Colorthành SystemColor.

    Build lại dự án, sau đó quay lại XCAssets + Generated.swift . Trên dòng 62, bạn sẽ thấy mã từ mẫu của mình, hiện được tạo dưới dạng mã thực.

    Bây giờ, bạn cần hoán đổi bất kỳ tham chiếu được mã hóa cứng nào thành một màu để sử dụng chức năng mới của mình. Mở DrinksListView.swift và tìm vị trí đặt màu nền trước trên văn bản tiêu đề điều hướng. Thay thế nó như sau:

    Text(Strings.DrinkList.Navigation.title)
      .font(Font.custom("NotoSans-Bold", size: 17, relativeTo: .body))
      .foregroundColor(Asset.Colors.textColor.color)

    Tại đây, bạn đã chuyển từ sử dụng chuỗi mã hóa cứng, với sự hỗ trợ SwiftUI thực sự để tham chiếu màu trực tiếp.

    Bạn cũng có thể sử dụng màu trực tiếp trong UIKit. Mở AppMain.swift . Thay đổi dòng mã sau:

    appearance.backgroundColor = UIColor(named: "header")

    Thành đoạn code dưới đây:

    appearance.backgroundColor = Asset.Colors.header.systemColor

    Đây systemColor là tham chiếu đến loại màu cụ thể của nền tảng. Sẽ như UIColor vậy nếu bạn đang sử dụng iOS và NSColor nếu bạn đang sử dụng macOS.

    Hai tệp có màu được khai báo bằng cách sử dụng chuỗi:

    • AppMain.swift
    • DrinkListView.swift

    Hoàn tất chuyển đổi các màu còn lại từ việc sử dụng các chuỗi trong mỗi tệp này theo ví dụ trên.

    Hỗ trợ Phông chữ SwiftUI

    Cũng như SwiftUI Color hiện không được hỗ trợ trong SwiftGen, Font cũng không được hỗ trợ. Bắt đầu bằng cách mở font_swift5_swiftui.stencil và thêm nhập sau vào đầu tệp:

    import SwiftUI

    Tiếp theo, thay thế khối mã này được tìm thấy gần cuối tệp:

    // Add Support For SwiftUI here
    fileprivate extension Font {
    }

    Bằng đoạn code dưới đây:

    fileprivate extension Font {
      // 1
      static func mappedFont(_ name: String, textStyle: TextStyle) -> Font {
        let fontStyle = mapToUIFontTextStyle(textStyle)
        let fontSize = UIFont.preferredFont(forTextStyle: fontStyle).pointSize
        return Font.custom(name, size: fontSize, relativeTo: textStyle)
      }
    
      // 2
      static func mapToUIFontTextStyle(
        _ textStyle: SwiftUI.Font.TextStyle
      ) -> UIFont.TextStyle {
        switch textStyle {
        case .largeTitle:
          return .largeTitle
        case .title:
          return .title1
        case .title2:
          return .title2
        case .title3:
          return .title3
        case .headline:
          return .headline
        case .subheadline:
          return .subheadline
        case .callout:
          return .callout
        case .body:
          return .body
        case .caption:
          return .caption1
        case .caption2:
          return .caption2
        case .footnote:
          return .footnote
        @unknown default:
          fatalError("Missing a TextStyle mapping")
        }
      }
    }

    Đây là những gì bạn đã thêm:

    1. mappedFont(_:textStyle:)tạo một phông chữ tùy chỉnh từ tên và TextStyle. Kiểu được sử dụng để lấy kích thước phông chữ tiêu chuẩn, mặc định cho mỗi phông chữ.
    2. mapToUIFontTextStyle(_:)chỉ đơn giản là cung cấp ánh xạ 1: 1 của SwiftUI TextStyletới UIKitTextStyle

    Tiếp theo, tìm comment này ở giữa tệp:

    // Add Support For SwiftUI Here

    Thay thế nó bằng đoạn code dưới đây:

    {{accessModifier}} func textStyle(_ textStyle: Font.TextStyle) -> Font {
      Font.mappedFont(name, textStyle: textStyle)
    }

    Khối mã này tương tự như những gì bạn đã thêm để cung cấp Color hỗ trợ. Sự khác biệt duy nhất ở đây là nó dành riêng cho việc cung cấp phông chữ trực tiếp trong SwiftUI.

    Bây giờ, mở swiftgen.yml và thêm mục sau:

    ## Fonts
    fonts:
      inputs:
        - Resources/Noto_Sans
      outputs:
        templatePath: Templates/fonts/fonts_swift5_swiftui.stencil
        output: Fonts+Generated.swift
    

    Ứng dụng này sử dụng hai phông chữ, cả hai đều nằm trong nhóm Tài nguyên :

    1. NotoSans
    2. NotoSans-Bold

    Mục nhập mới này chỉ cần biết thư mục mẹ của phông chữ bạn muốn hỗ trợ ở đâu. Mọi thứ khác tương tự như tất cả các mục nhập khác mà bạn đã thêm trước đó.

    Xây dựng ứng dụng của bạn và thêm Fonts+Generated.swift vào Generated . Sau khi thực hiện, hãy mở Fonts+Generated.swift . Tại đây, bạn có thể thấy cách tổ chức họ phông chữ. Bạn sẽ thấy rằng NotoSans có sẵn các biến thể sau:

    • Thường
    • In đậm
    • In đậm nghiêng
    • In nghiêng

    Giống như tất cả các mã được tạo khác trong ứng dụng, nó khá dễ sử dụng. Mở AppMain.swift và thay thế dòng đầu tiên application(_:didFinishLaunchingWithOptions:)bằng dòng sau:

    let buttonFont = FontFamily.NotoSans.bold.font(size: 16)

    Tại đây, bạn đặt trực tiếp kích thước phông chữ thành phông chữ NotoSans Bold(in đậm) .

    Tiếp theo, truy cập DrinksListView.swift , tìm tham chiếu đầu tiên đến một phông chữ, trong NavigationLink và thay thế nó bằng như sau:

    Text(drinkStore.drinks[index].name)
      .font(FontFamily.NotoSans.bold.textStyle(.body))

    Tại đây, bạn tận dụng mã của mẫu tùy chỉnh của mình để có thể tạo phông chữ SwiftUI tùy chỉnh, kích thước phù hợp với mặc định TextStyle: trong trường hợp này body.

    Cuối cùng, hoàn tất việc chuyển đổi tất cả các cách sử dụng của phông chữ trong toàn bộ ứng dụng. Trong cả DrinksListView.swift và DrinkDetailView.swift , bạn sẽ tìm thấy một số nơi đặt phông chữ. Theo ví dụ trên, bạn có thể chuyển đổi mã từ việc sử dụng một chuỗi sang trọng số thích hợp của NotoSans . Mỗi vị trí trong số này đã cho biết TextStylechúng nên có vị trí nào.

    Build và Run. Ứng dụng của bạn trông vẫn giống như cách nó hoạt động khi bạn bắt đầu. Nhưng bây giờ bạn sẽ có tất cả các tài nguyên được tham chiếu theo cách an toàn về kiểu loại!

    Thức uống

    Tổng kết

    Bây giờ bạn có thể sử dụng SwiftGen để:

    • Loại bỏ nhu cầu sử dụng chuỗi để tham chiếu tài nguyên trong ứng dụng của bạn, cho dù bạn sử dụng SwiftUI hay UIKit.
    • Tùy chỉnh các tệp đầu ra bằng cách sử dụng các thông số cài sẵn.
    • Sử dụng các mẫu của riêng bạn để tạo mã.

    Để tìm hiểu thêm về nó, hãy xem SwiftGen trên GitHub . Bạn cũng có thể tìm hiểu thêm về Stencil trên GitHub .

    Chúng tôi hy vọng bạn thích hướng dẫn này. Nếu bạn có bất kỳ câu hỏi hoặc ý kiến ​​nào, hãy tham gia thảo luận của diễn đàn bên dưới!

    Nội dung bài viết được dịch từ link: https://www.raywenderlich.com/23709326-swiftgen-tutorial-for-ios

  • HƯỚNG DẪN THỰC HIỆN ỨNG DỤNG MOBILE HỖ TRỢ ĐA NGÔN NGỮ

    HƯỚNG DẪN THỰC HIỆN ỨNG DỤNG MOBILE HỖ TRỢ ĐA NGÔN NGỮ

    Trong thời đại công nghệ 4.0, các công ty đua nhau chuyển đổi số, vì vậy có rất nhiều những ứng dụng di động được phát triển để giúp tiếp cận người dùng một cách dễ dàng hơn. Để những ứng dụng có thể vươn xa ra tầm thế giới, tiếp cận được với những người dùng nước ngoài, thì ứng dụng đó cần phải hỗ trợ đa ngôn ngữ. Vì vậy hôm nay mình sẽ hướng dẫn các bạn một số cách thực hiện một ứng dụng iOS hỗ trợ đa ngôn ngữ.

    Cài đặt dự án hỗ trợ đa ngôn ngữ

    Bước 1: Thực hiện thêm ngôn ngữ hỗ trợ bằng cách Chọn Project -> Info(thông tin) -> Bấm nút +

    Thêm ngôn ngữ
    Bỏ tích ở các file storyboard để xCode không gen ra các file String cho các files storyboard

    Bước 2: Tạo file String để chứa nội dung theo các ngôn ngữ New File… -> tìm string và chọn Strings File -> Next

    Bước 3: Localize file string vừa mới tạo Chọn File vừa tạo -> Bấm vào nút Localize… -> Popup hiển thị lên thì chọn Localize

    Bước 4: Tích vào ngôn ngữ bạn hỗ trợ, Chọn File String localize vừa được tạo -> Ở menu bên phải mục Localization tích vào những ngôn ngữ mà ứng dụng của bạn hỗ trợ.

    Chọn ngôn ngữ hỗ trợ

    Vậy là việc cài đặt đa ngôn ngữ cho ứng dụng của bạn đã hoàn thành.

    Thực hiện đa ngôn ngữ với các chuỗi (Localize String)

    1. Để tránh các lỗi sai chính tả và việc thực hiện đa ngôn ngữ trở nên dễ dàng hơn thì chúng ta sẽ tạo ra một enum Localization để liệt kê các item/string dưới dạng keyword để sử dụng như sau:
    
    enum Localization {
        static let helloWorld: String = "Hello"
        static let buttonChangeLanguageTitle: String = "btn.changeLanguage"
    }

    Với mỗi 1 key của string chúng ta cần tạo ra value tương ứng với nó ở trong file Localizable mà chúng ta đã tạo.

    Thêm key/value cho các file ngôn ngữ tương ứng:

    Thêm key/value cho ngôn ngữ Tiếng Anh
    Thêm key/value cho ngôn ngữ Tiếng Việt

    NOTE:
    – Kết thúc của dòng code phải là dấu chấm phẩy “;” nếu 1 dòng code bị thiếu nó sẽ khiến ứng dụng của bạn hiển thị không đúng ngôn ngữ.
    – Key phải trùng với giá trị của enum Localization

    2. Tạo một file chung để quản lí ngôn ngữ như đoạn code dưới đây

    // for manage language
    class LanguageManager {
        static let shared = LanguageManager()
        private init(){}
        
        // save language to UserDefault
        func changeLanguage(_ language: Language) {
            UserDefaults.standard.set(language.rawValue, forKey: "APP_LANGUAGE")
        }
        
        /// Get language of set on app, if nil use device language
        /// - Returns: current language of application
        func getAppLanguage() -> Language {
            if let language = UserDefaults.standard.value(forKey: "APP_LANGUAGE") as? String {
                return Language(rawValue: language) ?? .english
            } else {
                // default lan is english
                let currentLanguage: String = Locale.current.languageCode ?? Language.english.rawValue
                return Language(rawValue: currentLanguage) ?? .english
            }
        }
        
        // define enum language
        enum Language: String {
            case vietnamese = "vi"
            case english = "en"
        }
    }

    Ở đây mình tạo ra một file quản lí ngôn ngữ nhằm mục đích tập trung tất cả những tính năng liên quan tới ngôn ngữ: thay đổi, lấy ra ngôn ngữ …

    Chúng ta cần lưu ngôn ngữ của ứng dụng vào Local Storage để khi người dùng tắt ứng dụng vào lại thay đổi vẫn được áp dụng. Trong trường hợp này mình chọn cách dùng UserDefault vì những lí do sau:
    – Dễ sử dụng
    – Dữ liệu cần lưu không yêu cầu bảo mật
    – Dữ liệu lưu có dung lượng nhỏ

    3. Vậy là chúng ta đã tạo xong file quản lí ngôn ngữ. Tiếp đến để việc sử dụng localization dễ dàng chúng ta sẽ tạo ra một var localized trong extension String như sau:

    
    extension String {
        var localized: String {
            let currentLanguage = LanguageManager.shared.getAppLanguage().rawValue
            guard let bundlePath = Bundle.main.path(forResource: currentLanguage, ofType: "lproj"), let bundle = Bundle(path: bundlePath) else {
                return self
            }
            return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
        }
    }
    

    Sau khi tạo xong extension thì việc thực hiện code localized trở nên rất dễ dàng. Khi cần sử dụng chúng ta chỉ cần .localized là xong.

    4. Demo và cách sử dụng:

    Trong ví dụ này mình sẽ tạo ra 1 label hiển thị text và một nút để thay đổi ngôn ngữ của ứng dụng. Sau đó mình tạo ra một func setDataForUI() nhằm mục đích set tất cả các data liên quan tới localization ở đây. Nếu sau đó đổi ngôn ngữ ta chỉ cần gọi lại hàm này để thực hiện set lại.

        private func setDataForUI() {
            lbHello.text = Localization.helloWorld.localized
            btnChangeLanguage.setTitle(Localization.buttonChangeLanguageTitle.localized, for: .normal)
        }
    
        @IBAction func changeLanguage(_ sender: Any) {
            if LanguageManager.shared.getAppLanguage() == .english {
                LanguageManager.shared.changeLanguage(.vietnamese)
            } else {
                LanguageManager.shared.changeLanguage(.english)
            }
            setDataForUI()
        }

    Kết quả

    Vậy là chúng ta đã hoàn thành việc thực hiện localize String cho ứng dụng. Mình hi vọng bài viết này có thể giúp được chút gì đó cho các bạn. Cảm ơn các bạn đã đọc bài viết này, chúc các bạn thành công!

  • Adobe xD to SWIFT

    Adobe xD to SWIFT

    Xin chào! Bài viết này mình muốn chia sẻ cho mọi người về một số cách để code SWIFT giống với Adobe xD và hạn chế phần nào việc bị bắt bug UI không đáng.

    Trong nỗi trăn trở ở mỗi dự án mobile có hàng trăm, hàng ngàn các bug UI được log. Tự nhiên mình lại nghĩ phải làm việc gì đó để giúp cho anh em code UI ngon hơn, đỡ tạo ra bug UI hơn. Vì vậy mình đã viết bài viết này hi vọng sẽ giúp anh em được phần nào trong việc tránh dính phải những bug UI.

    Khi làm việc với xD các anh em thường bỏ qua các chỉ số của xD mà hay tự thực hiện code để nhìn sao cho giống UI nhất có thể, nếu không hiểu rõ bản chất nó khiến cho anh em mất khá nhiều thời gian để có thể làm giống được với file thiết kế cụ thể ở đây là file Adobe xD.

    Drop shadow

    Để giúp mọi người làm việc dễ dàng hơn nên mình đã tạo ra một hàm trong CALayer để giúp anh em đổ bóng bao chuẩn, bao giống xD :v Việc của anh em là lấy chỉ số ở xD và truyền vào func để setup là xong.

    extension CALayer {
        
        /// make shadow like Adobe xD, all prameters using same value with Adobe xD
        /// - Parameters:
        ///   - color: shadow color
        ///   - opacity: alpha of shadow color (0-100)
        ///   - x: x
        ///   - y: y
        ///   - b: shadow radius
        func dropShadowLikeXD(color: UIColor = .black,
                              opacity: Int = 50,
                              x: CGFloat = 0,
                              y: CGFloat = 3,
                              b: CGFloat = 6) {
            masksToBounds = false
            shadowColor = color.cgColor
            shadowOpacity = Float(opacity) / 100.0
            shadowOffset = CGSize(width: x, height: y)
            shadowRadius = b / 2.0
            shadowPath = nil
        }
    }

    Để anh em dễ hiểu hơn thì mình xin giải thích như sau:
    – color: đây là shadow color, nó là màu shadow trên xD, cái này khá đơn giản mọi người chỉ cần lấy màu trên xD và fill vào là xong
    – opacity: đây là độ trong suốt của shadow, trên Adobe xD không có thuộc tính này mà giá trị này sẽ là Opacity của shadow color
    – x: độ lệch của shadow so với view, tính từ trái qua phải. x > 0 thì shadow lệch qua phải và ngược lại
    – y: độ lệch của shadow so với view, tính từ trên xuống dưới, x > thì shadow lệch xuống dưới và ngược lại.
    – b: là thuộc tính blur trên xD, nhưng trong Swift không có thuộc tính này, mà chỉ có shadowRadius nó là bán kính của shadow, và nó bằng 1/2 blur trên xD.
    – masksToBounds: thuộc tính này bằng true sẽ không thể tạo được shadow vì nó sẽ cắt mất view shadow đi. vì trong hàm mình đã set lại giá trị này bằng false.

    Lưu ý: Để vừa đổ bóng được kết hợp với bo góc chúng ta cần thực hiện bo góc trước khi gọi hàm dropShadowLikeXD()

    Border

    Border trong Adobe xD cho phép custom khá nhiều thuộc tính, tuy nhiên mấy ông Dev tạo ra CALayer của Apple lại chỉ cho set mỗi 2 thuộc tính là borderWidth và borderColor. Vì vậy để làm giống Adobe xD chúng ta sẽ mất công hơn 1 chút. Cụ thể chúng ta sẽ cần thêm 1 enum và một func trong extension của UIView như sau:

    extension UIView {
        enum BorderStrokeStyle {
            case inner 
            case outer
            case center
        }
        
        func borderLikeXD(size: CGFloat = 1,
                          color: UIColor = .black,
                          dash: CGFloat = 0,
                          cap: CAShapeLayerLineCap = .butt,
                          join: CAShapeLayerLineJoin = .miter,
                          stroke: BorderStrokeStyle = .inner) {
            if dash <= 0 {
                layer.borderColor = color.cgColor
                layer.borderWidth = size
            } else {
                let newShapeLayer  = CAShapeLayer()
                newShapeLayer.strokeColor = color.cgColor
                newShapeLayer.lineWidth = size
                newShapeLayer.lineDashPattern = [NSNumber(value: dash), NSNumber(value: dash)]
                newShapeLayer.frame = self.bounds
                newShapeLayer.fillColor = nil
                newShapeLayer.lineCap = cap
                newShapeLayer.lineJoin = join
                switch stroke {
                case .inner:
                    let innerBounds = CGRect(x: bounds.origin.x + size / 2, y: bounds.origin.y + size / 2, width: bounds.size.width - size, height: bounds.size.height - size)
                    newShapeLayer.path = UIBezierPath(roundedRect: innerBounds, cornerRadius: layer.cornerRadius).cgPath
                case .outer:
                    let outerBounds = CGRect(x: bounds.origin.x - size / 2, y: bounds.origin.y - size / 2, width: bounds.size.width + size, height: bounds.size.height + size)
                    newShapeLayer.path = UIBezierPath(roundedRect: outerBounds, cornerRadius: layer.cornerRadius).cgPath
                case .center:
                    newShapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
                }
                
                self.layer.addSublayer(newShapeLayer)
                
            }
        }
    }

    Lưu ý: Do hàm này thực hiện thêm mới sublayer nên mọi người không nên gọi nó thực hiện ở func có thể gọi nhiều lần như: viewWillAppear(), viewDidAppear() …

    Line spacing

    Do định nghĩa về line spacing của Apple và Adobe xD khác nhau nên chúng ta không thể sử dụng cùng chỉ số được, vì vậy chúng ta cần tạo ra một phương thức để sửa lại công thức sao cho khớp với Adobe xD.

    Adobe xD định nghĩa line spacing: là khoảng cách từ Top của dòng trên so với Top của dòng dưới liền kề.

    Apple định nghĩa line spacing: là khoảng cách giữa Bot của dòng trên so với Top của dòng dưới liền kề.

    Line spacing

    Chúng ta có thể nhận ra sự chênh lệch giá trị line spacing của Apple so với Adobe chính là chiều cao của 1 dòng. Vậy nên mình có tạo ra một func giúp mọi người set lại giá trị line spacing giống xD mà không phải đau đầu tính toán nữa.

    extension UILabel {
        /// Set line spacing for label
        ///
        /// - Parameter lineSpacing: Line spacing
        func setLineSpacing(_ lineSpacing: CGFloat) {
            // Check label text empty
            guard let labelText: String = self.text,
                  let font = self.font else {
                return
            }
            let constraintRect: CGSize = CGSize(width: self.bounds.width, height: .greatestFiniteMagnitude)
            let boundingBox: CGRect = "Ok".boundingRect(with: constraintRect,
                                                        options: .usesLineFragmentOrigin,
                                                        attributes: [NSAttributedString.Key.font: font],
                                                        context: nil)
            let heightLabel: CGFloat = ceil(boundingBox.height)
            let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle()
            // line spacing on xD - height of one line
            paragraphStyle.lineSpacing = lineSpacing - heightLabel
            
            let attributedString: NSMutableAttributedString
            if let labelattributedText: NSAttributedString = self.attributedText {
                attributedString = NSMutableAttributedString(attributedString: labelattributedText)
            } else {
                attributedString = NSMutableAttributedString(string: labelText)
            }
            
            // Line spacing attribute
            attributedString.addAttribute(NSAttributedString.Key.paragraphStyle,
                                          value: paragraphStyle,
                                          range: NSRange(location: 0,
                                                         length: attributedString.length))
            
            self.attributedText = attributedString
        }
    }

    Trên đây là những gì mình muốn chia sẻ lại cho mọi người, hi vọng nó sẽ giúp được mọi người phần nào trong công việc. Nếu mọi người có câu hỏi hay thắc mắc gì có thể đặt câu hỏi ở dưới comment mình sẽ cố gắng giải đáp những thắc mắc của mọi người.

    Xin cảm ơn mọi người đã đọc bài viết của mình!

  • Vision:  Person Segmentation

    Vision: Person Segmentation

    Overview

    Vision framework được giới thiệu lần đầu vào WWDC 2017. Vision giúp thực hiện phát hiện khuôn mặt, phát hiện văn bản, nhận dạng mã vạch, đăng ký hình ảnh… Vision cũng cho phép sử dụng Core ML để tùy chỉnh cho các tác vụ như phân loại hoặc phát hiện đối tượng.

    Trong sample này chúng ta sẽ cùng tìm hiểu Person Segmentation API, giúp ứng dụng của bạn tách mọi người trong hình ảnh khỏi môi trường xung quanh.

    alt text

    Notes: Sample yêu cầu chạy Physical device với ios 15+ hoặc chạy Simulator ios 15+ trên macOS chip Intel

    Cách tạo một Vision requests

    Muốn sử dụng bất kỳ thuật toán nào của Vision, bạn sẽ cần ba bước:

    1. Request: Yêu cầu xác định loại thứ bạn muốn phát hiện và một trình xử lý hoàn thành sẽ xử lý kết quả. Đây là subclass của VNRequest.

    2. Request handler: Sử dụng VNImageRequestHandler để thực hiện Request.

    3. Results: Kết quả sẽ được đính kèm với Request ban đầu và được chuyển đến trình xử lý hoàn thành được xác định khi tạo Request. Chúng là các subclass của VNObservation.

    Prepare the Requests

    // 1. Request
    let request = VNGeneratePersonSegmentationRequest { (request, error) in
        // 3. Results
        self.personSegmentation(request: request, error: error)
    }
            
    request.qualityLevel = .accurate
    request.outputPixelFormat = kCVPixelFormatType_OneComponent8
            
    // 2. Request Handler
    let imageRequestHandle = VNImageRequestHandler(cgImage: cgImage, options: [:])
    

    Chúng ta sẽ tạo 1 request với VNGeneratePersonSegmentationRequest. VNGeneratePersonSegmentationRequest giúp tạo mask hình ảnh cho người mà nó phát hiện trong hình ảnh. Việc set thuộc tính qualityLevel cho request thành .fast, .balanced, hoặc .accurate giúp xác định chất lượng của mask được tạo như trong hình minh họa bên dưới.

    alt text

    Việc tăng độ chính xác của mask cũng đồng hời làm giảm suất làm việc của app nên chúng ta cần lựa chọn qualityLevel phù hợp cho từng tác vụ mà nó xử lý

    alt text

    Thuộc tính tiếp theo là format đầu ra của mask: outputPixelFormat. outputPixelFormat định dạng mà mặt nạ kết quả sẽ được trả về, có 3 định dạng có thể chọn đó là:

    alt text

    Trong sample chúng ta set outputPixelFormat với kCVPixelFormatType_OneComponent8 với range từ 0 đến 255.

    Bước cuối cùng là thực hiện request với VNImageRequestHandler đã tạo từ trước. result trả về của request handle sẽ là 1 instance của VNPixelBufferObservation

    DispatchQueue.global(qos: .userInitiated).async {
        do {
            try imageRequestHandle.perform([request])
        } catch let error {
            print(error)
        }
    }
    

    Xử lý Results

    alt text

    Chúng ta đã tìm hiểu qua về request và các thuộc tính của nó, ta sẽ đến với phần tiếp theo là xử lý result trả về. Những gì ta cần làm ở đây là thay thế background của ảnh gốc nằm ngoài mask trả về từ result.

    guard let result = request.results?.first as? VNPixelBufferObservation else {
        loadingView.stopAnimating()
        return
    }
    // 1. Processing
    let buffer: CVPixelBuffer = result.pixelBuffer
    let maskImage: CIImage = CIImage(cvImageBuffer: buffer)
    let bgImage = UIImage(named: "background")!
    let background = CIImage(cgImage: bgImage.cgImage!)
    let input = UIImage(named: "humanFace")!
    let inputImage = CIImage(cgImage: input.cgImage!)
            
    // 2. Scale mask, and background to size of original image
    let maskScaleX = inputImage.extent.width / maskImage.extent.width
    let maskScaleY = inputImage.extent.height / maskImage.extent.height
    let maskScaled =  maskImage.transformed(by: __CGAffineTransformMake(maskScaleX, 0, 0, maskScaleY, 0, 0))
            
    let backgroundScaleX = inputImage.extent.width / background.extent.width
    let backgroundScaleY = inputImage.extent.height / background.extent.height
    let backgroundScaled =  background.transformed(by: __CGAffineTransformMake(backgroundScaleX, 0, 0, backgroundScaleY, 0, 0))
            
    // 3. Blending Image
    let blendFilter = CIFilter.blendWithMask()
    blendFilter.inputImage = inputImage
    blendFilter.maskImage = maskScaled
    blendFilter.backgroundImage = backgroundScaled
    
    // 4. Handle Result
    if let blendedImage = blendFilter.outputImage {
        let context = CIContext(options: nil)
        let maskDisplayRef = context.createCGImage(maskScaled, from: maskScaled.extent)
        let filteredImageRef = context.createCGImage(blendedImage, from: blendedImage.extent)
        DispatchQueue.main.async {
            self.imageDisplay.image = UIImage(cgImage: filteredImageRef!)
            self.maskImage.image = UIImage(cgImage: maskDisplayRef!)
        }
    }
    

    Đây là những gì ta làm trong đoạn code trên:

    1. Import ảnh gốc, hình nền cần thay thế và mask từ result trả về.

    2. Scale kích thước của mask và hình nền về size của ảnh gốc.

    3. Tạo ra 1 CoreImage blend filter, ta dùng blendWithRedMask() vì khi tạo ra CIImage của mask từ CVPixelBuffer của nó, nó sẽ tạo ra 1 object mặc định ở red chanel.

    4. Xử lý result filter, update UI.

    Đây là kết quả sau khi đã xử lý xong:

    alt text

    Kết luận

    Qua bài viết này mình muốn chia sẻ tới mọi người trình tự tạo một Vision requests, hiểu hơn về Person Segmentation API và cách sử dụng của nó. Mong rằng bài viết tới mình có thể chia sẻ tới các bạn cách sử dụng của Person Segmentation API với video.

    Refer

  • Async await trong swift – Part 1

    Async await trong swift – Part 1

    Apple đã giới thiệu cho cộng đồng dev iOS về Swift 5.5, trong đó có sự cập nhật rất là lớn. Đó là về bất đồng bộ, với async await

    Để xem async / await giúp ngôn ngữ như thế nào, sẽ hữu ích khi xem cách chúng tôi đã giải quyết vấn đề tương tự trước đây.

    Một ví dụ về networking trước khi async await được update

    class SeverConnection {
        let defaultSession = URLSession(configuration: URLSessionConfiguration.default)
        var dataTask: URLSessionDataTask?
        var downloadTask: URLSessionDownloadTask?
        var uploadTask: URLSessionUploadTask?
    }
    
    extension SeverConnection {
        func fetchAPIFromURL(_ url: String, completion: @escaping (String?, Error?) -> Void) {
            guard let url = URL(string: url) else {
                completion(nil, SeverConnectionError.badURL)
                return
            }
            dataTask = defaultSession.dataTask(with: url, completionHandler: { (data, response, error) in
                if let error = error {
                    completion(nil, SeverConnectionError.errorWithDataTask)
                    return
                }
                guard let httpResponse = response as? HTTPURLResponse,
                    (200...299).contains(httpResponse.statusCode) else {
                        completion(nil, SeverConnectionError.badResponse)
                        return
                }
                guard let data = data else { return }
                let dataString = String(data: data, encoding: .utf8)
                completion(dataString, nil)
            })
            dataTask?.resume()
        }
    }
    

    khi sử dụng:

    private func fetchListData() {
            let urlString = "https://vapor-mock.herokuapp.com/pic_dic.json"
            severConnection.fetchAPIFromURL(urlString) { [weak self] (data, error) in
                guard let self = self else {
                    return
                }
                if let error = error {
                    print(error)
                }
                if let data = data {
                    self.convertData(data)
                }
            }
        }
        
        private func convertData(_ data: String) {
            let responseData = Data(data.utf8)
            let decoder = JSONDecoder()
            
            var responseImageData: [ImageData]?
            
            do {
                responseImageData = try decoder.decode([ImageData].self, from: responseData)
                infoImages = responseImageData
                DispatchQueue.main.async {
                    self.mainTableView.reloadData()
                }
            } catch {
                print("Error decoding imageData: \(error)")
            }
        }
    

    Còn với async await

    class FetchAPITask {
        static let shared = FetchAPITask()
        
        func fetchAPI<D: Decodable>(url: URL) async throws -> D {
            let task = Task { () -> D in
                try await fetchAndDecode(url: url)
            }
            return try await task.value
        }
        
        func fetchAPIGroup<D: Decodable>(urls: [URL]) async throws -> [D] {
            try await withThrowingTaskGroup(of: D.self) { (group)  in
                for url in urls {
                    group.async { try await self.fetchAndDecode(url: url) }
                }
                var results = [D]()
                for try await result in group {
                    results.append(result)
                }
                return results
            }
        }
        
        func fetchAndDecode<D: Decodable>(url: URL) async throws -> D {
            let data = try await URLSession.shared.fetchData(with: url)
            let decodedData = try JSONDecoder().decode(D.self, from: data)
            return decodedData
        }
    }
    

    khi sử dụng:

    func fetchAllAPI() async {
        do {
            if let url = url {
                let datas: [ImageData] = try await FetchAPITask.shared.fetchAPI(url: url)
                self.datas = datas
            }
        } catch {
            print(error)
        }
    }
    
    Task.init(priority: .default, operation: {
        await self.fetchAllAPI()
        DispatchQueue.main.async {
            self.mainTableView.reloadData()
        }
    })
    

    Kết luận

    • Sử dụng async await với syntax đơn giản hơn dễ đọc hơn giúp việc đọc hiểu và maintain sẽ dễ dàng hơn
    • Các vấn đề với closure (điển hình như việc nhiều callback lồng nhau)
    • Đây là một ví dụ về việc sử dụng async await call api async await còn rất nhiều thứ hay ho mọi người có thể đọc tại đây