반응형

Swift Style Guide

Raywenderlich.com 에서 사용하는 Swift Style Guide

원문 : https://github.com/raywenderlich/swift-style-guide/

정확성(Correctness)

경고(warnings)없이 컴파일되도록 노력한다. 이 규칙은 문자열 원문(literals) 대신 #selector타입을 사용하는 것과 같이 많은 스타일을 결정하는 것을 제공한다.

이름 지정하기(Naming)

설명적이고 일관적인 이름을 지정하는 것은 소프트웨어를 읽고 이해하는데 더 쉽게 해준다. API 디자인 가이드라인(API Design Guidelines)에 설명된 Swift 이름 지정 규칙을 사용한다. 일부 핵심사항은 다음과 같다.

  • 호출하는 곳에서 명확한 작업을 하기 위해 노력한다
  • 간결성보다는 명확성에 우선순위를 둔다
  • camel 표기법을 사용(snake 표기법이 아님)
  • 타입(과 프로토콜)에 대해서 대문자 사용하기, 그 밖의 나머지는 소문자 사용
  • 불필요한 단어들은 생략하면서 필요한 단어는 포함한다
  • 타입이 아니라 기능에 기반한 이름을 사용한다
  • 가끔 weak 타입 정보에 대해 보충한다
  • 유창하게 사용하기 위해서 노력한다(?)
  • make로 팩토리 메소드 시작하기
  • 부작용(side effects)에 대한 메소드 이름 지정하기
    • 동사 메소드는 비 수정(non-mutating) 버젼에 대해 -ed, -ing규칙을 따른다.
    • 명사 메소드는 비 수정(non-mutating) 버젼에 대해 formX 규칙을 따른다.
    • boolean 타입은 assertions처럼 읽어야 한다
    • 무엇을 해야 하는지(what something is) 설명하는 프로토콜은 명사로 읽어야 한다
    • 가능성(capability)을 설명하는 프로토콜은 -able 또는 -ible으로 끝나야 한다
  • 전문가를 놀라게 하거나 초보자에 혼동을 주지 않는 용어를 사용한다
  • 일반적으로 약어를 피한다
  • 이름에 대해 선례를 사용한다
  • 함수를 사용하지 않는 메소드와 프로퍼티를 선호한다
  • 머릿글자(acronyms and initialisms)의 경우 위 아래로 균일하게 한다
  • 같은 의미를 공유하는 메소드들에 대해 동일한 기본 이름을 준다
  • 반환 타입에 부담(overloads)을 피하기
  • 문서에 사용하는 좋은 매개변수 이름 선택하기
  • 클로저와 튜플 매개변수에 레이블(label) 지정하기
  • 기본 매개변수의 장점 활용하기

산문(Prose)

산문(prose)에서 메소드를 언급할때, 모호하지 않는 것이 중요하다. 메소드 이름을 참조하려면, 가능하면 가장 간단한 형식을 사용한다

  1. 매개변수 없이 메소드 이름을 작성한다. 예제: 다음으로 addTarget메소드를 호출해야 한다
  2. 인자 레이블을 가지는 메소드 이름을 작성한다. 예제: 다음으로 addTarget(_:action:) 메소드를 호출해야 한다
  3. 인자 레이블과 타입을 가지는 메소드 전체 이름을 작성한다. 예제: 다음으로, addTarget(_: Any?, action: Selector?) 메소드를 호출해야 한다.

UIGestureRecognizer을 사용하는 위의 예제에서, 1은 모호하지 않고 선호된다.

전문가 팁: XCode의 점프 바(jump bar)를 사용하여 인자 레이블이 있는 메소드들을 조회 할수 있다.

클래스 접두사(Class Prefixes)

Swift의 타입은 포함하는 모듈에 의해 자동으로 namespaced 되고 RW 처럼 클래스 접두사를 추가하지 않아야 한다. 다른 모듈의 두 이름이 충돌하는 경우, 타입 이름 앞에 모듈 이름을 붙여 명확하게 할수 있다. 하지만, 혼동스러울 수 있는 경우에만 모듈 이름을 지정한다.

import SomeModule

let myClass = MyModule.UsefulClass()

위임(Delegates)

사용자 지정 위임(delegate) 메소드를 만들때, 이름없는 첫번째 매개변수는 위임자(delegate) 원본이어야 한다. (UIKit에 이에 대한 많은 예제가 있다)

바람직한 예제:

func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool

바람직하지 않은 예제:

func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool

