Contents
Chào bạn đến với series Lập trình iOS cho mọi người. Bài viết hôm nay sẽ khá là đơn giản về mặt lý thuyết. Mục đích chính là chuẩn bị giúp cho bạn những gì cần thiết cho 1 project iOS.
Chắc bạn không cần phải chuẩn bị kiến thức gì nhiều và bây giờ …
Bắt đầu thôi!
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. Tạo project
Việc học iOS càng tiến sâu vào thì độ phức tạp của project Xcode lại càng tăng thêm. Mọi thứ không còn đơn giản như ngày mới bắt đầu, với một View Controller cân cả thế giới. Để có thể quản lý được cả project, nhất khi team dự án của bạn có nhiều người cùng làm. Tất nhiên, chúng ta không thể bỏ tất cả mọi thứ vào cùng 1 thư mục …
Nhiều vấn đề như vậy, thì yêu cầu phải có một mẫu hướng dẫn chung, ít nhất là trong phạm vi team dự án của bạn. Để đảm bảo 2 yếu đố chính:
Cấu trúc và mô hình.
- Cấu trúc là hệ thống các thư mục có sự phân cấp. Và sử dụng theo mục đích khác nhau. Trong các thư mục đó chứa mã nguồn của project
- Mô hình là mẫu thiết kế mà project của bản sẽ sử dụng chính. Các mô hình hay sử dụng hiện nay như sau: MVC, MVVM, MVP, MV*, VIPER
Vâng, rất nhiều ý nghĩa nhân văn ở đây. Chúng ta sẽ giải quyết chúng ở các phần sau. Còn bây giờ thì bạn hãy
Tạo một iOS project bằng Xcode.
Phần này mình đã mô tả rất cụ thể ở link sau:
Bạn làm theo hướng dẫn và mình sẽ tiến sang phần tiếp theo. Cũng khá là quan trọng.
Lưu ý: Việc không sử dụng Storyboard trong iOS Project thì cũng có trình bày ở link trên rồi.
Bạn nên xoá hết các dòng comment code trong các file của project, cho code mình sạch đẹp hơn. Ví dụ trong file ScreenDelegate.swift
import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) // ... code đây nha self.window = window window.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) {} func sceneDidBecomeActive(_ scene: UIScene) {} func sceneWillResignActive(_ scene: UIScene) {} func sceneWillEnterForeground(_ scene: UIScene) {} func sceneDidEnterBackground(_ scene: UIScene) {} }
2. Cài đặt CococaPods
Cocoapods là chương trình giúp quản lý các bộ thư viện được được sử dụng trong ứng dụng mobile được phát triển dựa trên khung làm việc Cocoa.
Thông thường một ứng dụng phần mềm sẽ cần phải sử dụng nhiều bộ thư viện khác nhau và ứng dụng như vậy sẽ phụ thuộc vào các bộ thư viện này. Chính vì vậy Cocoapod còn được gọi là dependencies manager hay phần mềm quản lý các bộ thư viện phụ thuộc của ứng dụng.
Cocoa chính là framwork chính của MAC OS. Với iOS thì framwork chính là CocoaTouch.
2.1. Cài đặt CocoaPods cho máy
Cocoadpods được viết sử dụng ngôn ngữ Ruby và nó có thể cài đặt giống như một RubyGem. Trên MAC OS, mở Terminal và bạn sử dụng câu lệnh sau để cài đặt Cocoapods.
sudo gem install cocoapods
2.2. Tạo Podfile
- Di chuyển tới thư mục của project của bạn trên máy tính
cd <đường dẫn tới thư mục project>
- Gõ lệnh tạo Podfile
pod init
Kết quả là sẽ có một file tên là Podfile
được tạo ra. Và ở trong thư mục project của bạn.
Podfile là một tập tin đặc biệt được Cocoapods sử dụng để xác định danh sách các bộ thư viên sẽ được cài đặt để sử dụng trong ứng dụng.
2.3. Pod install
Tiếp tục thì bạn gõ tiếp lệnh cài đặt Pod cho project trong Terminal.
pod install
Kết quả như sau:
Vì Podfile của mình chưa có gì hết nên cũng chưa có thư viện nào thêm vào project. Bạn sẽ thấy sau khi gõ lệnh install thì có một file *.xcworkspace
được tạo ra. Và bạn kích đúp mở file đó lên.
Bạn sẽ thấy trong workspace bao gồm 2 project:
- Project của bạn
- Project là Pods của CocoaPods tạo ra
Bạn kích vào file Podfile
. Và thêm các thư viện cần thiết vào. Ví dụ: cho thư viện kinh điển Almofire.
# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'ProjectTemplate' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for ProjectTemplate pod 'Alamofire', '~> 5.0.0-rc.3' end
Sau đó bạn lại quay lại Terminal và gõ lại lệnh install, để cài đặt các thư viện mới thêm vào Podfile.
Trong hình trên bạn cũng thấy thư viện Almofire đã được cài đặt. Bạn có thể tham khảo thêm về cú pháp của Podfile ở đây.
Mỗi lần thay đổi, thêm mới hoặc xoá đi các thư viện trong Podfile thì bạn nên chạy lại lệnh
pod install
.Nếu như mới clone project thì cùng nên chạy lại lệnh
pod install
.Nếu như thư viện bạn quá cũ hoặc có vấn đề gì thì hãy chạy lệnh
pod update
.
3. Cấu trúc của Project
Phần này chỉ mang tính chất tạo thư mục và gom các file mã nguồn về đúng nơi, theo đúng chức năng mà nó đảm nhận. Chúng ta tạo các thư mục như hình sau.
3.1. AppDelegate
Bao gồm 2 file:
- AppDelegate.swift
- Để im không đụng chạm gì tới nó. Thời đại mới rồi và nó mất dần đi tầm ảnh hưởng.
- Chú ý từ khoá
@UIApplicationMain
ở trong file đó. Mang ý nghĩa xác định file AppDelegate nào sẽ chạy lúc khởi động ứng dụng.
- ScreneDelegate.swift
- Vài trò khá lớn. Có thể điều phối giao diện ở mức cao nhất.
- Chúng ta tạo thêm 1 singleton cho class này. Vì đối tượng của nó là duy nhất và được tạo sẵn rồi. Nên chúng ta chỉ trỏ tới chứ không tạo mới thêm.
static func shared() -> SceneDelegate { let scene = UIApplication.shared.connectedScenes.first return (scene?.delegate as? SceneDelegate)! }
3.2. Define
Chứa các khai báo dùng cho toàn bộ project. Bạn tham khảo hình sau tạo các file tương ứng
Ở trên chỉ tham khảo những cái cần sử dụng. Đối với bạn, nếu muốn thêm gì thì có thể thêm tuỳ ý. Về code thì như sau:
- App.swift
import Foundation struct App { struct Key {} struct Text {} struct Color {} }
- App.Key.swift
import Foundation extension App.Key { static var authenToken = "....." static var admobID = "....." static var bannerID = "....." }
- App.Text.swift
import Foundation extension App.Text { //Information app static var appName = "Project Template" //... }
- App.Color.swift
import Foundation import UIKit extension App.Color { static var mainColor = UIColor.lightGray static var textColor = UIColor.black static var titleColor = UIColor.blue //.... }
Tất cả chúng đều khai báo là static
và struct
. Để sử dụng thì bạn có thể tham khảo như sau:
let name = App.Text.appName let color = App.Color.mainColor
3.3. Controllers
Thư mục này sẽ chứa các View Controller của project. Tham khảo hình sau
Có một thư mục là Base
. Tai đây chính là các super class
của các Controller sử dụng trong project.
- BaseViewController.swift
- Kế thừa từ UIViewController
- Các View Controller khác sẽ kế thừa lại nó.
- Chứa các function xử lý chung cho tất cả các View Controller con.
setupUI
xử lý các UI lúc khởi tạo View ControllersetupData
xử lý phần Data, load Data, fetch Data ….
import UIKit class BaseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupData() setupUI() } func setupData() {} func setupUI() {} }
- BaseNavigationController.swift
- Tương tự như BaseViewController, nhưng kế thừa trực tiếp từ UINavigationController
- Nếu không làm gì thì bạn có thể để im hoặc xoá đi
import UIKit class BaseNavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }
- BaseTabBarController.swift
- Kế thừa từ UITabBarController và cũng tương tự 2 em trên
import UIKit class BaseTabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }
Lưu ý không tạo file
*.xib
cho cácBase
class này. Vì chúng chỉ mang tính chất làsuper class
. Không có thể hiện của mình. Và dùng kế thừa lại cho các class con.
Sử dụng như sau:
- HomeViewController.swift
- Kế thừa lại
BaseViewController
- Yêu cầu cài đặt 2 function cần thiết:
setupData
&setupUI
- Vì khi khởi tạo xong thì function
viewDidLoad
được chạy. Nó lại gọisuper.viewDidLoad()
, thì ở BaseViewController sẽ gọi 2 functionsetupData
vàsetupUI
. Vì đã override lại chúng rồi, nên 2 function ở lớp con sẽ được.
- Kế thừa lại
import UIKit class HomeViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() } override func setupUI() { title = App.Text.titleHome } override func setupData() { } }
- Xét root của Window
- Thay vì dùng UINavigationController, chúng ta dùng lớp Base của nó vừa mới tạo ra.
let vc = HomeViewController() let navi = BaseNavigationController(rootViewController: vc) window.rootViewController = navi
Tuỳ thuộc vào dự án của bạn có bao nhiêu màn hình. Thì bạn sẽ tạo ra thêm như vậy.
- ViewModel
- Nó sẽ ở cùng thư mục với View / View Controller của bạn.
- Mục đích cho dễ tìm kiếm
- Sử dụng hậu tố
ViewModel
cho đặt tên các class này.
Tham khảo ViewModel cho HomeViewController và HomeCell
3.4. Views
Phần này sẽ chứa các class View phục vụ cho cả project. Chúng không phải là View trong MVC hay MVVM. Không thuộc quản chế của bất cứ class hay Controller nào. Ta có thể chia làm 3 loại sau:
3.4.1. Custom View
Ta sẽ sử dụng rất nhiều các Custom View trong dự án. Ví dụ tham khảo qua hình sau:
Chú ý việc sử dụng hậu tố
View
, cho cách đặt tên file. Để có thể nhận biết nhanh chúng là một custom view.
3.4.2. Cells
Chính là các cell của TableView
& CollectionView
. Để cho dễ tìm thì chúng ta sẽ để chúng ở gần với View Controller của nó.
Sử dụng hậu tố
Cell
để phân biệt chúng với các View khác.
3.4.3. Common Control
Nếu bạn cần phải tạo ra các Control dùng chung cho toàn bộ project thì hay tạo ra nó. Ví dụ cho việc sử dụng các Button và Lable chung.
Ví dụ code
- Button.swift
import UIKit class Button: UIButton { /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ }
- Label.swift
import UIKit class Label: UILabel { /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ }
3.5. Models
Phần này sẽ có rất nhiều thứ, nhiều class, model, file managers, các bộ core … Nhưng ta có thể chia thành 3 nhóm chính:
- Objects
- Manages
- Core
Tham khảo hình sau:
3.5.1. Object & Class
Là thư mục chứa các class object. Là các lớp đối tượng dữ liệu, phục vụ cho nhiều màn hình trong project.
Ví dụ như:
- User.swift
import Foundation final class User { var name: String var age: Int var gender: Bool init(name: String, age: Int, gender: Bool) { self.name = name self.age = age self.gender = gender } }
- Music.swift
import Foundation import UIKit final class Music { var id: String var artistName: String var releaseDate: String var name: String var artworkUrl100: String var thumbnailImage: UIImage? init(json: JSON) { self.id = json["id"] as! String self.artistName = json["artistName"] as! String self.releaseDate = json["releaseDate"] as! String self.name = json["name"] as! String self.artworkUrl100 = json["artworkUrl100"] as! String } }
Ở trên chỉ là ví dụ mang tính chất tham khảo, nên bạn tuỳ ý thêm vào hay bớt ra theo những gì bạn muốn. Cần chú ý:
Từ khoá
final
cho việc khai báo class. Để xác định nó không thể kế thừa. Điều này làm tăng tốc độ build của Swift.
3.5.2. Managers
Thư mục Managers
sẽ chứa các class liên quan tới việc tương tác tập trung của một thư viện, hay 1 class khác, hay 1 thao tác với hệ thống…. Theo ví dụ ở hình trên thì:
- DataManager.swift : xử lý việc tương tác với UserDefault
import Foundation class DataManager { //singleton private static var sharedDataManager: DataManager = { let dataManager = DataManager() return dataManager }() class func shared() -> DataManager { return sharedDataManager } // init private init() {} //open database func read() -> (String, String) { let email = UserDefaults.standard.string(forKey: "email") ?? "" let password = UserDefaults.standard.string(forKey: "password") ?? "" return (email, password) } //save database func save(email: String, password: String) { UserDefaults.standard.set(email, forKey: "email") UserDefaults.standard.set(password, forKey: "password") UserDefaults.standard.synchronize() } }
- FileManager.swift : làm việc với file
- LocationManager.swift : làm việc với thư viện CoreLocation
- AudioManager.swift : làm việc với các file âm thanh
Mình chỉ ví dụ cho 1 file thôi và cũng là mang tính chất tham khảo. Nhưng bạn cần chú ý các điểm sau cho các class Manager:
- Thường sẽ sử dùng chung và phục vụ nhiều ViewController hay ViewModel
- Sử dụng
Singleton Pattern
là chính - Mang tính bao đóng lại các thao tác, chứ không viết thêm nhiều các chức năng mới.
- Đồng nhất trong việc sử dụng call back cho phù hợp:
3.5.3. Service & Core
Phần này như đã trình bày ở bài Core API. Nó mang tính chất lớn hơn phần Manager nhiều, nhưng chưa đủ để thành một framework. Có tính dùng đi dùng lại trong nhiều project. Nên
- Phần Login Model : thường sẽ bất biến trong nhiều project
- Phần Business Model : sẽ thay đổi đối với từng project khác nhau
Qua hình trên, phần Core phục vụ cho tương tác với API được add vào thư mục Models. Và thường sẽ có ít bộ Core được thêm vào nên chúng ta không cần dùng tiền tố Core
để đặt tên cho thư mục.
3.6. Extension
Đây là thư mục chứa phần mở rộng cho các class có sẵn và các class của hệ thống.
Các file này không phải là class
. Mà là các extension
của các class. Mục đích là thêm các function mà không phải kế thừa lại class đó. Mục đích để phục vụ cho project, tránh việc phải viết đi viết lại nhiều lần.
Ví dụ:
- Data.Ext.swift
- Biết Data thành JSON
import Foundation typealias JSON = [String: Any] extension Data { func toJSON() -> JSON { var json: [String: Any] = [:] do { if let jsonObj = try JSONSerialization.jsonObject(with: self, options: .mutableContainers) as? JSON { json = jsonObj } } catch { print("JSON casting error") } return json } }
- Color.Ext.swift
- Thêm hàm khởi tạo 1 UIColor từ hex string
import UIKit extension UIColor { convenience init(hexString: String) { let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int = UInt64() Scanner(string: hex).scanHexInt64(&int) let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) case 6: // RGB (24-bit) (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) case 8: // ARGB (32-bit) (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) default: (a, r, g, b) = (255, 0, 0, 0) } self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) } }
- String.Ext.swift
- trim string
- encode & decode
- base64
import Foundation enum Process { case encode case decode } extension String { var trimmed: String { return trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) } func base64(_ method: Process) -> String? { switch method { case .encode: guard let data = data(using: .utf8) else { return nil } return data.base64EncodedString() case .decode: guard let data = Data(base64Encoded: self) else { return nil } return String(data: data, encoding: .utf8) } } } extension String { /// Initializes an NSURL object with a provided URL string. (read-only) var url: URL? { return URL(string: self) } /// The host, conforming to RFC 1808. (read-only) var host: String { if let url = url, let host = url.host { return host } return "" } }
Muốn thêm gì thêm thì bạn hãy thêm vào. Tuy nhiên, bạn đừng lạm dụng nó quá nhiều. Vì nhiều function hay extension không cần thiết thì cũng không cần tạo ra.
3.7. Framework & Libraries
Là các thư viện từ bên ngoài được thêm vào project. Chúng ta có 2 cách thêm:
- Thông qua CocoaPod:
- Nhanh gọn lẹ, không cần suy nghĩ nhiều
- Tuy nhiên mỗi lầm thay đổi Podfile thì phải chạy lại lệnh
pod install
- Đồng nhất môi trường dev giữa các thành viên
- Thông qua việc thêm file trực tiếp vào project.
- Phải thêm file bằng tay. Thêm vào thư mục
Libs
- Nhanh nhưng không gọn lẹ được
- Khó khăn nếu thư viện bắt bạn phải config nhiều thứ trong settings
- Có thể custom một cách dễ dàng, trực tiếp edit code của thư viện
- Phải thêm file bằng tay. Thêm vào thư mục
Ngoài ra, có thêm một số dịch vụ tương tự như CocoaPods:
Bạn có thể cài cùng lúc 3 dịch vụ trên vào chung project vẫn không sao. Mỗi cái có ưu và nhược riêng.
Khi thêm thư viện bạn cần chú ý các vấn đề sau:
- Bản quyền của thư viện
- Độ phổ biến của thư viện
- Thời gian update hay còn được support hay không
- Version của thư viện tương thích với môi trường của project hay không
Nên đọc
README
của thư viện trước khi sử dụng.
Ví dụ cho Almofire
3.8. Resources
Phần này sẽ chứa các tài nguyên cho project sử dụng. Bao gồm các file như:
- Hình ảnh
- Âm thanh
- Video
- Font chữ
Vì giai đoạn gần đây thì file Assets.xcassets
của Xcode đã rất mạnh mẽ. Support cho bạn rất nhiều, như:
- Images
- Splash
- Scale image
- Vector & Render
- Apple Device
- Text
- ARKit
- GameKit
- icon app
- Stickers app
- Size image (1x, 2x, 3x)
- Light & Dark Mode
- …
Nên bạn nên tận dụng nó tối đa nhất có thể. Lúc nào không sử dụng được nữa thì hãy thêm file trực tiếp vào project của bạn.
Cuối cùng, toàn bộ bài viết mang tính chất tham khảo. Bạn hãy tạo ra một template riêng của bạn. Xem lại toàn bộ cây thư mực của project và có thể checkout mã nguồn tại đây. Chúc bạn thành công!
Tạm kết
- Các thành phần cần thiết trong project
- Ý nghĩa các thành phần
- Một số quy tắc áp dụng như: đặt tên class, đối tượng, hậu tố, tiền tố …
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)