来自Linkedin的Swift编程风格指南

iOS系列文章地址
原文地址

首先推荐阅读下 Apple's API Design Guidelines

Table Of Contents

1. Code Formatting:代码格式化

  • 1.1 使用4个空格来代替Tabs

  • 1.2 避免过长的行,可以在XCode中进行设置单行最大长度:(Xcode->Preferences->Text Editing->Page guide at column: 160 is helpful for this)

  • 1.3 保证每个文件结尾都存在一个新行 Ensure that there is a newline at the end of every file.

  • 1.4 避免无意义的尾随空格: (Xcode->Preferences->Text Editing->Automatically trim trailing whitespace + Including whitespace-only lines).

  • 1.5 避免将单独的左花括号放置到一行,我们参考了:1TBS style.

    class SomeClass {
        func someMethod() {
            if x == y {
                /* ... */
            } else if x == z {
                /* ... */
            } else {
                /* ... */
            }
        }
    
        /* ... */
    }
  • 1.6 在写变量的类型声明、字典类型的键、函数参数、协议的声明或者父类的时候,不要在冒号前添加空格。

    // specifying type
    let pirateViewController: PirateViewController
    
    // dictionary syntax (note that we left-align as opposed to aligning colons)
    let ninjaDictionary: [String: AnyObject] = [
        "fightLikeDairyFarmer": false,"disgusting": true
    ]
    
    // declaring a function
    func myFunction<T,U: SomeProtocol where T.RelatedType == U>(firstArgument: U,secondArgument: T) {
        /* ... */
    }
    
    // calling a function
    someFunction(someArgument: "Kitten")
    
    // superclasses
    class PirateViewController: UIViewController {
        /* ... */
    }
    
    // protocols
    extension PirateViewController: UITableViewDataSource {
        /* ... */
    }
  • 1.7 一般来说,逗号后面都要跟随一个空格。

    let myArray = [1,2,3,4,5]
  • 1.8 在二元操作符譬如+,==,或者 ->的前后需要加上空格,但是对于( 、`)的前后不需要加空格。

    let myValue = 20 + (30 / 2) * 3
    if 1 + 1 == 3 {
        fatalError("The universe is broken.")
    }
    func pancake() -> Pancake {
        /* ... */
    }
  • 1.9 我们默认使用Xcode推荐的格式化风格(CTRL-I) ,在声明某个函数的时候会多行排布参数。

    // Xcode indentation for a function declaration that spans multiple lines
    func myFunctionWithManyParameters(parameterOne: String,parameterTwo: String,parameterThree: String) {
        // Xcode indents to here for this kind of statement
        print("\(parameterOne) \(parameterTwo) \(parameterThree)")
    }
    
    // Xcode indentation for a multi-line `if` statement
    if myFirstVariable > (mySecondVariable + myThirdVariable)
        && myFourthVariable == .SomeEnumValue {
    
        // Xcode indents to here for this kind of statement
        print("Hello,World!")
    }
  • 1.10 在调用多参数函数的时候,会把多个参数放置到单独的行中:

    someFunctionWithManyArguments(
        firstArgument: "Hello,I am a string",secondArgument: resultFromSomeFunction()
        thirdArgument: someOtherLocalVariable)
  • 1.11 对于大型的数组或者字典类型,应该将其分割到多行内,[]类比于花括号进行处理。对于闭包而言也应该同样适合于该规则。

    someFunctionWithABunchOfArguments(
        someStringArgument: "hello I am a string",someArrayArgument: [
            "dadada daaaa daaaa dadada daaaa daaaa dadada daaaa daaaa","string one is crazy - what is it thinking?"
        ],someDictionaryArgument: [
            "dictionary key 1": "some value 1,but also some more text here","dictionary key 2": "some value 2"
        ],someClosure: { parameter1 in
            print(parameter1)
        })
  • 1.12 尽可能地使用本地变量的方式来避免多行的判断语句。

    // PREFERRED
    let firstCondition = x == firstReallyReallyLongPredicateFunction()
    let secondCondition = y == secondReallyReallyLongPredicateFunction()
    let thirdCondition = z == thirdReallyReallyLongPredicateFunction()
    if firstCondition && secondCondition && thirdCondition {
        // do something
    }
    
    // NOT PREFERRED
    if x == firstReallyReallyLongPredicateFunction()
        && y == secondReallyReallyLongPredicateFunction()
        && z == thirdReallyReallyLongPredicateFunction() {
        // do something
    }

2. Naming:命名

  • 2.1 Swift中不需要再使用Objective-C那样的前缀,譬如使用 GuybrushThreepwood 而不是LIGuybrushThreepwood

  • 2.2 对于类型名即struct,enum,class,typedef,associatedtype等等使用 PascalCase

  • 2.3 对于函数名、方法名、变量名、常量、参数名等使用camelCase

  • 2.4 在使用首字母缩写的时候尽可能地全部大写,并且注意保证全部代码中的统一。不过如果缩写被用于命名的起始,那么就全部小写。

    // "HTML" is at the start of a variable name,so we use lowercase "html"
    let htmlBodyContent: String = "<p>Hello,World!</p>"
    // Prefer using ID to Id
    let profileID: Int = 1
    // Prefer URLFinder to UrlFinder
    class URLFinder {
        /* ... */
    }
  • 2.5 对于静态常量使用 k 前缀 + PascalCase。

    class MyClassName {
        // use `k` prefix for constant primitives
        static let kSomeConstantHeight: CGFloat = 80.0
    
        // use `k` prefix for non-primitives as well
        static let kDeleteButtonColor = UIColor.redColor()
    
        // don't use `k` prefix for singletons
        static let sharedInstance = MyClassName()
    
        /* ... */
    }
  • 2.6 对于泛型或者关联类型,使用PascalCase描述泛型,如果泛型名与其他重复,那么可以添加一个Type后缀名到泛型名上。

    class SomeClass<T> { /* ... */ }
    class SomeClass<Model> { /* ... */ }
    protocol Modelable {
        associatedtype Model
    }
    protocol Sequence {
        associatedtype IteratorType: Iterator
    }
  • 2.7 命名必须要是不模糊的并且方便表述的

    // PREFERRED
    class RoundAnimatingButton: UIButton { /* ... */ }
    
    // NOT PREFERRED
    class CustomButton: UIButton { /* ... */ }
  • 2.8 不要使用缩写,可以选择较为简短的单词。

    // PREFERRED
    class RoundAnimatingButton: UIButton {
        let animationDuration: NSTimeInterval
    
        func startAnimating() {
            let firstSubview = subviews.first
        }
    
    }
    
    // NOT PREFERRED
    class RoundAnimating: UIButton {
        let aniDur: NSTimeInterval
    
        func srtAnmating() {
            let v = subviews.first
        }
    }
  • 2.9 对于不是很明显的类型需要将类型信息包含在属性名中。

    // PREFERRED
    class ConnectionTableViewCell: UITableViewCell {
        let personImageView: UIImageView
    
        let animationDuration: NSTimeInterval
    
        // it is ok not to include string in the ivar name here because it's obvious
        // that it's a string from the property name
        let firstName: String
    
        // though not preferred,it is OK to use `Controller` instead of `ViewController`
        let popupController: UIViewController
        let popupViewController: UIViewController
    
        // when working with a subclass of `UIViewController` such as a table view
        // controller,collection view controller,split view controller,etc.,// fully indicate the type in the name.
        let popupTableViewController: UITableViewController
    
        // when working with outlets,make sure to specify the outlet type in the
        // variable name.
        @IBOutlet weak var submitButton: UIButton!
        @IBOutlet weak var emailTextField: UITextField!
        @IBOutlet weak var nameLabel: UILabel!
    
    }
    
    // NOT PREFERRED
    class ConnectionTableViewCell: UITableViewCell {
        // this isn't a `UIImage`,so shouldn't be called image
        // use personImageView instead
        let personImage: UIImageView
    
        // this isn't a `String`,so it should be `textLabel`
        let text: UILabel
    
        // `animation` is not clearly a time interval
        // use `animationDuration` or `animationTimeInterval` instead
        let animation: NSTimeInterval
    
        // this is not obviously a `String`
        // use `transitionText` or `transitionString` instead
        let transition: String
    
        // this is a view controller - not a view
        let popupView: UIViewController
    
        // as mentioned previously,we don't want to use abbreviations,so don't use
        // `VC` instead of `ViewController`
        let popupVC: UIViewController
    
        // even though this is still technically a `UIViewController`,this variable
        // should indicate that we are working with a *Table* View Controller
        let popupViewController: UITableViewController
    
        // for the sake of consistency,we should put the type name at the end of the
        // variable name and not at the start
        @IBOutlet weak var btnSubmit: UIButton!
        @IBOutlet weak var buttonSubmit: UIButton!
    
        // we should always have a type in the variable name when dealing with outlets
        // for example,here,we should have `firstNameLabel` instead
        @IBOutlet weak var firstName: UILabel!
    }
  • 2.10 在编写函数参数的时候,要保证每个参数都易于理解其功能。

  • 2.11 根据 Apple's API Design Guidelines,对于protocol,如果其描述的是正在做的事情,譬如Collection,那么应该命名为名词。而如果是用于描述某种能力,譬如Equatable,ProgressReporting,那么应该添加 able,ible,或者 ing 这样的后缀。如果你的协议并不符合上述两种情形,那么应该直接添加一个Protocol后缀,譬如:

    // here,the name is a noun that describes what the protocol does
    protocol TableViewSectionProvider {
        func rowHeight(atRow row: Int) -> CGFloat
        var numberOfRows: Int { get }
        /* ... */
    }
    
    // here,the protocol is a capability,and we name it appropriately
    protocol Loggable {
        func logCurrentState()
        /* ... */
    }
    
    // suppose we have an `InputTextView` class,but we also want a protocol
    // to generalize some of the functionality - it might be appropriate to
    // use the `Protocol` suffix here
    protocol InputTextViewProtocol {
        func sendTrackingEvent()
        func inputText() -> String
        /* ... */
    }

3. Coding Style

3.1 General

  • 3.1.1 尽可能地使用let来代替var

  • 3.1.2 尽可能地使用 map,filter,reduce的组合来进行集合的转换等操作,并且尽可能地避免使用带有副作用的闭包。

    // PREFERRED
    let stringOfInts = [1,3].flatMap { String($0) }
    // ["1","2","3"]
    
    // NOT PREFERRED
    var stringOfInts: [String] = []
    for integer in [1,3] {
        stringOfInts.append(String(integer))
    }
    
    // PREFERRED
    let evenNumbers = [4,8,15,16,23,42].filter { $0 % 2 == 0 }
    // [4,42]
    
    // NOT PREFERRED
    var evenNumbers: [Int] = []
    for integer in [4,42] {
        if integer % 2 == 0 {
            evenNumbers(integer)
        }
    }
  • 3.1.3 尽可能地显式声明不方便进行类型推测的变量或者常量的类型名。

  • 3.1.4 如果你的函数需要返回多个参数,那么尽可能地使用Tuple来代替inout参数。如果你会多次使用某个元组,那么应该使用typealias设置别名。如果返回的参数超过三个,那么应该使用结构体或者类来替代。

    func pirateName() -> (firstName: String,lastName: String) {
        return ("Guybrush","Threepwood")
    }
    
    let name = pirateName()
    let firstName = name.firstName
    let lastName = name.lastName
  • 3.1.5 在创建delegates/protocols的时候需要小心所谓的保留环(retain cycles),这些属性需要被声明为weak

  • 3.1.6 在闭包中直接调用self可能会导致保留环,可以使用capture list 在这种情况下:

    myFunctionWithClosure() { [weak self] (error) -> Void in
        // you can do this
    
        self?.doSomething()
    
        // or you can do this
    
        guard let strongSelf = self else {
            return
        }
    
        strongSelf.doSomething()
    }
  • 3.1.7 不要使用 labeled breaks。

  • 3.1.8 不要在控制流逻辑判断的时候加上圆括号

    // PREFERRED
    if x == y {
        /* ... */
    }
    
    // NOT PREFERRED
    if (x == y) {
        /* ... */
    }
  • 3.1.9 避免在使用enum的时候写出全名

    // PREFERRED
    imageView.setImageWithURL(url,type: .person)
    
    // NOT PREFERRED
    imageView.setImageWithURL(url,type: AsyncImageView.Type.person)
  • 3.1.10 在写类方法的时候不能用简短写法,应该使用类名.方法名,这样能够保证代码的可读性

    // PREFERRED
    imageView.backgroundColor = UIColor.whiteColor()
    
    // NOT PREFERRED
    imageView.backgroundColor = .whiteColor()
  • 3.1.11 在非必要的时候不要写self.

  • 3.1.12 在编写某个方法的时候注意考虑下这个方法是否有可能被复写,如果不可能被复写那么应该使用final修饰符。还要注意加上final之后也会导致无法在测试的时候进行复写,所以还是需要综合考虑。一般而言,加上final修饰符后会提高编译的效率,所以应该尽可能地使用该修饰符。

  • 3.1.13 在使用譬如else,catch等等类似的语句的时候,将关键字与花括号放在一行,同样遵循1TBS style规范,这边列出了常见的if/else 以及 do/catch 示范代码。

    if someBoolean {
        // do something
    } else {
        // do something else
    }
    
    do {
        let fileContents = try readFile("filename.txt")
    } catch {
        print(error)
    }

3.2 Access Modifiers

  • 3.2.1 在需要的时候应该将访问修饰符放在关键字的第一位。

    // PREFERRED
    private static let kMyPrivateNumber: Int
    
    // NOT PREFERRED
    static private let kMyPrivateNumber: Int
  • 3.2.2 访问修饰符不应该单独放一行:

    // PREFERRED
    public class Pirate {
        /* ... */
    }
    
    // NOT PREFERRED
    public
    class Pirate {
        /* ... */
    }
  • 3.2.3 一般来说,不要显式地写默认的 internal访问修饰符。

  • 3.2.4 如果某个变量需要在测试的时候被使用到,那么应该标识为internal来保证@testable import ModuleName。这里需要注意的是,对于某些应该被声明为private的变量因为测试用途而声明为了internal,那么应该在注释里特别地注明。

    /**
     This variable defines the pirate's name.
     - warning: Not `private` for `@testable`.
     */
    let pirateName = "LeChuck"

3.3 Custom Operators:自定义操作符

尽可能地选用命名函数来代替自定义操作符。如果你打算引入一个自定义的操作符,那么一定要有非常充分的理由来说明为啥要讲一个新的操作符引入到全局作用域,而不是使用其他一些可替代的方式。你也可以选择去复写一些现有的操作符,譬如==来适应一些新的类型,不过要保证你添加的用法一定要与语义相符。譬如== 应该只能用于表示相等性测试并且返回一个布尔值。

3.4 Switch Statements and enums

  • 3.4.1 在使用枚举类型作为switch的参数的时候,避免引入default关键字,而应该将没有使用的情形放到下面然后使用break关键字来避免被执行。

  • 3.4.2 Swift中默认会在每个case的结尾进行break,因此没必要的时候不需要显式地声明break关键字。

  • 3.4.3 The case statements should line up with the switch statement itself as per default Swift standards.

  • 3.4.4 When defining a case that has an associated value,make sure that this value is appropriately labeled as opposed to just types (e.g. case Hunger(hungerLevel: Int) instead of case Hunger(Int)).

    enum Problem {
        case attitude
        case hair
        case hunger(hungerLevel: Int)
    }
    
    func handleProblem(problem: Problem) {
        switch problem {
        case .attitude:
            print("At least I don't have a hair problem.")
        case .hair:
            print("Your barber didn't know when to stop.")
        case .hunger(let hungerLevel):
            print("The hunger level is \(hungerLevel).")
        }
    }
  • 3.4.5 优先使用譬如case 1,3:这样的列表表达式而不是使用fallthrough关键字。

  • 3.4.6 如果你添加了一个默认的case并且该case不应该被使用,那么应该在default情形下抛出异常。

    func handleDigit(digit: Int) throws {
        case 0,1,5,6,7,9:
            print("Yes,\(digit) is a digit!")
        default:
            throw Error(message: "The given number was not a digit.")
    }

3.5 Optionals

  • 3.5.1 只应该在 @IBOutlet中使用隐式地未包裹的Options。否则其他情况下就应该使用Non-Optional或者正常的Optional的变量。虽然有时候你能保证某个变量肯定非nil,不过这样用的话还是比较安全并且能保证上下一致性。
    The only time you should be using implicitly unwrapped optionals is withs. In every other case,it is better to use a non-optional or regular optional variable. Yes,there are cases in which you can probably "guarantee" that the variable will never be nil when used,but it is better to be safe and consistent.

  • 3.5.2 不要使用 as! 或者 try!.

  • 3.5.3 如果你只是打算判断存放在Optional中的值是否为空,那么你应该直接与nil进行判断而不是使用if let语句将值取出来。

    // PREFERERED
    if someOptional != nil {
        // do something
    }
    
    // NOT PREFERRED
    if let _ = someOptional {
        // do something
    }
  • 3.5.4 不要使用 unowned。你可以将unowned当做对于weak变量的隐式解包,虽然有时候unownedweak相比有小小地性能提升,不过还是不建议进行使用。

    // PREFERRED
    weak var parentViewController: UIViewController?
    
    // NOT PREFERRED
    weak var parentViewController: UIViewController!
    unowned var parentViewController: UIViewController
  • 3.5.5 当对Optionals进行解包的时候,使用与Optionals变量一致的变量名

    guard let myVariable = myVariable else {
        return
    }

3.6 Protocols

在实现协议的时候,大体上有两种代码组织方式:

  1. 使用 // MARK: 来注释你的专门用于实现协议中规定的方法

  2. 在你的类或者结构体实现之外使用一个扩展来存放实现代码,不过要保证在一个源文件中

不过需要注意的是,如果你是使用了Extension方式,那么定义在Extension中的方法是无法被子类复写的,这样可能会无法进行测试。

3.7 Properties

  • 3.7.1 如果是定义一个只读的需要经过计算的属性,那么不需要声明 get {}

    var computedProperty: String {
        if someBool {
            return "I'm a mighty pirate!"
        }
        return "I'm selling these fine leather jackets."
    }
  • 3.7.2 在使用 get {},set {},willSet,以及 didSet,注意块的缩进

  • 3.7.3 尽管你可以在willSet/didSet以及 set方法中使用自定义的名称,不过建议还是使用默认的newValue/oldValue 变量名

    var computedProperty: String {
        get {
            if someBool {
                return "I'm a mighty pirate!"
            }
            return "I'm selling these fine leather jackets."
        }
        set {
            computedProperty = newValue
        }
        willSet {
            print("will set to \(newValue)")
        }
        didSet {
            print("did set from \(oldValue) to \(newValue)")
        }
    }
  • 3.7.4 将任何类常量设置为static

    class MyTableViewCell: UITableViewCell {
        static let kReuseIdentifier = String(MyTableViewCell)
        static let kCellHeight: CGFloat = 80.0
    }
  • 3.7.5 可以使用如下方式便捷地声明一个单例变量:

    class PirateManager {
        static let sharedInstance = PirateManager()
    
        /* ... */
    }

3.8 Closures:闭包

  • 3.8.1 如果闭包中的某个参数的类型是显而易见的,那么可以避免声明类型。不过有时候为了保证可读性与一致性,还是会显示声明参数类型。

    // omitting the type
    doSomethingWithClosure() { response in
        print(response)
    }
    
    // explicit type
    doSomethingWithClosure() { response: NSURLResponse in
        print(response)
    }
    
    // using shorthand in a map statement
    [1,3].flatMap { String($0) }
  • 3.8.2 在参数列表中,如果是使用了捕获变量或者声明了非Void的返回值,那么应该将参数列表写在一个圆括号里,其他情况下则可以省略圆括号。

    // parentheses due to capture list
    doSomethingWithClosure() { [weak self] (response: NSURLResponse) in
        self?.handleResponse(response)
    }
    
    // parentheses due to return type
    doSomethingWithClosure() { (response: NSURLResponse) -> String in
        return String(response)
    }
  • 3.8.3 如果你是将闭包声明为一个类型,那么除非该类型为Optional或者该闭包是另一个闭包的参数,否则不需要使用圆括号进行包裹。不过需要用圆括号来标注参数列表,并且使用Void来指明没有任何结果返回。

    let completionBlock: (success: Bool) -> Void = {
        print("Success? \(success)")
    }
    
    let completionBlock: () -> Void = {
        print("Completed!")
    }
    
    let completionBlock: (() -> Void)? = nil
  • 3.8.4 尽可能地将参数名与左括号放在一行,不过要避免打破每行最长160个字符的限制。
    Keep parameter names on same line as the opening brace for closures when possible without too much horizontal overflow (i.e. ensure lines are less than 160 characters).

  • 3.8.5 尽可能地使用 trailing closure表达式,除非需要显示地声明闭包参数的外部参数名。

    // trailing closure
    doSomething(1.0) { parameter1 in
        print("Parameter 1 is \(parameter1)")
    }
    
    // no trailing closure
    doSomething(1.0,success: { parameter1 in
        print("Success with \(parameter1)")
    },failure: { parameter1 in
        print("Failure with \(parameter1)")
    })

3.9 Arrays

  • 3.9.1 一般来说,避免使用下标直接访问某个数组,而应该使用类似于.first.last这样的访问器进行访问。另外,应该优先使用for item in items语法来替代for i in 0..<items.count。如果你打算用下标遍历数组,那么一定保证不能越界。

  • 3.9.2 永远不要使用+= 或者 +运算符来增加或者连接数组,应该使用.append() 或者 .appendContentsOf() 方法。如果你想定义一个从其他数组生成的不可变数组,那么应该使用let关键字,即: let myNewArray = arr1 + arr2,或者 let myNewArray = [arr1,arr2].flatten()

3.10 Error Handling

假设某个函数 myFunction 需要去返回一个String类型,不过有可能会在某个点抛出异常,一般来说会将该函数的返回值设置为String?

Example:

func readFile(withFilename filename: String) -> String? {
    guard let file = openFile(filename) else {
        return nil
    }

    let fileContents = file.read()
    file.close()
    return fileContents
}

func printSomeFile() {
    let filename = "somefile.txt"
    guard let fileContents = readFile(filename) else {
        print("Unable to open file \(filename).")
        return
    }
    print(fileContents)
}

不过作为异常处理的角度,我们应该使用Swift的try-catch表达式,这样能显式地知道错误点:

struct Error: ErrorType {
    public let file: StaticString
    public let function: StaticString
    public let line: UInt
    public let message: String

    public init(message: String,file: StaticString = #file,function: StaticString = #function,line: UInt = #line) {
        self.file = file
        self.function = function
        self.line = line
        self.message = message
    }
}

Example usage:

func readFile(withFilename filename: String) throws -> String {
    guard let file = openFile(filename) else {
        throw Error(message: "Unable to open file named \(filename).")
    }

    let fileContents = file.read()
    file.close()
    return fileContents
}

func printSomeFile() {
    do {
        let fileContents = try readFile(filename)
        print(fileContents)
    } catch {
        print(error)
    }
}

总而言之,如果某个函数可能会出错,并且出错的原因不能显式地观测到,那么应该优先抛出异常而不是使用一个Optional作为返回值。

3.11 Using guard Statements

  • 3.11.1 一般来说,我们会优先使用所谓的"early return"策略来避免if表达式中的多层嵌套的代码。在这种情况下使用guard语句能够有效地提升代码的可读性。

    // PREFERRED
    func eatDoughnut(atIndex index: Int) {
        guard index >= 0 && index < doughnuts else {
            // return early because the index is out of bounds
            return
        }
    
        let doughnut = doughnuts[index]
        eat(doughnut)
    }
    
    // NOT PREFERRED
    func eatDoughnuts(atIndex index: Int) {
        if index >= 0 && index < donuts.count {
            let doughnut = doughnuts[index]
            eat(doughnut)
        }
    }
  • 3.11.2 在对Optional类型进行解包的时候,优先使用 guard 语句来避免if语句中较多的缩进。

    // PREFERRED
    guard let monkeyIsland = monkeyIsland else {
        return
    }
    bookVacation(onIsland: monkeyIsland)
    bragAboutVacation(onIsland: monkeyIsland)
    
    // NOT PREFERRED
    if let monkeyIsland = monkeyIsland {
        bookVacation(onIsland: monkeyIsland)
        bragAboutVacation(onIsland: monkeyIsland)
    }
    
    // EVEN LESS PREFERRED
    if monkeyIsland == nil {
        return
    }
    bookVacation(onIsland: monkeyIsland!)
    bragAboutVacation(onIsland: monkeyIsland!)
  • 3.11.3 在决定是要用if表达式还是guard表达式进行Optional类型解包的时候,最重要的点就是要保证代码的可读性。很多时候要注意因时而变,因地制宜:

    // an `if` statement is readable here
    if operationFailed {
        return
    }
    
    // a `guard` statement is readable here
    guard isSuccessful else {
        return
    }
    
    // double negative logic like this can get hard to read - i.e. don't do this
    guard !operationFailed else {
        return
    }
  • 3.11.4 当需要进行多可能性处理的时候,应该优先使用if表达式而不是guard表达式。

    // PREFERRED
    if isFriendly {
        print("Hello,nice to meet you!")
    } else {
        print("You have the manners of a beggar.")
    }
    
    // NOT PREFERRED
    guard isFriendly else {
        print("You have the manners of a beggar.")
        return
    }
    
    print("Hello,nice to meet you!")
  • 3.11.5 一般来说,guard应该被用于需要直接退出当前上下文的情形。而对于下面这种两个条件互不干扰的情况,应该使用两个if而不是两个guard

    if let monkeyIsland = monkeyIsland {
        bookVacation(onIsland: monkeyIsland)
    }
    
    if let woodchuck = woodchuck where canChuckWood(woodchuck) {
        woodchuck.chuckWood()
    }
  • 3.11.6 有时候我们会碰到要用guard语句进行多个optionals解包的情况,一般而言,对于复杂的错误处理的Optional类型需要将其拆分到多个单个表达式中。

    // combined because we just return
    guard let thingOne = thingOne,let thingTwo = thingTwo,let thingThree = thingThree else {
        return
    }
    
    // separate statements because we handle a specific error in each case
    guard let thingOne = thingOne else {
        throw Error(message: "Unwrapping thingOne failed.")
    }
    
    guard let thingTwo = thingTwo else {
        throw Error(message: "Unwrapping thingTwo failed.")
    }
    
    guard let thingThree = thingThree else {
        throw Error(message: "Unwrapping thingThree failed.")
    }
  • 3.11.7 不要将guard表达式强行缩写到一行内。

    // PREFERRED
    guard let thingOne = thingOne else {
        return
    }
    
    // NOT PREFERRED
    guard let thingOne = thingOne else { return }

4. Documentation/Comments

4.1 Documentation

如果某个函数不是简单地O(1)操作,那么最好就是为该函数添加一些注释文档,这样能有效地提高代码的可读性与可维护性。之前有个非常不错的文档工具VVDocumenter。推荐阅读Apple的官方指南中的描述:described in Apple's Documentation.

Guidelines:

  • 4.1.1 每行不应超过160个字符

  • 4.1.2 即使某些注释只有一行,也应该使用块注释符: (/** */).

  • 4.1.3 不用给每行的开头都加上: *.

  • 4.1.4 使用新的 - parameter 标识符来代替老的:param: syntax (注意这边是小写的 parameter 而不是Parameter).

  • 4.1.5 如果你准备对参数/返回值/异常值来写注释,那么注意要一个不落的全局加上,尽管有时候会让文档显得重复冗余。有时候,如果只需要对单个参数进行注释,那么还不如直接放在描述里进行声明,而不需要专门的为参数写一个注释。

  • 4.1.6 对于复杂的使用类,应该添加一些具体的使用用例来描述类的用法。注意Swift的注释文档中是支持MarkDown语法的,这是一个很好的特性。

    /**
     ## Feature Support
    
     This class does some awesome things. It supports:
    
     - Feature 1
     - Feature 2
     - Feature 3
    
     ## Examples
    
     Here is an example use case indented by four spaces because that indicates a
     code block:
    
         let myAwesomeThing = MyAwesomeClass()
         myAwesomeThing.makeMoney()
    
     ## Warnings:告警
    
     There are some things you should be careful of:
    
     1. Thing one
     2. Thing two
     3. Thing three
     */
    class MyAwesomeClass {
        /* ... */
    }
  • 4.1.7 使用 - ` 在注释中著名引用的代码

    /**
     This does something with a `UIViewController`,perchance.
     - warning: Make sure that `someValue` is `true` before running this function.
     */
    func myFunction() {
        /* ... */
    }
  • 4.1.8 保证文档的注释尽可能的简洁

4.2 Other Commenting Guidelines:其他的注释规则

  • 4.2.1 //后面总是要跟上一个空格

  • 4.2.2 注释永远要放在单独的行中

  • 4.2.3 在使用// MARK: - whatever的时候,注意MARK与代码之间保留一个空行

    class Pirate {
    
        // MARK: - instance properties
    
        private let pirateName: String
    
        // MARK: - initialization
    
        init() {
            /* ... */
        }
    
    }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。