Bạn hãy tưởng tượng rằng khi bạn hoàn thành một giai đoạn nào đó của dự án và tiến tới release sản phẩm. Bạn sẽ gặp phải vấn đề về API, asset, url, icon... Vì chúng có thể khác nhau trên các môi trường khác nhau. Đơn giản bạn có thể hiểu thế này: trong quá trình phát triển thì bạn chỉ được phép sử dụng api từ server này, nhưng khi nó là sản phẩm tới tay người dùng thì nó lại sử dụng api từ server khác. Và nó cũng tương tự với asset, url, icon, device...

Vì vậy, thông thường bạn sẽ phải sửa đổi tất cả chúng trước khi release sản phẩm của bạn. Ôi điều đó thật tệ nếu như chẳng may bạn quên sửa một chỗ nào đó và sản phẩm download từ store về nó không hoạt động. Bạn sẽ là người phải chịu trách nhiệm về vấn đề đó.

Với một ứng dụng khổng lồ thì tác hại càng lớn nếu bạn không quản lý được vấn đề đó và trên thực tế thì rất nhiều dự án release hàng tuần, điều đó có nghĩa là bạn phải thực hiện công việc sửa đổi đó để chuyển sang release hàng tuần. Vâng, một lần làm sai bạn còn có cơ hội để sửa đổi, nhưng không thể quá tam ba bận được. Bạn sẽ bị chuyển dự án hay đuổi việc đó.

Đúng là một ác mộng.

Thay vì các tiếp cận lộn xộn đó, thay vì mỗi lần release bạn lại đi sửa một đống thứ như vậy. Bạn có thể tự định nghĩa các môi trường và chỉ thay đổi chúng khi cần thiết, hoặc bạn cũng có thể config để khi nào release thì toàn bộ api, url, asset, icon đều tự động nhận đúng môi trường của nó... và tất nhiên khi đó bạn sẽ không còn bị lỗi ra giải trình với khách hàng về những sai lầm của bạn nữa.

Trong giới hạn bài viết này mình sẽ hướng dẫn các bạn 4 cách từ đơn giản tới phức tạp và nó đồng nghĩa với việc lượng công việc trước khi release bạn phải làm là từ nhiều tới ít.

1. Sử dụng comment mã nguồn
2. Sử dụng global variable hoặc enum
3. Sử dụng global flag
4. Sử dụng file plist

Nào, chúng ta cùng bắt đầu thôi!

Sử dụng comment mã nguồn

  • Khi ứng dụng của bạn có 3 môi trường khác nhau, ứng dụng sẽ phải biết khi nào kết nối tới môi trường nào. Tưởng tượng rằng bạn đang có Production, Development và Stagging. Cách đơn giản nhất là bạn tạo 3 biến tương ứng, sử dụng 1 biến và comment 2 biến còn lại. Khi đổi môi trường bạn cũng sẽ tiến hành như vậy.

  • Ví dụ cho trường hợp này:

// MARK: - Development enviroment
let url = "http://site.com/dev/api"
let key = "Development"

// MARK: - Production enviroment
// let url = "http://site.com/prod/api"
// let key = "Production"

// MARK: - Staging enviroment
// let url = "http://site.com/staging/api"
// let key = "Staging"
  • Bạn nhìn vào nó bạn sẽ thấy nó thật cùi mía đúng không, ngoài việc bạn thấy code rất ngứa..., rất lộn xộn, có thể bạn sẽ khiến người khác phát khóc vì đoạn code đó, thật thiếu chuyên nghiệp.

  • Nó chỉ có thể được sử dụng khi chất lượng mã nguồn của bạn không cần phải quan tâm. Nhưng nếu là một lập trình viên, trong bất kỳ một trường hợp nào tôi vẫn khuyên bạn không nên sử dụng.

Note: Sau nhiều lần phải comment code để chuyển đổi môi trường trước khi release, bạn nhận ra rằng công việc đó thật thừa thãi, lúc này bạn sẽ khôn ra một tí. Bạn sẽ nghĩ ra cách nào đó giảm thiểu lượng công việc phải làm. Haha, bạn cũng như tôi, tự hỏi tại sao không sử dụng enum? Nếu bạn nghĩ vậy thì đọc tiếp phần bên dưới nhé.

Sử dụng global variable hoặc enum

  • Cách tiếp cận này cũng khá phổ biến. Lúc này bạn sẽ phải khai báo một biến toàn cục hay một enum để quản lý các môi trường khác nhau.

  • Ví dụ cho trường hợp này:

enum Environment {
    case development
    case staging 
    case production
}
 
let environment: Environment = .development
 
