Swift 1. 스위프트 여행
전통적으로 새로운 언어를 배울때 첫 프로그램에 “Hello world!” 를 출력합니다. 스위프트에서는 다음과 같이 할 수 있습니다.
print("Hello, world!")C 또는 Objective-C 로 코드를 써왔다면, 이 문법이 친숙하게 보일 것 입니다. 스위프트에서는 이 코드 한줄 그 자체가 완전한 프로그램입니다. 다른 별도의 라이브러리를 함수를 위해 import 할 필요 없습니다. 마치 input/output 또는 string handling 을 위한 라이브러리 처럼 말이에요. global scope 로 쓰여진 코드는 프로그램의 entry point 로 사용됩니다. 그렇기에 main() 함수가 필요 없습니다. 또한 모든 statement 의 끝에 semicolons 을 쓸 필요가 없습니다.
이 여행은 너에게 다양한 프로그래밍 작업들을 어떻게 달성하는지 보여줌으로써 스위프트에서 코드 작성을 시작하기 위한 충분한 정보를 줄것입니다. 지금 이 여행에서 몇가지가 이해가 되지 않더라도 이 책의 나머지 부분에서 자세하게 설명해 줄 것이니 너무 걱정하지 마시길 바랍니다.
let 을 사용해서 상수를 만들고 var 을 사용해서 변수를 만듭니다. 상수의 값은 compile time 에 알려질 필요는 없으나, 반드시 값을 딱 한번만 할당해야 합니다. 이 것은 딱 한번 결정해두고 많은 곳에서 사용하는 값을 저장하는데 사용할 수 있다는 뜻입니다.
var myVariable = 42 // 변수
myVariable = 50
let myConstant = 42 // 상수상수나 변수는 반드시 그 값이 할당하려는 값과 동일한 타입이여야 합니다. 그러나, 타입을 명백하게 명시할 필요가 없기에, 상수나 변수를 만들 때, 값을 제공하면 컴파일러가 해당 타입을 추론해 줍니다. 위의 예제에서, 컴파일러는 초기 값이 integer 이기에 myVaraible 을 integer 라고 추론합니다.
만약 초기 값이 충분한 정보를 제공하지 않는다면, 변수 다음에 type을 명시하여 줍니다. colon 으로 띄어서 말이죠.
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70 // 타입 명시한번 해보세요!
상수를 Float 타입을 명시하고 값은 4로 해서 만들어 보세요
Value 는 절대 암시적이게 다른 타입으로 변형되지 않습니다. 만약 value 를 다른 타입으로 바꾸고 싶다면, 원하는 타입의 instance 를 명백하게 해야 합니다.
let label = "The width is "
let width = 94
let widthLabel = label + String(width)한번 해보세요!
마지막 줄의 String 으로 바꾸는 것을 제거해보세요. 어떤 에러가 있나요?
좀 더 쉽게 strings 안에 value 를 포함하는 방법이 있습니다. value 를 가로 안에 적고, backslash 를 가로 전에 적어줍니다. 예를 들면
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I habe \(apples + oranges) pieces of fruit."double quotation marks 를 strings 에 사용해서 multiple lines 를 표현합니다. 인용된 각 줄의 시작부분에 있는 들여쓰기는 제거됩니다.
arrays 와 dictionaries 를 brackets ([]) 을 사용해서 생성합니다. 그리고 그들의 elements 를 brackets 안에 index 또는 key 를 적어서 접근합니다. 마지막 element 에도 ,(comma) 가 허락됩니다.
var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"arrays 는 자동적으로 요소가 추가되면 커집니다.
shoppingList.append("blue paint")
print(shoppingList)빈 array 나 dictionary 를 만드려면, 초기화 문법을 사용합니다.
let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]변수에 새로운 값을 설정하거나, 함수의 argument 전달 되어서 타입정보가 추론될 수 있다면, empty array 는 [], empty dictionary 는 [:] 로 할 수 있습니다.
shoppingList = []
occupations = [:]if, switch 를 사용해서 conditionals 를 만들고, for-in, while, repeat-while 을 사용해서 loops 를 만듭니다. condition 이나 loop variable 주변에 괄호는 선택사항 입니다. 중괄호는 필수 입니다.
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}if statement 안에서, conditional 은 반드시 Boolean expression 이여야 합니다. 이는 0 등등에 대해 암시적 비교가 아님을 의미합니다.
if 랑 let 을 함께 사용해서 optional 한 values 와 함께 작업 할 수 있습니다. optional value 는 value 또는 nil 이 될 수 있음을 이야기 합니다. ? 를 타입 뒤에 적어서 optional 임을 표현 합니다.
var optionalString: String? = "Hello"
print(optionalString == nil)
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName { // nil 이 아니면
greeting = "Hello, \(name)"
}한번 해보세요!
optionalName 을 nil 로 바꿔보세요. 어떻게 되나요? else 절도 추가해 보세요.
만약 optional value 가 nil 이면, conditional 은 false 가 되고 code 의 중괄호는 생략됩니다. 그렇치 않다면, optional value 는 벗겨져서 let 다음에 있는 constant 에 할당됩니다. 그렇게 해서 벗겨진 value 는 code block 안에서 이용가능해집니다.
optional value 를 다루는 또 다른 방법은 ?? 를 사용해서 default value 를 제공하는 것입니다. 만약 optional value 가 nil 이였다면, default value 가 대신 사용됩니다.
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"switch 는 모든 종류의 data 를 지원하고 다양한 비교 연산자를 지원합니다.
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.")
}패턴과 일치하는 값을 상수에 할당하기 위해 어떤 방법을 썻는지 살펴보세요.
for-in 을 사용해서 dictionary 안에 있는 items 을 순회 할 수 있습니다.
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 (_, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)while 을 사용해서 조건이 끝날 때 까지 블록의 코드를 반복할 수 있습니다. repeat-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)func 을 사용해서 함수를 선언하세요. 함수의 이름과 arguments 들을 괄호 안에 넣어주면 함수를 호출 할 수 있습니다. → 을 사용해서 parameter 의 이름들과 함수의 반환 타입을 구분지어 주세요.
func greet(person: String, dev: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
// 만약 argument parameter 인 person: 을 붙이지 않으면 에러가 발생합니다.기본적으로 파라미터의 이름을 arguments 에 대한 별명으로 사용합니다. 다른 별명을 붙여주려면 parameter 이름 앞에 새로운 별명을 적으면 됩니다. 별명을 사용하지 않을 거면 _ 을 적어주면 됩니다.
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("Im", on: "수요일")합쳐진 value 를 만드려면 tuple 을 사용합니다. 예를 들어, 함수에서 다수의 값을 반환하고 싶을때 말이죠. tuple 의 요소들은 이름 또는 순서 로 불려질 수 있습니다.
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]) // argument label 꼭 넣어주고
print(statistics.sum) // 이름으로 가능하고
print(statistics.1) // 순서로도 가능하다! 0, 1, 2함수는 함수안에 포함되는 것이 가능합니다. 포함된 함수들은 포함하는 함수에 선언된 variables(상수포함)에 접근 할 수 있습니다. 길고 복잡한 함수 안에 코드를 조작할 때 nested functions 를 사용 할 수 있습니다.
func returnFifteen() -> Int {
var y = 10
func add() {
y+= 5 // 포함하는 함수의 변수에 접근중
}
add()
return y
}
returnFifteen()함수는 first-class type 입니다. 이는 함수가 value 처럼 반환으로도 쓰일 수 있음을 의미합니다.
func makeIncrementer() -> ((Int) -> Int) { // 함수 반환 한다고 명시, 괄호 안씌워도 되나 눈에 잘 띄려고 씌움
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne // 함수 반환
}
let increment = makeIncrementer() // 함수를 참조중
increment(7)함수는 다른 함수를 arguments 로 취할 수 있습니다
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 = [29, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen) // 함수를 인자(argument) 로 사용중함수는 closues 들중 특별한 케이스 인데, 블록의 코드가 나중에 실행될 수 있기 때문입니다. closure 안에 있는 코드는 해당 closure 가 만들어질때의 scope 에서 이용가능한 variables, function 같은 것들을 접근할 수 있습니다. 이는 심지어 closure 가 다른 scope 에서 실행되도 말이죠. 이름이 없는 closure 를 (braces) 로 둘러싸지 않고도 만들 수 있습니다. in 을 사용해서 body 로 부터 arguments 와 return type 을 분리합니다.
numbers.map({ (number: Int) -> Int in // arguments 와 return type
let result = 3 * number // body
return result // body
})closure 적을 때 좀더 간결한 방법이 몇가지 있는데, closure 의 타입이 이미 알려졌을 때, 파라미터 타입과 반환 타입을 생략할 수 있다는 것입니다. 여기에다가 만약 closure 가 single statement 이면 함축적으로 해당 statement 를 반환하는 것으로 합니다.
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)더 대박인 것은 parameters 를 이름 대신에 순서 번호로 참조 할 수 있다는 것입니다. 이 방법은 특히 짧은 closure 에 유용합니다. 거기다가 만약 함수에 마지막 argument 로 closure 가 전달될 때에는 괄호를 닫고 그 뒤에 바로 적을 수 도 있습니다. 만약 closure 가 함수의 단 하나뿐인 인자로 전달 될 때는? 그냥 괄호도 없이 closure 만 적어서 호출 할 수 도 있습니다!!! 어썸~
let sortedNumebrs = numbers.sorted {$0 > $1}
// 1. 이 경우 클로저의 (파라미터랑 반환 타입)을 아예 생략해버리고
// 2. closure 에 전달된 인자 조차 이름이 아닌 순서 번호로 참조해버리고
// 3. 클로저의 반환도 single statement 그 자체가 되어버리고
// 4. sorted 함수는 인자가 하나만 있기에 (하나만 있다는 것은 마지막인 것과도 같다)
// 괄호로 sorted 함수를 호출하는 방법에서
// () 를 생략해버리고 closure 만 적어버리는 획기전이 방법을 사용하고 있다.class 키워드를 적어주고 이름을 적어주면 class 를 만들 수 있습니다. class 안에 property 를 선언해줍니다 (선언하는 방식은 constant 와 varaible 과 같습니다). 속성이랑 똑같이 method, function 선언도 같습니다.
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}class 의 instance 는 class 이름 다음에 괄호를 붙여서 생성할 수 있습니다. dot syntax 를 사용하면 인스턴스의 속성들과 methods 에 접근 할 수 있습니다.
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()initializer 를 사용해서 class 의 인스턴스가 만들어 질때 class 를 설정할 수 있습니다. initializer 를 생성하려면 init 을 사용합니다
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name // self 키워드
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}self 는 속성의 이름과 initializer 에게 전달된 argument 의 이름을 구별하기 위해 사용합니다. initializer 의 argument 는 함수처럼 클래스의 인스턴스를 생성할 때 전달 됩니다. 모든 속성들은 반드시 값(value)가 할당되어야 하는데, 선언시에 바로 해줄 수도 있고 initializer 를 통해서 할 수 도 있습니다.
deinit 을 사용해서 deinitializer 를 만들고, object 가 deallocated 되기 전에 deinitializer 를 수행하도록 할 수 있습니다.
subclass 들은 class 이름 옆에 colon 으로 띄우고 supreclass 의 이름을 적어줍니다.
superclass 의 method 구현을 override 키워드와 함께 override 할 수 있습니다. 만약 override 키워드 없이 하면 컴파일에러가 발생합니다. superclass 에 존재하지 않는데 override 라고 적어도 컴파일러가 탐지합니다.
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
deinit() {
// 여기는 object 가 메모리에서 deallocated 되기 직전에 실행됩니다.
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test: Square? = Square(sideLength: 5.2, name: "my test square")
test?.area()
test?.simpleDescription()
test = nil간단히 속성 선언 외에도, 속성들은 getter 와 setter 를 가질 수 있습니다.
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name) // argument label 꼭 적어주고
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
self.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 를 보면, new value 가 newValue 라는 이름을 가진 것을 볼 수 있습니다. 명확하게 이름을 줄 수 도 있는데 set 옆에 괄호를 넣고 그 안에 이름을 적으면 됩니다.
위에 있는 EquilateralTriangle 클래스의 initializer 는 3개의 단계를 거치고 있는데,
- subclass 에 선언된 속성을 설정한다.
- superclass 의 initializer 를 호출한다.
- superclass 에 선언된 속성의 값을 바꾼다.
만약 속성을 계산할 필요는 없는데, 뭔가 새로운 값을 설정하기 전이나 하고난 뒤에 특정 코드를 실행하고 싶다면, willSet 과 didSet 을 사용하면 됩니다. 이 코드는 initializer 와 상관 없이 value 가 바뀌면 언제든 실행되는 것입니다. 예를 들어, 아래의 class 는 사각형과 삼각형의 sideLength 가 항상 같음을 보장합니다.
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.square.sideLength) // 사각형 50 으로 바꾸면? 삼각형 길이도 50으로~optional values 를 작업한다면, ? 를 넣어주면 만약 nil 이였을 때는 ? 뒤의 expression 의 값이 nil 로 설정됩니다. nil 이 아니면 ? 뒤가 optional value 가 벗겨져서 실행됩니다. 둘다 whole expression 의 값은 optional value 입니다.
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
if let sideLength = optionalSquare?.sideLength {
print(sideLength)
}enum 키워드를 사용해서 enumeration 을 생성합니다.
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기본적으로, Swift 는 raw value 0부터 시작해서 하나씩 증가시켜 줍니다. 물론 명백하게 값을 할당해서 바꿀 수도 있습니다. string 이나 floating-point 도 enum 의 raw type 으로 사용 가능합니다. 각 enumeration case 의 raw value 에 접근하려면 rawValue 속성을 사용하면 됩니다.
enum 의 init?(rawValue:) 함수를 사용하면 해당하는 rawValue 의 enum case 를 반환하거나 nil 을 반환합니다.
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}enum 의 각 case 의 값은 raw value 를 쓰는 것도 있지만 자기 자신을 쓸 수 도 있습니다. raw value 로 할 만한게 없다면 제공하지 않아도 됩니다.
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()각 enum case 를 참조하는 2가지 방법이 있는데, 상수에 enum case 를 할당할 때는 Suit.hearts 처럼 풀네임을 적어주어야 하지만 (왜냐면 명확한 타입이 적혀있지 않기 때문입니다), switch 문을 보면 .hearts 로 축약된 형식으로 적었는데, 이는 타입을 미리 알 수 있기 때문입니다. value 의 type 이 알려져 있다면, 언제든지 축약된 형태를 써도 됩니다. ex) let hearts: Suit = .hearts
enumeration 이 raw value 를 사용할 때는, 선언시에 그 값이 결졍됩니다. 이는 enum의 특정 case의 모든 인스턴스는 항상 같은 raw value 를 갖는 다는 것입니다. enum의 case 에 대한 다른 선택지는 해당 case 와 연관된 값을 가지도록 하는 것인데, 이런 값들은 instance 를 생성할 때 결정됩니다. 그렇기에 특정 enum case 의 모든 인스턴스는 가지고 있는 값이 다를 수 있습니다. 예를들어, 서버에서 일출과 일몰의 시간을 요청받은 경우를 고려해봅시다. 시간을 반환할 수도 있겠지만, 뭔가 잘못됬으면 그에대한 설명을 반환 할 수도 있겠죠.
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am". "8:09 pm")
let failure = ServerReponse.failure("Out of cheese.")
switch sueccess {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}ServerReponse 의 인스턴스로부터 각각의 enum case 에 맞게끔 값이 추출 됬는지를 보십시요.
struct 키워드를 사용해서 Structure 를 생성할 수 있습니다. 클래스에서 제공하는 많은 것들을 똑같이 제공하지만, 가장 중요한 차이점은 structure 은 전달시에 복붙이지만, class 는 참조가 전달된다는 점입니다.
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()protocol 키워드를 사용해 protocol 을 선언하세요.
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust() // 돌연변이 함수
}Classes, enumerations, structs 모두 protocol 을 채택할 수 있습니다.
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.simpleDescriptionSimpleStructure 의 mutating 키워드는 메소드가 structure 를 변경시킬수 있다는 것을 의미합니다. SimpleClass 는 표시되지 않았는데, 이는 class 의 method 는 항상 class 를 변경시키기 때문입니다.
extension 키워드는 이미 존재하는 타입에 functionality 를 추가할 때 사용합니다. 새로운 method 또는 computed properties 같은 것을 추가할 수 있습니다. 이 extension 을 사용해서 선언된 곳이 어디든, 심지어 library 나 framework 로 부터 imported 된 것이라도 protocol 에 준수사항을 추가할 수 있습니다.
extension Int: ExampleProtocol { // Int 타입에 extension 으로 프로토콜을 추가
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)한번 해보세요!
Double 타입에 absoluteValue 속성을 추가해봅시다.
protocol 이름은 다른 named type 처럼 사용할 수 있습니다. 예를 들어 모두 다른 타입의 객체이지만 single protocol 을 따르고 있을 때, 해당 객체들을 protocol 의 collection 으로 모을 수 있습니다. 대신 protocol type 인 value 들과 작업할 때는 protocol 밖에서 선언된 것들을 사용하지 못합니다.
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty) // Uncomment to see the errorruntime 에서 protocolValue 의 타입은 SimpelClass 이지만, 컴파일러는 이 객체를 주어진 타입인 ExampleProtocol 처럼 다룹니다.
Error protocol 을 채택하는 모든 타입은 error 로 사용할 수 있습니다.
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}throw 를 사용해서 error 을 던질 수 있습니다. throws 는 함수가 error 을 throw 할 수 있음을 이야기 합니다. 만약 너가 error 를 함수 안에서 throw 했다면, 함수는 그 즉시 return 되고, 이 함수를 부른 곳에서 error 를 다루게 됩니다.
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}error 를 다루는데 몇가지 방법이 있는데, 첫번째 방법은 do-catch 를 사용하는 것입니다. do 블록 안에서 error 를 던질 수도 있는 코드 앞에 try 를 명시해줍니다. catch 블록 안에는, 다른 이름을 주지 않아도 error 라는 이름으로 error 가 주어집니다.
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerReponse)
} catch {
print(error) // 자동으로 주어진 이름
}다수의 catch 블록을 만들어서 특정 error 들에 대해 따로 다룰 수도 있습니다. catch 다음에 switch 의 case 처럼 똑같은 pattern 으로 사용하면 됩니다.
do {
let printerReponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerReponse)
} 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)
}error 을 다루는 또 다른 방법은 try? 를 사용해서 result 를 optional 로 바꾸는 것입니다. 만약 함수가 error 를 던진다면, 해당 error 는 버려지고 결과로 nil 이 주어집니다. 에러가 아니면 결과 값이 optional 로 감싸져서 주어집니다.
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")defer 을 사용해서 함수안의 다른 모든 코드들이 다 실행된 후에 결과값이 반환되기 직전에 실행되는 코드를 작성할 수 있습니다. 이 코드는 error 가 던져져도 실행됩니다. defer 를 사용해서 setup 과 cleanup code 를 눈으로 보기에 나란히 작성할 수 있게 됩니다.
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true // 설정코드
defer { // clean up 코드
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)generic (function, type) 를 만들기 위해 꺽쇠 괄호(angle brackets) 안에 이름을 적어줍니다.
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result: [Item] = []
for _ in 0..<numberOfTimes { // 1...numberOfTimes 랑 동일
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)generic 한 형태의 함수와 메소드를 만들 수 있습니다. 클래스, enumeration, structures 모두 가능합니다.
// 스위프트의 optional 타입을 구현해본다
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)where 키워드를 body 전에 사용해서 요구사항들을 나열할 수 있습니다. 예를 들면 type 이 특정 protocol 을 구현해야한다던지, 두 타입이 동일해야 한다던지, 특정 상위 타입을 가지고 있어야 한다던지.
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
}
anyCommonElements([1, 2, 3], [3])한번 해보세요!
위 함수를 두 sequence 에서 공통인 elements 들의 array 를 반환하는 것으로 만들어 보세요.
고생하셨습니다. ㅎㅎ
출처 : https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html (opens in a new tab)
작성일 : 2022.01.15
© Muleo.