문맥에 맞는 타입 추론 사용하기(Use Type Inferred Context)

컴파일러에 의해 추론된 짧고, 명확한 코드를 사용한다. (타입 추론(Type Inference)를 보라)

바람직한 예제:

let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)

바람직하지 않은 예제:

let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)

제네릭(Generics)

제네릭 타입 매개변수는 대문자 카멜 표기법 이름으로 서술해야 한다. 타입 이름에 의미있는 관계나 규칙이 없을때, 전통적으로 T, U, V처럼 하나의 대문자를 사용한다.

바람직한 예제:

struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)

바람직하지 않은 예제:

struct Stack<T> { ... }
func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)

언어(Language)

애플의 API와 일치하는 미국 영어 철자(US English spelling) 사용한다.

바람직한 예제:

let color = "red"

바람직하지 않은 예제:

let colour = "red"

코드 구성(Code Organization)

논리적인 기능 블록으로 코드를 구성 하기위해 확장을 사용한다. 각 확장을 잘 구성하기 위해 // MARK: - 주석으로 설정하는게 좋다.

프로토콜 적합성(Protocol Conformance)

특히, 모델에 프로토콜 적합성을 추가할때, 프로토콜 메소드에 대해 별도의 확장을 추가하는 것을 선호한다. 이렇게 하면 관련 메소드가 프로토콜과 함께 그룹화되고 클래스와 관련된 메소드를 프로토콜과 추가하기 위한 지시사항을 단순화 할 수 있다.

바람직한 예제:

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
}

바람직하지 않은 예제:

class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // all methods
}

파생된(derived) 클래스에서 프로토콜 적합성을 재선언하는 것을 컴파일러가 허용하지 않으며, 기본 클래스의 확장 그룹을 복사하는 것을 항상 요구하지 않는다. 파생된 클래스가 말단(terminal)의 클래스이고 소수의 메소드를 재정의 하는 경우에 특히 그렇다.

파생 클래스가 터미널 클래스이고 소수의 메서드를 재정의하는 경우에 해당된다. 확장 그룹을 유지하는 것은 작성자의 자유이다.

UIKit의 뷰 컨트롤러들, 수명주기(lifecycle) 그룹화 고려, 사용자정의 접근자들, IBAction 등을 클래스 확장으로 구분한다.

사용되지 않는 코드(Unused Code)

사용되지 않는(죽은) 코드, XCode 템플릿 코드를 포함하여, 견본이름(placeholder) 주석을 제거해야 한다. 튜토리얼이나 책이 사용자에게 주석처리된 코드를 사용하도록 지시하는 경우에는 예외이다.

직접 관련되지 않은 고급 메소드와 함께 단순히 상위클래스를 호출하는 구현의 튜토리얼도 제거해야 한다. 여기에는 비어있거나/사용되지 않는 UIApplicationDelegate 메소드들도 포함한다.

바람직하지 않은 예제:

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
}

바람직한 예제:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return Database.contacts.count
}

Import를 최소화(Minimal Imports)

import를 최소화한다. 예를들어, Foundation을 가져올때(importing) UIKit을 import하지 않아도 된다.

공백(Spacing)

  • 공간을 절약하고 줄 바꿈을 방지하기 위해 탭 대신에 공백 2칸을 사용하여 들여쓰기를 한다. 이 설정은 아래 그림과 같이 XCode와 프로젝트 환경 설정에서 설정해야한다.


  • 메소드 중괄호와 다른 중괄호들은(if / else / switch / while) 항상 구문과 같은줄에서 열고 새로운 줄에서 닫는다.
  • 팁 : 일부코드를 선택하고(전체선택하기 위해 ⌘A)나서 Control-I(또는 메뉴에서 Editer\Structure\Re-indent)하여 다시 들여쓰기(re-indent) 할수 있다. 일부 XCode의 템플릿 코드들은 공백 4칸 탭으로 하드코딩되어 있으며, 이는 그것을 고치는 좋은 방법이다.

바람직한 예제:

if user.isHappy {
  // Do something
} else {
  // Do something else
}

바람직하지 않은 예제:

if user.isHappy
{
  // Do something
}
else {
  // Do something else
}
  • 명확하고 구조적으로 보이도록 도움을 주기위해 메소드들 사이에는 정확히 빈 줄이 하나 있어야 한다. 메소드내의 공백은 기능을 구분해주지만, 메소드에 너무 많은 섹션이 있으면 종종 여러개의 메소드로 리팩토링해야 한다.

  • 콜론은 항상 왼쪽에 공백이 없고 오른쪽에 공백 하나가 있다.

