Trong suốt quá trình phát triển ứng dụng iOS, Swift đã trở thành một ngôn ngữ lập trình mạnh mẽ và phổ biến nhờ vào tính năng hiện đại, hiệu suất cao và khả năng bảo mật mạnh mẽ. Bài viết này chia sẻ một số kinh nghiệm khi lập trình Swift trong các dự án thực tế, giúp các lập trình viên nắm bắt được các vấn đề phổ biến và cách khắc phục chúng.

1. Thiết Kế Kiến Trúc Ứng Dụng

  • Khi bắt đầu xây dựng một dự án chúng ta cần lựa chọn mô hình kiến trúc phù hợp giống như việc xây một ngôi nhà thì cần xây dựng bản thiết kế. Có một mô hình kiến trúc tốt sẽ giúp ích cho bạn rất nhiều, khi triển khai giúp cho code của bạn dễ đọc, dễ mở rộng và dễ bảo trì.
  • Sau đây tôi xin chia sẻ một vài kinh nghiệm lựa chọn mô hình kiến trúc phù hợp cho các loại dự án.
    Khi bắt đầu dự án với Swift, việc lựa chọn kiến trúc là rất quan trọng. Các kiến trúc phổ biến như Model-View-Controller (MVC), Model-View-ViewModel (MVVM), hay Clean Architecture đều có những ưu nhược điểm riêng. Sau đây tôi sẽ đi vào từng mô hình giải thích những ưu điểm và nhược điểm cho từng loại mô hình.

a. Mô hình MVC

Mô hình MVC
Mô hình MVC

Dự án nhỏ (Small Projects)

  • Dự án nhỏ thường có yêu cầu đơn giản, không cần xử lý nhiều logic phức tạp hay các chức năng phức tạp liên quan đến quản lý trạng thái.
  • MVC là một kiến trúc cơ bản, dễ hiểu và dễ áp dụng. Nó phù hợp cho các ứng dụng không có quá nhiều tầng xử lý dữ liệu và logic.
  • Dễ duy trì, triển khai nhanh chóng.
  • Ứng dụng cho các ứng dụng có chức năng cơ bản không quá nhiều logic và màn hình.

b. Mô hình MVVM

Mô hình MVVM
Mô hình MVVM

Dự án vừa (Medium Projects)

  • Khi dự án có nhiều màn hình và yêu cầu phức tạp hơn một chút, MVVM giúp tách biệt rõ ràng giữa giao diện người dùng và logic xử lý.
  • MVVM đặc biệt hữu ích khi sử dụng với các thư viện như RxSwift hoặc Combine để quản lý trạng thái bất đồng bộ, giúp dễ dàng cập nhật giao diện khi dữ liệu thay đổi mà không cần can thiệp trực tiếp vào view.
  • MVVM giúp quản lý code dễ dàng hơn khi ứng dụng phát triển, đồng thời cung cấp khả năng tái sử dụng mã nguồn.
  • Với mô hình MVC mọi logic sẽ được xử lý trong Controller (ViewController trong iOS), điều này sẽ dẫn đến Controller sẽ bị phình to lên khi dự án được mở rộng. Và MVVM sẽ giúp mô hình MVC giải quyết vấn đề này, logic sẽ được di chuyển (move) sang ViewModel, giảm tải việc xử lý phức tạp ở Controller.

(*) Lưu ý: Đối với các bạn mới bắt đầu thì có thể tiếp cận với mô hình MVVM sử dụng protocol - delegate, bạn có thể tham khảo một dự án thử nghiệm do tôi triển khai ở đây

Trong nhiều dự án thực tế, tôi ưa chuộng sử dụng MVVM kết hợp với RxSwift để quản lý trạng thái và dữ liệu trong ứng dụng, nguồn tài liệu tham khảo.

c. Clean architecture

Clean architecture
Clean architecture

Dự án lớn (Large Projects)

  • Clean Architecture phù hợp với những dự án có quy mô lớn, nơi việc quản lý và bảo trì mã nguồn trở nên phức tạp.
  • Với mô hình này logic không chỉ còn được triển khai ở Controller hay ViewModel, logic được phân bổ ở nhiều lớp khác nhau như Controller, Presenters, Gateways tuy theo từng chức năng.
  • Dự án lớn có thể bao gồm nhiều tầng, nhiều nhóm phát triển, và yêu cầu rõ ràng trong việc phân tách logic giữa các lớp, như: Presentation, Domain, Data.
  • Kiến trúc này giúp ứng dụng dễ dàng mở rộng, thay thế các phần khác nhau (ví dụ: API, cơ sở dữ liệu) mà không làm ảnh hưởng đến phần còn lại của ứng dụng.
  • Giúp ứng dụng có thể dễ dàng được kiểm thử (unit test) và duy trì lâu dài.
  • Ứng dụng cho các ứng dụng phức tạp như ngân hàng điện tử, ứng dụng mạng xã hội, ứng dụng quản lý doanh nghiệp, ứng dụng có yêu cầu về bảo mật và hiệu suất cao.

Tài liệu tham khảo clean architecture: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

2. Quản Lý Bộ Dữ Liệu

