반응형

[최종 수정일 : 2017.06.15]

원문 : https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1

Swift 둘러보기(A Swift Tour)

전통적으로 새로운 언어의 첫번째 프로그램은 화면에 Hello, world!라는 단어를 출력한다고 제안합니다. Swift에서, 한 줄로 할수 있습니다.

print("Hello, world!")

C나 Objective-C로 코드를 작성한 경우에, 이 문법은 친근하게 보일것입니다. - Swift에서, 이 코드 줄은 완전한 프로그램입니다. 입력/출력 또는 문자열 처리와 같은 기능을 위해 별로듸 라이브러리를 가져올(import) 필요가 없습니다. 전역 범위에서 작성된 코드는 프로그램의 시작 지점에서 사용되며, main()함수가 필요하지 않습니다. 또한, 모든 문장의 끝에 세미콜론(;)을 작성할 필요가 없습니다.

이 둘러보기(tour)는 다양한 프로그래밍 작업을 수행하는 방법을 보여줌으로써, Swift로 코드를 작성하기 위한 충분한 정보를 줍니다. 뭔가 이해하지 못하여도 걱정하지 마세요 - 이 둘러보기(tour)에서 소개된 모든 내용은 이책의 나머지 부분에서 자세히 설명됩니다.

주의
좋은 경험을 위해, 이 챕터를 Xcode의 플레이그라운드로 엽어줍니다. 플레이그라운드는 코드 목록을 편집하고 결과를 즉시 볼수 있습니다. 
플레이그라운드 다운로드

간단한 값(Simple Values)

상수(constant)를 만들기 위해 let과 변수(variable)를 만들기 위해 var을 사용합니다. 상수 값은 컴파일시에 알 필요가 없지만, 반드시 한번은 정확한 값을 할당해야 합니다. 이것은 한번 결정한 값의 이름을 지정하기 위해 상수를 사용하지만 여러군데에서 사용할 수 있다는 의미입니다.

var myVariable = 42
myVariable = 50
let myConstant = 42

상수나 변수는 반드시 할당하려는 값과 동일한 타입이어야 합니다. 하지만, 항상 타입을 명시적으로 작성할 필요는 없습니다. 상수나 변수를 만들때 값을 제공하면 컴파일러는 타입을 추론합니다. 위의 예제에서, 컴파일러는 myVariable을 초기 값이 정수형이기 때문에, 정수형으로 추론합니다.

초기 값이 중분한 정보를 제공하지 못한 경우(또는 초기값이 없는 경우), 콜론(:)으로 구분하여, 변수 뒤에 타입을 작성합니다.

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

실험
Float 타입이고 값이 4인 상수를 만들어 보세요.

값은 절대 암시적으로 다른 타입으로 변환되지 않습니다. 다른 타입으로 값 변환이 필요한 경우, 명시적으로 원하는 타입의 인스턴스를 만듭니다.

let label = "The width is "
let width = 94
let widthLabel = label + String(width)

실험
마지막 줄에서 String 변환을 제거해 보세요. 어떤 에러가 발생하나요?

문자열에 값을 포함하는 간단한 방법이 있습니다 : 값을 괄호(parentheses ())안에 작성하고 백슬러쉬(\)를 괄호 앞에 붙입니다. 예를 들어 :

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

실험
문자열에 부동소스점 계산을 포함하고 인사말에 다른 사람의 이름을 포함하기 위해 \()를 사용하세요.

