注:以下皆為翻譯,如有錯誤或疏漏,請指正。謝謝?
簡介
(raywenderlich 版)
您看到的這份規范可能與其他的有所不同,因為這份關注點是web顯示和打印。我們創建這份規范已保證我們書中的代碼,教程和 sdk 一致。
Objective-C 代碼規范:Objective-C Style Guide.
Table of Contents(目錄)
- Correctness----正確性
- Naming----命名
- Code Organization----代碼組織
- Spacing----空格
- Comments----注釋
- Classes and Structures----類和結構體
- Function Declarations----函數定義
- Closure Expressions----閉包表達式
- Types----類型
- Functions vs Methods----函數和方法
- Memory Management----內存管理
- Access Control----訪問控制
- Control Flow----控制流
- Golden Path----黃金路線
- Semicolons----分號
- Parentheses----圓括號
- Copyright Statement
- Smiley Face
- Credits
<a id="correctness"></a>
Correctness(正確性)
將 warnings 視為 errors. 比如不要用 ++ 、-- 或 c 風格的 循環 或 string 型的 selectors.
<a id="naming"></a>
Naming(命名)
classes, structures, enumerations and protocols 等類型名字以首字母大寫駝峰命名。變量、方法名以小寫駝峰方式命名
Preferred(推薦):
private let maximumWidgetCount = 100
class WidgetContainer {
var widgetButton: UIButton
let widgetHeightPercentage = 0.85
}
Not Preferred(不推薦):
let MAX_WIDGET_COUNT = 100
class app_widgetContainer {
var wBut: UIButton
let wHeightPct = 0.85
}
盡量避免“簡稱”和“縮略詞”,通用縮略詞應該整體大寫或整體小寫
Preferred(推薦)
let urlString: URLString
let userID: UserID
Not Preferred(不推薦)
let uRLString: UrlString
let userId: UserId
初始化方法或其他方法,每個參數前都應該有明確說明。
func dateFromString(dateString: String) -> NSDate
func convertPointAt(column column: Int, row: Int) -> CGPoint
func timedAction(afterDelay delay: NSTimeInterval, perform action: SKAction) -> SKAction!
// 調用方式:
dateFromString("2014-03-14")
convertPointAt(column: 42, row: 13)
timedAction(afterDelay: 1.0, perform: someOtherAction)
遵循 Apple 官方習俗為方法命名。
class Counter {
func combineWith(otherCounter: Counter, options: Dictionary?) { ... }
func incrementBy(amount: Int) { ... }
}
<a id="protocols"></a>
Protocols(委托)
遵循Apple's API Design Guidelines。 protocols
描述某物的應該是名詞。如:Collection
, WidgetFactory
。
protocols是描述能力的應該以-ing, -able或 -ible結尾。如: Equatable
, Resizing
。
<a id="enumerations"></a>
Enumerations(枚舉)
遵循 swift 3 Apple's API Design Guidelines
。每個枚舉值用小寫字母開始。
enum Shape {
case rectangle
case square
case rightTriangle
case equilateralTriangle
}
<a id="prose"></a>
Prose
When referring to functions in prose (tutorials, books, comments) include the required parameter names from the caller's perspective or _
for unnamed parameters. Examples:
Call
convertPointAt(column:row:)
from your owninit
implementation.If you call
dateFromString(_:)
make sure that you provide a string with the format "yyyy-MM-dd".If you call
timedAction(afterDelay:perform:)
fromviewDidLoad()
remember to provide an adjusted delay value and an action to perform.You shouldn't call the data source method
tableView(_:cellForRowAtIndexPath:)
directly.
This is the same as the #selector
syntax. When in doubt, look at how Xcode lists the method in the jump bar – our style here matches that.