콜론은 항상 왼쪽에는 공백이없고 오른쪽에는 공백이 없습니다. 삼항 연산자(? :)와 빈 딕셔너리([:]), #selector의 이름없는 매개변수에 대한 구문((_:))은 예외이다.

바람직한 예제:

class TestDatabase: Database {
  var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}

바람직하지 않은 예제:

class TestDatabase : Database {
  var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}
  • 긴 줄은 70 글자 내외로 감싸야 한다. 강제적인 제한은 의도적으로 지정되지 않는다.
  • 줄 끝에 공백은 피한다.
  • 각 파일의 끝에 하나의 개행문자를 추가한다.

주석(Comments)

필요한 경우, 특정코드가 무엇인가를 왜(why) 하는지 설명하기 위해 주석을 사용한다. 주석은 반드시 최신상태로 유지하거나 삭제되어야 한다.

코드는 가능하면 자체적으로 문서가 되어야 하므로, 코드와 함께 있는 인라인(inline) 주석은 피한다. 예외: 문서 생성에 사용되는 주석은 제외된다.

클래스와 구조체(Classes and Structures)

어떤 것을 사용해야 하나?(Which one to use?)

구조체는 값의 의미(value semantics)를 가지는 것을 기억한다. 구조체는 식별성(identity)을 가지지 않은 것에 사용한다. [a, b, c]가 포함하는 배열은 [a, b, c]을 포함하는 다른 배열과 정말 똑같고 교환이 가능하다. 그것들은 똑같은 것을 나타내기 때문에, 첫번째 배열이나 두번째 배열을 사용하는 것은 중요하지 않다. 그것은 배열이 구조체인 이유이다.

클래스는 참조의 의미(reference semantics)를 가지고 있다. 식별성(identity)을 가지거나 특정 생명주기(lifecycle)가 있는 것은 클래스를 사용한다. 두 사람에 대한 객체들은 둘 다 다르기 때문에 사람을 하나의 클래스로 모델링한다. 두 사람의 이름과 생년월일이 같기 때문에, 그들이 같은 사람이라는 것을 의미하지는 않는다. 하지만, 1950년 3월 3일의 날짜는 1950년 3월 3일에 대한 다른 날짜 객체와 같기 때문에, 사람들의 생년월일은 구조체가 될것이다. 날짜는 스스로 식별성(identity)이 없다.

가끔씩, 물건은 구조체가 되어야 한지만 AnyObject나 이미 클래스로 모델링된 것들(NSDate, NSSet)을 준수해야 한다. 이 가이드라인을 준수하도록 최대한 노력해야한다.

예제 정의(Example definition)

다음은 잘 꾸며진 클래스 정의의 예입니다.

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))"
  }
}

위의 예제는 다음과 같은 스타일 가이드라인을 따른다.

  • 프로퍼티, 변수, 상수, 인자 선언과 콜론 다음에(이전은 아님) 공백있는 구문 타입을 지정한다. 예를들어, x: Int와 Circle: Shape
  • 공통의 목적와 / 맥락(context)을 공유하는 경우에, 한줄에 여러개의 변수와 구조체를 정의한다.
  • getter과 setter 정의와 프로퍼티 옵져버는 들여쓰기 한다.
  • 기본값으로 이미 되어 있는 경우, internal과 같은 수정자를 추가하지 않는다. 비슷하게, 메소드를 오버라이딩(overriding) 할때 접근자를 반복하지 않는다.
  • 확장에서 추가 기능을 구성한다 (예를들어, 프린팅)
  • private 접근 제어를 사용하여 확장에서 centerString 같은 구현 세부 정보는 숨긴다.

Self 사용하기(Use of Self)

간결함을 위해, Swift는 객체의 프로퍼티에 접근하거나 메소드 호출할 필요가 없는 경우에 self 사용을 피한다.

컴파일러에 의해 요구될때에만 self를 사용한다(@escaping 클로저 나 초기화에서 인자가 프로퍼티와 애매모호 할때). 다른 말로, self없이 컴파일하는 경우에 생략한다.

계산 프로퍼티(Computed Properties)

간결함을 위해, 계산 프로퍼티가 읽기 전용인 경우, get 클로저는 생략한다. get 클로저는 set 클로저가 제공될때에만 필요하다.

바람직한 예제:

var diameter: Double {
  return radius * 2
}

바람직하지 않은 예제:

var diameter: Double {
  get {
    return radius * 2
  }
}