여러줄의 문자열을 가져오기 위해서 쌍따옴표 3개를(""") 사용합니다. 종료 따옴표의 들여쓰기와 일치하는 한, 따옴표로 묶은 각 줄의 시작부분에 있는 들여쓰기는 제거됩니다. 예를 들어 :

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.
"""

대괄호(brackets [])을 사용해서 배열과 딕셔너리를 만들고, 대괄호 안에 인덱스 또는 키를 작성해서 요소에 접근합니다. 콤마(,)는 마지막 요소 뒤에 올수 있습니다.

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
 
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

빈 배열이나 딕셔너리를 만들기 위해 초기화 문법을 사용합니다.

let emptyArray = [String]()
let emptyDictionary = [String: Float]()

타입 정보를 추론 할수 있는 경우, []처럼 빈 배열과 [:]처럼 빈 딕셔너리를 작성할수 있습니다. - 예를 들어, 변수에 새로운 값을 설정하거나 함수에 인자를 전달할때

shoppingList = []
occupations = [:]

흐름제어(Control Flow)

조건문을 만들기 위해 if와 switch를 사용하며, 반복문을 만들기 위해 for-in, while, repeat-while를 사용합니다. 조건 또는 반복(loop) 변수 주위의 괄호는 선택적입니다. 본문 주위에 중괄호(braces {})가 필요합니다.

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)

if 문에서, 조건은 반드시 Boolean 표현식이어야 합니다 - 이것은 if score { ... }같은 코드가 오류를 의미하고, 0에 대해서 암시적인 비교하지 않습니다.

누락된 값으로 작업하기 위해 if와 let을 함께 사용할 수 있습니다. 이 값은 옵셔널로 표시됩니다. 옵셔널 값은 값을 포함하거나 값이 없음을 가리키기 위해 nil을 포함합니다. 값 타입 뒤에 물음표(?)를 작성해서 값을 옵셔널로 표시합니다.

var optionalString: String? = "Hello"
print(optionalString == nil)
 
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

실험
optionalName을 nil로 변경합니다. 어떤 인사말을 얻나요? optionalName이 nil인 경우 다른 인사말을 설정하는 else절을 추가하세요.

옵셔널 값이 nil인 경우, 조건은 false이고 중괄호(braces {})에 있는 코드는 건너뜀니다. 그렇지 않으면, 코드 블록 내부에서 업래핑된 값을 사용할수 있도록, 옵셔널 값은 언래핑되고 let 이후에 상수로 할당됩니다.

옵셔널 값을 처리하는 다른 방법은 ?? 연산자를 사용하여 기본 값을 제공합니다. 옵셔널 값이 누락된 경우, 기본 값이 대신 사용됩니다.

let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"

Switch는 모든 종류의 데이터와 다양한 비교연산을 지원합니다 - 정수와 같음을 비교하는 테스트로 제한되지 않습니다.

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

실험
default case를 제거해보세요. 무슨 오류가 발생하나요?

패턴에 일치하는 값을 상수에 할당하기 위해 let 패턴 사용 방법을 주목합니다.

일치하는 switch case 내부에서 코드를 실행한 후에, 프로그램은 switch 문을 종료합니다. 실행은 다음 case로 계속되지 않으며, 따라서 각 case의 코드 끝에 switch문을 벗어나는 break가 명시적으로 필요하지 않습니다.

for-in을 사용해서 각 key-value 쌍에 대해 사용하는 이름의 쌍을 제공하여 딕셔너리의 항목들을 반복합니다. 딕셔너리는 정렬되지 않은 컬렉션이므로, 그 키와 값들은 임의의 순서로 반복됩니다.

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)

실험
가장 큰 숫자가 무엇인지 뿐만 아니라, 어떤 종류의 숫자가 가장 큰지 추적하기 위해 다른 변수를 추가하세요.

조건이 변경될때까지 코드 블록을 반복하기 위해 while을 사용합니다. 반복문의 조건이 끝에 있을 수 있으며, 반복문이 적어도 한번은 실행되는 것을 보장합니다.

var n = 2
while n < 100 {
    n *= 2
}
print(n)
 
var m = 2
repeat {
    m *= 2
} while m < 100
print(m)

범위 인덱스를 만들기 위해 ..< 인덱스를 사용해서 반복문의 인덱스를 유지할수 있습니다.

var total = 0
for i in 0..<4 {
    total += i
}
print(total)

상위 값을 생략하는 범위를 만들기 위해 ..<을 사용하고, 두 값을 모두 포함하는 범위를 만들기 위해 ...을 사용합니다.