Khi làm việc với bộ dữ liệu lớn, việc sử dụng Core Data hoặc Realm để quản lý và lưu trữ dữ liệu là một lựa chọn tốt. Tuy nhiên, một vấn đề thường gặp là hiệu suất khi truy xuất hoặc cập nhật dữ liệu.

Core Data: Là công cụ mạnh mẽ của Apple, giúp quản lý đối tượng và các mối quan hệ giữa chúng. Tuy nhiên, Core Data có thể gặp phải một số vấn đề khi truy xuất dữ liệu quá lớn hoặc khi cấu trúc cơ sở dữ liệu thay đổi. Trong trường hợp này, tôi khuyên bạn nên sử dụng các kỹ thuật như NSFetchedResultsController để tối ưu hóa việc lấy dữ liệu.

Realm: cung cấp một giải pháp thay thế cho Core Data và SQLite với các tính năng ưu việt, giúp việc quản lý dữ liệu trong ứng dụng trở nên dễ dàng và hiệu quả hơn. Với truy vấn no-SQL đây là cách tiếp cận khá tốt cho các bạn mới.

Tài liệu tham khảo:

3. Xử Lý Lỗi

Xử lý lỗi trong Swift là một phần quan trọng để đảm bảo ứng dụng luôn ổn định. Swift cung cấp cơ chế Error Handling rất mạnh mẽ với các từ khóa như do-catch, try, và throws.

-Quản lý lỗi mạng: Trong các ứng dụng có giao tiếp với API, việc xử lý các lỗi như timeout hoặc không thể kết nối tới server là cần thiết. Hãy luôn chuẩn bị cho các trường hợp này bằng cách sử dụng các mã lỗi có sẵn của hệ thống hoặc tự định nghĩa các mã lỗi cụ thể cho ứng dụng của mình. Danh sách các mã lỗi của hệ thống.

- Custom Error Handling: Đôi khi, bạn sẽ cần tạo ra các lỗi tùy chỉnh cho các trường hợp đặc biệt như khi dữ liệu không hợp lệ hoặc thiếu thông tin. Hãy chắc chắn rằng các lỗi này được ném ra một cách rõ ràng và dễ hiểu.

Ví dụ:

public enum OpenAIError: Error {
    case emptyData
    case invalidaData
    case invalidUrl
    case unknow
}
CustomError.swift

4. Quản Lý Tài Nguyên Và Hiệu Suất

Khi làm việc với Swift trong các dự án thực tế, bạn sẽ gặp phải các tình huống cần tối ưu hóa tài nguyên như bộ nhớ và CPU, đặc biệt khi ứng dụng sử dụng hình ảnh, video hoặc thực hiện các tính toán phức tạp.

- Memory Management: Swift sử dụng cơ chế Automatic Reference Counting (ARC) để quản lý bộ nhớ. Tuy nhiên, bạn cần lưu ý khi làm việc với các đối tượng mạnh mẽ như closure hoặc delegate, chúng có thể dẫn đến retain cycles. Việc sử dụng weak và unowned để tránh vấn đề này là rất quan trọng.

Ví dụ với dự án thử nghiệm này của tôi về sử dụng delegate:

protocol MainViewModelDelegate: AnyObject {
    func didLoadClimateSuccess(at index: Int)
}

class MainViewModel: NSObject {
    
    var arrayAddress: [Address] = []
    weak var delegate: MainViewModelDelegate?
}
MainViewModel.swift

Ở đây khi sử dụng delegate chúng ta luôn sử dụng với cú pháp weak var delegate: MainViewModelDelegate? để tránh retain cycles.

Ví dụ dưới dây là 2 closure mà chúng ta cần phải xử lý tránh để bị leak memory ta cần khai báo cú pháp [weak self]:

// 1. 
let contextItem = UIContextualAction(style: .destructive, title: "Delete") { [weak self] (contextualAction, view, boolValue) in
   guard let self = self else { return }
   boolValue(true) // pass true if you want the handler to allow the action
   self.viewModel.removeAddress(at: indexPath)
   tableView.reloadData()
   // 2. 
   DispatchQueue.main.async { [weak self] in
      self?.delegate?.didDeleteAddress(at: indexPath.row)
   }
}

MenuViewController.swift

- Lazy Loading và Caching: Để tối ưu hóa hiệu suất, bạn có thể sử dụng lazy loading cho những đối tượng không cần thiết phải khởi tạo ngay từ đầu và caching các dữ liệu hoặc hình ảnh đã tải về. Sử dụng lazy loading và caching này sẽ giúp ứng dụng của chúng ta mượt mà và hiệu quả khi dùng UI dạng Collection hay List.

Kết Luận

Lập trình Swift trong các dự án thực tế đòi hỏi sự kết hợp giữa kỹ năng lập trình, quản lý dự án và kỹ thuật giải quyết vấn đề. Việc áp dụng các phương pháp và công cụ đúng đắn sẽ giúp bạn xây dựng những ứng dụng iOS chất lượng cao và hiệu quả. Thực hành thường xuyên và học hỏi từ những sai sót sẽ giúp bạn phát triển kỹ năng lập trình Swift của mình hơn nữa.