Final

튜토리얼에서 클래스나 멤버를 final로 표시하는 것은 핵심 주제를 혼란시킬수 있고 필요하지도 않다. 그렇지만, final의 사용은 가끔씩 의도를 명확하게 해주고 비용의 가치가 있다. 아래 예제에서, Box는 특별한 목적을 가지고 파생된 클래스의 사용자 정의는 의도되지 않는다. 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
  }
}

함수 선언(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
}

클로져 표현(Closure Expressions)

인자 목록의 끝에 단일 클로져 표현 매개변수가 있는 경우에만 후행 클로저 문법을 사용한다. 클로져 매개변수를 설명하는 이름을 준다.

바람직한 예제:

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()
})

바람직하지 않은 예제:

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()
}

문맥(context)이 명확한 단일 표현 클로져에 대해, 암시적인 반환을 사용한다.

attendeeList.sort { a, b in
  a > b
}

후행 클로저를 사용하여 연결된(chained) 메서드는 문맥(context)에 따라 명확하고 읽기 쉬워야 한다. 익명으로 이름지어진 인자를 사용할때에는 간격, 줄바꿈을 결정하는 것은 작성자 자유이다. 예제:

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}

타입

가능하면 언제나 Swift의 기본 타입을 사용한다. Swift는 Objective-C에 연결(bridging)을 제공하며, 필요에 따라 전체 메소드 세트를 계속 사용 할 수 있다.

바람직한 예제:

let width = 120.0                                    // Double
let widthString = (width as NSNumber).stringValue    // String

바람직하지 않은 예제:

let width: NSNumber = 120.0                          // NSNumber
let widthString: NSString = width.stringValue        // NSString

Sprite Kit 코드에서, 많은 변환을 피함으로써 코드를 더 간결하게 만드는 경우에 CGFloat을 사용한다.

상수(Constants)

상수는 let키워드를, 변수는 var키워드를 사용하여 정의된다. 변수의 값이 변하지 않을 경우에, 언제나 var대신에 let을 사용한다.

팁: 좋은 방법은 언제나 let을 사용하고 변경할때만 컴파일러가 불평하게 되면 var을 사용한다.

타입 프로퍼티를 사용하여 타입의 인스턴스가 아닌 타입에 상수를 정의 할수 있다. 타입 프로퍼티를 상수로 선언하려면 단순히 static let을 사용한다. 타입 프로퍼티 선언된 이 방법은 인스턴스 프로퍼티와 구별하기 쉽기 때문에, 일반적으로 전역상수보다 선호한다. 예제:

바람직한 예제:

enum Math {
  static let e = 2.718281828459045235360287
  static let root2 = 1.41421356237309504880168872
}

let hypotenuse = side * Math.root2

주의 : case가 없는 열거형 사용의 장점은 실수로 인스턴스화(instantiated) 되지 않고 순수한 네임스페이스로 동작한다

바람직하지 않은 예제:

let e = 2.718281828459045235360287  // pollutes global namespace
let root2 = 1.41421356237309504880168872

let hypotenuse = side * root2 // what is root2?

Static 메소드와 변수 타입 프로퍼티(Static Method and Variable Type Properties)

Static 메소드와 타입 프로퍼티는 전역 함수와 전역 변수와 비슷하게 동작하고 드물게 사용해야 한다. 기능이 특정 타입으로 범위가 지정되거나 Objective-C 와 상호 운용이 필요한 경우에 경우에 유용하다.

옵셔널(Optionals)

변수와 함수 반환 타입을 nil값을 접근 할수 있도록 ?으로 옵셔널로 선언한다.

사용하기 전에 초기화된 인스턴스 변수에 대해 !으로 암시적으로 언래핑(unwrapped)된 타입을 사용하며, viewDidLoad에서 subviews은 설정될것이다.

옵셔널 값에 접근할때, 값을 한번만 접근하거나 체인(chain)에 많은 옵셔널이 있는 경우에 옵셔널 체이닝(optional chaining)을 사용한다

self.textContainer?.textLabel?.setNeedsDisplay()

한 번만 언래핑(unwrap)하고 여러작업을 수행할때 옵셔널 바인딩을 사용한다.

if let textContainer = self.textContainer {
  // do many things with textContainer
}

옵셔널 변수와 프로퍼티에 이름을 지정할때, 옵셔널은 이미 타입 선언에 있기 때문에, optionalString이나 maybeView같은 이름은 피한다.