함수와 클로저(Functions and Closures)

함수 선언에 func를 사용합니다. 함수 이름 뒤에 괄호 안의 인자 목록으로 함수를 호출합니다. 함수의 반환 타입으로 부터 매개변수 이름과 타입을 구분하기 위해 ->를 사용합니다.

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

실험
day 매개변수를 제거하세요. 인사말에 오늘의 스페셜 점심을 포함하는 매개변수를 추가하세요

기본적으로, 함수는 인자에 대한 레이블(labels) 이름처럼 매개변수 이름을 사용합니다. 매개변수 이름 앞에 사용자정의 인자(argument) 레이블 또는 인자가 없는 레이블을 사용하기 위해 _을 작성합니다.

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

복합적인 값을 만들기 위해 튜플을 사용합니다 - 예를 들어, 하나의 함수에서 여러 값을 반환할수 있습니다. 튜플의 요소들은 각각 이름이나 숫자로 참조(referred)할 수 있습니다.

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)

함수는 중첩될수 있습니다. 중첩된 함수는 외부 함수에 선언된 변수에 접근할 수 있습니다. 길거나 복잡한 함수에서 코드를 구성하기 위해 중첩된 함수를 사용할 수 있습니다.

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

함수는 고급(first-class) 타입입니다. 이것은 함수가 다른 함수를 값으로 반환 할수 있다는 의미입니다.

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

함수는 다른 함수를 인자의 하나로 가질수 있습니다.

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]
hasAnyMatches(list: numbers, condition: lessThanTen)

함수는 실제로 클로저의 특별한 경우입니다 : 나중에 호출될수 있는 코드 블록. 클로저가 실행될때 다른 범위에 있더라도, 클로저 내의 코드는 클로저가 작성된 범위에서 사용가능한 변수와 함수와 같은 것을 접근 할 수 있습니다 - 이미 중첩된 함수가 있는 예제를 봤습니다. 중괄호({})로 둘러싼 코드로 이름없이 클로저를 작성할 수 있습니다. 본문의 인자와 반환 타입을 구분하기 위해 in을 사용합니다.

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})

실험
모든 홀수(odd)에 대해서 0을 반환하도록 클로저를 재작성하세요.

클로저를 보다 더 간결하게 작성하기 위한 몇가지 옵션이 있습니다. 클로저의 타입은 이미 알고 있을때, delegate에 대한 콜백처럼, 매개변수의 타입, 반환값, 또는 둘다 생략할 수 있습니다. 단일 표현 클로저는 표현(statment)에 대한 값만을 암시적으로 반환합니다.

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

이름 대신 숫자로 매개변수를 참조할 수 있습니다 - 이 방법은 짧은 클로저에서 매우 유용합니다. 함수의 마지막 인자로 전달된 클로저는 괄호(parentheses) 바로 뒤에 나타날 수 있습니다. 클로져가 함수의 유일한 인자일때, 괄호를 완전히 생략할 수 있습니다.

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

객체와 클래스(Objects and Classes)

클래스를 만들기 위해 클래스의 이름 앞에 class를 사용합니다. 클래스 내의 프로퍼티 선언은 클래스의 context에 있다는 것만 제외하면, 상수나 변수 선언과 같은 방식으로 작성합니다. 마찬가지로, 메소드와 함수 선언은 같은 방식으로 작성됩니다.

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

실험
let으로 상수 프로퍼티를 추가하고 인자를 가지는 다른 메소드를 추가하세요.

클래스 이름 뒤에 괄호를 넣어 클래스의 인스턴스를 만듭니다. 인스턴스의 프로퍼티와 메소드에 접근하기 위해 dot(.) 문법을 사용합니다.

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