<a id="class-prefixes"></a>
Class Prefixes(Class 前綴)
swift 被自動包含了 module 前綴,你不需要添加額外前綴。如果兩個不同的 module 的兩個名字有沖突,你可以通過在 module 前添加前綴來消除該沖突。當然,僅當 module 名稱可能沖突的時候才為他特定設置一個名稱(這種情況應該很少見)。
import SomeModule
let myClass = MyModule.UsefulClass()
<a id="selectors"></a>
Selectors(選擇器)
Preferred(推薦):
let sel = #selector(viewDidLoad)
Not Preferred(不推薦):
let sel = #selector(ViewController.viewDidLoad)
<a id="generics"></a>
Generics(泛型)
泛型參數應該描述清楚所規定的泛型。當不確定泛型類型是才使用傳統的大寫字母 如T
, U
, or V
表示泛型。
Preferred(推薦):
struct Stack<Element> { ... }
func writeTo<Target: OutputStream>(inout target: Target)
func max<T: Comparable>(x: T, _ y: T) -> T
Not Preferred(不推薦):
struct Stack<T> { ... }
func writeTo<target: OutputStream>(inout t: target)
func max<Thing: Comparable>(x: Thing, _ y: Thing) -> Thing
<a id="language"></a>
Language(語言)
用美式英語拼寫才符合Apple's API
。
Preferred(推薦):
let color = "red"
Not Preferred(不推薦):
let colour = "red"
<a id="code-organization"></a>
Code Organization(代碼組織)
Use extensions to organize your code into logical blocks of functionality. Each extension should be set off with a // MARK: -
comment to keep things well-organized.
<a id="protocol-conformance"></a>
Protocol Conformance
每個 protocol 的實現分別對應一個 extension 的方式實現。
Preferred(推薦):
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
}
由于編譯器不允許一個類重新申明 protocol 實現,因此,不是一直要求每個 protocols 都要分離出一個 extension.特別是當該 class 是個最終 class 或該 class 方法很少。
對UIKit view controllers,將lifecycle, custom accessors, and IBAction等方法單獨放在一個 class extension 中。
<a id="unused-code"></a>
Unused Code(無用代碼)
無用的code ,包括 Xcode 注釋都應該被移除。空方法應該被移除。
Not Preferred(不推薦):
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(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
}
Preferred(推薦):
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
<a id="minimal-imports"></a>
Minimal Imports(最少引入)
不要引用UIKit
和 Foundation
<a id="spacing"></a>
Spacing(空格)
- 用4個空格而不是 tabs調整對其,xcode 中設置如下:


- 用命令Ctr+I重新排列代碼。
Preferred(推薦):
if user.isHappy {
// Do something
} else {
// Do something else
}
Not Preferred(不推薦):
if user.isHappy
{
// Do something
}
else {
// Do something else
}
每兩個方法之間應該空一行。
冒號左邊無空格,右邊有一個空格。
? :
和 空字典[:]
例外
Preferred(推薦):
class TestDatabase: Database {
var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
Not Preferred(不推薦):
class TestDatabase : Database {
var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}
<a id="comments"></a>
Comments(注釋)
必要的時候添加注釋注明 為什么 。
避免一大段注釋解釋一行代碼,代碼應該有自我解釋性Exception: This does not apply to those comments used to generate documentation.
<a id="classes-and-structures"></a>
Classes and Structures(類和結構體)
<a id="which-one-to-use"></a>
用哪一個?
struct 是值類型,當你不需要唯一身份的時候用struct.arrayOne = [a, b, c] 和arrayTwo = [a, b, c]意義是一樣的。不需要在乎他們是否是同一個 array。這就是用 struct 的原因。
class 是引用類型。需要唯一標識或有生命周期的才用 class。你可以把一個人定義為一個 class 實例,因為每個人都是不一樣的。僅僅只有名字和生日相同的兩人也不是同一個人。
有時候,應該是 struct 確是 class ,如以下情況 NSDate
NSSet
<a id="example-definition"></a>
Example Definition(示例)
以下是一個好的 class 代碼示范:
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)
}
func describe() -> String {
return "I am a circle at \(centerString()) with an area of \(computeArea())"
}
override func computeArea() -> Double {
return M_PI * radius * radius
}
private func centerString() -> String {
return "(\(x),\(y))"
}
}
以上示例演示遵循如下指導:
- 聲明的類型用
:
銜接在后面 - 多個變量公用 關鍵字 var let 可以聲明在同一行
- 縮進 getter setter willSet didSet
- 變量前不需要添加
internal
關鍵字,因為這是默認的。同樣不需要重復寫重寫方法內的代碼
<a id="use-of-self"></a>
Use of Self(self 用法)
避免使用self
去調用屬性或方法。
僅在初始化方法的參數名和屬性名相同時用self
class BoardLocation {
let row: Int, column: Int
init(row: Int, column: Int) {
self.row = row
self.column = column
let closure = {
print(self.row)
}
}
}
<a id="computed-properties"></a>
Computed Properties(計算屬性)
如果一個計算屬性的返回值很簡單,可以省去 get{}。有 set{} 就一定要有 get{}
Preferred(推薦):
var diameter: Double {
return radius * 2
}
Not Preferred(不推薦):
var diameter: Double {
get {
return radius * 2
}
}
<a id="final"></a>
Final(關鍵字)
當你不希望該類被繼承時,用 final
// Turn any generic type into a reference type using this Box class.
final class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
<a id="function-declarations"></a>
Function Declarations(函數聲明)
保持短方法名在一行
func reticulateSplines(spline: [Double]) -> Bool {
// reticulate code goes here
}
如果方法名換行,下一行添加一個縮進距離
func reticulateSplines(spline: [Double], adjustmentFactor: Double,
translateConstant: Int, comment: String) -> Bool {
// reticulate code goes here
}
<a id="closure-expressions"></a>
Closure Expressions(閉包表達式)
閉包放在最后面
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()
}
對單行的閉包,返回值可以用含蓄方式
attendeeList.sort { a, b in
a > b
}
鏈式方法后面跟隨閉包,方法名應該清晰明了。
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.indexOf(90)
let value = numbers
.map {$0 * 2}
.filter {$0 > 50}
.map {$0 + 10}
<a id="types"></a>
Types(類型)
如無必要,用 swift 類型,不要用 oc 類型
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
在Sprite Kit編程中,用CGFloat
以便代碼更簡明,避免各種轉換。
<a id="constants"></a>
Constants
常量用 let
,變量用var
。
Tip: A good technique is to define everything using let
and only change it to var
if the compiler complains!
你可以將常量定義到一個類型中而不是作為實例的類型屬性。 類型屬性用static let
。
Preferred(推薦):
enum Math {
static let e = 2.718281828459045235360287
static let pi = 3.141592653589793238462643
}
radius * Math.pi * 2 // circumference
Note:
The advantage of using a case-less enumeration is that it can't accidentally be instantiated and works as a pure namespace.
Not Preferred(不推薦):
let e = 2.718281828459045235360287 // pollutes global namespace
let pi = 3.141592653589793238462643
radius * pi * 2 // is pi instance data or a global constant?
<a id="static-methods-and-variable-type-properties"></a>
Static Methods and Variable Type Properties
static方法和類型的功能類似全局函數和全局變量
<a id="optionals"></a>
Optionals(可選值)
聲明一個函數的某個參數可以為 nil 時,用?
當你確定某個變量在使用時已經確定不是 nil 時,在后面加!
......
self.textContainer?.textLabel?.setNeedsDisplay()
Use optional binding when it's more convenient to unwrap once and perform multiple operations:
if let textContainer = self.textContainer {
// do many things with textContainer
}
命名一個 optional 變量,避免使用optionalString
或 maybeView
,因為他們已經在聲明時體現出來了。
optional 綁定,使用原始名稱而不是unwrappedView
或 actualLabel
Preferred(推薦):
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, volume = volume {
// do something with unwrapped subview and volume
}
Not Preferred(不推薦):
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
if let realVolume = volume {
// do something with unwrappedSubview and realVolume
}
}
<a id="struct-initializers"></a>
Struct Initializers(結構體初始化)
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)
Prefer the struct-scope constants CGRect.infinite
, CGRect.null
, etc. over global constants CGRectInfinite
, CGRectNull
, etc. For existing variables, you can use the shorter .zero
.
<a id="lazy-initialization"></a>
Lazy Initialization(懶加載)
Consider using lazy initialization for finer grain control over object lifetime. This is especially true for UIViewController
that loads views lazily. You can either use a closure that is immediately called { }()
or call a private factory method. Example:
用懶初始化
lazy var locationManager: CLLocationManager = self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
Notes:
-
[unowned self]
is not required here. A retain cycle is not created. - Location manager has a side-effect for popping up UI to ask the user for permission so fine grain control makes sense here.
- 不需要使用
[unowned self]
,因為沒有創建循環引用 - ......
<a id="type-inference"></a>
Type Inference(類型)
Prefer compact code and let the compiler infer the type for constants or variables of single instances. Type inference is also appropriate for small (non-empty) arrays and dictionaries. When required, specify the specific type such as CGFloat
or Int16
.
Preferred(推薦):
let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5
Not Preferred(不推薦):
let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
let names = [String]()
<a id="type-annotation-for-empty-arrays-and-dictionaries"></a>
Type Annotation for Empty Arrays and Dictionaries
用以下方式創建 array dictionary
Preferred(推薦):
var names: [String] = []
var lookup: [String: Int] = [:]
Not Preferred(不推薦):
var names = [String]()
var lookup = [String: Int]()
NOTE: Following this guideline means picking descriptive names is even more important than before.
<a id="syntactic-sugar"></a>
Syntactic Sugar(語法竅門)
推薦用短語義聲明
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>
<a id="functions-vs-methods"></a>
Functions vs Methods(函數 方法)
class 或類型以外的自由函數應該單獨使用。如果可以,用方法而不是函數。這樣會更容易獲得和發現。
自由函數不應該跟任何類型或實例關聯。
Preferred(推薦)
let sorted = items.mergeSort() // easily discoverable
rocket.launch() // clearly acts on the model
Not Preferred(不推薦)
let sorted = mergeSort(items) // hard to discover
launch(&rocket)
Free Function Exceptions
let tuples = zip(a, b) // feels natural as a free function (symmetry)
let value = max(x,y,z) // another free function that feels natural
<a id="memory-management"></a>
Memory Management(內存管理)
代碼盡量避免循環引用,避免強引用,用weak
和unowned
引用。用值類型避免循環引用。
<a id="extending-lifetime"></a>
Extending object lifetime(擴展生命周期)
用[weak self]
和 guard let strongSelf = self else { return }
模式擴展生命周期。用[weak self]
比 [unowned self]
更好。
Preferred(推薦)
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else { return }
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}
Not Preferred(不推薦)
// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
Not Preferred(不推薦)
// 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)
}
<a id="access-control"></a>
Access Control(訪問控制)
Full access control annotation in tutorials can distract from the main topic and is not required. Using private
appropriately, however, adds clarity and promotes encapsulation. Use private
as the leading property specifier. The only things that should come before access control are the static
specifier or attributes such as @IBAction
and @IBOutlet
.
......
Preferred(推薦):
class TimeMachine {
private dynamic lazy var fluxCapacitor = FluxCapacitor()
}
Not Preferred(不推薦):
class TimeMachine {
lazy dynamic private var fluxCapacitor = FluxCapacitor()
}
<a id="control-flow"></a>
Control Flow(控制流)
更推薦 for-in
而不是 while-condition-increment
Preferred(推薦):
for _ in 0..<3 {
print("Hello three times")
}
for (index, person) in attendeeList.enumerate() {
print("\(person) is at position #\(index)")
}
for index in 0.stride(to: items.count, by: 2) {
print(index)
}
for index in (0...3).reverse() {
print(index)
}
Not Preferred(不推薦):
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
}
<a id="golden-path"></a>
Golden Path(黃金路線)
多個條件判斷時,不要多個if
嵌套,用 guard
Preferred(推薦):
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
}
Not Preferred(不推薦):
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
}
}
判斷多個 optional 時,用 guard
或 if let
。減少嵌套。
Preferred(推薦):
guard let number1 = number1, number2 = number2, number3 = number3 else { fatalError("impossible") }
// do something with numbers
Not Preferred(不推薦):
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")
}
<a id="failing-guards"></a>
Failing Guards
Guard 要求以某種方式退出。一般可以用 return
, throw
, break
, continue
, and fatalError()
。避免大量代碼
<a id="semicolons"></a>
Semicolons(分號)
swift 不要求寫;
,僅僅在你把多個語句寫在同一行時才要求;
不要把多個狀態語句寫在同一行
for-conditional-increment
才會將多個語句寫在同一行。但是 swift 推薦用for-in
Preferred(推薦):
let swift = "not a scripting language"
Not Preferred(不推薦):
let swift = "not a scripting language";
NOTE: generally considered unsafe
<a id="parentheses"></a>
Parentheses(圓括號)
圓括號也不是必須的
Preferred(推薦):
if name == "Hello" {
print("World")
}
Not Preferred(不推薦):
if (name == "Hello") {
print("World")
}
<a id="smiley-face"></a>
Smiley Face(笑臉)
笑臉的正確表示方法
Preferred(推薦):
:]
Not Preferred(不推薦):
:)