本篇文章介紹如何用最規范的形式書寫Swift。作者力求團隊內部合作的時候代碼風格統一,減少模糊代碼,盡量美觀,保持最佳實踐。
基本
- 縮進使用Tab而不是Space
- 文件結尾添加一行空行
- 行尾不建議使用『;』
- 正括號不換行,與之前的字母空一個空格
if user.isHappy {
// Do something
} else {
// Do something else
}
- 『:』,『,』左邊不空格,右邊空一個
- 『[』,『]』,『(』,『)』內部不建議空格
let a: [String] = ["1", "b"]
- 單行注釋『//』后面空一格
- 使用『// MARK:』,『// TODO:』,『// FIXME:』來注釋
// MARK: 添加鍵盤方法
// MARK: - UITableViewDelegate (帶橫線版本會被編譯成水平分割線)
// TODO: zjs, 多線程保護
// FIXME: 缺少類型判斷
- 不使用namespace前綴(NSXXX,RRDXXX),swift會根據framework自動添加namespace
進階
盡量使用let代替var
Preferred:
let tempValue = "tempValue"
Not Preferred:
var temp = "tempValue"
推薦使用類型推斷
Preferred:
let message = "Click the button"
let currentBounds = computeViewBounds()
var names = [String]()
let maximumWidth: CGFloat = 106.5
Not Preferred:
let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
var names: [String] = []
推薦使用便捷語法
Preferred:
var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
Not Preferred:
var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
盡量使用String代替NSString
Preferred:
let width = 120.0 // Double
let widthString = (width as NSNumber).stringValue // String
Not Preferred:
let width: NSNumber = 120.0 // NSNumber
let widthString: NSString = width.stringValue // NSString
全局變量命名使用UpperCamelCase
Preferred:
public let MaximumWidgetCount = 100
Not Preferred:
public let maximumWidgetCount = 100
public let MAX_WIDGET_COUNT = 100
枚舉類型命名使用UpperCamelCase
enum Shape {
case Rectangle
case Square
case Triangle
case Circle
}
盡早的使用guard判斷退出
Preferred:
func login(name: String?, password: String?) {
guard let name = name, password = password else {return}
// ...do something
}
Not Preferred:
func login(name: String?, password: String?) {
// ...do something
if let name = name, password = password {
// ...do something
} else {return}
}
盡量使用as?代替as!
Preferred:
func parse(dict: [String: AnyObject]) {
guard let data = dict["data"] as? [String: AnyObject] else {return}
// ...do something
}
Not Preferred:
func parse(dict: [String: AnyObject]) {
let data = dict["data"] as! [String: AnyObject]
// ...do something
}
盡量使用Optional Chaining
Preferred:
func pop(viewController: UIViewController?) {
viewController?.navigationController?.popViewControllerAnimated(true)
}
Not Preferred:
func pop(viewController: UIViewController?) {
if let viewController = viewController {
// ...do something
}
}
read-only properties 推薦使用隱式的get方法
Preferred:
var myGreatProperty: Int {
return 4
}
Not Preferred:
var myGreatProperty: Int {
get {
return 4
}
}
對外使用的類顯示標注public和private
public var whoopsGlobalState: Int
private func doTheThings(things: [Thing]) {}
private(set) var offset:Int = 0;
訪問類變量時不省略self
Preferred:
class History {
var events: [Event] = [Event]()
func rewrite() {
self.events = []
}
}
Not Preferred:
class History {
var events: [Event]
func rewrite() {
events = []
}
}
括號表達式
Preferred:
UIView.animateWithDuration(1.0) {
self.myView.alpha = 0
}
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
},
completion: { finished in
self.myView.removeFromSuperview()
}
)
Not Preferred:
UIView.animateWithDuration(1.0, animations: {
self.myView.alpha = 0
})
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
}) { f in
self.myView.removeFromSuperview()
}
Struct初始化
Preferred:
let bounds = CGRect(x: 40, y: 20, width: 120, height: 80)
let centerPoint = CGPoint(x: 96, y: 42)
Not Preferred:
let bounds = CGRectMake(40, 20, 120, 80)
let centerPoint = CGPointMake(96, 42)
不需要時盡量省略參數信息
Preferred:
struct Composite<T> {
…
func compose(other: Composite) -> Composite {
return Composite(self, other)
}
}
Not Preferred:
struct Composite<T> {
…
func compose(other: Composite<T>) -> Composite<T> {
return Composite<T>(self, other)
}
}
自定義運算符加空格
Preferred:
func <| (lhs: Int, rhs: Int) -> Int
func <|< <A>(lhs: A, rhs: A) -> A
Not Preferred:
func <|(lhs: Int, rhs: Int) -> Int
func <|<<A>(lhs: A, rhs: A) -> A
最佳實踐
單例
class Account {
static let sharedInstance = Account()
}
推薦使用extension實現protocol
Prefrerred:
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
}
Not Preferred:
class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// all methods
}
盡量使用Struct而不是Class
使用Struct的情況:
- 賦值操作默認是copy操作
- 類型不屬于AnyObject,屬于Any
- 使用protocol+extension代替繼承
protocol Vehicle {
var numberOfWheels: Int { get }
}
func maximumTotalTirePressure(vehicle: Vehicle, pressurePerWheel: Float) -> Float {
return pressurePerWheel * Float(vehicle.numberOfWheels)
}
struct Bicycle: Vehicle {
let numberOfWheels = 2
}
struct Car: Vehicle {
let numberOfWheels = 4
}
使用Class的情況:
- 賦值操作是指針復制操作
- 屬于AnyObject
- 盡量使用protocol+extension代替繼承
class默認添加final關鍵字
非基礎類聲明默認添加final關鍵字,如果后面有人需要繼承再刪除。
final class History {
var events: [Event] = [Event]()
}
lazy寫法
final class MyViewController: UIViewController {
lazy var mainView: UIView = {
let view = UIView()
... // do somethig
return view
}()
}
block weakself
func myFuction() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {[weak self] () -> Void in
guard let `self` = self else {return}
self.backgroundColor = UIColor.blackColor()
}
Json解析
if let JSONObject = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [[String: AnyObject]],
let username = (JSONObject[0]["user"] as? [String: AnyObject])?["name"] as? String {
// There's our username
}
lazy sequence
lazy方式使用map,flatmap方法。
使用lazy方式每次訪問都會進行計算。
let array = Array(1...1000)
let mapArray = array.lazy.map {$0 + 1}
print(mapArray[2]) // 訪問時執行map運算
參考資料
https://github.com/raywenderlich/swift-style-guide
https://github.com/github/swift-style-guide
http://nshipster.cn/swift-documentation/
http://swift.gg/2016/03/25/being-lazy/