self는 초기화에서 name인자로부터 name프로퍼티를 구분하기 위해 사용됩니다. 초기화에서의 인자는 클래스의 인스턴스를 생성할때 함수 호출처럼 전달됩니다. 모든 프로퍼티는 할당된 값이 필요합니다 - 선언(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")
test.area()
test.simpleDescription()

실험
초기화에서 반지름과 이름을 인자로 받는 NamedShape의 다른 서브클래스 Circle를 만들어보세요. Circle클래스에 area()와 simpleDescription() 메소드를 구현하세요.

저장되는 간단한 프로퍼티 외에, 프로퍼티는 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)

perimeter에 대한 setter에서, 새 값은 암시적인 이름 newValue를 가집니다. set뒤의 괄호 안에 명시적인 이름을 입력할 수 있습니다.

EquilateralTriangle 클래스에 대한 초기화는 3 단계가 있습니다 :

  1. 서브클래스가 선언한 프로퍼티의 값 설정하기
  2. 슈퍼클래스의 초기화를 호출하기
  3. 슈퍼클래스에 의해 정의된 프로퍼티의 값 변경하기. 이 시점에 메소드, 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)

옵셔널 값으로 작업할때, 메소드, 프로퍼티, 서브스크립트를 수행하기 전에 ?를 작성할 수 있습니다. 이전 ?값이 nil이면, ?뒤는 무시되고, 전체 표현식의 값은 nil이 됩니다. 그렇지 않으면, 옵셔널 값은 언래핑되고, ? 뒤 모두 언래핑된 값이 적용됩니다. 두가지 경우 모두, 전체 표현식의 값은 옵셔널 값 입니다.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

열거형과 구조체(Enumerations and Structures)

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
let aceRawValue = ace.rawValue

실험
원시 값 비교로 두 Rank값을 비교하는 함수를 작성하세요.

기본적으로, Swift는 0에서 시작하여 매번 1씩 증가하는 원시값을 할당 하지만, 명시적으로 지정된 값으로 변경할 수 있습니다. 위 예제에서, Ace는 명시적으로 원시 값 1을 주었고, 나머지 원시값은 순차적으로 할당됩니다. 또한 열거형의 원시 타입을 문자열이나 부동소수점 숫자를 사용할 수 있습니다. 열거형 case의 원시 값을 접근하기 위해 rawValue 프로퍼티를 사용합니다.

원시 값으로 열거형의 인스턴스를 만들기 위해 init?(rawValue:) 초기화를 사용합니다. 원시 값과 일치하는 열거형 case를 반환하거나 Rank와 일치하는게 없으면 nil을 반환합니다.

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

열거형의 case 값은 실제 값이며, 원시값을 작성하는 다른 방법이 아닙니다. 사실, 의미있는 원시 값이 없는 경우, 제공할 필요가 없습니다.

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

실험
Suit에 스페이스와 클로버일때 검정색을 반환하고 하트와 다이아몬드에 대해 빨간색을 반환하는 color()메소드를 추가하세요.

위에서 열거형의 hearts case를 참조하는 두가지 방법을 주목하세요 : hearts 상수에 값을 할당할때, 상수는 명시적 타입으로 지정되지 않았기 때문에, 열겨형 case Suit.hearts는 전체 이름으로 참조됩니다. switch 내부에서, suit는 self의 값은 이미 알고 있기 때문에 열거형 case는 .hearts으로 축약된 형태로 참조됩니다. 값 타입을 이미 알고 있는 경우 언제든지 축약된 형식으로 사용할 수 있습니다.

열거형이 원시값을 가지는 경우, 이러한 값들은 선언의 일부로 결정되며, 특정 열거형 case의 모든 인스턴스가 항상 동일한 원시값을 가지는 것을 의미합니다. 열거형 case에 대한 다른 선택은 case와 관련된 값을 가집니다 - 이러한 값들은 인스턴스를 만들때 결정되고, 그것들은 열거형의 각 인스턴스마다 다를 수 있습니다. 관련된 값을 열거형 case 인스턴스의 저장 프로퍼티처럼 동작하는 것으로 생각할 수 있습니다. 예를 들어, 서버로부터 일출(sunrise)과 일몰(sunset) 시간을 요청하는 경우를 생각해 보세요. 서버는 요청된 정보를 응답하거나 무엇이 잘못됐는지 설명을 응답합니다.

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

