선언 (Declarations)
타입, 연산자, 변수, 등을 도입하고 작성합니다.
선언 (declaration) 은 프로그램에 새로운 이름 또는 구성을 도입합니다. 예를 들어 함수와 메서드를 도입하기 위해, 변수와 상수를 도입하기 위해, 그리고 열거형, 구조체, 클래스, 그리고 프로토콜 타입을 정의하기 위해 선언을 사용합니다. 기존에 명명된 타입의 동작을 확장하기 위해 그리고 다른 곳에서 선언된 기호를 프로그램으로 가져오기 위해 선언을 사용할 수도 있습니다.
Swift 에서 대부분의 선언은 선언과 동시에 구현되거나 초기화 된다는 의미에서도 정의이기도 합니다. 이 말은 프로토콜은 멤버를 정의하지 않으므로 대부분 프로토콜 멤버는 선언만 있습니다. 편의상 그리고 Swift 에서 구별이 그다지 중요하지 않으므로 선언 (declaration) 은 선언과 정의를 모두 포함합니다.
Grammar of a declaration:declaration → import-declaration declaration → constant-declaration declaration → variable-declaration declaration → typealias-declaration declaration → function-declaration declaration → enum-declaration declaration → struct-declaration declaration → class-declaration declaration → actor-declaration declaration → protocol-declaration declaration → initializer-declaration declaration → deinitializer-declaration declaration → extension-declaration declaration → subscript-declaration declaration → macro-declaration declaration → operator-declaration declaration → precedence-group-declaration declarations → declaration declarations?
Swift 소스 파일에서 최상위-수준 코드는 비어있거나 구문, 선언, 그리고 표현식으로 구성됩니다. 기본적으로 소스 파일의 최상위-수준에 선언된 변수, 상수, 그리고 다른 명명된 선언은 동일한 모듈의 일부인 모든 소스 파일 코드에 접근할 수 있습니다. 접근 제어 수준 (Access Control Levels) 에서 설명된대로 접근-수준 수정자 (access-level modifier) 로 선언을 표시하여 기본 동작을 재정의할 수 있습니다.
최상위-수준 선언 (top-level declarations) 과 실행 가능한 최상위-수준 코드 (executable top-level code) 인 최상위-수준 코드의 두가지 종류가 있습니다. 최상위-수준 선언은 선언으로만 구성되고 모든 Swift 소스 파일에서 허용됩니다. 실행 가능한 최상위-수준 코드는 선언 뿐만 아니라 구문과 표현식을 포함하고 프로그램에 대해 최상위-수준 진입점으로만 허용됩니다.
실행 가능하게 만들기 위해 컴파일 한 Swift 코드는 코드가 파일과 모듈로 구성되는 방식과 상관없이 최상위-수준 진입점을 나타내는 다음 접근 방식 중 하나만 포함할 수 있습니다:
main
속성, NSApplicationMain
속성, UIApplicationMain
속성, main.swift
파일, 또는 최상위-수준 실행 가능한 코드를 포함하는 파일.Grammar of a top-level declaration:top-level-declaration → statements?
코드 블럭 (code block) 은 선언 및 제어 구조에서 구문을 그룹화 하기위해 사용됩니다. 다음의 형식을 가집니다:
{
<#statements#>
}
코드 블럭 내에 구문 (statements) 은 선언, 표현식, 그리고 구문의 다른 종류가 포함되고 소스 코드에 나타나는 순서대로 실행됩니다.
Grammar of a code block:code-block →{
statements?}
가져오기 선언 (import declaration) 을 사용하여 현재 파일 바깥에 선언된 기호에 접근할 수 있습니다. 기본 형식은 전체 모듈을 가져옵니다;
import
키워드 다음에 모듈 이름이 따라오도록 구성됩니다:import <#module#>
가져올 기호에 대한 자세한 제한을 제공하면 모듈 또는 하위 모듈 내에 특정 하위 모듈 또는 특정 선언을 지정할 수 있습니다. 이런 상세한 형식이 사용되면 선언한 모듈이 아닌 가져온 기호만 현재 범위에서 사용할 수 있습니다.
import <#import kind#> <#module#>.<#symbol name#>
import <#module#>.<#submodule#>
Grammar of an import declaration:import-declaration → attributes?import
import-kind? import-pathimport-kind →typealias
|struct
|class
|enum
|protocol
|let
|var
|func
import-path → identifier | identifier.
import-path
상수 선언 (constant declaration) 은 프로그램에 명명된 상수값을 도입합니다. 상수 선언은
let
키워드를 사용하여 선언되고 다음의 형식을 가집니다:let <#constant name#>: <#type#> = <#expression#>
상수 선언은 상수 이름 (constant name) 과 초기화 구문 표현식 (expression) 의 값 사이의 변경 불가능한 바인딩을 정의합니다; 상수의 값이 설정된 후에 변경할 수 없습니다. 이 말은 상수가 클래스 객체로 초기화되면 이 객체 자체는 변경할 수 있지만 이 상수 이름과 참조하는 객체 간의 바인딩은 변경할 수 없습니다.
상수가 전역 범위로 선언되면 값으로 반드시 초기화 되어야 합니다. 상수 선언이 함수 또는 메서드의 컨텍스트에 나타나면 해당 값을 처음 읽기 전에 값을 설정한다는 보장이 있는 한 나중에 초기화 될 수 있습니다. 컴파일러가 상수의 값을 읽지 않는다는 것을 증명할 수 있으면 상수에 값을 설정하지 않아도 됩니다. 상수 선언이 클래스 또는 구조체 선언의 컨텍스트에서 나타나면 상수 프로퍼티 (constant property) 로 간주됩니다. 상수 선언은 계산된 프로퍼티가 아니므로 getter 또는 setter 를 가지지 않습니다.
상수 선언의 상수 이름 (constant name) 이 튜플 패턴인 경우 튜플에서 각 항목의 이름은 초기화 구문 표현식 (expression) 에서 해당 값으로 바인딩 됩니다.
let (firstNumber, secondNumber) = (10, 42)
이 예제에서
firstNumber
는 값 10
에 대한 명명된 상수이고 secondNumber
는 값 42
에 대한 명명된 상수 입니다. 두 상수 모두 독립적으로 사용할 수 있습니다:print("The first number is \(firstNumber).")
// Prints "The first number is 10."
print("The second number is \(secondNumber).")
// Prints "The second number is 42."
타입 주석 (
:
타입) 은 타입 추론 (Type Inference) 에서 설명한대로 상수 이름 (constant name) 의 타입이 추론될 수 있을 때 상수 선언에서 선택사항입니다.상수 타입 프로퍼티 (constant type property) 를 선언하려면
static
선언 수식어로 선언을 표시해야 합니다. 클래스의 상수 타입 프로퍼티는 암시적으로 final 입니다; class
또는 final
선언 수식어로 표시하여 하위 클래스에 의한 재정의를 허용하거나 금지할 수 있습니다. 타입 프로퍼티는 타입 프로퍼티 (Type Properties) 에 설명되어 있습니다.상수에 대한 자세한 내용과 사용시 지침에 대한 내용은 상수와 변수 (Constants and Variables) 와 저장된 프로퍼티 (Stored Properties) 를 참고 바랍니다.
Grammar of a constant declaration:constant-declaration → attributes? declaration-modifiers?let
pattern-initializer-listpattern-initializer-list → pattern-initializer | pattern-initializer,
pattern-initializer-list pattern-initializer → pattern initializer? initializer →=
expression
변수 선언 (variable declaration) 은 프로그램에 명명된 변수 값을 도입하고
var
키워드를 사용하여 선언됩니다.변수 선언은 명명된, 변경 가능한 값, 저장된 그리고 계산된 변수와 프로퍼티, 저장된 변수와 프로퍼티 관찰자, 그리고 정적 변수 프로퍼티의 다양한 종류의 선언 형식을 가집니다. 사용할 적절한 형식은 변수가 선언되는 범위와 선언하려는 변수의 종류에 따 라 다릅니다.
다음의 형식으로 저장된 변수 (stored variable) 또는 저장된 변수 프로퍼티 (stored variable property) 를 선언할 수 있습니다:
var <#variable name#>: <#type#> = <#expression#>
이러한 형식의 변수 선언은 전역 범위, 함수의 지역 범위, 또는 클래스나 구조체 선언의 컨텍스트에서 정의합니다. 이러한 형식의 변수 선언이 전역 범위나 함수의 지역 범위로 선언되면 저장된 변수 (stored variable) 로 참조됩니다. 클래스나 구조체 선언의 컨텍스트에서 선언되면 저장된 변수 프로퍼티 (stored variable property) 로 참조됩니다.
초기화 구문 표현식 (expression) 은 프로토콜 선언에 있을 수 없지만 다른 모든 컨텍스트에서 초기화 구문 표현식 (expression) 은 선택사항입니다. 즉, 초기화 구문 표현식 (expression) 이 없으면 변수 선언은 명시적 타입 주석 (
:
타입) 을 포함해야 합니다.상수 선언과 마찬가지로 변수 이름 (variable name) 이 튜플 패턴인 경우 튜플에서 각 항목에 이름은 초기화 구문 표현식 (expression) 에서 해당 값에 바인딩 됩니다.
이름에서 알 수 있듯이, 저장된 변수 또는 저장된 변수 프로퍼티의 값 은 메모리에 저장됩니다.
다음의 형식은 계산된 변수 (computed variable) 또는 계산된 프로퍼티 (computed property) 를 선언합니다:
var <#variable name#>: <#type#> {
get {
<#statements#>
}
set(<#setter name#>) {
<#statements#>
}
}
이러한 형식의 변수 선언은 전역 범위, 함수의 지역 범위, 또는 클래스, 구조체, 열거형, 또는 확장 선언의 컨텍스트에서 정의합니다. 이러한 형식의 변수 선언을 전역 범위 또는 함수의 지역 범위로 선언되면 계산된 변수 (computed variable) 로 참조됩니다. 클래스, 구조체, 또는 확장 선언의 컨텍스트에서 선언되면 계산된 프로퍼티 (computed property) 로 참조됩니다.
getter 는 값을 읽기위해 사용되고 setter 는 값을 작성하기 위해 사용됩니다. setter 절은 선택사항이며 getter 만 필요하다면 읽기-전용 계산된 프로퍼티 (Read-Only Computed Properties) 에 설명한대로 getter 와 setter 절 모두 생략할 수 있고 간단하게 요구된 값을 직접적으로 반환할 수 있습니다. 그러나 setter 절을 제공하면 getter 절도 제공되어야 합니다.
setter 이름 (setter name) 과 둘러싸인 소괄호는 선택사항입니다. setter 이름을 제공하면 setter 에 파라미터의 이름으로 사용됩니다. setter 이름을 제공하지 않으면 짧은 Setter 선언 (Shorthand Setter Declaration) 에서 설명한대로 setter 에 기본 파라미터 이름은
newValue
입니다.저장된 명명된 값과 저장된 변수 프로퍼티와 다르게 계산된 명명된 값 또는 계산된 프로퍼티의 값은 메모리에 저장되지 않습니다.
willSet
과 didSet
관찰자를 저장된 변수 또는 프로퍼티에 선언할 수도 있습니다. 괄찰자와 함께 선언한 저장된 변수 또는 프로퍼티는 다음의 형식을 가집니다:var <#variable name#>: <#type#> = <#expression#> {
willSet(<#setter name#>) {
<#statements#>
}
didSet(<#setter name#>) {
<#statements#>
}
}
전역 범위, 함수의 지역 범위, 또는 클래스나 구조체 선언의 컨텍스트에서 이 형식의 변수 선언으로 정의합니다. 이 형식의 변수 선언이 전역 범위 또는 함수의 지역 범위로 선언되면 관찰자는 저장된 변수 관찰자 (stored variable observers) 로 참조됩니다. 클래스나 구조체 선언의 컨텍스트에서 선언되면 관찰자는 프로퍼티 관찰자 (property observers) 로 참조됩니다.
모든 저장된 프로퍼티에 프로퍼티 관찰자를 추가할 수 있습니다. 프로퍼티 관찰자 재정의 (Overriding Property Observers) 에서 설명한대로 하위 클래스 내에 프로퍼티 재정의로 저장된 또는 계산된 프로퍼티와 상관없이 모든 상속된 프로퍼티에 프로퍼티 관찰자를 추가할 수도 있습니다.
초기화 구문 표현식 (expression) 은 클래스나 구조체 선언의 컨텍스트에서 선택사항 이지만 다른곳에선 필수입니다. 타입이 초기화 구문 표현식 (expression) 에서 유추될 수 있으면 타입 (type) 주석은 선택사항 입니다. 이 표현식은 프로퍼티의 값을 처음 읽을 때 평가됩니다. 프로퍼티의 초기값을 읽지않고 재작성하면 이 표현식은 프로퍼티에 처음 쓰기 전에 평가됩니다.
변수 또는 프로퍼티의 값이 설정되려고 하면
willSet
과 didSet
관찰자는 관찰 및 적절한 반응을 위한 방법을 제공합니다. 관찰자는 변수 또는 프로퍼티가 처음 초기화될 때는 호출되지 않습니다. 대신에 값이 초기화 컨텍스트 바깥에서 설정될 때만 호출됩니다.willSet
관찰자는 변수 또는 프로퍼티의 값이 설정되기 전에만 호출됩니다. 새로운 값은 상수로 willSet
관찰자로 전달되고 willSet
절의 구현에서 변경할 수 없습니다. didSet
관찰자는 새로운 값이 설정된 후 바로 호출됩니다. willSet
관찰자와 달리 변수 또는 프로퍼티의 이전 값은 여전히 접근이 필요한 경우에 didSet
관찰자로 전달됩니다. 이 말은 didSet
관찰자 절 내에 변수 또는 프로퍼티에 값을 할당하면 할당한 새 값이 방금 설정 되어 willSet
관찰자에 전달된 값을 대체합니다.willSet
과 didSet
절에 setter 이름 (setter name) 과 둘러싸인 소괄호는 선택사항 입니다. setter 이름을 제공한다면 willSet
과 didSet
관찰자의 파라미터 이름으로 사용됩니다. setter 이름을 제공하지 않는다면 willSet
관찰자에서 기본 파라미터 이름은 newValue
이고 didSet
관찰자에서 기본 파라미터 이름은 oldValue
입니다.willSet
절을 제공하면 didSet
절은 선택사항 입니다. 마찬가지로 willSet
절은 didSet
절이 제공되면 선택사항 입니다.didSet
관찰자의 본문은 이전 값을 참조하면 getter 는 이전 값이 사용 가능하도록 관찰자 전에 호출됩니다. 아래 예제는 상위 클래스에 의해 정의되고 관찰자를 추가하기 위해 하위 클래스에 의해 재정의 된 계산된 프로퍼티를 보여줍니다.class Superclass {
private var xValue = 12
var x: Int {
get { print("Getter was called"); return xValue }
set { print("Setter was called"); xValue = newValue }
}
}
// This subclass doesn't refer to oldValue in its observer, so the
// superclass's getter is called only once to print the value.
class New: Superclass {
override var x: Int {
didSet { print("New value \(x)") }
}
}
let new = New()
new.x = 100
// Prints "Setter was called"
// Prints "Getter was called"
// Prints "New value 100"
// This subclass refers to oldValue in its observer, so the superclass's
// getter is called once before the setter, and again to print the value.
class NewAndOld: Superclass {
override var x: Int {
didSet { print("Old value \(oldValue) - new value \(x)") }
}
}
let newAndOld = NewAndOld()
newAndOld.x = 200
// Prints "Getter was called"
// Prints "Setter was called"
// Prints "Getter was called"
// Prints "Old value 12 - new value 200"
타입 변수 프로퍼티 (type variable property) 를 선언하기 위해
static
선언 수식어와 함께 선언에 표시합니다. 클래스는 상위 클래스의 구현을 하위 클래스가 재정의 할 수 있도록 class
선언 수식어와 함께 타입 계산된 프로퍼티를 표시할 수 있습니다. 타입 프로퍼티는 타입 프로퍼티 (Type Properties) 에 설명되어 있습니다.Grammar of a variable declaration:variable-declaration → variable-declaration-head pattern-initializer-list variable-declaration → variable-declaration-head variable-name type-annotation code-block variable-declaration → variable-declaration-head variable-name type-annotation getter-setter-block variable-declaration → variable-declaration-head variable-name type-annotation getter-setter-keyword-block variable-declaration → variable-declaration-head variable-name initializer willSet-didSet-block variable-declaration → variable-declaration-head variable-name type-annotation initializer? willSet-didSet-blockvariable-declaration-head → attributes? declaration-modifiers?var
variable-name → identifiergetter-setter-block → code-block getter-setter-block →{
getter-clause setter-clause?}
getter-setter-block →{
setter-clause getter-clause}
getter-clause → attributes? mutation-modifier?get
code-block setter-clause → attributes? mutation-modifier?set
setter-name? code-block setter-name →(
identifier)
getter-setter-keyword-block →{
getter-keyword-clause setter-keyword-clause?}
getter-setter-keyword-block →{
setter-keyword-clause getter-keyword-clause}
getter-keyword-clause → attributes? mutation-modifier?get
setter-keyword-clause → attributes? mutation-modifier?set
willSet-didSet-block →{
willSet-clause didSet-clause?}
willSet-didSet-block →{
didSet-clause willSet-clause?}
willSet-clause → attributes?willSet
setter-name? code-block didSet-clause → attributes?didSet
setter-name? code-block
타입 별칭 선언 (type alias declaration) 은 프로그램에 기존 타입 명명된 별칭을 도입합니다. 타입 별칭 선언은
typealias
키워드를 사용하여 선언되고 다음의 형식을 가집니다:typealias <#name#> = <#existing type#>
타입 별칭이 선언된 후 별칭된 이름 (name) 은 프로그램에서 기존 타입 (existing type) 대신에 사용될 수 있습니다. 기존 타입 (existing type) 은 명명된 타입 또는 복합 타입일 수 있습니다. 타입 별칭은 새로운 타입을 생성하지 않습니다; 간단하게 기존 타입을 참조하도록 이름을 허용합니다.
타입 별칭 선언은 기존 제너릭 타입에 이름을 제공하기 위해 제너릭 파라미터를 사용할 수 있습니다. 타입 별칭은 기존 타입의 일부 또는 모든 제너릭 파라미터에 대한 구체적인 타입을 제공할 수 있습니다:
typealias StringDictionary<Value> = Dictionary<String, Value>
// The following dictionaries have the same type.
var dictionary1: StringDictionary<Int> = [:]
var dictionary2: Dictionary<String, Int> = [:]
타입 별칭이 제너릭 파라미터로 선언되면 파라미터의 제약조건은 기존 타입의 제너릭 파라미터의 제약조건과 완벽하게 일치해야 합니다. 예를 들어:
typealias DictionaryOfInts<Key: Hashable> = Dictionary<Key, Int>
타입 별칭과 기존 타입은 서로 바꿔서 사용될 수 있으므로 타입 별칭은 추가로 제너릭 제약조건을 도입할 수 없습니다.
타입 별칭은 선언에서 모든 제너릭 파라미터를 생략하여 기존 타입의 제너릭 파라미터를 전달할 수 있습니다. 예를 들어 여기 선언한
Diccionario
타입 별칭은 Dictionary
와 동일한 제너릭 파라미터와 제약조건을 가집니다.typealias Diccionario = Dictionary
프로토콜 선언 내에 타입 별칭은 자주 사용되는 타입에 더 짧고 더 편리한 이름으로 제공할 수 있습니다. 예를 들어:
protocol Sequence {
associatedtype Iterator: IteratorProtocol
typealias Element = Iterator.Element
}
func sum<T: Sequence>(_ sequence: T) -> Int where T.Element == Int {
// ...
}
타입 별칭이 없다면
sum
함수는 T.Element
대신에 T.Iterator.Element
로 연관된 타입을 참조해야 합니다.Grammar of a type alias declaration:typealias-declaration → attributes? access-level-modifier?typealias
typealias-name generic-parameter-clause? typealias-assignment typealias-name → identifier typealias-assignment →=
type
함수 선언 (function declaration) 은 프로그램에 함수 또는 메서드를 도입합니다. 클래스, 구조체, 열거형, 또는 프로토콜의 컨텍스트에 선언된 함수는 메서드 (method) 로 참조됩니다. 함수 선언은
func
키워드를 사용하여 선언되고 다음의 형식을 가집니다:func <#function name#>(<#parameters#>) -> <#return type#> {
<#statements#>
}
함수가
Void
의 반환 타입을 가지면 반환 타입은 다음과 같이 생략될 수 있습니다:func <#function name#>(<#parameters#>) {
<#statements#>
}
각 파라미터의 타입은 유추될 수 없으므로 반드시 도입되어야 합니다. 파라미터의 타입 앞에
inout
을 작성하면 파라미터는 함수의 범위 내에서 수정될 수 있습니다. In-out 파라미터는 아래 In-Out 파라미터 (In-Out Parameters) 에서 자세하게 설명되어 있습니다.구문 (statements) 에 단일 표현식만 포함된 함수 선언은 해당 표현식의 값을 반환하는 것으로 이해됩니다. 이 암시적 반환 구문은 표현식의 타입과 함수의 반환 타입이
Void
가 아니고 어떠한 케이스가 아닌 Never
와 같은 열거형이 아닌 경우에만 간주됩니다.함수는 함수의 반환 타입으로 튜플 타입을 사용하여 여러값을 반환할 수 있습니다.
함수 정의는 다른 함수 선언 안에 나타날 수 있습니다. 이러한 함수를 중첩 함수 (nested function) 라 합니다.
중첩 함수는 in-out 파라미터와 같이 절대 탈출되지 않는 값을 캡처하거나 비탈출 함수 인수로 전달된 경우 비탈출 입니다. 그렇지 않으면 중첩 함수는 탈출 함수 입니다.
함수 파라미터는 각 파라미터가 여러 형식 중 하나를 갖는 콤마로 구분된 리스트입니다. 함수 호출에서 인수의 순서는 함수의 선언 내에 파라미터의 순서와 일치해야 합니다. 파라미터 리스트에서 가장 간단한 형식은 다음과 같습니다:
<#parameter name#>: <#parameter type#>
파라미터는 함수 본문 내에서 사용되는 이름 뿐만 아니라 함수 또는 메서드 호출할 때 사용되는 인수 라벨이 있습니다. 기본적으로 파라미터 이름은 인수 라벨로도 사용됩니다. 예를 들어:
func f(x: Int, y: Int) -> Int { return x + y }
f(x: 1, y: 2) // both x and y are labeled
다음의 형식 중 하나로 인수 라벨의 기본 동작을 재정의 할 수 있습니다:
<#argument label#> <#parameter name#>: <#parameter type#>
_ <#parameter name#>: <#parameter type#>
파라미터 이름 전에 이름은 파라미터 이름과 다를 수 있는 명시적 인수 라벨을 파라미터에 제공합니다. 해당 인수는 함수 또는 메서드 호출에서 주어진 인수 라벨을 사용해야 합니다.
파라미터 이름 전에 언더바 (
_
) 는 인수 라벨을 숨깁니다. 해당 인수는 함수 또는 메서드 호출에서 라벨이 없어야 합니다.func repeatGreeting(_ greeting: String, count n: Int) { /* Greet n times */ }
repeatGreeting("Hello, world!", count: 2) // count is labeled, greeting is not
In-out 파라미터는 다음과 같이 전달됩니다:
- 1.함수가 호출될 때 인수의 값은 복사됩니다.
- 2.함수의 본문 내에서 복사본은 수정됩니다.
- 3.함수가 반환될 때 복사본의 값은 기존 인수에 할당됩니다.
이 동작은 copy-in copy-out 또는 call by value 결과 라고 합니다. 예를 들어 계산된 프로퍼티 또는 관찰자가 있는 프로퍼티가 in-out 파라미터로 전달되는 경우 getter 는 함수 호출의 부분으로 호출되고 setter 는 함수 반환의 부분으로 호출됩니다.
최적화로 인수가 메모리의 물리적 주소에 저장된 값인 경우 동일한 메모리 위치가 함수 본문 내부 및 외부에서 사용됩니다. 이런 최적화 동작을 call by reference 라고 합니다; copy-in copy-out 모델의 모든 요구사항을 충족하는 동시에 복사의 오버헤드를 제거합니다. call-by-reference 최적화에 의존하지 않고 copy-in copy-out 에 의해 주어진 모델을 사용하여 작성하면 최적화에 상관없이 올바르게 작동되도록 합니다.
함수 내에서 기존 값이 현재 범위에서 사용가능 하더라도 in-out 인수로 전달된 값은 접근하면 안됩니다. 기존 값에 접근하는 것은 값에 대한 동시 접근이며 Swift 의 메모리 독점 보장을 위반합니다. 같은 이유로 여러개의 in-out 파라미터에 동일한 값을 전달할 수 없습니다.
in-out 파라미터를 캡처하는 클로저 또는 중첩 함수는 비탈출 이어야 합니다. in-out 파라미터를 변경하지 않고 캡처 해야하는 경우 캡처 리스트를 사용하여 파라미터를 변경하지 않고 명시적으로 캡처해야 합니다.
func someFunction(a: inout Int) -> () -> Int {
return { [a] in return a + 1 }
}
in-out 파라미터를 캡처하고 변경이 필요한 경우 함수가 반환하기 전에 모든 변경이 완료되었는지 확인하는 멀티 쓰레드 코드와 같이 명시적으로 지역 복사 (local copy) 를 사용합니다.
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
// Make a local copy and manually copy it back.
var localX = x
defer { x = localX }
// Operate on localX asynchronously, then wait before returning.
queue.async { someMutatingOperation(&localX) }
queue.sync {}
}
파라미터는 무시될 수 있고 다음의 형식을 사용하여 가변의 값을 가지고 기본값을 제공할 수 있습니다:
_ : <#parameter type#>
<#parameter name#>: <#parameter type#>...
<#parameter name#>: <#parameter type#> = <#default argument value#>
언더바 (
_
) 파라미터는 명시적으로 무시되고 함수의 본문 내에서 접근될 수 없습니다.기본 타입 이름 바로 뒤에 세 개의 점 (
...
) 이 오는 파라미터는 가변 파라미터 (variadic parameter) 입니다. 가변 파라미터 뒤에 오는 파라미터는 인수 라벨이 있어야 합니다. 함수는 여러개 가변 파라미터를 가질 수 있습니다. 가변 파라미터는 기본 타입 이름의 요소를 포함한 배열로 처리됩니다. 예를 들어 가변 파라미터 Int...
는 [Int]
로 처리됩니다. 가변 파라미터를 사용하는 예제는 가변 파라미터 (Variadic Parameters) 를 참고 바랍니다.등호 (
=
) 가 있는 파라미터와 타입 뒤의 표현식은 주어진 표현식의 기본값을 가지는 것으로 간주됩니다. 함수가 호출될 때 주어진 표현식은 평가됩니다. 함수 를 호출할 때 파라미터가 생략되면 기본값이 대신 사용됩니다.func f(x: Int = 42) -> Int { return x }
f() // Valid, uses default value
f(x: 7) // Valid, uses the value provided
f(7) // Invalid, missing argument label
self
를 수정하는 열거형 또는 구조체에 메서드는 mutating
선언 수식어로 표시되어야 합니다.상위 클래스 메서드를 재정의한 메서드는
override
선언 수식어로 표시되어야 합니다. override
수식어 없이 메서드를 재정의하거나 상위 클래스 메서드를 재정의 하지 않는 메서드에 override
수식어를 사용하면 컴파일 에러가 발생합니다.타입의 인스턴스가 아닌 타입과 관련된 메서드는 열거형과 구조체의 경우
static
선언 수식어나 클래스의 경우 static
또는 class
선언 수식어로 표시되어야 합니다. class
선언 수식어로 표시된 클래스 타입 메서드는 하위 클래스 구현에 의해 재정의 될 수 있습니다; class final
또는 static
으로 표시된 클래스 타입 메서드는 재정의 될 수 없습니다.특별한 이름을 가지는 메서드는 함수 호출 구문에 대해 구문 설탕 (syntactic sugar) 을 활성화 합니다. 이러한 메서드 중 하나로 타입을 정의하면 타입의 인스턴스는 함수 호출 구문에서 사용될 수 있습니다. 함수 호출은 해당 인스턴스에서 특별하게 명명된 메서드 중 하나로 호출됩니다.
클래스, 구조체, 또는 열거형 타입은 dynamicCallable 에서 설명 한대로
dynamicallyCall(withArguments:)
메서드 또는 dynamicallyCall(withKeywordArguments:)
메서드를 정의하거나 아래에서 설명 한대로 call-as-function 메서드를 정의하여 함수 호출 구문을 제공할 수 있습니다. 타입이 call-as-function 메서드와 dynamicCallable
속성에 의해 사용되는 메서드 중 하나를 모두 정의하면 컴파일러는 두 메서드를 사용할 수 있는 환경에서 call-as-function 메서드를 더 선호합니다.call-as-function 메서드의 이름은
callAsFunction()
또는 callAsFunction(
으로 시작하고 라벨이 있거나 없는 인수를 추가하는 이름이 있습니다 — 예를 들어 callAsFunction(_:_:)
와 callAsFunction(something:)
도 call-as-function 메서드 이름으로 유효합니다.다음의 함수 호출은 동일합니다:
struct CallableStruct {
var value: Int
func callAsFunction(_ number: Int, scale: Int) {
print(scale * (number + value))
}
}
let callable = CallableStruct(value: 100)
callable(4, scale: 2)
callable.callAsFunction(4, scale: 2)
// Both function calls print 208.
call-as-function 메서드와
dynamicCallable
속성의 메서드는 타입 시스템으로 인코딩하는 정보의 양과 런타임에 가능한 동적 동작의 양 사이에 서로 다른 절충안을 만듭니다. call-as-function 메서드를 선언할 때 인수의 숫자와 각 인수의 타입과 라벨을 지정합니다. dynamicCallable
속성의 메서드는 인수의 배열을 보유하기 위해 사용된 타입만 지정합니다.call-as-function 메서드 또는
dynamicCallable
속성의 메서드를 정의해도 함수 호출 표현식이 아닌 다른 컨텍스트에서 함수처럼 해당 타입의 인스턴스로 사용할 수 없습니다. 예를 들어:let someFunction1: (Int, Int) -> Void = callable(_:scale:) // Error
let someFunction2: (Int, Int) -> Void = callable.callAsFunction(_:scale:)
에러를 던질 수 있는 함수와 메서드는
throws
키워드로 표시되어야 합니다. 이러한 함수와 메서드는 던지는 함수 (throwing functions) 와 던지는 메서드 (throwing methods) 라고 합니다. 다음의 형식을 가집니다:func <#function name#>(<#parameters#>) throws -> <#return type#> {
<#statements#>
}
던지는 함수 또는 메서드 호출은
try
또는 try!
표현식 (try
또는 try!
연산자의 범위 내) 으로 래핑되어야 합니다.throws
키워드는 함수의 타입의 일부분이고 던지지 않는 함수는 던지는 함수의 하위 타입입니다. 결과적으로 던지는 함수와 같은 위치에서 던지지 않는 함수를 사용할 수 있습니다.함수가 에러를 발생할 수 있는지 여부를 기준으로 함수를 오버로드 할 수 없습니다. 이 말은 함수 파라미터 (parameter) 가 에러를 발생할 수 있는 여부에 따라 함수를 오버로드 할 수 있습니다.
던지는 메서드는 던지지 않는 메서드를 재정의할 수 없고 던지는 메서 드는 던지지 않는 메서드에 대해 프로토콜 요구사항을 충족할 수 없습니다. 이 말은 던지지 않는 메서드는 던지는 메서드를 재정의할 수 있고 던지지 않는 함수는 던지는 메서드에 대한 프로토콜 요구사항을 충족할 수 있습니다.
함수 또는 메서드는 함수 파라미터 중 하나만 에러를 발생하는 것을 나타내기 위해
rethrows
키워드로 선언될 수 있습니다. 이러한 함수와 메서드는 다시 던지는 함수 (rethrowing functions) 와 다시 던지는 메서드 (rethrowing methods) 라고 합니다. 다시 던지는 함수와 메서드는 적어도 하나의 던지는 함수 파라미터를 가져야 합니다.func someFunction(callback: () throws -> Void) rethrows {
try callback()
}
다시 던지는 함수 또는 메서드는
catch
절 안에만 throw
구문을 포함할 수 있습니다. 이렇게 하면 do
-catch
구문 내에서 던지는 함수를 호출하고 다른 에러를 발생시켜 catch
절의 에러를 처리할 수 있습니다. 또한 catch
절은 다시 던지는 함수의 던지는 파라미터 중 하나에서 발생한 에러만 처리해야 합니다. 예를 들어 다음은 catch
절이 alwaysThrows()
에서 던져진 에러를 처리하므로 유효하지 않습니다.func alwaysThrows() throws {
throw SomeError.error
}
func someFunction(callback: () throws -> Void) rethrows {
do {
try callback()
try alwaysThrows() // Invalid, alwaysThrows() isn't a throwing parameter
} catch {
throw AnotherError.error
}
}
던지는 메서드는 다시 던지는 메서드를 재정의할 수 없고 던지는 메서드는 다시 던지는 메서드에 대한 프로토콜 요구사항을 충족할 수 없습니다. 이 말은 다시 던지는 메서드는 던지는 메서드를 재정의할 수 있고 다시 던지는 메서드는 던지는 메서드에 대해 프로토콜 요구사항을 충족할 수 있습니다.
비동기적으로 실행되는 함수와 메서드는
async
키워드로 표시되어야 합니다. 이 함수와 메서드는 비동기 함수 (asynchronous functions) 와 비동기 메서드 (asynchronous methods) 라고 합니다. 이 형태는 다음과 같습니다:func <#function name#>(<#parameters#>) async -> <#return type#> {
<#statements#>
}
비동기 함수 또는 메서드에 대한 호출은
await
표현식으로 래핑되어야 합니다 — 즉, await
연산자 범위 안에 있어야 합니다.async
키워드는 함수 타입의 부분이고 동기 함수는 비동기 함수의 하위 타입입니다. 결과적으로 비동기 함수가 필요한 컨텍스트에서 동기 함수를 사용할 수 있습니다. 예를 들어 비동기 메서드를 동기 메서드로 재정의 할 수 있으며 동기 메서드는 비동기 메서드가 필요한 프로토콜 요구사항을 충족할 수 있습니다.함수가 비동기인지 아닌지에 따라 함수를 오버로드 할 수 있습니다. 호출 부분에서 컨텍스트는 사용될 오버로드를 결정합니다: 비동기 컨텍스트에서는 비동기 함수가 사용되고 동기 컨텍스트에서는 동기 함수가 사용됩니다.
비동기 메서드는 동기 메서드를 재정의할 수 없으며 비동기 메서드는 동기 메서드에 대한 프로토콜 요구사항을 충족시킬 수 없습니다. 이 말은, 동기 메서드는 비동기 메서드를 재정의할 수 있으며 동기 메서드는 비동기 메서드에 대한 프로토콜 요구사항을 충족할 수 있습니다.
Swift 는 함수 또는 메서드가 호출자에게 반환하지 않음을 나타내는
Never
타입을 정의합니다. Never
반환 타입이 있는 함수와 메서드는 비반환 (nonreturning) 이라고 합니다. 비반환 함수와 메서드는 복구할 수 없는 에러를 발생하거나 무한으로 계속되는 작업을 시작합니다. 이것은 호출 직후 코드가 실행되지 않음을 뜻합니다. 던지고 다시 던지는 함수는 비반환인 경우에도 적절한 catch
블럭으로 프로그램 제어를 전송할 수 있습니다.비반환 메서드를 재정의할 수 있지만 새로운 메서드는 반환 타입과 비반환 동작을 유지해야 합니다.
Grammar of a function declaration:function-declaration → function-head function-name generic-parameter-clause? function-signature generic-where-clause? function-body?function-head → attributes? declaration-modifiers?func
function-name → identifier | operatorfunction-signature → parameter-clauseasync
?throws
? function-result? function-signature → parameter-clauseasync
?rethrows
function-result? function-result →->
attributes? type function-body → code-blockparameter-clause →(
)
|(
parameter-list)
parameter-list → parameter | parameter,
parameter-list parameter → external-parameter-name? local-parameter-name type-annotation default-argument-clause? parameter → external-parameter-name? local-parameter-name type-annotation parameter → external-parameter-name? local-parameter-name type-annotation...
external-parameter-name → identifier local-parameter-name → identifier default-argument-clause →=
expression
열거형 선언 (enumeration declaration) 은 프로그램에 명명된 열거형 타입을 도입합니다.
열거형 선언은 두 가지 기본 형식을 가지고 있고
enum
키워드를 사용하여 선언됩니다. 두 형식 중 하나를 사용하여 선언된 열거형의 본문은 열거형 케이스 (enumeration cases) 라고 불리는 없거나 더 많은 값과 계산된 프로퍼티, 인스턴스 메서드, 타입 메서드, 초기화 구문, 타입 별칭, 그리고 다른 열거형, 구조체, 클래스 그리고 액터 선언을 포함합니다. 열거형 선언은 초기화 해제 구문 또는 프로토콜 선언을 포함할 수 없습니다.열거형 타입은 여러 프로토콜을 채택할 수 있지만 클래스, 구조체, 또는 다른 열거형을 상속할 수 없습니다.
클래스와 구조체와 다르게 열거형 타입은 암시적으로 제공된 기본 초기화 구문을 가지지 않습니다; 모든 초기화 구문은 명시적으로 선언되어야 합니다. 초기화 구문은 열거형 내에 다른 초기화 구문으로 위임할 수 있지만 초기화 프로세스는 초기화 구문이 열거형 케이스 중 하나를
self
에 할당 한 후에만 완료됩니다.구조체와 비슷하지만 클래스와 달리 열거형은 값 타입입니다; 열거형의 인스턴스는 변수 또는 상수에 할당될 때나 함수 호출에 인수로 전달될 때 복사됩니다. 값 타입에 대한 자세한 내용은 구조체와 열거형은 값 타입 (Structures and Enumerations Are Value Types) 을 참고 바랍니다.
다음의 형식은 모든 타입의 열거형 케이스를 포함하는 열거형 타입을 선언합니다:
enum <#enumeration name#>: <#adopted protocols#> {
case <#enumeration case 1#>
case <#enumeration case 2#>(<#associated value types#>)
}
이 형식으로 선언된 열거형은 다른 프로그래밍 언어에서 구별된 공용체 (discriminated unions) 라고도 합니다.
이 형식에서 각 케이스 블럭은
case
키워드 다음에 콤마로 구분된 하나 이상의 열거형 케이스로 구성됩니다. 각 케이스의 이름은 고유해야 합니다. 각 케이스는 주어진 타입의 값을 저장하도록 지정할 수도 있습니다. 이러한 타입은 케이스의 이름 바로 다음에 연관된 값 타입 (associated value types) 튜플로 지정됩니다.연관된 값을 저장하는 열거형 케이스는 지정된 연관된 값을 사용하여 열거형의 인스턴스를 생성하는 함수로 사용될 수 있습니다. 함수와 마찬가지로 코드에 열거형 케이스에 대한 참조를 가져오고 적용할 수 있습니다.
enum Number {
case integer(Int)
case real(Double)
}
let f = Number.integer
// f is a function of type (Int) -> Number
// Apply f to create an array of Number instances with integer values
let evenInts: [Number] = [0, 2, 4, 6].map(f)