- Correctness
- Using SwiftLint
- Naming
- Spacing
- Comments
- Classes and Structures
- Function Declarations
- Function Calls
- Closure Expressions
- Types
- Functions vs Methods
- Memory Management
- Access Control
- Control Flow
- Golden Path
- Semicolons
- Parentheses
- Multi-line String Literals
- No Emoji
- No #imageLiteral or #colorLiteral
- Copyright Statement
- Smiley Face
- References
Cố gắng để code của bạn khi biên dịch không có cảnh báo
Tìm hiểu SwiftLint tại đây.
-
Cố gắng diễn đạt rõ ràng (ưu tiên sự rõ ràng hơn là ngắn gọn).
-
Sử dụng camelCase thay vì snake_case.
-
Sử dụng 'UpperCamelCase' cho
Objects
vàProtocols
, 'lowerCamelCase' cho những thứ còn lại. -
Đảm bảo sự rõ ràng và hiệu quả bằng cách sử dụng những từ cần thiết, tránh sử dụng các từ không cần thiết và lặp lại.
-
Sử dụng tên theo vai trò, không theo kiểu dữ liệu (vd: numberOfLists thay vì
intCounter) có lẽ vậy, câu này hơi khó hình dung 🥶. -
Bắt đầu tên của Factory Methods với
make
, vd:x.makeIterator()
. -
Đặt tên cho phương thức dựa trên tác động của chúng.
- Thêm đuôi '-ed' hoặc '-ing' cho phương thức non-mutating
Ví dụ:
class TextEditor { var text = "" func capitalized() -> String { return text.uppercased() } func appending(_ string: String) -> String { return text + string } }
-
Tên phương thức (danh từ) thì thêm
form
đằng trước nếu đó là phương thức mutating, vd:y.formUnion(z)
;c.formSuccessor(&i)
. -
Kiểu
bool
nên được đặt như một sự khẳng định, vd:isEmpty()
-
Các
protocol
với mục đích để diễn tả đối tượng nên được đặt là một danh từ.
Ví dụ:
protocol Vehicle { var numberOfWheels: Int { get } var color: String { get } } class Car: Vehicle { var numberOfWheels: Int { return 4 } var color: String { return "red" } } class Bike: Vehicle { var numberOfWheels: Int {return 2} var color: String { return "blue"} }
- Các
protocol
với mục đích để diễn tả khả năng của đối tượng nên kết thúc bằng-able
,-ible
.
Ví dụ:
protocol Printable { func print() } class Document: Printable { func print() { // In ra thông tin của tài liệu } }
-
Sử dụng thuật ngữ mà mọi dev đều thường dùng, không nên gây hoảng sợ cho người mới 🙂, vd: sử dụng
users
thay vìclients
. Tuy nhiên thì vẫn còn tuỳ vào trường hợp, mình cho ví dụ để dễ hình dung thôi. -
Tránh sử dụng viết tắt (nếu có thể)
-
Đặt tên theo cách đặt tên của hệ thống, vd:
viewDidLoad()
chẳng hạn. -
Ưu tiên sử dụng methods và properties hơn là các hàm tự do, vd:
user.login()
,login(user). -
Đặt cùng tên cho các phương thức có cùng ý nghĩa.
-
Tránh việc định nghĩa nhiều phương thức trùng tên với kiểu trả về khác nhau (ở đây khắc phục với Generics)
Generics là kiểu đại diện cho bất kỳ kiểu dữ liệu nào trong Swift.
- Sử dụng tốt tên tham số miêu tả, vd:
func calculateArea(width: Double, height: Double)
. - Sử dụng các tham số có giá trị mặc định (nếu có thể) để làm giảm bớt số lượng tham số khi gọi hàm, vd:
func printName(firstName: String, lastName: String = "Ngo")
.
When creating custom delegate methods, an unnamed first parameter should be the delegate source.
Nên:
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
Không nên:
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
Trình biên dịch có thể tự suy luận ra ngữ cảnh để đoạn code ngắn gọn hơn
Nên:
let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
Không nên:
let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
Nên được đặt tên ở dạng UpperCamelCase, khi tên loại không có mối quan hệ sử dụng chữ cái viết hoa như
T
,U
,V
.
Nên:
struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)
Không nên:
struct Stack<T> { ... }
func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)
Sử dụng cách viết Anh Mỹ để phù hợp với API của Apple
Nên:
let color = "pink"
Không nên:
let colour = "pink"
Sử dụng
extension
để tổ chức code thành các khối chức năng, mỗiextension
nên được phân chia bằng//MARK:
- giúp dễ dàng khi đọc code.
Nên:
class MyViewController: UIViewController {
// class stuff here
}
// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
// table view data source methods
}
// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
// scroll view delegate methods
}
Không nên:
class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// all methods
}
- Đoạn code không được sử dụng, bao gồm code mẫu của Xcode, các comment trong thân hàm nên được loại bỏ.
- Các phương thức chỉ gọi lại lớp cha cũng cần được loại bỏ.
Nên:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
Không nên:
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return Database.contacts.count
}
- Chỉ nên
import
module mà source file yêu cầu, không nênimport UIKit
khi màimport Foundation
đã đủ, tương tự khôngimport Foundation
khi đãimport UIKit
.
Nên:
import UIKit
var view: UIView
var deviceModels: [String]
Nên:
import Foundation
var deviceModels: [String]
Không nên:
import UIKit
import Foundation
var view: UIView
var deviceModels: [String]
Không nên:
import UIKit
var deviceModels: [String]
- Thụt đầu dòng bằng 2 spaces thay vì tab để tiết kiệm không gian, nhưng mình thấy Xcode mặc định (1 tab = 2 spaces) nên phần này không cần quan tâm lắm 😁.
- Đối với phần dấu ngoặc nhọn của
if/else/switch/while/etc
.
Nên:
if user.isHappy {
// Do something
} else {
// Do something else
}
Không nên:
if user.isHappy
{
// Do something
}
else {
// Do something else
}
Tip: bạn có thể Re-Indent bằng cách chọn đoạn code hoặc chọn tất cả bằng
cmd + A
sau đóControl + I
hoặcEditor ▸ Structure ▸ Re-Indent
trên Menu
- Nên có một dòng trống để phân cách các phương thức.
- Trong phương thức phân cách các chức năng bằng một dòng trắng, nhưng nếu có nhiều khoảng trắng thì bạn nên cấu trúc lại thành nhiều phương thức có chức năng riêng.
- Không nên có dòng trống sau dấu ngoặc nhọn mở hoặc trước dấu ngoặc nhọn đóng.
- Dấu ngoặc đơn đóng không được tự xuất hiện trên một dòng.
Nên:
let user = try await getUser(
for: userID,
on: connection)
Không nên:
let user = try await getUser(
for: userID,
on: connection
) //<-- xuất hiện một mình trên 1 dòng
- Dấu
:
luôn không có khoảng cách ở bên trái, và 1 space ở phía bên phải. Ngoại lệ trong cú pháp toán tử bậc 3 (toán tử 3 ngôi)age >= 18 ? "Yes" : "No"
hay một Dictionary rỗng[:]
và#selector
cú phápaddTarget(_:action:)
(vì trường hợp này 2 phía đều 0 space).
Nên:
class TestDatabase: Database {
var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
Không nên:
class TestDatabase : Database {
var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}
- Trên 1 dòng nên được gói gọn với khoảng 70 ký tự.
- Tránh các khoảng trắng ở cuối dòng.
- Ở cuối mỗi source file nên có thừa 1 dòng trống.
Tránh sử dụng comments kiểu C
/* ... */
, thay vào đó hãy sử dụng//
hoặc///
.
- Tìm hiểu và phân biệt chúng tại đây
Đây là một ví dụ khởi tạo class nên dùng:
class Circle: Shape {
var x: Int, y: Int
var radius: Double
var diameter: Double {
get {
return radius * 2
}
set {
radius = newValue / 2
}
}
init(x: Int, y: Int, radius: Double) {
self.x = x
self.y = y
self.radius = radius
}
convenience init(x: Int, y: Int, diameter: Double) {
self.init(x: x, y: y, radius: diameter / 2)
}
override func area() -> Double {
return Double.pi * radius * radius
}
}
extension Circle: CustomStringConvertible {
var description: String {
return "center = \(centerString) area = \(area())"
}
private var centerString: String {
return "(\(x),\(y))"
}
}
Áp dụng đúng theo các quy tắc đã nhắc ở trên.
Tránh sử dụng
self
nếu trình biên dịch không yêu cầu.
- Sử dụng trong các
closure @escaping
hoặc trong các khởi tạo để làm rõthuộc tính
vàđối số
.
Tóm lại nếu biên dịch được mà không cần
self
thì vứtself
đi.
Để ngắn gọn, nếu một computed property
là read-only (chỉ đọc) thì có thể bỏ qua get
, chỉ phải viết đầy đủ khi có cả get
lẫn set
.
Nên:
var diameter: Double {
return radius * 2
}
Không nên:
var diameter: Double {
get {
return radius * 2
}
}
Khai báo func
ngắn gọn trên một dòng, bao gồm cả dấu ngoặc nhọn mở.
func reticulateSplines(spline: [Double]) -> Bool {
// reticalate code goes here
}
Đối với func
dài với nhiều tham số thì đặt mỗi tham số ở một dòng mới như sau:
func reticulateSplines(
spline: [Double],
adjustmentFactor: Double,
translateConstant: Int,
comment: String
) -> Bool {
// reticulate code goes here
}
Cách sử dụng (Void)
:
Nên:
func updateConstraints() -> Void {
// magic happens here
}
typealias CompletionHandler = (result) -> Void
Không nên:
func updateConstraints() -> () {
// magic happens here
}
typealias CompletionHandler = (result) -> ()
Đây là cách gọi một func
phù hợp:
let success = reticulateSplines(splines)
let success = reticulateSplines(
spline: splines,
adjustmentFactor: 1.3,
translateConstant: 2,
comment: "normalize the display")
Nên:
UIView.animate(withDuration: 1.0) {
self.myView.alpha = 0
}
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
}, completion: { finished in
self.myView.removeFromSuperview()
})
Không nên:
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
})
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
}) { f in
self.myView.removeFromSuperview()
}
Đối với các single-expression closure có ngữ cảnh rõ ràng, sử dụng mà không cần return
attendeeList.sort { a, b in
a > b
}
Một ví dụ sử dụng đối số vô danh $0
,$1
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90)
let value = numbers
.map {$0 * 2}
.filter {$0 > 50}
.map {$0 + 10}
Luôn sử dụng các kiểu dữ liệu và biểu thức có sẵn của Swift.
Nên:
let width = 120.0 // Double
let widthString = "\(width)" // String
Nên:
let width = 120.0 // Double
let widthString = (width as NSNumber).stringValue // String
Không nên:
let width: NSNumber = 120.0 // NSNumber
let widthString: NSString = width.stringValue // NSString
Các hằng số được khai báo với từ khoá let
và các biến số được khai báo với từ khoá var
, luôn dùng let
nếu giá trị của biến đó không thay đổi.
Tip: Luôn khai báo với từ khoá
let
và chỉ chuyển sangvar
khi trình biên dịch kêu gào 😂.
Nên:
enum Math {
static let e = 2.718281828459045235360287
static let root2 = 1.41421356237309504880168872
}
let hypotenuse = side * Math.root2
Không nên:
let e = 2.718281828459045235360287 // pollutes global namespace
let root2 = 1.41421356237309504880168872
let hypotenuse = side * root2 // what is root2?
Lợi ích của việc sử dụng
enum
là nếu không có trường hợp đó nó sẽ không được khởi tạo một cách tuỳ ý.
- Các
static method
và cáctype property
có cách hoạt động tương tự như hàm toàn cục và biến toàn cục nên phải thận trọng khi sử dụng.
- Khai báo biến hoặc hàm có kiểu trả về của hàm là
optional
dùng dấu?
để có thể chấp nhận giá trịnil
.
Sử dụng optional chaining truy cập vào giá trị optional
:
textContainer?.textLabel?.setNeedsDisplay()
Sử dụng optional binding để truy cập và thực hiện nhiều thao tác:
if let textContainer = textContainer {
// do many things with textContainer
}
Swift 5.7 giới thiệu cú pháp ngắn gọn hơn:
if let textContainer {
// do many things with textContainer
}
Khi đặt tên cho biến và thuộc tính tuỳ chọn, cần tránh đặt theo kiểu optionalString
hay maybeView
vì tính tuỳ chọn của chúng đã được khai báo rõ ràng trong kiểu chữ liệu.
Nhưng đối với optional binding, thì có thể sử dụng tên theo kiểu unwrappedView
hay actualLabel
.
Nên:
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, let volume = volume {
// do something with unwrapped subview and volume
}
// another example
resource.request().onComplete { [weak self] response in
guard let self = self else { return }
let model = self.updateModel(response)
self.updateUI(model)
}
Không nên:
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
if let realVolume = volume {
// do something with unwrappedSubview and realVolume
}
}
// another example
UIView.animate(withDuration: 2.0) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.alpha = 1.0
}
Xem xét sử dụng khởi tạo lười biếng (lazy initialization) để kiểm soát chính xác thời gian sống của đối tượng. Điều này đặc biệt đúng đối với cách UIViewController tải view lười biếng. Bạn có thể sử dụng một closure
được gọi ngay lập tức {}()
hoặc gọi một private factory method
.
Ví dụ:
lazy var locationManager = makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
Trong ví dụ này không cần dùng tới [unowned self]
vì không tạo ra vòng lặp giữa các đối tượng.
CLLocationManager
có tác động đến việc hiển thị giao diện người dùng để yêu cầu quyền truy cập vị trí, vì vậy sử dụng khởi tạo lười biếng giúp kiểm soát tốt hơn trong trường hợp này.
Để code gọn gàng hơn, hãy để trình biên dịch tự suy ra kiểu dữ liệu cho hằng số hoặc biến số đó. Khi cần thiết, hãy chỉ đỉnh kiểu cụ thể như CGFloat
, Int16
.
Nên:
let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5
Không nên:
let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
var names = [String]()
Đối với các mảng và từ điển rỗng, nên chú thích kiểu dữ liệu (type annotation) rõ ràng. (Đối với một mảng hoặc từ điển được gán cho một biểu thức nhiều dòng lớn, hãy sử dụng chú thích kiểu dữ liệu.)
Nên:
var names: [String] = []
var lookup: [String: Int] = [:]
Không nên:
var names = [String]()
var lookup = [String: Int]()
Sử dụng phiên bản ngắn gọn cho khai báo kiểu thay vì dùng cú pháp Generics đầy đủ.
Nên:
var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
Không nên:
var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
Các hàm tự do (Free functions), không được liên kết với một lớp hay kiểu dữ liệu nào, nên được sử dụng một cách hạn chế. Khi có thể, nên ưu tiên sử dụng phương thức thay vì hàm tự do. Việc này giúp cho code dễ đọc và dễ tìm kiếm hơn.
Hàm tự do thường phù hợp nhất khi chúng không liên quan đến bất kỳ kiểu dữ liệu hay thực thể cụ thể nào.
let sorted = items.mergeSorted() // easily discoverable
rocket.launch() // acts on the model
let sorted = mergeSort(items) // hard to discover
launch(&rocket)
let tuples = zip(a, b) // feels natural as a free function (symmetry)
let value = max(x, y, z) // another free function that feels natural
Code kể cả demo, không phải sản phẩm thực tế thì không nên tạo một reference cycles, ngăn ngừa tham chiếu mạnh bằng [weak self]
, [unowned self]
hoặc sử dụng struct
, enum
.
Kéo dài lifetime của đối tượng bằng cú pháp [weak self]
và guard let self = self else { return }
. [weak self]
thường được sử dụng nhiều hơn [unowned self]
.
Phân biệt [weak self]
, [unowned self]
:
-
Giống nhau: Đều giữ cho đối tượng sống lâu hơn, hoàn thành nhiệm vụ trước khi được giải phóng khỏi bộ nhớ.
-
Khác nhau:
-
[weak self]
trả về mộtoptional
nên khi dùng cần phải kiểm tra xem có nil không trước khi truy cập vào đối tượng. -
[unowned self]
trả về mộtnon-optional
nên nếu đối tượng bị giải phóng trước khi closure hoàn thành thì dẫn đến crash ứng dụng.
resource.request().onComplete { [weak self] response in
guard let self = self else {
return
}
let model = self.updateModel(response)
self.updateUI(model)
}
// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
// deallocate could happen between updating the model and updating UI
resource.request().onComplete { [weak self] response in
let model = self?.updateModel(response)
self?.updateUI(model)
}
Deallocate
đề cập đến việc giải phóng bộ nhớ của một đối tượng trong Swift khi không còn tham chiếu nào đến đối tượng đó nữa, ở ví dụ Không nên thứ hai, đối tượng vẫn có thể bị giải phóng ngay trong khi đang cập nhật lại UI hoặc model.
Ghi đầy đủ access control
có thể làm sao lãng đến chủ đề chính, và không cần thiết phải làm vậy. Tuy nhiên nếu sử dụng private
và fileprivate
một cách phù hợp sẽ làm rõ ràng hơn và tăng tính đóng gói. Nên ưu tiên sử dụng private
hơn và chỉ dùng fileprivate
khi mà trình biên dịch yêu cầu.
Chỉ sử dụng open
, public
và internal
khi bạn cần một thông số kiểm soát truy cập đầy đủ.
Các access control
sẽ đứng sau từ khóa static
hoặc các thuộc tính như @IBAction
, @IBOutlet
và @discardableResult
.
Nên:
private let message = "Great Scott!"
class TimeMachine {
private dynamic lazy var fluxCapacitor = FluxCapacitor()
}
Không nên:
fileprivate let message = "Great Scott!"
class TimeMachine {
lazy dynamic private var fluxCapacitor = FluxCapacitor()
}
Ưu tiên sử dụng vòng lặp
for-in
hơn làwhile-condition-increment
.
Nên:
for _ in 0..<3 {
print("Hello three times")
}
for (index, person) in attendeeList.enumerated() {
print("\(person) is at position #\(index)")
}
for index in stride(from: 0, to: items.count, by: 2) {
print(index)
}
for index in (0...3).reversed() {
print(index)
}
Không nên:
var i = 0
while i < 3 {
print("Hello three times")
i += 1
}
var i = 0
while i < attendeeList.count {
let person = attendeeList[i]
print("\(person) is at position #\(i)")
i += 1
}
Chỉ dùng toán tử 3 ngôi khi nó làm tăng sự gọn gàng, dễ hiểu của code, dưới đây là cách nên dùng và không nên dùng:
Nên:
let value = 5
result = value != 0 ? x : y
let isHorizontal = true
result = isHorizontal ? x : y
Không nên:
result = a > b ? x = c > d ? c : d : y // thảm hoạ
Khi code với câu điều kiện nếu lồng các câu lệnh if
với nhau sẽ làm đoạn code bị rối và khó đọc và câu lệnh guard
sinh ra để giải quyết vấn đề này.
Nên:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
guard let context = context else {
throw FFTError.noContext
}
guard let inputData = inputData else {
throw FFTError.noInputData
}
// use context and input to compute the frequencies
return frequencies
}
Không nên:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
if let context = context {
if let inputData = inputData {
// use context and input to compute the frequencies
return frequencies
} else {
throw FFTError.noInputData
}
} else {
throw FFTError.noContext
}
}
Khi unwrapped
nhiều optional với guard
hoặc if let
, tối thiểu hóa các "lồng nhau" bằng cách sử dụng phiên bản kết hợp nếu có thể, dưới đây là ví dụ:
Nên:
guard
let number1 = number1,
let number2 = number2,
let number3 = number3
else {
fatalError("impossible")
}
// do something with numbers
Không nên:
if let number1 = number1 {
if let number2 = number2 {
if let number3 = number3 {
// do something with numbers
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
Swift không yêu cầu ;
sau mỗi đoạn code của bạn, bạn chỉ sử dụng nếu trên một dòng có nhiều đoạn code. Nhưng tuy nhiên bạn không nên kết hợp nhiều câu lệnh trên cùng một dòng.
Nên:
let swift = "not a scripting language"
Không nên:
let swift = "not a scripting language";
Việc sử dụng dấu ngoặc đơn quanh điều kiện là không bắt buộc và nên loại bỏ nó.
Nên:
if name == "Hello" {
print("World")
}
Không nên:
if (name == "Hello") {
print("World")
}
Dấu ngoặc đơn có thể làm code của bạn dễ đọc hơn trong trường hợp này :
let playerMark = (player == current ? "X" : "O")
Khi làm việc với văn bản có nhiều dòng bạn nên sử dụng """
.
Nên:
let message = """
You cannot charge the flux \
capacitor with a 9V battery.
You must use a super-charger \
which costs 10 credits. You currently \
have \(credits) credits available.
"""
Không nên:
let message = """You cannot charge the flux \
capacitor with a 9V battery.
You must use a super-charger \
which costs 10 credits. You currently \
have \(credits) credits available.
"""
Không nên:
let message = "You cannot charge the flux " +
"capacitor with a 9V battery.\n" +
"You must use a super-charger " +
"which costs 10 credits. You currently " +
"have \(credits) credits available."
Đừng sử dụng biểu tượng trong
project
của bạn, mặc dù nó dễ thương. Nên sử dụngemoji
nếu viếtmarkdown
.
Thay vào đó hãy sử dụng
UIColor(red:green:blue)
,UIImage(imageLiteralResourceName:)
,...
Nên:
:]
Không nên:
:)
Nụ cười
:)
không thành tâm cho lắm 😂.