raywenderlich/swift-style-guide를 바탕으로 개인적으로 정리가 필요한 부분을 추가하며 작성하였습니다 :]
개인적인 정리를 목적으로 작성한 것이어서 번역에 오류가 많지만, 최대한 이해되는 방향으로 작성하였습니다!
Updated for Swift 4.2
Naming
기술적이고 일관된 명칭은 소프트웨어를 더 쉽게 읽고 이해할 수 있게 한다.
API Design Guide에 설명된 Swift 명명 규칙을 사용하십시오.
몇 가지 핵심 요인은 다음과 같은 것들이 있다.
-
striving for clarity at the call site
→ 사용하는 곳에 명확하게 이해할 수 있게끔 이름을 만들어라
-
prioritizing clarity over brevity
→ 간결함 보다는 명료함을 우선시!
-
using camel case (not snake case)
→ snake_case 말고, CamelCase를 사용하라
-
using uppercase for types (and protocols), lowercase for everything else
→ 타입이나 프로토콜은 UpperCamelCase를 따르고 그 외에는 lowerCamelCase를 따른다.
-
including all needed words while omitting needless words
→ 불필요한 말은 생략하고 필요한 말은 모두 포함해라
-
using names based on roles, not types
→ 타입이 아니라 기능에 기반한 이름을 사용한다
-
beginning factory methods with make
→ 팩토리 메서드는 make라는 이름으로 시작한다.
-
side effects에 대한 메서드 이름 지정하기
> verb methods follow the -ed, -ing rule for the non-mutating version
> noun methods follow the formX rule for the mutating version
: 쉽게 말해, mutating함수는 명사형 동사를 쓰고, nonMutating함수는 -ed, -ing를 붙혀서 사용한다.
> boolean types should read like assertions
: boolean타입은 assertions(평서문)처럼 읽어야한다. ex) point.hasX, line1.intersects(line2)
> protocols that describe what something is should read as nouns
: 무엇을 해야하는지 설명하는 프로토콜은 명사로 읽어야한다.
> protocols that describe a capability should end in -able or -able
: 능력(Capability)을 설명하는 프로토콜은 -able, -ible로 끝나야 한다.
-
using terms that don't surprise experts or confuse beginners
→ 전문가를 놀라게 하거나 초보자에 혼동을 주지 않는 용어를 사용한다
-
generally avoiding abbreviations
→ 일반적으로 약어를 피한다
-
using precedent for names
→ 이름에 대해 선례를 사용한다 (기존 문화를 따르는 대신 전체적으로 초보자용 용어로 최적화하지 말아라)
-
preferring methods and properties to free functions
→ free function 대신 Method와 Properties를 선호해라.
ex) function과 Method의 차이점
function과 Method의 의미에는 차이점이 있다.
둘다 재 사용할 수 있는 코드지만, Method는 Class, Struct, Enum에 속해있고, function은 속해있지 않다.
func thisIsFunction() {
}
struct SomeA {
func thisIsMethod() {
}
}
- preferring methods and properties to free function
→ 대문자 약어는 CamelCase에 따라 전체 대문자 혹은 소문자로 통일한다.
var utf8Bytes: [Utf8.CodeUnit] // ✗
var utf8Bytes: [UTF8.CodeUnit] // ✓
var isRepresentableAsAscii = true // ✗
var isRepresentableAsASCII = true // ✓
var userSmtpServer: SecureSmtpServer // ✗
var userSMTPServer: SecureSMTPServer // ✓
- giving the same base name to methods that share the same meaning
→ 같은 의미를 공유하는 메소드들에 대해 동일한 기본 이름을 준다 (오버로딩)
- avoiding overloads on return type
→ 반환형식으로된 오버로딩을 피해라?
→ 반환 타입에 부담(overloads)을 피해라 같은 번역이 많던데
개인적으로는 return type에 대한 오버로딩을 피해라 라는 의미 같습니다.
func A() -> Int {
return 1
}
func A() -> String {
return “a”
}
반환형식뿐아니라 오버로딩을 쓰는것에 대해 신중히 사용해야 됩니다.
오버로딩으로 어떤 메서드가 실제로 발동될 것인지 구별하기 어렵고, 혼동의 원인이 됩니다.
또한 다른 방법으로 해결할 수 있고, 오버로딩 자체에 필요한 이유가 거의 없다고 볼수 있습니다.
오버로딩 냄새가 난다고 표현하기도 한다.
- taking advantage of default parameters
→ 기본 매개변수의 장점 활용하기
- choosing good parameter names that serve as documentation
→ 좋은 파라미터 이름을 선택해야 된다.
크게 메서드와 생성자로 나눌수가 있다.
1. 메서드
Swift API는 명확하게 전치사(at, from, in, with, to, by, for)등을 많이 사용한다.
일반적으로 전치사가 필요한 메서드들은 파라미터를 써주고, 자연스러운 흐름이 되는 메서드들은 생략한다. 즉, 자연스럽게 문장이 이어지게 만들어야된다는 뜻.
// 전치사구로 인해 정확한 표현 가능
view.frame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
CGPoint(x: 30, y: 30)
[1,2,3].remove(at: 1)
// 생략 가능
view.addSubview(view2)
view.convert(point: CGPoint, from: UICoordinateSpace)
view.convert(point: CGPoint, to: UICoordinateSpace)
2. 생성자
Objectvice-C처럼 생성자의 첫번째 파라미터명은 전치사나 접속사를 써서 문장처럼 이어지게 만들면 안된다.
선호하는 예제:
let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)
let ref = Link(target: destination)
선호하지 않은 예제:
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
let ref = Link(to: destination)
산문(Prose)
산문(prose)에서 메서드를 언급할때, 모호하지 않는 것이 중요하다. 메소드 이름을 참조하려면, 가능하면 가장 간단한 형식을 사용한다
- 매개변수 없이 메소드 이름을 작성한다.
- 예제: 다음으로 addTarget메서드를 호출해야 한다
- 인자 레이블을 가지는 메소드 이름을 작성한다.
- 예제: 다음으로 addTarget(_:action:) 메소드를 호출해야 한다
- 인자 레이블과 타입을 가지는 메소드 전체 이름을 작성한다.
- 예제: 다음으로, addTarget(_: Any?, action: Selector?) 메소드를 호출해야 한다.
UIGestureRecognizer을 사용하는 위의 예제에서, 1은 모호하지 않고 선호된다
클래스 접두사(Class Prefixes)
Swift 타입은 모듈에 의해 자동적으로 namespaced가 포함되고, RW처럼 클래스 접두사를 추가하지 않아야 한다. 다른 모듈의 이름이 충돌하는 경우, 타입 이름 앞에 모듈 이름을 붙여 명확하게 할 수 있다.
하지만 혼동스러울 수 있는 경우에만 모듈 이름을 지정해서 구분해준다!
import SomeModule
let myClass = MyModule.UsefulClass()
델리게이트(Delegates)
델리게이트 메서드를 만들 때 이름이 없는 첫 번째 매개변수는 델리게이트 이름이여야 한다.
(UIKit에 이에 대한 많은 예제가 있다)
선호하는 예제:
func namePickerView(_ namePickerView: NamePickerView, didSelected name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
선호하지 않은 예제:
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
문맥에 맞는 타입 추론 사용하기(Use Type Inferred Context)
컴파일러에 의해 추론된 짧고, 명확한 코드를 사용해라
선호하는 예제:
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 같은 하나의 대문자를 사용한다.
선호하는 예제:
stuct Stack<Element> { ... }
func wirte<Target: OutputStream>(to target: inout Target)
func swp<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)
Extension을 사용하여 코드를 논리적 기능 블록으로 구성해라.
//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) 클래스에서 프로토콜 준수하는 것에 대해 재 선언(re-declare)하는 것은 컴파일러가 허용하지 않기 때문에, 항상 기본 클래스의 그룹을 extension으로 복사하는 것을 요구하지 않는다.
특히 파생된 클래스가 터미널 클래스이고 소수의 메서드가 재정의되고 있는 경우에 그러하다.
extension 그룹을 유지하는 것은 개발자의 재량이라고 볼 수 있다.
UIKit 뷰 컨트롤러의 경우 별도의 클래스 확장에서 그룹화 수명주기, 사용자 정의 접근자들 및 IBAction을 고려하십시오.
결국 개발자의 재량껏!
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
//MARK: - Action
extension ViewController {
@IBAction func openPickerView(_ sender: UIButton) {
let pickerVC = UIImagePickerController()
pickerVC.delegate = self
present(pickerVC, animated: true, completion: nil)
}
}
//MARK: - UIImagePickerDelegate
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
}
}
사용되지 않는 코드(Unused Code)
XCode 템플릿 코드를 포함하여 사용되지 않는(죽은) 코드, placeholder 주석을 제거해야 한다.
직접 관련되지 않은 고급 메서드와 함께 단순히 상위 클래스를 호출하는 구현의 튜토리얼도 제거해야 한다. 여기에는 비어있거나/사용되지 않는 UIApplicationDelegate 메서드들도 포함한다.
AppDelegate.swift 파일 보면 Xcode에서 기본적으로 제공하는 템플릿 함수들과 주석들이 있는데 안 쓰는 건 제거하라는 뜻이다
선호하는 예제:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
선호하지 않은 예제:
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
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
Imports를 최소화하기(Minimal Imports)
예를 들어 Foundation을 import 했으면 UIKit은 하지 않아도 된다. 유사하게 UIKit을 임포트 했으면 Foundation을 임포트 하지 않아도 된다.
공백(Spacing)
공간을 절약하고 줄 바꿈을 방지하기 위해 탭 대신에 공백 2칸을 사용하여 들여 쓰기를 한다. 이 설정은 아래 그림과 같이 XCode와 프로젝트 환경 설정에서 설정해야 한다.
Xcode → Preferences → Text Editing → Indentation
선호하는 예제:
if user.isHappy {
// Do something
} else {
// Do something else
}
선호하지 않은 예제:
if user.isHappy
{
// Do something
}
else {
// Do something else
}
위의 올바른 예제처럼 오프닝 브레이스 `{` 후 또는 클로징 브레이스 `}` 앞에 빈 줄이 없어야 한다.
Colon(:)은 항상 왼쪽에는 공백이 없고 오른쪽엔 하나의 공백이 있다.
-> 예외적으로 ? : , 빈 딕셔너리 [:], #selector문법의 addTarget(:action:)
선호하는 예제:
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) 주석은 피한다. 예외: 문서 생성에 사용되는 주석은 제외된다.
C 스타일의 /* ... */ 의 주석을 피하고 //, /// 주석을 사용해라
클래스와 구조체(Classes and Structures)
참조 :
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
}
set {
radius = newValue / 2
}
}
선호하지 않은 예제:
var diameter: Double {
get {
return radius * 2
}
}
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
}
}
final은 상속되거나 재정의되는 것을 막는 클래스 수식어.
메서드 선언(Function Declarations)
opening brace를 포함하여 한 줄의 메서드 선언이면 형태 그래도 유지해라
func reticulateSplines(spline: [Double]) -> Bool {
// reticulate code goes here
}
긴 함수에 대해 각각의 파마리터들을 새로운 라인으로 만들어라
func reticulateSplines(
spline: [Double],
adjustmentFactor: Double,
translateConstant: Int, comment: String
) -> Bool {
// reticulate code goes here
}
input에 (Void)를 사용하지 말고 ()를 사용해라!
클로저나 함수의 output에 ()를 사용하지말고 Void를 사용해라!
선호하는 예제:
func updateConstraints() -> Void {
// magic happens here
}
typealias CompletionHandler = (result) -> Void
선호하지 않은 예제:
func updateConstraints() -> () {
// magic happens here
}
typealias CompletionHandler = (result) -> ()
메서드 호출(Function Call)
파라미터가 한 개일 때:
let success = reticulateSplines(splines)
여러 개일 때:
let success = reticulateSplines(
spline: splines,
adjustmentFactor: 1.3,
translateConstant: 2,
comment: "normalize the display")
클로저 표현(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()
}
후행클로저를 사용으로 Chained 된 메서드는 문맥에 따라 명확하고 읽기 쉬워야 된다.
익명 인자로 지어진 이름을 사용할 때 간격, 줄 바꿈을 결정하는 것은 개발자의 자유!
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)" // String
덜 선호하는 예제:
let width = 120.0 // Double
let widthString = (width as NSNumber).stringValue // String
선호하지 않은 예제:
let width: NSNumber = 120.0 // NSNumber
let widthString: NSString = width.stringValue // NSString
상수(Constants)
상수는 let키워드를 사용, 변수는 var를 사용
변수의 값이 바뀌지 않을 경우에 언제나 let을 사용해라!
팁 : 좋은 방법은 let을 항상 사용하고, 컴파일러가 불평하면 var를 사용해라!
타입 프로퍼티(Type Properties)는 static let을 사용하여 타입의 인스턴스가 아닌 타입의 상수를 정의할 수 있다. 타입 프로퍼티는 인스턴스 프로퍼티와 구별하기 쉽기 때문에, 일반적인 전역 변수보다 선호한다.
선호하는 예제:
enum Math {
static let e = 2.718281828459045235360287
static let root2 = 1.41421356237309504880168872
}
let hypotenuse = side * Math.root2
Note: case가 없는 열거형의 장점은 실수로 인스턴스 화가 되지 않고, 순수 네임스페이스로 사용할 수 있다.
선호하지 않은 예제:
let e = 2.718281828459045235360287 // pollutes global namespace
let root2 = 1.41421356237309504880168872
let hypotenuse = side * root2 // what is root2?
static 메서드와 변수 타입 프로퍼티 (static Methods and Variable Type Properties)
Static 메서드와 타입 프로퍼티는 전역 함수와 전역 변수와 유사하게 동작하며, sparingly하게 사용되야된다.
기능이 특정 타입으로 범위가 지정되거나 Objective-C와 상호 운용이 필요한 경우에 유용하다.
옵셔널(Optionals)
변수와 메서드 리턴 타입을 nil로 접근할 수 있도록 ?으로 옵셔널 선언을 한다.
옵셔널 값에 접근할 때 값이 한 번만 접근되거나 체인에 옵셔널이 많은 경우 옵셔널 체인(optional chaining)을 사용하십시오.
textContainer?.textLabel?.setNeedsDisplay()
한 번 언랩(unwrap)을하고 여러 작업을 수행하는 것이 더 편리할 때 옵셔널 바인딩을 사용하십시오.
if let textContainer = 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
}
// another example
UIView.animate(withDuration: 2.0) { [weak self] in
guard let self = self else { return }
self.alpha = 1.0
}
선호하지 않은 예제:
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
}
느린 초기화(Lazy Initialization)
lazy 변수는 사용되기 전까지는 연산이 되지 않는다.
개체 수명(lifetime)에 대한 보다 세밀한 제어를 위해 lazy initialization 사용을 고려해볼 수 있다.
특히 View를 lazy 하게 로드하는 UIViewController의 경우 더욱 그러하다.
아래의 예제와 같이 lazy에 클로저를 이용하거나 private factory 메서드를 이용할 수가 있다.
// 방법 1
lazy var location: CLLocationManager = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}()
// 방법 2
lazy var locationManager = makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
미세하게 제어에 대한 이해를 더 해보면 사용자에게 권한 팝업을 보여줄게 여러 개 있다고 했을 때 제어를 한다고 생각하면 이해되지 않을까
타입 추론(Type Inference)
간단한 코드나 컴파일러가 단일 인스턴스의 변수나 상수에 대해 추론하는 것을 선호한다.
타입 추론은 for small에 적합하고, 비어있지 않은(non-empty) 배열이나 딕셔너리에 적합하다.
CGFloat, Int16같이 필요한 경우에 타입을 지정한다.
선호하는 예제:
let message = "Click the button"
let currentBounds = computeViewBounds()
let names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5
선호하지 않은 예제:
let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
var names = [String]()
빈 배열과 빈 딕셔너리에 대한 타입 주석(Type Annotation for Empty Arrays and Dictionaries)
빈 배열과 빈 딕셔너리의 경우 Type Annotation을 사용한다.
선호하는 예제:
var names: [String] = []
var lookup: [String: Int] = [:]
선호하지 않은 예제:
var names = [String]()
var lookup = [String: Int]()
문법적 설탕(Syntactic Sugar)
제네릭으로 선언한 구문보다 아래와 타입 선언의 짧은 버전을 선호한다.
선호하는 예제:
var deviceModels: [String]
var emplyees: [Int: String]
var faxNumber: Int?
선호하지 않은 예제:
var deviceModels: Array<String>
var emplyees: Dictionary<Int, String>
var faxNumber: Optional<Int>
함수 vs 메서드(Functions vs Methods)
Class, Struct, Enum에 속해있지 않은 Free Funciton은 드물게 사용되야된다.
가능한 찾기도 쉽고 읽기도 쉬운 메서드를 사용해야 된다.
function은 특정 타입이나 인스턴스에 관련이 없을 때 사용하는 것이 적합하다.
선호하는 예제:
let sorted = items.mergeSorted() // easily discoverable
rocket.launch() // acts on the model
선호하지 않은 예제:
let sorted = mergeSort(items) // hard to discover
launch(&rocket)
free function이 사용되야될 경우:
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)
코드는 순환 참조(reference cycles)를 생성하면 안 된다.
weak와 unowned를 사용하여 강한 순환 참조를 방지해야 된다.
그렇지 않으면, 값 타입(struct, enum)을 이용해서 순환 참조를 방지해야 된다!
객체의 수명 연장(Extending object lifetime)
[weak self]나 guard let self = self else { return }구문을 사용하여 객체 수명 연장을 한다.
[weak self]는 self가 클로저에서 살아 있는지 불확실할 때 [unowned self]보다 선호된다.
명시적으로 수명 연장은 옵셔널 체이닝보다 선호한다.
아래의 예제를 보면 이해가 될 것이다.
선호하는 예제:
resource.request().onComplete { [weak self] response in
guard let self = self else {
return
}
let model = self.updateModel(response)
self.updateUI(model)
}
선호하지 않은 예제:
// response를 받기 전에 self가 해제되면 Crash가 일어날 수 있다.
// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
선호하지 않은 예제:
// 모델 업데이트와 UI 업데이트 사이에 할당 취소 가능
// 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보다 private를 더 선호한다.
컴파일러에서 fileprivate를 요구하는 경우에만 사용한다.
open, public, internal같이 완전한 접근 제어 규격이 필요한 경우에만 명시적으로 사용하십시오.
static지정자나 @IBAction, @IBOutlet, @discardableResult같은 속성들은 유일하게 접근제어자 전에 올 수 있고,
static private let message = "Hi"
@IBOutlet private weak var testView: UIView!
그 이외는 맨 앞에 사용되어야 한다.
private dynamic lazy var fluxCapacitor = FluxCapacitor()
선호하는 예제:
private let message = "Great Scott!"
class TimeMachine {
private dynamic lazy var fluxCapacitor = FluxCapacitor()
}
선호하지 않은 예제:
fileprivate let message = "Great Scott!"
class TimeMachine {
lazy dynamic private 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
}
삼항 연산자(Ternary Operator)
삼항 연산자(? : )는 명확성 또는 코드의 깔끔성을 높일 때 사용해야 한다.
하나의 조건을 계산하는 것에 보통 사용되고,
복수의 조건을 계산하는 것은 일반적으로 if 문으로 이해하거나 인스턴스 변수로 리팩터링 한다.
일반적으로, 삼항 연산자의 최선의 사용은 변수를 할당하고 사용할 값을 결정하는 것이다.
선호하는 예제:
let value = 5
result = value != 0 ? x : y
let isHorizontal = true
result = isHorizontal ? x : y
선호하지 않은 예제:
result = a > b ? x = c > d ? c : d : y
Golden Path
조건문으로 코딩을 할 때, 코드의 왼쪽 여백은 "golden"이나 "happy" path여야 한다.
즉, if문을 중첩하지 않는다.
여러 개의 return문은 괜찮다. 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문법은 어떤 식으로든 종료할 때 필요하다.
guard문 안에는 큰 코드 블럭은 피하고, 일반적으로 return, throw, break, continue, fatalError()처럼 간단히 한 줄의 문장이 되어야 한다.
여러 종료 지점에 대해 cleanup code가 필요한 경우 cleanup code 중복을 방지하기 위해 defer블록을 사용하는 것을 고려해야 된다.
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
guard let context = context else {
print("FFTError")
throw FFTError.noContext
}
guard let inputData = inputData else {
print("FFTError")
throw FFTError.noInputData
}
return frequencies
}
위의 소스를 보면 예를 들어 print("FFTError") 프린트를 찍는로직이 있다고 했을 때,
guard문이 종료되는 시점에 전부 넣어줘야 된다.
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
defer {
print("FFTError")
}
guard let context = context else {
throw FFTError.noContext
}
guard let inputData = inputData else {
throw FFTError.noInputData
}
return frequencies
}
cleanup code의 중복을 defer를 통해서 한 번에 처리된 것을 볼 수 있습니다.
이런 상황에서 defer를 사용해서 중복을 제거할 수 있다는 뜻입니다.
세미콜론(Semicolons)
Swift는 코드에서 각 문장 뒤에 세미콜론이 필요하지 않다. 한 줄에 여러 개의 문장을 결합할 때에만 필요하다.
한 줄에 세미콜론으로 구분하여 여러 문장들을 사용하지 말라.
선호하는 예제:
let swift = "not a scripting language"
선호하지 않은 예제:
let swift = "not a scripting language";
괄호(Parentheses)
조건문 주변의 괄호는 필요하지 않으므로 생략해야 한다.
선호하는 예제:
if name == "Hello" {
print("World")
}
선호하지 않은 예제:
if (name == "Hello") {
print("World")
}
더 큰 표현에서 선택적 괄호는 때때로 코드를 더 명확하게 읽을 수 있다.
선호하는 예제:
let playerMark = (player == current ? "X" : "O")
Multi-line String Literals
긴 문자열을 문자열로 작성할 때 multi-line string literal syntax(""") 구문을 사용하는 것이 좋다.
선호하는 예제:
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.
"""
선호하지 않은 예제:
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.
"""
선호하지 않은 예제:
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."
조직과 번들 식별자(Organization and Bundle Identifier)
Xcode 프로젝트와 관련된 경우,
Organization은 Ray Wenderlich로 해야 되고,
Bundle Identifier는 com.raywenderlich.TutorialName이 되어야 한다.
여기서 TutorialName은 프로젝트 이름을 나타낸다.
예) Domain.Organization.ProjectName
저작권 선언문(Copyright Statement)
저작권 선언문은 언제나 소스파일 맨 위에 포함해야 한다.
/// Copyright (c) 2019 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.
///
/// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
/// distribute, sublicense, create a derivative work, and/or sell copies of the
/// Software in any work that is designed, intended, or marketed for pedagogical or
/// instructional purposes related to programming, coding, application development,
/// or information technology. Permission for such use, copying, modification,
/// merger, publication, distribution, sublicensing, creation of derivative works,
/// or sale is expressly withheld.
///
/// 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.
'Swift' 카테고리의 다른 글
[Swift] Codable - Encoding 방법 (0) | 2019.12.30 |
---|---|
[Swift] 2. 예제로 알아보는 함수의 합성(Composition) (0) | 2019.08.12 |
[Swift] 1. 순수함수, Pure Function (Functional Programming in Swift) (0) | 2019.07.25 |
[Swift] 문자열 다루기 (0) | 2019.07.25 |
[Swift] 고차함수(2) - map, flatMap, compactMap (0) | 2019.07.25 |