실험
ServerResponse와 switch에 세번째 case를 추가하세요.

switch case에 대해서 일치하는 값의 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()

실험
Card에 rank와 suit의 조합이 각각 하나씩 있도록, 카드 전체 한 벌(dect)을 만드는 메소드를 추가하세요.

프로토콜과 확장(Protocols and Extensions)

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()
b.adjust()
let bDescription = b.simpleDescription

실험
프로토콜을 준수하는 열거형을 작성하세요.

SimpleStructure의 선언에서 구조체를 변경하는 메소드를 표시하기 위해 mutating키워드를 사용하는 것을 주목하세요. 클래스는 항상 수정할 수 있기 때문에, SimpleClass의 선언은 mutating 으로 표시된 메소드가 필요하지 않습니다.

extension을 사용하여 새로운 메소드와 계산 프로퍼티 처럼, 존재하는 타입에 기능을 추가합니다. 다른 곳에서 선언된 타입에 프로토콜을 준수하도록 확장을 추가할 수 있으며, 심지어는 라이브러리 또는 프레임워크에서 가져온 타입에도 가능합니다.

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)

실험
absoluteValue프로퍼티를 추가하는 Double타입에 대한 확장을 작성하세요.

다른 이름의 타입과 마찬가지로 프로토콜 이름을 사용할수 있습니다 - 예를 들어, 서로 다른 타입을 가지지만 모두 하나의 프로토콜을 준수하는 객체의 컬렉션을 만들수 있습니다. 타입이 프로토콜 타입인 값으로 작업할때, 프로토콜 정의 외부의 메소드를 사용할 수 없습니다.

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty)  // Uncomment to see the error

protocolValue 변수에 SimpleClass의 실시간 타입을 가지고 있으며, 컴파일러는 그것을 ExampleProtocol의 주어진 타입으로 취급합니다. 이것은 프로토콜을 준수하기 위해 클래스는 추가적으로 구현하는 메소드나 프로퍼티에 실수로 접근할 수 없다는 의미입니다.

오류 처리(Error Handling)

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

실험
프린터 이름을 "Never Has Toner"으로 변경해서 send(job:toPrinter:) 함수가 오류를 던지게 하세요.

특정 오류를 처리하는 catch블록을 여러개 제공할 수 있습니다. switch에서 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)
}

실험
do블록 안족에 오류를 던지도록 코드를 추가하세요. 오류가 첫번째 catch블록에서 처리되도록 하려면 어떤 종류의 오류가 필요한가요? 두번째와 세번째 블록은 어떤가요?

오류를 처리하는 다른 방법은 옵셔널로 결과를 변환하는 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
}
fridgeContains("banana")
print(fridgeIsOpen)

제네릭(Generics)

제네릭 함수나 타입을 만들기 위해 꺾긴 괄호(<>) 안에 이름을 작성합니다.

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)

클래스, 열거형, 구조체 뿐만아니라 함수와 메소드의 제네릭 타입을 만들수 있습니다.

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

요구사항 목록을 지정하여 본문 바로 앞에 where를 작성합니다 - 예를 들어, 타입을 프로토콜을 구현하는 것이 요구되거나, 두 타입이 동일한것이 요구되거나, 특정 슈퍼클래스를 가지도록 요구할수 있습니다.

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return true
                }
            }
        }
        return false
}
anyCommonElements([1, 2, 3], [3])

실험
두 시퀀스가 ​​공통으로 가지고있는 요소들의 배열을 배열을 반환하도록 anyCommonElements(_:_:) 함수를 수정하세요.

<T: Equatable>은 <T> ... where T: Equatable 처럼 작성합니다.

반응형

'Swift > Welcome To Swift' 카테고리의 다른 글

버젼 호환성(Version Compatibility)  (0) 2017.06.15
Swift 소개(About Swift)  (0) 2017.06.15
Posted by 까칠코더
,