옵셔널 바인딩의 경우, unwrappedView 또는 actualLabel 처럼 이름을 사용하는 대신에 적절한 경우에 원래 이름을 그림자처럼 사용한다

바람직한 예제:

var subview: UIView?
var volume: Double?

// later on...
if let subview = subview, let volume = volume {
  // do something with unwrapped subview and volume
}

바람직하지 않은 예제:

var optionalSubview: UIView?
var volume: Double?

if let unwrappedSubview = optionalSubview {
  if let realVolume = volume {
    // do something with unwrappedSubview and realVolume
  }
}

느린 초기화(Lazy Initialization)

객체의 수명(lifetime)에 대한 미세한 제어하는 경우 느린(lazy) 초기화 사용을 검토한다. 뷰를 느리게(lazy) 로딩하는 UIViewController의 경우에 특히 그렇다. { }()로 즉시 호출되는 클로져를 사용하거나 private 팩토리 메소드 호출을 할수 있다. 예제:

lazy var locationManager: CLLocationManager = self.makeLocationManager()

private func makeLocationManager() -> CLLocationManager {
  let manager = CLLocationManager()
  manager.desiredAccuracy = kCLLocationAccuracyBest
  manager.delegate = self
  manager.requestAlwaysAuthorization()
  return manager
}

주의: 
[unowned self]는 여기서 필요하지 않다. 유지 주기(retain cycle)가 생성되지 않는다.
* 위치 관리자(Location manager)는 UI 팝업에 대해 부작용(side-effect)을 가지고 있다. 사용자에게 권한을 물어보기때문에 여기에서 미세한 제어하는게 좋다.

타입 추론(Type Inference)

간단한 코드와 컴파일러가 단일 인스턴스의 상수나 변수 타입을 추론하는 것을 선호한다. 타입 추론은 작은(비어있지않은) 배열과 딕셔너리에 적절하다. 필요한 경우, CGFloat나 Int16처럼 특정 타입을 지정한다.

바람직한 예제:

let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5

바람직하지 않은 예제:

let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
let names = [String]()

빈 배열과 딕셔너리에 대한 타입 주석(Type Annotation for Empty Arrays and Dictionaries)

빈 배열과 딕셔너리의 경우, 타입 주석(annotation)을 사용한다. (배열 또는 딕셔너리는 크게 할당되며, 여러줄의 리터럴(multi-line literal), 타입 주석을 사용한다)
바람직한 예제:

var names: [String] = []
var lookup: [String: Int] = [:]

바람직하지 않은 예제:

var names = [String]()
var lookup = [String: Int]()

주의 : 이 가이드라인에 따르면 설명적인 이름을 선택하는 것이 이전보다 훨씬 중요하다.

문법적 설탕 (Syntactic Sugar)

전체 일반적인 문법보다 타입 선언의 짧은 버젼을 선호한다.

바람직한 예제:

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?

바람직하지 않은 예제:

var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>

함수 vs 메소드(Functions vs Methods)

클래스나 타입에 대해 연결되어 있지 않은 자유 함수(free functions)는 드물게 사용해야 한다. 가능하면, 자유 함수 대신에 메서드를 사용하는 것을 선호한다. 이는 가독성과 발견성을 도와준다.

자유 함수들은 특정 타입이나 인스턴스와 연관되지 않을때 가장 적절하다.

바람직한 예제:

let sorted = items.mergeSorted()  // easily discoverable
rocket.launch()  // acts on the model

바람직하지 않은 예제:

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

메모리 관리(Memory Management)

코드(비-제품, 튜토리얼 데모 코드)는 참조 순환을 만들면 안된다. weak와 unowned참조를 사용하여 객체 그래프를 분석하고 강한 순환을 방지한다. 그 대신에, 값 타입(struct, enum)을 사용하여 전체적으로 순환을 방지한다.

객체 수명 연장(Extending object lifetime)

[weak self]와 guard let strongSelf = self else { return } 구문을 사용하여 객체 수명 연장 한다. [weak self]는 self 가 클로져에서 살아 있는지 불확실 할때 [unowned self]보다 선호된다. 명시적인 수명 연장은 옵셔널 언래핑보다 선호한다.

바람직한 예제:

resource.request().onComplete { [weak self] response in
  guard let strongSelf = self else { 
    return 
  }
  let model = strongSelf.updateModel(response)
  strongSelf.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)
}

접근 제어(Access Control)