switch environment {
case .development:
    print("Development")
case .staging:
    print("Staging")
case .production:
    print("Production")
}
  • Với cách tiếp cận này thì thay vì bạn phải comment nhiều dòng code như cách bên trên, bạn chỉ cần thay đổi một biến là environment sang các môi trường trong enum bạn định nghĩa.

  • So với cách bên trên thì cách này tốt hơn nhiều. Nó nhanh, dễ đọc, dễ hiểu nhưng vẫn còn có hạn chế. Mình sẽ chỉ ra hạn chế ở đây đó là: vào một ngày đẹp trời, khách hàng bảo bạn làm một feature. Sau khi bạn làm xong bạn được yêu cầu build cho tester test feature đó. Bạn build feature đó cho tester test, sau một hồi ngồi ngáp ngắn ngáp dài, tester thấy bạn hôm nay ngứa mắt nên không tin là bạn làm feature đó không bị ảnh hưởng tới phần khác. Ngay lập tức thiết bị bay tới chỗ bạn với yêu cầu build bản trước khi bạn implement feature đó. Bạn chỉ có 1 device, giờ revert code để build thì nó lại ghi đè lên ứng dụng bạn vừa build, lấy gì mà tester so sánh. Bạn nghĩ ngay đến việc request device khác để build. Rất tiếc, công ty đang hết tiền nên chưa mua được thêm device cho bạn. Chả nhẽ bỏ tiền túi ra mua...

  • Lúc này bạn sẽ nhận ra mã nguồn bạn viết có hạn chế, đó là không thể build thành các app khác nhau với 2 môi trường khác nhau là Development và Production. (Vấn đề này được đề cập với điều kiện bạn không được sửa bundle ID vì ở một góc độ khác nếu muốn build cùng một mã nguồn thành 2 ứng dụng bạn chỉ cần thay đổi bundle ID).

  • Trên đây là hạn chế mà bạn dễ thấy nhất, bởi vì ngoài hạn chế này bạn còn rất nhiều hạn chế khác nếu bạn sử dụng cách viết code này. Mình không đề cập thêm ở đây.

  • Với các ứng dụng đơn giản thì bạn cũng có thể sử dụng cách này. Nhưng mình xin nói đây chưa phải là cách hay, chuyên nghiệp cho bạn đâu.

Note: Lúc này bạn sẽ nghĩ cách để cải thiện vấn đề mà tester yêu cầu trong khi bạn chỉ có 1 device. Bạn sẽ tìm cách build cùng một mã nguồn thành 2 ứng dụng trên cùng 1 device. Nếu vậy hãy đọc tiếp phần dưới nhé.

Sử dụng global flag

  • Trong cách tiếp cận này chúng ta cần tạo ra 3 cấu hình khác nhau và kết nối ứng dụng tương ứng với cấu hình. Lúc này bạn hãy tạo target mới.

  • Để làm điều đó hãy nhìn vào bức ảnh sau:

alt text

  • Lúc này chúng ta có thêm một target mới tự động được đặt tên "Environments copy". Hãy đặt tên cho nó bằng cách click vào target đó và chuyển thành "Environments Dev"

  • Tiếp theo đi tới "Manage Schemes", chọn scheme mà bạn đã tạo trước đó, nhấn Enter và đổi tên giống bước trên.

alt text

  • Đồng thời với việc đó bạn hãy tạo bộ asset khác tương ứng với target bạn vừa tạo bên trên. Ví dụ "AppIcon-Dev". Hãy làm theo bức ảnh sau:

alt text

  • Bước tiếp theo bạn cần mapping bộ asset của bạn đúng với target bạn muốn sử dụng.

alt text

  • Ngoài việc tạo bộ asset mới bạn cũng có thể tạo file plist khác tương ứng với target để thay đổi cấu hình tương ứng với target đó.

  • Tới đây bạn sẽ thấy mọi thứ trở lên dễ chịu hơn rất nhiều. Có thể công việc này phức tạp nhưng nó chỉ làm 1 lần và bạn không cần phải vất vả đi sửa đống code mỗi lần. Xin nhắc lại với bạn, chỉ 1 lần thôi bạn có thể thoải mái custom cho từng target nhé. Thật đơn giản.

  • Tiếp theo bạn sẽ có 2 hướng tiếp cận để xử lý 2 cấu hình đó (2 Target bạn đã tạo bên trên). Đó là:

    • Sử dụng preprocessing macro/compiler flag
    • Thêm biến vào file plist
  • Chúng ta cùng xem cả hai cách trên nhé.

alt text

  • Sau khi thêm flag bạn sẽ thấy nó có vẻ giống với đoạn code sau:
#if DEVELOPMENT
let URL = "http://dev.server.com/api/"
let TOKEN = "DEVELOPMENT"
#else
let URL = "http://prod.server.com/api/"
let TOKEN = "PRODUCTION"
#endif
  • Nào, bây giờ bạn hãy chọn target và build, bạn sẽ nhận ra nó tự động kết nối với server tương ứng với target của bạn. Lúc này bạn lại thấy mọi thứ thật đơn giản phải không. Bạn không cần sửa code, không cần phải check xem sửa hết chưa, không cần lo lắng xem có sai gì không. Một việc duy nhất bạn chỉ cần làm trước khi release là chọn target PRODUCTION, nhấn build và đi trà đá thôi.

Note: Các tiếp cận thứ 2 là sử dụng file plist để cấu hình. Để hiểu hơn hãy theo dõi phần dưới.

Sử dụng file plist

  • Công việc của chúng ta là thêm các giá trị cần thiết vào file plist. Ví dụ chúng ta thêm biến serverBaseURL vào cả 2 file plist đã tạo bên trên. Chúng ta chỉ cần lấy nó ra bằng cách tạo một extension chẳng hạn:
extension Bundle {
    var url: String {
	return object(forInfoDictionaryKey: "serverBaseURL") as? String ?? ""
    }
}

let url = Bundle.main.url
  • Với cá nhân tôi thích cách cấu hình này nhất, nó sẽ tự động trả ra giá trị mà bạn đã cấu hình tương ứng. Bạn không cần phải comment code, tạo enum, global variable hay tạo macro... như bên trên bạn đã làm.

  • Một điểm trừ đó là việc đọc từ file plist sẽ là không an toàn. Đặc biệt là khi bạn định nghĩa các key trong đó. Ở một khía cạnh nào đó các bạn hãy tìm hiểu các thiết lập file plist cho an toàn nhé.

Note: Trên đây là tổng hợp một số cách giúp bạn quản lý các môi trường hay các target khác nhau một các đơn giản. Tuỳ và từng dự án, cách nào phù hợp thì bạn hãy sử dụng nhé. Mỗi cách đều có lợi thế và bất lợi khác nhau. Vì vậy hãy suy nghĩ kỹ trước khi sử dụng.