Swift 정리 - 2. Swift 시작 전 알아야 할 규칙
본격적으로 Swift를 공부하기 전 알아야 할 내용에 대해 보자.
Swift를 본격적으로 공부하기 전에 어떤 스타일로 코드를 작성해야 하는지에 대한 가이드를 보는 것은 매우 도움이 된다.
API Design Guidelines
Swift의 coding convention과 관련해서 아래의 자료들을 보면 좋을 것 같다.
기본
- 용도의 분명함이 가장 중요하다. 메서드나 프로퍼티와 같은 엔티티는 한 번 정의되지만 반복적으로 사용된다. 따라서 이들의 사용이 분명하고 정확할 수 있게 API를 디자인 해야 한다. 디자인을 평가할 때, 단순히 정의를 읽는 것이 아니라 문맥 상에서 사용되는 게 분명하고 자연스러운지를 판단해야 한다.
- 분명함은 간결함보다 더 중요하다. Swift가 간결하게 표현할 수 있다하더라도, 적은 단어로 가장 작은 코드를 작성하는 것이 목표는 아니다.
- 모든 정의마다 문서 주석을 작성해라. 설명을 작성해서 얻을 수 있는 안목은 디자인에 깊은 영향을 줄 수 있으니 작성하는 것이 좋다.
만약 API의 기능을 간단한 단어로 표현하는 것이 어렵다면 잘못된 API를 디자인 한 것이다.
- Swift의 마크다운을 사용하자.
- 엔티티에 대한 정의를 설명하는 요약문으로 시작해라. API는 정의와 요약문에 의해 완전히 이해될 수 있는 경우가 많다.
/// Returns a "view" of `self` containing the same elements in /// reverse order. func reversed() -> ReverseCollection
- 요약문에 집중해라. 가장 중요한 부분으로, 많은 훌륭한 문서화 주석들은 좋은 요약문으로만 구성되어 있다.
- 가능한 한 점으로 끝나는 하나의 문장 단편(sentence fragment)를 사용한다. 완전한 문장은 사용하지 않는다.
- 함수나 메서드가 무엇을 하고, 무엇을 리턴하는지를 설명한다. Null effect나 Void를 리턴하는 것은 설명하지 않는다.
/// Inserts `newHead` at the beginning of `self`. mutating func prepend(_ newHead: Int) /// Returns a `List` containing `head` followed by the elements /// of `self`. func prepending(_ head: Element) -> List /// Removes and returns the first element of `self` if non-empty; /// returns `nil` otherwise. mutating func popFirst() -> Element?
위 예시의
popFirst
와 같이 여러 문장 단편으로 요약문을 구성해야 한다면 세미콜론으로 구분한다. - subscript가 접근하는 것을 설명한다.
/// Accesses the `index`th element. subscript(index: Int) -> Element { get set }
- 이니셜라이저가 생성하는 것을 설명한다.
/// Creates an instance containing `n` repetitions of `x`. init(count n: Int, repeatedElement x: Element)
- 다른 정의문에서는 정의된 엔티티가 무엇인지를 설명한다.
/// A collection that supports equally efficient insertion/removal /// at any position. struct List { /// The element at the beginning of `self`, or `nil` if self is /// empty. var first: Element? ...
- 선택적으로, 하나 이상의 문단과 연속된 아이템들로 이어나갈 수 있다. 문단들은 빈칸으로 구분되고 완전한 문장으로 구성되어야 한다.
/// Writes the textual representation of each ← Summary /// element of `items` to the standard output. /// ← Blank line /// The textual representation for each item `x` ← Additional discussion /// is generated by the expression `String(x)`. /// /// - Parameter separator: text to be printed ⎫ /// between items. ⎟ /// - Parameter terminator: text to be printed ⎬ Parameters section /// at the end. ⎟ /// ⎭ /// - Note: To print without a trailing ⎫ /// newline, pass `terminator: ""` ⎟ /// ⎬ Symbol commands /// - SeeAlso: `CustomDebugStringConvertible`, ⎟ /// `CustomStringConvertible`, `debugPrint`. ⎭ public func print( _ items: Any..., separator: String = " ", terminator: String = "\n")
- Symbol documentation markup 을 사용해서 적절한 곳에 정보를 더 추가할 수 있다.
- Symbol command syntax 를 사용해서 아이템들을 명시한다. Xcode는 키워드로 시작하는 bullet item들을 지원한다. 자세한 내용은 Inserting Callouts를 확인하자.
기본 명명 규칙
우선 꼭 알아야 할 기본 명명 규칙을 먼저 보겠다.
- 변수, 상수, 함수, 메서드, 타입 등의 이름은 유니코드에서 지원하는 어떤 문자(한글, 한자, 영문, 숫자 이모티콘 ..)라도 사용할 수 있지만 아래의 예외가 있다.
- Swift에서 미리 정한 예약어, 키워드
- 해당 코드 범위 내에서 미리 사용되는 기존 이름과 동일한 이름
- 연산자로 사용될 수 있는 기호 (
+
,-
,/
,*
) - 숫자로 시작하는 이름
- 공백이 포함된 이름
- 함수, 메서드, 인스턴스 이름은 첫 글자를 소문자로 시작하는 Lower Camel Case를 사용한다.
- 클래스, 구조체, 익스텐션, 프로토콜, 열거형 이름은 타입의 이름이기 때문에 첫 글자를 대문자로 사용하는 Upper Camel Case를 사용한다.
- 대소문자를 다르게 구별한다.
예약어 / 키워드? |
예약어는 프로그래밍 언어에서 미리 사용하기로 약속한 단어로, 식별자로 쓰일 수 없는 단어를 말한다. |
키워드는 프로그래밍 언어 문법의 일부로, 특별한 의미가 있는 단어를 의미한다. |
참고로 예약어와 키워드의 목록 등을 확인하려면 이 글을 참고하면 좋을 것 같다.
분명한 이용 용도를 알린다.
- 불확실성을 줄이는데 필요한 모든 단어를 포함한다. 예를 들어 특정 위치의 인덱스에 있는 요소를 제거하는 아래의 메서드를 생각해보자.
extension List { public mutating func remove(at position: Index) -> Element } employees.remove(at: x)
그런데 여기에서 method signature에서 단어를 빼서 아래와 같이 만들면 그 의미가 불분명해진다.
employees.remove(x) // unclear: are we removing x?
- 필요하지 않은 단어를 뺀다. 이름 안에 있는 모든 단어는 사용된 곳에서 중요한 정보를 전달해야 한다. 주로 그냥 타입에 대한 정보를 반복하는 단어를 뺀다.
- 변수, 파라미터, 연관 타입의 이름을 그들의 역할에 맞게 정한다. 아래와 같이 타입 이름을 다시 사용하는 것은 분명함과 표현성을 떨어뜨린다.
var string = "Hello" protocol ViewController { associatedtype ViewType : View } class ProductionLine { func restock(from widgetFactory: WidgetFactory) }
대신, 엔티티의 역할을 표현하는 이름을 정한다.
var greeting = "Hello" protocol ViewController { associatedtype ContentView : View } class ProductionLine { func restock(from supplier: WidgetFactory) }
만약 연관 타입이 프로토콜에 강하게 묶여져 있는 경우에는 프로토콜 이름이 역할이 되는 것이고, 프로토콜 이름에
Protocol
이라 붙이면 된다.protocol Sequence { associatedtype Iterator : IteratorProtocol } protocol IteratorProtocol { ... }
- 파라미터의 역할을 분명하게 하기 위해 약한 타입 정보를 제공한다. 파라미터 타입이 특히 NSObject, Any, AnyObject, Int, String 등일 때 사용할 때의 타입 정보와 문맥은 의도를 완전히 전달하지 못할 수 있다. 예를 들어 아래의 코드에서 정의는 분명하지만, 어디에 사용해야 할지는 분명하지 않다.
func add(_ observer: NSObject, for keyPath: String) grid.add(self, for: graphics) // vague
분명함을 전달하기 위해 역할을 설명하는 명사 뒤에 약한 타입 파라미터를 붙인다.
func addObserver(_ observer: NSObject, forKeyPath path: String) grid.addObserver(self, forKeyPath: graphics) // clear
유연한 사용을 위해 노력해라.
- 영어 문법에서 기인한 메서드와 함수 이름을 사용하자.
✅ x.insert(y, at: z) “x, insert y at z” x.subViews(havingColor: y) “x's subviews having color y” x.capitalizingNouns() “x, capitalizing nouns” ⛔️ x.insert(y, position: z) x.subViews(color: y) x.nounCapitalize()
물론 유연성을 위해 하나 혹은 두 개의 인자 뒤의 인자가 호출의 의미에 중점을 두고 있지 않다면 위의 규칙을 어느 정도 어겨도 괜찮다.
AudioUnit.instantiate( with: description, options: [.inProcess], completionHandler: stopProgressBar)
- 팩토리 메서드 이름의 시작을 “make”라고 짓는다. (e.g. x.makeIterator())
- 이니셜라이저와 팩토리 메서드 호출의 첫 번째 인자는 base name과 문장을 이루어서는 안된다. 예를 들어 아래 호출의 첫 번째 인자는 base 이름(호출자의 이름)과 문장으로 이루어지지 않는다.
✅ let foreground = Color(red: 32, green: 64, blue: 128) let newPart = factory.makeWidget(gears: 42, spindles: 14) let ref = Link(target: destination)
반면 아래 호출을 보면 API 설계자가 첫 번째 인자와 문법적으로 연결되도록 설계한 것을 확인할 수 있다.
⛔️ let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128) let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14) let ref = Link(to: destination)
- 함수와 메서드를 그들의 부작용에 따라 네이밍해라.
- 부작용이 없는 것들은 명사로 지어져야 한다. e.g.
x.distance(to:y)
,i.successor()
- 부작용이 있는 것은 명령형 동사로 지어져야 한다. e.g.
print(x)
,x.sort()
,x.append(y)
- Mutating/nonmutating 메서드 쌍을 일관적으로 네이밍해라.
- 연산이 원래 동사로 명명되어 있다면, 동사의 명령형을 mutating 메서드에 사용하고, nonmutating 메서드에 “ed”나 “ing” suffix를 붙인다. |Mutating|Nonmutating| |-|-| |
x.sort()
|z = x.sorted()
| |x.append(y)
|z = x.appending(y)
| - 주로 “ed”를 우선적으로 붙이고, 이미 동사가 객체를 포함하고 있다면 “ing”을 쓴다.
/// Strips all the newlines from `self` mutating func stripNewlines() /// Returns a copy of `self` with all the newlines stripped. func strippingNewlines() -> String ... s.stripNewlines() let oneLine = t.strippingNewlines()
- 연산이 원래 명사로 명명되어 있다면, 명사를 nonmutating에 사용하고 “form” prefix를 mutating에 붙인다. |Mutating|Nonmutating| |-|-| |
y.formUnion(z)
|x = y.union(z)
| |c.formSuccessor(&i)
|j = c.successor(i)
|
- 연산이 원래 동사로 명명되어 있다면, 동사의 명령형을 mutating 메서드에 사용하고, nonmutating 메서드에 “ed”나 “ing” suffix를 붙인다. |Mutating|Nonmutating| |-|-| |
- 부작용이 없는 것들은 명사로 지어져야 한다. e.g.
- Boolean 메서드와 프로퍼티는 nonmutating일 때 수신자에 대해 단정하는 것으로 읽혀야 한다. e.g.
x.isEmpty
,line1.intersects(line2)
- 무언가를 설명하는 프로토콜은 명사로 명명돼야 한다. e.g.
Collection
- 무언가가 가능함, 능력을 설명하는 프로토콜은 suffix
able
,ible
,ing
가 붙어야 한다. e.g.Equatable
,ProgressReporting
- 다른 타입, 프로퍼티, 변수, 상수는 명사로 명명되어야 한다.
전문 용어를 적절히 사용한다.
Term of Art : (명사) - 특정 분야에서 정확하고 특별화된 의미를 가지고 있는 단어를 의미한다. |
- 만약 더 일반적인 단어가 같은 의미로 쓰일 수 있다면 안전한 단어를 쓰는 것을 지양한다. 예를 들어 “skin”을 써도 되는데 “epidermis”를 쓰지 말자. Terms of art는 필수적인 커뮤니케이션 툴이지만, 다른 단어를 사용했을 때 중요한 의미를 놓칠 경우에만 사용되어야 한다.
- Term of art를 사용할 때는 단어의 고착화된 의미를 고수한다. 일반적인 단어 말고 기술적인 용어를 사용하는 유일한 이유는 오로지 그 방법이 정확히 의미를 전달하고 나머지는 모호하거나 불분명하기 때문이다. 따라서 API는 단어가 받아들여지는 의미를 강하게 지켜서 단어를 사용해야 한다.
- 전문가를 놀라게 하지 마라 : 만약 우리가 단어에 새로운 의미를 부여하는 것처럼 보인다면 이미 그 단어에 익숙한 사람은 놀라고 아마 화도 날 수 있을 것이다.
- 초보자를 헷갈리게 하지 마라 : 그 단어를 배우려는 사람은 검색을 통해 원래의 의미를 찾으려고 할 것이다.
- 약어를 지양한다. : 특히 표준화되어있지 않은 약어들은 약어가 아닌 형태로 적절히 잘 변환되냐에 이해가 기반하고 있기 때문에 terms-of-art라고 할 수 있다. | 약어의 의도한 의도는 검색을 통해 쉽게 찾을 수 있어야 한다.
- 선례를 쓴다. 기존 문화에서 쓰던 것을 희생해가면서까지 초보자를 위해 단어들을 최적화하지 마라.
예를 들어 연속적인 데이터 구조를 List
라는 간단한 용어를 쓰기보다는 Array
를 쓰는 것이 낫다. Array는 현대 컴퓨터 공학에서 기본적인 내용이고, 모든 프로그래머가 알거나, 혹은 곧 배울 것이다. 따라서 프로그래머가 친순한 용어를 써서 모르는 사람이라도 그걸 보고 검색하게끔 하는 것이 맞다.
Conventions
일반적 Conventions
- 복잡도가 O(1)이 아닌 연산 프로퍼티의 복잡도는 기록해둔다. 사람들은 stored property들을 멘탈 모델(이해, 추리, 예측을 가능하게 하는 어떤 도메인의 표현)로써 생각하기 때문에 프로퍼티에 접근하는 것이 중요한 연산을 포함하고 있다고 가정하지 않는다.
- Free function 대신 method와 property를 사용한다. Free function은 특별할 때만 사용한다.
- 명백한
self
가 없을 때 :min(x, y, z)
- 함수가 제약되지 않은 제네릭일 때 :
print(x)
- 함수의 문법이 이미 설립된 도메인의 표기법의 일부일 때 :
sin(x)
- 명백한
- Case conventions을 따른다. 타입과 프로토콜의 이름은 UpperCamelCase이고, 그 외 모든 것은 lowerCamelCase다.
- 메서드는 기본적인 의미를 공유하거나 별개의 도메인에서 작동할 때 base name을 공유할 수 있다. 예를 들어 아래의 코드에서 메서드들이 모두 본질적으로 같은 작업을 하고 있기 때문에 좋은 예시라고 할 수 있다.
extension Shape { /// Returns `true` iff `other` is within the area of `self`. func contains(_ other: Point) -> Bool { ... } /// Returns `true` iff `other` is entirely within the area of `self`. func contains(_ other: Shape) -> Bool { ... } /// Returns `true` iff `other` is within the area of `self`. func contains(_ other: LineSegment) -> Bool { ... } }
그리고 기하학적인 타입과 컬렉션이 별도의 도메인이기 때문에 아래의 코드도 가능하다.
extension Collection where Element : Equatable { /// Returns `true` iff `self` contains an element equal to /// `sought`. func contains(_ sought: Element) -> Bool { ... } }
하지만 아래의
index
메서드들은 다른 의미를 가지고 있기 때문에 다르게 명명되어야 한다.extension Database { /// Rebuilds the database's search index func index() { ... } /// Returns the `n`th row in the given table. func index(_ n: Int, inTable: TableID) -> TableRow { ... } }
마지막으로 “리턴타입을 오버로딩하는 것”이 타입 추론을 모호하게 만들기 때문에 지양하는 것이 좋다.
extension Box { /// Returns the `Int` stored in `self`, if any, and /// `nil` otherwise. func value() -> Int? { ... } /// Returns the `String` stored in `self`, if any, and /// `nil` otherwise. func value() -> String? { ... } }
Parameters
func move(from start: Point, to end: Point)
- 코드로 문서화를 할 수 있는 파라미터 이름을 고른다. 파라미터 이름이 사용할 때 함수나 메서드에서 나타나지 않는다고 해도 아주 중요한 설명적인 역할을 갖게 된다. 문서를 읽기 쉬운 이름을 선택한다. 예를 들어 아래의 이름은 설명을 자연스럽게 읽을 수 있게 해준다.
/// Return an `Array` containing the elements of `self` /// that satisfy `predicate`. func filter(_ predicate: (Element) -> Bool) -> [Generator.Element] /// Replace the given `subRange` of elements with `newElements`. mutating func replaceRange(_ subRange: Range, with newElements: [E])
하지만 아래의 코드는 설명을 어색하고 문법적으로 맞지 않게 만든다.
/// Return an `Array` containing the elements of `self` /// that satisfy `includedInResult`. func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element] /// Replace the range of elements indicated by `r` with /// the contents of `with`. mutating func replaceRange(_ r: Range, with: [E])
- 일반적인 사용을 단순화할 때 기본 파라미터를 활용한다. 유일한, 일반적으로 쓰이는 값을 가진 파라미터는 default의 후보가 될 수 있다. Default argument는 연관 없는 정보를 가려서 가독성을 늘릴 수 있다. 예를 들어 아래의 코드는
let order = lastName.compare(royalFamilyName, options: [], range: nil, locale: nil)
아래와 같이 단순해 질 수 있다.
let order = lastName.compare(royalFamilyName)
Default argument는 API를 이해하려는 사람들에게 부단을 덜 줄 수 있기 때문에 일반적으로 선호된다.
extension String { /// ...description... public func compare( _ other: String, options: CompareOptions = [], range: Range? = nil, locale: Locale? = nil ) -> Ordering }
위의 코드는 단순하지않아 보일 수 있지만, 아래의 코드보다는 단순하다.
extension String { /// ...description 1... public func compare(_ other: String) -> Ordering /// ...description 2... public func compare(_ other: String, options: CompareOptions) -> Ordering /// ...description 3... public func compare( _ other: String, options: CompareOptions, range: Range) -> Ordering /// ...description 4... public func compare( _ other: String, options: StringCompareOptions, range: Range, locale: Locale) -> Ordering }
- Default 파라미터는 뒤쪽에 위치시킨다. Default가 없는 파라미터는 메서드의 의미에 주로 더 필수적이다.
Argument Labels
func move(from start: Point, to end: Point)
x.move(from: x, to: y)
- 인자들이 구분되는 게 중요하지 않은 경우에는 모든 라벨을 생략한다. e.g.
min(number1, number2)
,zip(sequence1, sequence2)
- 이니셜라이저가 타입 전환을 수행한다면 첫 번째 인자 라벨을 생략한다. e.g.
Int64(someUInt32)
- 첫 번째 인자가 prepositional 문장을 형성한다면 argument label을 설정한다. Argument label은 주로 preposition의 시작이 되어야 한다. e.g.
x.removeBoxes(havingLength: 12)
- 만약 첫 번째 인자가 문법적인 문장의 일부를 형성한다면 label을 할당한다. (e.g.
x.removeBoxes(havingLength: 12)
) - 다른 모든 인자에 라벨을 붙인다.
콘솔 로그
로그는 애플리케이션의 상태, 내부 로직의 흐름을 관찰할 수 있게 출력한 정보를 말한다. Console log는 디버깅 중 디버깅 콘솔에 보여줄 로그르 말한다. Swift에서는 print()
혹은 dump()
를 사용해서 콘솔 로그를 출력할 수 있다.
print()와 dump()
스위프트 표준 라이브러리에는 print()
, dump()
함수가 있다.
print()
: 간략한 정보를 출력한다. 출력하려는 인스턴스의 description 프로퍼티에 해당하는 내용을 출력한다.print()
함수는 로그를 출력한 뒤 줄바꿈 문자(\n
)를 자동으로 삽입한다.dump()
: 더 자세한 정보를 출력한다. 출력하려는 인스턴스의 자세한 내부 콘텐츠까지 출력한다.
예를 들어 아래와 같은 구조체가 있다고 해보자.
print()
dump()
String Interpolation
Swift.org:String Interpolation
String Interpolation은 문자열 보간법으로, 변수, 상수 등의 값을 문자열 내에 나타내고 싶을 때 사용한다. 즉 변수, 상수, 리터럴, 표현 들을 문자열 리터럴 안에 포함시켜서 새로운 String
값을 구성하는 방법이다. 문자열 String Interpolation은 문자열 보간법으로, 변수, 상수 등의 값을 문자열 내에 나타내고 싶을 때 사용한다. 즉 변수, 상수, 리터럴, 표현 들을 문자열 리터럴 안에 포함시켜서 새로운 String
값을 구성하는 방법이다. String interpolation은 한 줄이나 여러 줄 문자열 리터럴 내에서 사용할 수 있다.
사용하는 법은 스트링 리터럴 내에서 \(변수나 상수)
와 같이 써서 표현한다. 추가로 extended string delimiter를 사용해서 string interpolation으로 여겨지는 문자열을 포함한 문자열을 생성할 수 있다.
Extended delimiter 내에서 string interpolation을 사용하려면 백슬래시 뒤에 샵을 붙이고 사용하면 된다.
String interpolation을 사용하면 기본적으로 인스턴스를 description
프로퍼티를 사용해 문자열로 치환한다. 이 description
프로퍼티는 CustomStringConvertible
프로토콜을 준수해서 구현할 수 있다. 만약 하나의 타입에만 국한되지 않거나 다양한 문자열 보간법을 구현하고 싶으면 StringInterpolationProtocol
을 사용하면 된다.
예를 들어 아래의 고양이 구조체가 있다고 해보자.
이 Cat 구조체가 CustomStringConvertible 프로토콜을 따르게 해서 description 프로퍼티를 구성해보겠다.
그리고 출력하면 아래와 같이 나온다.
Comments
주석을 사용해서 코드에서 실행되지 않는 텍스트를 남겨서 기록용이나 리마인더로 쓸 수 있다. 주석은 코드가 컴파일 될 때 Swift 컴파일러에 의해 무시된다.
Swift의 주석은 C의 주석과 매우 유사하다. 한 줄 주석은 슬래시 두 번으로 시작한다.
// This is comment.
여러 줄 주석은 /*
로 시작해서 */
으로 끝난다.
/* This is also a comment
but is written over muliple lines. */
Swift에서는 여러 줄 주석이 중첩되어 작성될 수도 있다. 여러 줄 주석은 이미 주석 처리가 되어 있는 부분이 있다 해도 이런 부분을 포함해서 큰 코드 블럭을 주석처리하게 할 수 있다.
/* This is the start of the first multiline comment.
/* This is the second, nested multiline comment. */
This is the end of the first multiline comment. */
Quick help
Xcode에는 말풍선의 형태로 reference 문서의 요약된 내용을 보여주는 quick help라는 기능이 있다. Quick help로 코드를 작성할 때 레퍼런스 문서로 이동하지 않고도 타입, 메서드 등의 간단한 정보를 확인할 수 있다.
Quick help를 보려면 option키를 누른 상태에서 quick help를 보고 싶은 항목을 클릭해주면 아래와 같이 quick help를 볼 수 있다.
Guick Help Inspector를 command + option + 3 를 통해서 열 수 있다. (참고로 Xcode 버전마다 달라질 수 있는 것 같다.) Quick help inspector를 열면 우측에 quick help가 나타난다.
마크업 문법으로 메서드, 변수, 클래스 등에 주석을 작성하면 quick help로 다른 사람이 이를 확인할 수 있다.
마크업 문법을 활용한 documentation comment
변수, 상수, 클래스, 메서드, 함수 등을 설명할 때 일정한 마크업 형식에 따라 주석을 작성하면 quick help로 이를 확인할 수 있다고 했다. 다만 문서화를 위한 주석은 일반 주석과는 형식이 다르다.
- 한 줄 주석 :
///
슬래시 세 개를 사용한다. command + option + / 을 사용해서 생성할 수 있다. - 여러 줄 주석 :
/**
로 시작한다.
마크업 주석으로 문서화를 하는 것은 그 내용이 많기 때문에 이는 따로 마크업 주석에 대한 doumentation을 보는 것을 추천한다.
Constants and Variables
Swift.org:Constants and Variables
상수와 변수는 이름을 특정 타입의 값과 연관 짓는다. 우리는 상수나 변수를 이용해서 프로그램에서 사용하는 데이터를 메모리에 임시로 저장한다. 상수의 값은 한 번 설정되면 바뀔 수 없지만, 변수는 미래에 다른 값을 가질 수 있다.
상수와 변수 선언하기
상수와 변수는 사용하기 전에 무조건 정의가 되어 있어야 한다. 상수는 let
, 변수는 var
키워드로 선언한다. 만약 저장된 값이 절대로 바뀌지 않는다면, 상수로 선언해야 한다.
let maximumNumberOfLoginAttempts = 10 // 앞으로 절대로 값이 바뀌지 않는다.
var currentLoginAttempt = 0 // 추후에 값이 바뀔 수 있다.
또한 한 줄에 여러 개의 상수, 변수를 컴마로 구분해서 선언할 수 있다.
var x = 0.0, y = 0.0, z = 0.0
상수를 사용하는 이유는 다양하다. 그 이유 중 중요한 것은 가독성으로, 상수를 선언하게 되면 이후 코드에서 값이 변경되지 않을 것이라는 것을 다른 코드나 문서를 읽을 필요 없이 바로 알 수 있다. 즉 값의 변경을 신경 쓰지 않아도 된다. 또한 특정 값에 의미를 부여하기 위해 상수를 사용할 수 있다. 위에서도 봤듯이, 로그인 최대 제한 횟수가 10번이면 이 10이라는 숫자에 의미를 부여하기 위해 maximumNumberOfLoginAttempts
상수를 선언하면 직관적으로 읽기도, 사용할 때도 편하다.
Type Annotations
상수나 변수를 선언할 때 type annotation을 제공해서 상수나 변수가 어떤 타입의 값을 저장할 수 있는지를 분명히 할 수 있다. 상수, 변수 이름 뒤에 콜론을 붙이고, 그 뒤에 타입의 이름을 붙여서 type annotation을 쓸 수 있다.
var welcomeMessage: String // welcomMessage라는 이름의 변수는 String 타입의 값을 저장할 수 있다
위의 코드와 같이 선언에서의 콜론은 “~의 타입” 이라는 뜻으로 welcomeMessage
를 String 타입의 변수로 선언한 것과 같다. 또한 한 줄에 같은 타입의 여러 변수들을 선언할 수 있다.
var red, green, blue: Double
Type annotation을 하지 않게 되면 상수나 변수의 타입을 Swift 컴파일러가 추론하게 된다. 컴파일러가 타입을 추론하게 되면 type annotation을 통해 타입이 명시되어 있을 때보다 더 오랜 시간이 걸리게 된다. 따라서 type annotation을 사용하지 않아도 되지만, type annotation을 사용하는 것도 도움이 될 수 있다. 이는 각자 어떤 부분이 더 중요한지를 생각해서 type annotation을 쓸 것인지, 말 것인지를 결정해서 사용하면 될 것 같다.
Naming Constants and Variables
상수와 변수의 이름은 유니코드 문자를 포함해서 어떤 문자든지 될 수 있다.
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
하지만 공백, 수학적인 심볼, 화살표나 선/박스로 그려진 문자들은 사용할 수 없다. 또한 숫자로 시작할 수도 없다.
만약 상수나 변수를 선언했다면 같은 이름으로 다시 선언하거나 저장할 값의 타입을 다른 타입으로 바꿀 수 없다. 또한 상수를 변수로, 변수를 상수로 바꿀 수도 없다.
만약 이미 예약되어 있는 Swift 키워드로 상수나 변수의 이름을 지었다면 키워드를 backtick(`)으로 감싸서 이름으로 쓸 수 있다. 하지만 이는 가급적 피하는 것이 좋다. |
존재하는 변수의 값을 바꿀 때는 호환되는 타입의 값으로 바꿔줄 수 있다. 상수의 값을 바꾸려고 하면 컴파일 타임 에러가 발생하게 된다.
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome is now "Bonjour!"
Printing Constants and Variables
print(_:separator:terminator:)
함수로 상수나 변수를 출력할 수 있다.
print(friendlyWelcome)
// Prints "Bonjour!"
이 함수는 하나 이상의 값을 적절한 출력 형태로 출력하는 전역 함수다. 예를 들어 Xcode에서 이 함수는 결과를 Xcode의 “console” 창에 출력한다. separator
와 terminator
파라미터는 기본 값을 가지고 있어서 함수를 호출할 때 이를 빼고 호출할 수 있다. 기본적으로 함수는 줄 끝에서 개행을 한다. 개행 대신에 빈 문자열을 넣어서 끝내고 싶다면 print(someValue, terminator: "")
와 같이 호출하면 된다.
Swift는 string interpolation을 사용해서 문자열 안에 placeholder로 상수나 변수의 이름을 포함시키고, Swift가 이걸 해당 상수나 변수의 현재 값으로 대체하게끔 한다. 앞에 백슬래쉬를 붙이고, 괄호를 뒤에 붙인 다음 괄호 안에 이름을 쓰면 된다.
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// Prints "The current value of friendlyWelcome is Bonjour!"
Semicolons
다른 많은 언어들과는 다르게 Swift는 코드 뒤에 세미콜론을 붙이지 않을 수 있지만, 할 수는 있다. 하지만 여러 별개의 문장을 한 줄에 쓸 때는 세미콜론을 꼭 써야 한다.
let cat = "🐱"; print(cat)
// Prints "🐱"