튜토리얼에서 전체 접근 제어 주석은 핵심 주제에 혼란을 줄수 있고 필요하지 않다. private와 fileprivate는 적절하게 사용하면, 명확성을 추가하고 캡슐화를 촉진한다. 가능하면 fileprivate보다 previte를 선호한다. 확장에서 사용하려면 fileprivate를 사용해야 할수 있다.

openpublicinternal은 전체 접근 제어 사양이 필요한 경우에만 명시적으로 사용한다.

주요 프로퍼티 지정자처럼 접근제어를 사용한다. 접근제어 전에 오는 유일한 것은 static지정이나 @IBAction, @IBOutlet, @discardableResult 같은 속성 이다.

바람직한 예제:

private let message = "Great Scott!"

class TimeMachine {  
  fileprivate dynamic lazy var fluxCapacitor = FluxCapacitor()
}

바람직하지 않은 예제:

fileprivate let message = "Great Scott!"

class TimeMachine {  
  lazy dynamic fileprivate var fluxCapacitor = FluxCapacitor()
}

흐름 제어(Control Flow)

while-condition-increment스타일 보다 for반복문의 for-in 스타일을 선호한다.

바람직한 예제:

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)
}

바람직하지 않은 예제:

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
}

Golden Path

조건문으로 코딩할때, 코드의 왼쪽 여백은 golden 또는 happy 경로여야 한다. 이것은, if문을 중첩하지 않는다. 여러개 반환문은 괜찮다. guard문은 이를 위해 만들었다.

바람직한 예제:

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
}

바람직하지 않은 예제:

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
  }
}

여러개의 옵셔널이 guard나 if let으로 언래핑된 경우, 가능한 복합 버젼을 사용하여 중첩을 최소화 한다. 예제:

바람직한 예제:

guard let number1 = number1, 
      let number2 = number2, 
      let number3 = number3 else { 
  fatalError("impossible") 
}
// do something with numbers

바람직하지 않은 예제:

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")
}

Guard의 약점(Failing Guards)

Guard 문법은 어떤 방식으로든 종료할때 필요하다. 일반적으로, return, throw, break, continue, fatalError()처럼 간단히 한 줄의 문장이 되어야 한다. 큰 코드 블럭은 피해야 한다. 여러개의 종료시점에 대해 정리코드가 필요한 경우, 정리코드 중복을 피하기 위해 defer블럭 사용을 고려한다.

세미콜론(Semicolons)

Swift는 코드에서 각 문장 뒤에 세미콜론이 필요하지 않다. 한 줄에 여러개의 문장을 결합할때에만 필요하다.

한줄에 세미콜론으로 구분하여 여러 문장들을 사용하지 말라.

바람직한 예제:

let swift = "not a scripting language"

바람직하지 않은 예제:

let swift = "not a scripting language";

주의: Swift는 세미콜론을 생략하면 일반적으로 안전하지 않는 것으로 간주하는(generally considered unsafe) JavaScript와 매우 다르다.

괄호(Parentheses)

조건문 주변의 괄호는 필요하지 않으므로 생략해야 한다.

바람직한 예제:

if name == "Hello" {
  print("World")
}

바람직하지 않은 예제:

if (name == "Hello") {
  print("World")
}

긴 표현에서는, 괄호로 인해 코드를 더 명확하게 읽을 수 있다.

바람직한 예제:

let playerMark = (player == current ? "X" : "O")

조직과 번들 식별자(Organization and Bundle Identifier)

XCode 프로젝트 관련된 곳에서 조직을 RayWenderlich로 설정하고 번들 식별자는 com.razeware.TutorialName으로 설정해야 하며, TutorialName은 튜토리얼 프로젝트 이름이다.


저작권 선언문(Copyright Statement)

다음 저작권 선언문은 언제나 소스파일 맨 위에 포함해야 한다.

/**
 * Copyright (c) 2016 Razeware LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

웃는 얼굴(Smiley Face)

웃는 얼굴은 raywenderlich.com 사이트에서 매우 눈에 띄는 스타일 기능이다. 코딩의 주제에 대해 엄청난 행복과 흥분을 나타내는 정확한 미소를 갖는 것이 매우 중요하다. ASCII 사용할수 있는 가장 큰 미소를 나타내기 때문에 닫힌 사각형 ]을 사용된다. 닫힌 괄호 )은 마음 편한 미소를 만들고, 앞에서 말한것과 같이 바람직하지 않다.

바람직한 예제:

:]

바람직하지 않은 예제:

:)


반응형

'Swift > Style Guide' 카테고리의 다른 글

Swift API Design Guidelines  (0) 2016.10.04
Posted by 까칠코더
,