Swift.org 의 원본 A Swift Tour를 수정하여 각색했습니다. 원본 콘텐츠는 Apple Inc.에서 저작했으며 Creative Commons Attribution 4.0 International(CC BY 4.0) 라이센스 에 따라 라이센스가 부여되었습니다.
TensorFlow.org에서 보기 | Google Colab에서 실행 | GitHub에서 소스 보기 |
전통에 따르면 새로운 언어의 첫 번째 프로그램은 "Hello, world!"라는 단어를 인쇄해야 합니다. 화면에. Swift에서는 이 작업을 한 줄로 수행할 수 있습니다.
print("Hello, world!")
Hello, world!
C 또는 Objective-C로 코드를 작성했다면 이 구문이 익숙할 것입니다. Swift에서는 이 코드 줄이 완전한 프로그램입니다. 입력/출력 또는 문자열 처리와 같은 기능을 위해 별도의 라이브러리를 가져올 필요가 없습니다. 전역 범위에서 작성된 코드는 프로그램의 진입점으로 사용되므로 main()
함수가 필요하지 않습니다. 또한 모든 명령문 끝에 세미콜론을 쓸 필요가 없습니다.
이 둘러보기는 다양한 프로그래밍 작업을 수행하는 방법을 보여줌으로써 Swift에서 코드 작성을 시작하는 데 충분한 정보를 제공합니다. 이해하지 못하는 부분이 있어도 걱정하지 마십시오. 이 둘러보기에서 소개된 모든 내용은 이 책의 나머지 부분에서 자세히 설명됩니다.
단순한 가치
상수를 만들려면 let
사용하고 변수를 만들려면 var
사용하세요. 상수의 값은 컴파일 타임에 알 필요는 없지만 값을 정확히 한 번 할당해야 합니다. 이는 상수를 사용하여 한 번 결정했지만 여러 곳에서 사용하는 값의 이름을 지정할 수 있음을 의미합니다.
var myVariable = 42
myVariable = 50
let myConstant = 42
상수 또는 변수는 할당하려는 값과 동일한 유형을 가져야 합니다. 그러나 항상 유형을 명시적으로 작성할 필요는 없습니다. 상수나 변수를 생성할 때 값을 제공하면 컴파일러가 해당 유형을 추론할 수 있습니다. 위의 예에서 컴파일러는 myVariable
의 초기 값이 정수이기 때문에 정수라고 추론합니다.
초기값이 충분한 정보를 제공하지 않는 경우(또는 초기값이 없는 경우) 변수 뒤에 콜론으로 구분하여 유형을 작성하여 지정합니다. 참고: 부동 소수점 숫자에 Float
대신 Double
사용하면 더 많은 정밀도가 제공되며 이는 Swift의 기본 부동 소수점 유형입니다.
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
// Experiment:
// Create a constant with an explicit type of `Float` and a value of 4.
값은 암시적으로 다른 유형으로 변환되지 않습니다. 값을 다른 유형으로 변환해야 하는 경우 원하는 유형의 인스턴스를 명시적으로 만드십시오.
let label = "The width is "
let width = 94
print(label + String(width))
The width is 94
// Experiment:
// Try removing the conversion to `String` from the last line. What error do you get?
문자열에 값을 포함하는 훨씬 더 간단한 방법이 있습니다. 괄호 안에 값을 쓰고 괄호 앞에 백슬래시(``)를 씁니다. 예를 들어:
let apples = 3
print("I have \(apples) apples.")
I have 3 apples.
let oranges = 5
print("I have \(apples + oranges) pieces of fruit.")
I have 8 pieces of fruit.
// Experiment:
// Use `\()` to include a floating-point calculation in a string and to include someone's name in a
// greeting.
여러 줄을 차지하는 문자열에는 세 개의 큰따옴표( """
)를 사용합니다. 닫는 따옴표의 들여쓰기와 일치하는 한 인용된 각 줄의 시작 부분에 있는 들여쓰기가 제거됩니다. 예를 들면 다음과 같습니다.
let quotation = """
Even though there's whitespace to the left,
the actual lines aren't indented.
Except for this line.
Double quotes (") can appear without being escaped.
I still have \(apples + oranges) pieces of fruit.
"""
print(quotation)
Even though there's whitespace to the left, the actual lines aren't indented. Except for this line. Double quotes (") can appear without being escaped. I still have 8 pieces of fruit.
대괄호( []
)를 사용하여 배열과 사전을 만들고 대괄호 안에 인덱스나 키를 작성하여 해당 요소에 액세스합니다. 마지막 요소 뒤에는 쉼표가 허용됩니다.
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
print(occupations)
["Jayne": "Public Relations", "Kaylee": "Mechanic", "Malcolm": "Captain"]
요소를 추가하면 배열이 자동으로 늘어납니다.
shoppingList.append("blue paint")
print(shoppingList)
["catfish", "bottle of water", "tulips", "blue paint", "blue paint"]
빈 배열이나 사전을 만들려면 초기화 구문을 사용하세요.
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
유형 정보를 추론할 수 있는 경우 빈 배열을 []
로, 빈 사전을 [:]
로 작성할 수 있습니다. 예를 들어 변수에 새 값을 설정하거나 함수에 인수를 전달할 때입니다.
shoppingList = []
occupations = [:]
제어 흐름
조건문을 만들려면 if
및 switch
사용하고, 루프를 만들려면 for
- in
, for
, while
및 repeat
- while
사용하세요. 조건 또는 루프 변수 주위의 괄호는 선택 사항입니다. 신체 주위에 교정 장치가 필요합니다.
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
11
if
문에서 조건은 부울 표현식이어야 합니다. 즉, if score { ... }
와 같은 코드는 0에 대한 암시적 비교가 아니라 오류입니다.
누락될 수 있는 값을 처리하기 위해 if
와 let
함께 사용할 수 있습니다. 이러한 값은 선택 사항으로 표시됩니다. 선택적 값은 값을 포함하거나 값이 누락되었음을 나타내는 nil
포함합니다. 값을 선택 사항으로 표시하려면 값 유형 뒤에 물음표( ?
)를 씁니다.
var optionalString: String? = "Hello"
print(optionalString == nil)
false
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
print(greeting)
Hello, John Appleseed
// Experiment:
// Change `optionalName` to `nil`. What greeting do you get?
// Add an `else` clause that sets a different greeting if `optionalName` is `nil`.
선택적 값이 nil
인 경우 조건은 false
이고 중괄호 안의 코드는 건너뜁니다. 그렇지 않으면 선택적 값이 래핑 해제되고 let
뒤의 상수에 할당되어 코드 블록 내에서 래핑 해제된 값을 사용할 수 있게 됩니다.
선택적 값을 처리하는 또 다른 방법은 ??
를 사용하여 기본값을 제공하는 것입니다. 연산자. 선택적 값이 누락된 경우 기본값이 대신 사용됩니다.
let nickName: String? = nil
let fullName: String = "John Appleseed"
print("Hi \(nickName ?? fullName)")
Hi John Appleseed
스위치는 모든 종류의 데이터와 다양한 비교 작업을 지원합니다. 이는 정수 및 동일성 테스트에만 국한되지 않습니다.
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
Is it a spicy red pepper?
// Experiment:
// Try removing the default case. What error do you get?
패턴의 해당 부분과 일치하는 값을 상수에 할당하기 위해 패턴에서 let
어떻게 사용할 수 있는지 확인하세요.
일치하는 스위치 케이스 내에서 코드를 실행한 후 프로그램은 스위치 문에서 종료됩니다. 실행은 다음 사례로 계속되지 않으므로 각 사례의 코드 끝에서 스위치를 명시적으로 중단할 필요가 없습니다.
각 키-값 쌍에 사용할 이름 쌍을 제공하여 사전의 항목을 반복하려면 for
- in
사용합니다. 사전은 순서가 지정되지 않은 컬렉션이므로 해당 키와 값은 임의의 순서로 반복됩니다.
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
25
// Experiment:
// Add another variable to keep track of which kind of number was the largest, as well as what that
// largest number was.
조건이 변경될 때까지 코드 블록을 반복하려면 while
사용합니다. 대신 루프의 조건이 끝에 있을 수 있으므로 루프가 최소한 한 번 실행되도록 할 수 있습니다.
var n = 2
while n < 100 {
n = n * 2
}
print(n)
128
var m = 2
repeat {
m = m * 2
} while m < 100
print(m)
128
..<
사용하여 인덱스 범위를 만들거나 명시적인 초기화, 조건 및 증분을 작성하여 루프에서 인덱스를 유지할 수 있습니다. 이 두 루프는 동일한 작업을 수행합니다.
var total = 0
for i in 0..<4 {
total += i
}
print(total)
6
..<
사용하면 상위 값을 생략하는 범위를 만들 수 있고, ...
를 사용하면 두 값을 모두 포함하는 범위를 만들 수 있습니다.
함수와 클로저
함수를 선언하려면 func
사용하세요. 괄호 안의 인수 목록과 함께 이름 뒤에 함수를 호출합니다. 매개변수 이름과 유형을 함수의 반환 유형에서 분리하려면 ->
사용하세요.
func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
print(greet(name: "Bob", day: "Tuesday"))
Hello Bob, today is Tuesday.
// Experiment:
// Remove the `day` parameter. Add a parameter to include today’s lunch special in the greeting.
기본적으로 함수는 매개변수 이름을 인수의 레이블로 사용합니다. 매개변수 이름 앞에 사용자 정의 인수 레이블을 쓰거나, 인수 레이블을 사용하지 않으려면 _
씁니다.
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
print(greet("John", on: "Wednesday"))
Hello John, today is Wednesday.
튜플을 사용하여 복합 값을 만듭니다(예: 함수에서 여러 값을 반환하는 경우). 튜플의 요소는 이름이나 번호로 참조될 수 있습니다.
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
120 120
함수는 중첩될 수 있습니다. 중첩 함수는 외부 함수에 선언된 변수에 액세스할 수 있습니다. 중첩된 함수를 사용하여 길거나 복잡한 함수의 코드를 구성할 수 있습니다.
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
print(returnFifteen())
15
함수는 일급 유형입니다. 이는 함수가 다른 함수를 값으로 반환할 수 있음을 의미합니다.
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
print(increment(7))
8
함수는 다른 함수를 인수 중 하나로 사용할 수 있습니다.
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
true
함수는 실제로 클로저의 특별한 경우입니다. 나중에 호출할 수 있는 코드 블록입니다. 클로저의 코드는 클로저가 생성된 범위에서 사용 가능한 변수 및 함수와 같은 항목에 액세스할 수 있습니다. 비록 클로저가 실행될 때 다른 범위에 있더라도 마찬가지입니다. 중첩된 함수가 포함된 이에 대한 예를 이미 보셨을 것입니다. 코드를 중괄호( {}
)로 묶어서 이름 없이 클로저를 작성할 수 있습니다. in
사용하여 인수와 반환 유형을 본문에서 분리합니다.
let mappedNumbers = numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
print(mappedNumbers)
[60, 57, 21, 36]
// Experiment:
// Rewrite the closure to return zero for all odd numbers.
클로저를 더 간결하게 작성하기 위한 몇 가지 옵션이 있습니다. 대리자에 대한 콜백과 같이 클로저의 유형이 이미 알려진 경우 매개변수 유형, 반환 유형 또는 둘 다를 생략할 수 있습니다. 단일 명령문 클로저는 암시적으로 유일한 명령문의 값을 반환합니다.
let mappedNumbers2 = numbers.map({ number in 3 * number })
print(mappedNumbers2)
[60, 57, 21, 36]
이름 대신 숫자로 매개변수를 참조할 수 있습니다. 이 접근 방식은 매우 짧은 클로저에 특히 유용합니다. 함수의 마지막 인자로 전달된 클로저는 괄호 바로 뒤에 나타날 수 있습니다. 클로저가 함수의 유일한 인수인 경우 괄호를 완전히 생략할 수 있습니다.
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
[20, 19, 12, 7]
객체와 클래스
클래스를 생성하려면 class
뒤에 클래스 이름을 사용하십시오. 클래스의 속성 선언은 클래스 컨텍스트에 있다는 점을 제외하면 상수 또는 변수 선언과 동일한 방식으로 작성됩니다. 마찬가지로 메서드와 함수 선언도 같은 방식으로 작성됩니다.
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
// Experiment:
// Add a constant property with `let`, and add another method that takes an argument.
클래스 이름 뒤에 괄호를 넣어 클래스의 인스턴스를 만듭니다. 인스턴스의 속성과 메서드에 액세스하려면 점 구문을 사용하세요.
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
이 버전의 Shape
클래스에는 중요한 사항, 즉 인스턴스가 생성될 때 클래스를 설정하는 초기화 프로그램이 누락되어 있습니다. init
사용하여 하나를 생성합니다.
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
name
속성과 초기화 프로그램의 name
인수를 구별하기 위해 self
어떻게 사용되는지 주목하세요. 초기화에 대한 인수는 클래스의 인스턴스를 생성할 때 함수 호출처럼 전달됩니다. 모든 속성에는 선언( numberOfSides
와 같이) 또는 초기화 프로그램( name
과 같이)에서 할당된 값이 필요합니다.
객체 할당이 취소되기 전에 일부 정리 작업을 수행해야 하는 경우 deinit
사용하여 deinitializer를 생성하세요.
하위 클래스에는 클래스 이름 뒤에 콜론으로 구분된 슈퍼클래스 이름이 포함됩니다. 클래스가 표준 루트 클래스를 하위 클래스로 분류할 필요가 없으므로 필요에 따라 슈퍼클래스를 포함하거나 생략할 수 있습니다.
슈퍼클래스의 구현을 재정의하는 하위 클래스의 메서드는 override
로 표시됩니다. 즉, override
없이 우연히 메서드를 재정의하는 것은 컴파일러에서 오류로 감지됩니다. 또한 컴파일러는 슈퍼클래스의 어떤 메서드도 실제로 재정의하지 않는 override
있는 메서드도 감지합니다.
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
print(test.area())
print(test.simpleDescription())
27.040000000000003 A square with sides of length 5.2.
// Experiment:
// - Make another subclass of `NamedShape` called `Circle` that takes a radius and a name as
// arguments to its initializer.
// - Implement an `area()` and a `simpleDescription()` method on the `Circle` class.
저장된 단순 속성 외에도 속성에는 getter 및 setter가 있을 수 있습니다.
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
9.3 3.3000000000000003
perimeter
설정자에서 새 값의 암시적 이름은 newValue
입니다. set
뒤의 괄호 안에 명시적인 이름을 제공할 수 있습니다.
EquilateralTriangle
클래스의 초기화에는 세 가지 단계가 있습니다.
하위 클래스가 선언하는 속성 값을 설정합니다.
슈퍼클래스의 초기화 프로그램을 호출합니다.
슈퍼클래스에 의해 정의된 속성 값을 변경합니다. 메소드, getter 또는 setter를 사용하는 추가 설정 작업도 이 시점에서 수행할 수 있습니다.
속성을 계산할 필요는 없지만 새 값 설정 전후에 실행되는 코드를 제공해야 하는 경우 willSet
및 didSet
사용하세요. 제공한 코드는 초기화 외부에서 값이 변경될 때마다 실행됩니다. 예를 들어, 아래 클래스는 삼각형의 변 길이가 항상 정사각형의 변 길이와 동일하도록 보장합니다.
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
10.0 10.0 50.0
선택적 값으로 작업할 때 ?
메서드, 속성, 첨자 같은 작업 이전에. ?
는 nil
입니다 ?
이후의 모든 내용은 다음과 같습니다. 무시되고 전체 표현식의 값은 nil
입니다. 그렇지 않으면 선택적 값이 래핑 해제되고 ?
래핑되지 않은 값에 작용합니다. 두 경우 모두 전체 표현식의 값은 선택적 값입니다.
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
print(optionalSquare?.sideLength)
Optional(2.5)
열거형과 구조
열거형을 만들려면 enum
사용하세요. 클래스 및 기타 모든 명명된 유형과 마찬가지로 열거형에는 연관된 메서드가 있을 수 있습니다.
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
print(ace)
let aceRawValue = ace.rawValue
print(aceRawValue)
ace 1
// Experiment:
// Write a function that compares two `Rank` values by comparing their raw values.
기본적으로 Swift는 0부터 시작하여 매번 1씩 증가하는 원시 값을 할당하지만 값을 명시적으로 지정하여 이 동작을 변경할 수 있습니다. 위의 예에서 Ace에는 원시 값 1
이 명시적으로 지정되고 나머지 원시 값은 순서대로 할당됩니다. 열거형의 원시 유형으로 문자열이나 부동 소수점 숫자를 사용할 수도 있습니다. 열거형 케이스의 원시 값에 액세스하려면 rawValue
속성을 사용합니다.
init?(rawValue:)
초기화를 사용하여 원시 값에서 열거형 인스턴스를 만듭니다. 원시 값과 일치하는 열거 사례를 반환하거나 일치하는 Rank
가 없으면 nil
반환합니다.
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
열거형의 케이스 값은 원시 값을 작성하는 또 다른 방법이 아니라 실제 값입니다. 실제로 의미 있는 원시 값이 없는 경우에는 값을 제공할 필요가 없습니다.
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
// Experiment:
// Add a `color()` method to `Suit` that returns "black" for spades and clubs, and returns "red" for
// hearts and diamonds.
위에서 열거형의 Hearts
케이스가 참조되는 두 가지 방식에 주목하세요. hearts
상수에 값을 할당할 때 열거형 케이스인 Suit.Hearts
는 상수에 명시적인 유형이 지정되어 있지 않기 때문에 전체 이름으로 참조됩니다. 스위치 내에서 열거형 케이스는 self
값이 이미 슈트로 알려져 있기 때문에 .Hearts
라는 축약형으로 참조됩니다. 값의 유형이 이미 알려져 있으면 언제든지 축약된 형식을 사용할 수 있습니다.
열거형에 원시 값이 있는 경우 해당 값은 선언의 일부로 결정됩니다. 즉, 특정 열거형 사례의 모든 인스턴스는 항상 동일한 원시 값을 갖습니다. 열거 케이스의 또 다른 선택은 케이스와 연관된 값을 갖는 것입니다. 이러한 값은 인스턴스를 만들 때 결정되며 열거 케이스의 각 인스턴스마다 다를 수 있습니다. 연관된 값이 열거 케이스 인스턴스의 저장된 속성처럼 동작하는 것으로 생각할 수 있습니다.
예를 들어, 서버에서 일출 및 일몰 시간을 요청하는 경우를 생각해 보세요. 서버는 요청된 정보로 응답하거나 무엇이 잘못되었는지에 대한 설명으로 응답합니다.
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
Sunrise is at 6:00 am and sunset is at 8:09 pm.
// Experiment:
// Add a third case to `ServerResponse` and to the switch.
스위치 케이스와 값을 일치시키는 과정의 일부로 ServerResponse
값에서 일출 및 일몰 시간이 어떻게 추출되는지 확인하세요.
구조체를 생성하려면 struct
사용하세요. 구조는 메서드와 초기화 프로그램을 포함하여 클래스와 동일한 많은 동작을 지원합니다. 구조와 클래스 간의 가장 중요한 차이점 중 하나는 구조가 코드에서 전달될 때 항상 복사되지만 클래스는 참조로 전달된다는 것입니다.
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
// Experiment:
// Write a function that returns an array containing a full deck of cards, with one card of each
// combination of rank and suit.
프로토콜 및 확장
프로토콜을 선언하려면 protocol
사용하세요.
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
클래스, 열거형 및 구조체는 모두 프로토콜을 채택할 수 있습니다.
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
print(b.adjust())
print(b.simpleDescription)
() A simple structure (adjusted)
// Experiment:
// Add another requirement to `ExampleProtocol`.
// What changes do you need to make to `SimpleClass` and `SimpleStructure` so that they still
// conform to the protocol?
구조를 수정하는 메서드를 표시하기 위해 SimpleStructure
선언에서 mutating
키워드를 사용하는 것에 주목하세요. SimpleClass
선언에는 클래스의 메서드가 항상 클래스를 수정할 수 있으므로 변경으로 표시된 메서드가 필요하지 않습니다.
extension
사용하여 새 메서드 및 계산된 속성과 같은 기존 유형에 기능을 추가합니다. 확장을 사용하여 다른 곳에서 선언된 유형이나 라이브러리나 프레임워크에서 가져온 유형에도 프로토콜 준수를 추가할 수 있습니다.
extension Int: ExampleProtocol {
public var simpleDescription: String {
return "The number \(self)"
}
public mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
The number 7
// Experiment:
// Write an extension for the `Double` type that adds an `absoluteValue` property.
다른 명명된 유형과 마찬가지로 프로토콜 이름을 사용할 수 있습니다. 예를 들어 다양한 유형을 가지고 있지만 모두 단일 프로토콜을 준수하는 개체 컬렉션을 만드는 경우입니다. 유형이 프로토콜 유형인 값으로 작업할 때 프로토콜 정의 외부의 메소드를 사용할 수 없습니다.
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
A very simple class. Now 100% adjusted.
// Uncomment to see the error.
// protocolValue.anotherProperty
변수 protocolValue
에 SimpleClass
런타임 유형이 있더라도 컴파일러는 이를 주어진 유형의 ExampleProtocol
로 처리합니다. 즉, 프로토콜 준수 외에도 클래스가 구현하는 메서드나 속성에 실수로 액세스할 수 없습니다.
오류 처리
Error
프로토콜을 채택하는 모든 유형을 사용하여 오류를 나타냅니다.
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
오류를 발생시키려면 throw
사용하고, 오류를 발생시킬 수 있는 함수를 표시하려면 throws
사용하세요. 함수에서 오류가 발생하면 함수는 즉시 반환되고 함수를 호출한 코드가 오류를 처리합니다.
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
오류를 처리하는 방법에는 여러 가지가 있습니다. 한 가지 방법은 do-catch
사용하는 것입니다. do
블록 내에서 앞에 try를 작성하여 오류를 던질 수 있는 코드를 표시합니다. catch
블록 내에서 오류에는 다른 이름을 지정하지 않는 한 자동으로 error
라는 이름이 지정됩니다.
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
Job sent
// Experiment:
// Change the printer name to `"Never Has Toner"`, so that the `send(job:toPrinter:)` function
// throws an error.
특정 오류를 처리하는 여러 catch
블록을 제공할 수 있습니다. 스위치에서 case
을 작성하는 것처럼 catch
뒤에 패턴을 작성합니다.
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
Job sent
// Experiment:
// Add code to throw an error inside the `do` block.
// What kind of error do you need to throw so that the error is handled by the first `catch` block?
// What about the second and third blocks?
오류를 처리하는 또 다른 방법은 try?
결과를 선택 사항으로 변환합니다. 함수에서 오류가 발생하면 특정 오류가 삭제되고 결과는 nil
입니다. 그렇지 않으면 결과는 함수가 반환한 값을 포함하는 선택 사항입니다.
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
함수가 반환되기 직전에 함수의 다른 모든 코드 이후에 실행되는 코드 블록을 작성하려면 defer
사용합니다. 함수에서 오류가 발생하는지 여부에 관계없이 코드가 실행됩니다. 서로 다른 시간에 실행되어야 하는 경우에도 defer
사용하여 설정 및 정리 코드를 나란히 작성할 수 있습니다.
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
print(fridgeContains("banana"))
print(fridgeIsOpen)
false false
제네릭
일반 함수나 유형을 만들려면 꺾쇠 괄호 안에 이름을 쓰세요.
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
print(makeArray(repeating: "knock", numberOfTimes: 4))
["knock", "knock", "knock", "knock"]
클래스, 열거형 및 구조뿐만 아니라 일반적인 형태의 함수 및 메서드를 만들 수 있습니다.
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
print(possibleInteger)
some(100)
요구사항 목록을 지정하려면 유형 이름 뒤에 where
사용하세요. 예를 들어 프로토콜을 구현하기 위해 유형을 요구하거나, 두 유형이 동일하도록 요구하거나, 클래스가 특정 슈퍼클래스를 갖도록 요구하는 경우입니다.
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
print(anyCommonElements([1, 2, 3], [3]))
true
<T: Equatable>
쓰는 것은 <T> ... where T: Equatable
쓰는 것과 같습니다.