The Swift Programming Language 번역본 보기
| 핵심만 골라 배우는 |
| SwiftUI 기반의 iOS프로그래밍 |
| SwiftUI, Xcode, 스위프트 언어로 iOS앱 개발하기 |
| 닐스미스 지음/ 황반석 옮김 |
Chap5. 데이터 타입, 상수, 그리고 변수
- 문자열 데이터 타입 : 문자열 보간(string interpolation) 개념을 이용하여 변수,상수, 표현식, 함수 호출을 조합하여 구성할 수도있다.
- 여러줄 문자열은 삼중 따옴표 안에 넣어서 선언할 수 있다.
- 변수는 var 키워드를 사용하여 선언하며, 변수를 생성할 때 값으로 초기화할 수있다. 만약 어떤 변수가 초깃값 없이 선언되었다면, 옵셔널(optional)로 선언된 것으로 간주한다.
- 상수는 let 키워드를 사용하여 선언한다.
- 상수 또는 변수의 타입을 지정하는 방법은 두 가지가 있다. 하나는 변수나 상수를 선언할 때 타입 애너테이션(type annotaion)을 사용하는것이다. 이것은 변수나 상수 이름 뒤에 콜론을 쓰고 타입을선언하는 것이다. 선언부에 타입 애너테이션이 없다면 스위프트 컴파일러는 타입 추론(type inference)이라는 기술을 사용하여 변수 또는 상수의 타입을 지정한다. 컴파일러가 타입 추론을 사용하게 되면 변수 또는 상수가 초기화되는 시점에 할당된 값의 타입이 무엇인지 판단하여 해당 타입으로 지정한다. 타입 애너테이션 없이 상수를 선언하게 될 경우에는 반드시 선언 시점에서 값을 할당해야 한다.
- 튜플은 여러 값을 하나의 항목으로 임시적으로 그루핑하는 매우 간단한 방법이다. 서로 다른 타입의 값들이 튜플에 저장될 수 있으며, 모두 동일한 타입의 값이어야 한다는 제약도 없다.
let myTuple = (10, 432.433, "This is a String") let myString = myTuple.2 print(myString) // 하나의 구문으로 튜플에 있는 모든 값을 추출하여 변수 또는 상수에 각각 할당하는 방법도 있다. let (myInt, myFloat, myString) = myTuple // 튜플을 생성할 시점에서 각각의 값을 변수에 할당할 수도 있다. let myTuple = (count: 10, length: 432.433, message: "This is a String") print(myTuple.message)
스위프트 옵셔널 타입
- 변수를 선언할 때, 데이터 타입 애너테이션 다음에 ‘?’ 문자를 두어 옵셔널이 되게 한다.
var index: Int?
- 이제 index변수는 정수값이 할당되거나 아무런 값도 할당되지 않을 수 있다. 내부적으로 컴파일러와 런타임의 관점에서 볼 때 어떤 값도 할당되지 않은 옵셔널은 실제로 nil의 값을 갖는다. 만약, 옵셔널에 값이 할당되었다면 해당 값이 옵셔널 내에서 래핑되었다(wrapped)고 말한다. 옵셔널안에 래핑된 값을 사용할 때는 강제 언래핑(forced unwrapping)이라는 개념을 이용하게 된다. 간략하게 말해, 래핑된 값은 옵셔널 데이터 타입에서 옵셔널 이름 뒤에 느낌표(!)를 두어 추출하게 된다.
var index: Int?
index = 3
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
if index != nil {
print(treeArray[index!]
} else {
print("index does not contain a value")
}
강제 언래핑대신, 옵셔널로 할당된 값을 옵셔널 바인딩을 이용하여 임시변수나 상수에 할당할 수 있으며, 구문은 다음과 같다. 여기서는 index 변수에 할당된 값이 언래핑되어 동일한 이름의 index라는 임시상수에 할당되어 배열에 대한 인덱스로사용된다. if 구문이 끝나면 이상수는 더이상 존재하지 않는다.
if let index = index {
print(treeArray[index])
}
위의 구문은 아래와 동일하다.
if let index {
print(treeArray[index])
}
암묵적으로 언래핑되도록 옵셔널을 선언할 수도 있다. 이런 방식으로 옵셔널을 선언하게 되면 강제 언래핑이나 옵셔널 바인딩을하지 않아도 값에 접근할 수 있다. 옵셔널을 선언할 때 물음표(?) 대신 느낌표(!)를 사용하여 암묵적으로 언래핑되도록 하는 것이다.
var index: Int! //이제 옵셔널은 암묵적으로 언래핑된다.
index = 3
var treeArray = ["Oak", "Pine", "Yew", "birch"]
if index != nil {
print(treeArray[index]
} else {
print("index does not contain a value")
}
스위프에서 할당된 값이 없거나 nil을 할당할 수 있는 것은 옵셔널 타입 뿐이다. 즉, 스위프트에서 옵셔널이 아닌 변수 또는 상수에는 nul을 할당할 수 없다.
타입 캐스팅과 타입 검사
컴파일러가 어떤 값의 특정 타입을 식별하지 못하는 경우가 발생할 것이다. 이런 경우는 메서드나 함수가 반환하는 값이 불명확하거나 예상되지 않는 타입의 값일 때 종종 발생한다. 이럴 때는 as 키워드를 사용하여 여러분의 코드가 의도하는 값의 타입을 컴파일러가 알 수 있게 해야 한다. 이것을 타입 캐스팅(type casting, 형변환)이라고 한다.
예를 들어 다음은 object(forKey:) 메서드가 변환하는 값을 String 타입으로 처리해야 한다고 컴파일러에게 알려주는 코드다.
let myValue = record.object(forkey: “comment”) as! String
실제로 타입 캐스팅에는 업캐스팅과 다운캐스팅이라는 두가지 형태가 있다. 업캐스팅은 특정 클래스의 객체가상위 클래스들 중의 하나로 변형되는 것을 말한다. 업캐스팅은 as 키워드를 사용하여 수행되며, 이러한 변환은 성공할 것이라고 컴파일러가 알려줄 수 있기 때문에 보장된 변환(guaranteed conversion)이라고도 한다. 예를 들어, 다음과같이 UIKit 클래스 계층 구조를 볼 때 UIButton 클래스는 UIControl 클래스의 하위 클래스이다.
let myButton: UIButton = UIButton()
let myControl = myButton as UIControl
다운캐스팅은 보통 어떤 클래스에서 그 클래스의 하위 클래스로 변환하게 된다. 다운캐스팅은 as! 키워드로 수행되며, 이를 강제변환(forced conversion)이라고 한다. 예를 들어, 다음과 같이 UIKit 의 UISscrollView 클래스는 UITableView 클래스와 UITextView 클래스라는 하위 클래스를 가지고 있다.
let myScrollView: UIScrollView = UIScrollView()
let myTextView = myScrollView as! UITextView
위의 코드는 에러 없이 컴파일될 것이나 UIScrollView를 UITextView로 변환할 수 없으니 실행 중에 충돌이 발생할 것이다.
다운캐스팅을 하는 더 안전한 방법은 as?를 사용한 옵셔널 바인딩을 사용하는 것이다. 만약 변환이 성공적으로 수행된다면 지정한 타입의 옵셔널 값이 변환될 것이며, 변환에 오류가 발생한다면 옵셔널 값은 nil이 될 것이다.
let myScrollView: UIScrollView = UIScrollView()
if let myTextView = myScrollView as? UITextView {
print("Type cast to UITextView succeeded")
} else {
print("Type cast to UiTextView failed")
}
is 키워드를 사용하여 타입검사를 할 수도 있다. 예를 들어, 다음은 해당 객체가 MyClass라는 이름의 클래스의 인스턴스이지를 검사하는 코드다.
if myobject is MyClass {
// myobject는 MyClass의 인스턴스다.
}
Chap6. 연산자와 표현식
연산자와 표현식
- 범위연산자
- 단힌 범위 연산자 : x…y
- 반개방 범위연산자: x..<y (예시: 5..<8은 5,6,7를 의미한다.)
- 단방향 범위연산자 : x… 또는 …y (예시: 2… 세번째(index 2)부터 마지막까지를 의미한다.)
- nil 병합 연산자
- 옵셔널에 nil값이 있는 경우 디폴트 값을 사용할 수 있다. 다음 예제에서는 customerName라는 옵셔널 변수가 nil로 설정되어 있기 때문에 ‘Welcome back, customer’라는 텍스트를 출력한다.
let customerName: String? = nil
print(“Welcome back, \(customerName ?? “customer”)”)
- guard 구문
- guard 구문은 불리언 표현식을 표함하며, true일 때만 guard구문 다음에 위치한 코드가 실행된다. guard구문은 불리언 표현식이 false일 때 수행될 else 절을 반드시 포함해야한다. else절의 코드는 반드시 현재의 코드 흐름에서 빠져나가는 구문(예를들어, return, break, continue 또는 throw 구문)을 포함해야 한다.
func multiplyByTen(value: Int?) {
guard let number = value, number < 10 else {
print("Number is too high")
return
}
let result = number * 10
print(result)
}
multiplyByTen(value: 5)
multiplyByTen(value: 10)
- switch 구문
let temperature = 83
switch (temperature) {
case 0...49:
print("Cold")
case 50...79:
print("Warm")
case 80...110:
print("Hot")
default:
print("Temperature out of range")
}
let temperature = 83
switch (temperature) {
case 0...49 where temperature % 2 == 0:
print("Cold and even")
case 50...79 where temperature % 2 == 0:
print("Warm and even")
case 80...110 where temperature % 2 == 0:
print("Hot and even")
default:
print("Temperature out of range or odd")
}
-
- case 구문 끝에 break를 쓸 필요가 없다. 하지만 fallthrough 구문을 사용하면 switch 구현부에 예외상황 효과를 주어, 실행흐름이 그 다음의 case 구문으로 계속 진행하게 할 수 있다.
let temperature = 83
switch (temperature) {
case 0...49 where temperature % 2 == 0:
print("Cold and even")
fallthrough
case 50...79 where temperature % 2 == 0:
print("Warm and even")
fallthrough
case 80...110 where temperature % 2 == 0:
print("Hot and even")
fallthrough
default:
print("Temperature out of range or odd")
}
Chap9. 함수, 메서드, 클로저
함수, 메서드, 클로저
- 함수가 호출될 때 받게 되는 값을 매개변수(parameter)라고 한다. 하지만, 실제로 함수가 호출되고 값이 전달되는 시점에서는 인자(argument)라고 부른다. 즉, 함수 안에서 사용하는 변수를 매개변수라고 하고, 호출할 때 넘겨주는 변수를 인자라고 한다.
func buildMessageFor(name: String, count: Int) -> String {
return("\(name), you are customer number \(count)")
}
//함수가 단일 표현식을 가지고 있다면 return구문을 생략할 수 있다.
func buildMessageFor(name: String, count: Int) -> String {
"\(name), you are customer number \(count)"
}
// 반환된 결과값을 사용하지 않을 경우 "_"에 할당하여 그 값을 버린다.
_ = buoldMessage(name: "John", count: 100)
- 기본적으로 함수 매개변수에는 동일한 지역 매개변수명과 외부 매개변수명이 할당된다. 매개변수에 할당된 디폴트 외부 매개변수명은 지역 매개변수명 앞에 밑줄 문자를 써서 없앨 수 있다.
func buildMessageFor(_ name: String, _ count: Int) -> String {
return("\(name), you are customer number \(count)")
}
//위와 같이 구현되도록 함수를 수정했다면, 이제는 다음과 같이 함수를 호출해야 한다.
let message = buildMessageFor("John", 100)
//다른 방법으로 함수 선언부에 지역 매개변수명 앞에 외부 매개변수명을 선언하면 간단하게
//외부 매개변수명이 추가된다.
func buildMessageFor(userName name: String, userCount count: Int) -> String {
return("\(name), you are customer number \(count)")
}
// 위와 같이 함수를 선언했다면 다음과 같이 호출해야 한다.
let message = buildMessageFor(userName: "John", userCount: 100)
//인자에 대한 외부 매개변수명을 사용하여 함수를 호출할 때는 함수를 선언했을 때와 동일한 순서로
//인자를 넣어야 한다.
- 함수에 디폴트 매개변수 선언하기
func buildMessageFor(_ name: String = "Customer", count: Int) -> String {
return ("\(name), you are customer number \(count)")
}
//이제 이 함수는 name인자를 전달하지 않고 호출될 수 있다.
let message = buildMessageFor(count: 100)
- 여러 결과값 반환하기
func sizeConverter(_ length: Float) -> (yards: Float, centimeters: Float,
meters: Float) {
let yards = length * 0.0277778
let centimeters = length * 2.54
let meters = length * 0.0254
return (yards, centimeters, meters)
}
let lengthTuple = sizeConverter(20)
print(lengthTuple.yards)
print(lengthTuple.centimeters)
print(lengthTuple.meters)
- 함수 매개변수의 변수 개수
func displayStrings(_ strings: String...) {
for string in strings {
print(string)
}
}
displayStrings("one", "two", "three", "four")
- 변수인 매개변수
함수가 받는 모든 매개변수는 기본적으로 상수로 취급된다. 즉, 함수의 코드 내에서 매개변수의 값이 변경되는 것을 막는다. 만약 함수 내에서 매개변수의 값을 변경하고 싶다면, 매개변수의 새도우 복사본을 반드시 생성해야 한다.
func calculateArea(length: Float, width: Float) -> Float { var length = length var width = width length = length * 2.54 width = width * 2.54 return length * width } print(calculateArea(length: 10, width: 20)) //함수가 값을 반환한 뒤에도 매개변수에 대한 변경을 유지하려면, 함수 선언부 내에서 매개변수를 //입출력 매개변수로 선언해야 한다. func doubleValue(_ value: inout Int) -> Int {var value = valuevalue += value return(value) } //함수 호출후 넘겨준 인자의 값이 바뀐 것을 확인할 수 있다.
- 매개변수인 함수
//스위프트에서는 함수가 데이터 타입처럼 취급될 수 있다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.0833333
}
let toFeet = inchesToFeet
//위와 같이 상수에 함수를 할당했다면 상수이름을 이용하여 함수를 호출할 수 있다.
let result = toFeet(10)
//보편적인 함수가 되게 하기 위해 매개변수로 타입이 일치하는 함수를 매개변수로 받을 수 있다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.0833333
}
func inchestoYards(_ inches: Float) -> Float {
return inches * 0.0277778
}
let toFeet = inchesToFeet
let toYards = inchesToYards
func outputConversion(_ converterFunc: (Float) -> Float, values: Float) {
let result = converterFunc(value)
print("Result of conversion is \(result)")
}
outputConversion(toYards, value: 10) // 야드로 변환하기
outputConversion(toFeet, value: 10) // 피트로 변환하기
//또한, 함수의 타입을 반환 타입으로 선언하면 함수도 데이터 타입으로 반환될 수 있다.
- 클로저 표현식
클로저 표현식은 독립적인 코드블록이다. 예를 들어 다음은 클로저표현식을 선언하고 그것을 sayHello라는 이름의 상수를 당당한 다음에 상수참조를 통해 함수를 호출한다.
let sayHello = { print("Hello") } sayHello() //클로저 표현식은 매개변수를 받아 결과값을 반환하도록 구성할 수도 있다. let multiply = {(_ val1: Int, _ val2: Int) -> Int in return val1 * val2 } let result = multiply(10, 20) //이 구문은 함수를 선언할 때 사용하는 것과 비슷하지만, 클로저 표현식은 이름을 갖지 않으며, //매개변수와 반환 타입은 괄호 안에 포함되고, 클로저 표현식 코드의 시작을 가리키기 위하여 //in 키워드를 사용한다. 사실, 함수는 이름이 있는 클로저 표현식일 뿐이다. // 클로저 표현식은 비동기 메서드 호출에 대한 완료핸들러를 선언할 때 종종 사용된다. //약식인수 이름 : 클로저를 단순화하는 유용한 기술은 약식인수이름을 사용하는 것이다. 이것은 //선언부에서 매개변수 이름과 in 키워드를 생략할 수 있게 하며, 인수를 $0, $1, $2 등으로 //참조할 수 있다. let join = { (string1: String, string2: String) -> String in string1 + string2 } // 약식 인수이름을 사용하면 다음과 같이 단순화할 수 있다. let join: (String, String) -> String = { $0 + $1 }
- 스위프트의 클로저
- 컴퓨터공학 용어에서의 클로저는 함수나 클로저 표현식과 같은 독립적인 코드블로과 코드블록 주변에 있는 하나 이상의 변수가 결합된 것을 말한다.
func functionA() -> () -> Int {
var counter = 0
func functionB() -> Int {
return counter + 10
}
return functionB
}
let myClosure = functionA()
let result = myclosure()
// 앞의 코드에서 functionA라는 이름의 함수를 반환한다. 사실 functionB는 functionB의
// 내부 영역 밖에 선언된 counter 변수에 의존하기 때문에 functionA 클로저를 반환하고 있다.
Chap10. 객체지향 프로그램 기초
스위프트의 객체지향프로그래밍 기초
- 메서드 정의하기
- 메서드는 타입 메서드와 인스턴스 메서드의 서로 다른 두 가지 형태로 나뉜다. 타입 메서드는 클래스 레벨에서 동작한다. 반면, 인스턴스 메서드는 클래스의 인스턴스에 대한 작업만 한다.
- 타입 메서드는 선언부 앞에 class 키워드가 붙는다.
class BankAccount {
var accountBalance: Float = 0
var accountNumber: Int = 0
init(number: Int, balance: Float) {
accountNumber = number
accountBalance = balance
}
deinit {
// 필요한 정리작업을 여기서 수행한다.
}
func displayBalance() {
print("Number \(accountNumber)")
print("Current balance is \(accountBalance)")
}
class func getMaxBalance() -> Float {
return 10000.00
}
}
- 클래스 인스턴스 선언하기와 초기화하기
var account1: BankAccount = BackAccount() var account1: BanckAccount(number:123456, balance: 400.54)
-
- 인스턴스를 생성하는 시점에 해야 할 초기화 작업은 init 메서드를 통해 한다. 반대로 스위프트 런타임 시스템에 의해 클래스 인스턴스가 없어지기 전에 해야 할 정리작업은 클래스 안에 deinit 소멸자를 구현하면 가능하다.
- 메서드 호출하기
클래스인스턴스.프로퍼티명 클래스인스턴스.인스터스메서드() 클래스이름.타입메서드()
- 스위프트의 클래스 프로퍼티는 저장 프로퍼티와 연산 프로퍼티로 나뉜다. 저장 프로퍼티는 상수나 변수에 담기는 값이다. 반면, 연산 프로퍼티는 프로퍼티에 값을 설정하거나 가져오는 시점에서 어떤 계산이나 로직에 따라 처리된 값이다. 연산 프로퍼티는 게터getter를 생성하고 선택적으로 세터setter메서드를 생성하며, 연산을 수행할 코드가 포함된다.
let fees: Float = 25.00
// 현재의 잔액에서 수수료를 뺀 값을 얻는 연산프로퍼티
var balanceLessFees: Float {
get {
return accountBalance - fees
}
set (newBalance) {
accountBalance = newBalance - fees
}
}
// 클래스 내에 위와 같이 게터와 세터를 설정했으면, 아래와 같이 연산프로퍼티를 호출해서 사용한다.
var balance1 = account1.balanceLessFees
account1.balanceLessFees = 12123.12
- 지연 저장 프로퍼티 : 클로저를 이용하여 초기화할 수 있다. 복잡한 클로저의 경우는 초기화 작업이 리소스와 시간을 많이 사용하게 될 수 있다. 클로저는 이용하여 선언하면 해당 프로퍼티가 코드 내에서 사용되는 지와는 상관없이 클래스의 인스턴스가 생성될 때마다 초기화 작업이 수행될 것이다. 프로퍼티를 최초로 접근할 때만 초기화작업을 하는 것이 효율적이다. 이 작업은 lazy로 프로퍼티를 선언하면 된다. 지연 프로퍼티는 반드시 변수(var)로 선언되어야 한다.
class MyClass {
lazy var myProperty: String = {
var result = resourceInensiveTask()
result = processData(data: result)
return result
}()
.
.
}
- 스위프트에서 self사용하기 : 클래스 인스턴스에 속한 메서드나 프로퍼티를 가리킬 때 프로퍼티와 메서드 앞에 self를 쓸 수 있다. 하지만, 대부분의 경우 스위프트로 프로그래밍할 때는 self를 사용할 필요가 없다. 왜냐하면 프로퍼티와 메스드에 대한 참조를 디폴트로 간주하기 때문이다.
- 프로토콜 : 프로토콜은 protocal 키워드를 이용하여 선언되며, 클래스가 반드시 포함해야 하는 메서드와 프로퍼티를 정의한다. 어떤 클래스가 프로토콜을 채택했으나 모든 프로토콜의 요구사항을 충족하지 않는다면, 그 클래스가 해당 프로토콜을 다르지 않는다는 에러가 발생한다.
protocal MessageBuilder {
var name: String { get }
func buildMessage() -> String
}
class MyClass: MessageBuilder {
var name: String
init(name: String) {
self.name = name
}
func buildMessage() -> String {
"Hello " + name
}
}
- 불투명 반환 타입: 특정 반환 타입을 지정하는 대신, 불투명 반환 유형을 사용하면 지정된 프로토콜을 따르는 모든 타입이 반환될 수 있게 한다. 불투명 반환 타입은 프로토콜 이름 앞에 some 키워드를 붙여 선언된다.
func doubleFunc1(value: Int) -> some Equatable {
value * 2
}
Chap11. 서브클래식와 익스텐션 개요
- 스위프트의 하위 클래스는 반드시 단 한개의 부모 클래스만 둘 수 있다. 이것은 단일상속이라는 개념이다.
- 오버라이딩: 하위클래스의 오버라이딩 메서는 오버라이딩되는 부모 클래스 메서드의 매개변수 개수와 타입이 정확하게 일치해야 한다. 또한 새롭게 오버라이딩하는 메서는 반드시 부모 클래스 메서드가 반환하는 타입과 일치해야 한다. override 키워드가 앞에 붙는다.
- 오버라이딩된 상위 클래스의 메서를 호출하기 위해서 super 키워드를 사용한다.
- 익스텐션: 하위 클래스를 생성하거나 참조하지 않고 기존 클래스에 메서드, 생성자, 그리고 연산 프로터피와 서브스크립트 등의 기능을 추가하기 위해서 사용될 수 있다.
extension 클래스 이름 {
//새로운 기능을 여기에 추가한다.
}
extension Double {
var squared: Double {
return self * self
}
var cubed: Double {
return self * self * self
}
}
let myValue: Double = 3.0
print(myValue.squared)
Chap12. 구조체와 열거형
- 스위프트 구조체 개요 : 구조체 선언은 클래스와 비슷하지만, class 키워드 대신 stuct 키워드를 사용한다.
struct SammpleStuct {
var name: String
init(name: String) {
self.name = name
}
func buildHelloMsg() {
"Hello " + name
}
}
let myStruct = SampleStruct(name: "Mark")
- 값 타입 vs. 참조 타입: 구조체의 인스턴스와 클래스의 인스턴스가 복사되거나 메서드 또는 함수에 인자가 전달될 때 발생하는 동작의 큰 차이가 있다. 구조체 인스턴스의 타입은 값(value) 타입이고, 클래스의 인스턴스 타입은 참조(reference) 타입이다.
struct SampleStuct {
var name: String
init(name: String) {
self.name = name
}
func buildHelloMsg() {
"Hello " + name
}
}
let myStrunct1 = SampleStruct(name: "Mark")
var myStrunct2 = myStrunct1
myStrunct2.name = "David"
print(myStrunct1.name)
print(myStrunct2.name)
//위의 코드를 실행하면 다음과 같이 출력된다.
Mark
David
class SampleClass {
var name: String
init(name: String) {
self.name = name
}
func buildHelloMsg() {
"Hello " + name
}
}
let myClass1 = SampleClass(name: "Mark")
var myClass2 = myClass1
myClass2.name = "David"
print(myClass1.name)
print(myClass2.name)
//위의 코드를 실행하면 다음과 같이 출력된다.
David
David
- 구조체는 클래스에 있던 상속이나 하위클래스를 지원하지 않는다. 클래스와 다르게 구조체는 소멸자 메서드(deinit)를 포함할 수 없다. 또한 런타임에서 클래스 인스턴스의 유형을 식별할 수 있지만 구조체는 그렇지 않다.
- 일반적으로 구조체가 클래스보다 효율적이고 멀티 스레드 코드를 사용하는 데 더 안정적인기 때문에 가능하다면 구조체를 권장한다.
- 열거형
enum Temperature {
case hot
case warm
case cold
}
func displayTempInfo(temp: Temperature) {
switch temp {
case .hot:
print("It is hot.")
case .warm:
print("It is ward.")
case .cold:
print("It is cold.")
}
}
displayTempInfo(temp: Temperature.warm)
chap13. 프로퍼티 래퍼
- 프로퍼티 래퍼: 스위프트5.1부터 기능이 도입되었으며, 기본적으로 연산 프로퍼티의 기능을 개별 클래스와 구조체와 분리할 수 있게 하며, 앱 코드에서 재사용할 수 있게 한다.
//연산 프로퍼티 예제
struct Address {
private var cityname: String = ""
var city: String {
get { cityname }
set { cityname = newValue.uppercased() }
}
}
var address = Address()
address.city = "London"
print(address.city)
// 위의 코드를 실행하면 아래와 같이 출력된다.
LONDON
// 프로퍼티 래퍼 구현 예시 : @propertyWrapper 지시자를 이용하여 선언되며, 클래스나
// 구조체 안에 구현된다. 모든 프로퍼티 래퍼는 값을 변경하거나 유효성을 검사하는 게터와
// 세터 코드가 포함된 wrapperValue 프로퍼티를 가져야 한다.
// 초기값이 전돨되는 초기화 메서드는 선택사항으로 포함될 수 있다.프로퍼티 래퍼를 사용하기
// 위해서는 프로퍼티 선언 앞에 @FixCase 지시자를 붙이면 된다.
@propertyWrapper
struct FixCase {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.uppercased() }
}
init(wrappedValue initialValue: String) {
self.wrappedValue = initialValue
}
}
struct Contact {
@FixCase var name: String
@FixCase var city: String
@FixCase var country: String
}
var contact = Contact(name: "John Smith", city: "London",
country: "United Kingdom")
print("\(contact.name), \(contact.city), \(contact. country)")
// 실행결과
JOHN SMITH, LONDON, UNITED KINGDOM
- 여러 변수와 타입 지원하기 : 입력값의 최소값과 최대값을 설정할 수도 있고 모든 타입을 사용할 수도 있다. 자세한 것은 책을 참고하시길(p118~121)
chap14 배열과 딕셔너리 컬렉션으로 작업하기
- 가변형/불변형 컬렉션ㅣ 불변형 컬렉션 인스턴스에 속한 것은 객체가 초기화된 이후에 변경될 수 없다. 불변형 컬렉션을 만들고 싶다면 컬렉션을 생성할 때 상수에 할당된다. 반면, 변수에 할당했다면 가변형이 된다.
- 배열은 동일한 타입의 값들만 저장할 수 있으나, 여러 타입이 혼합된 배열을 생성할 수도 있다. 배열의 타입은 타입 애너테이션을 사용하여 구체적으로 지정할 수도 있고, 타입 추론을 이용하여 컴파일러가 식별하게 할 수도 있다.
pre>var treeArray = [“Pine”, “Oak”, “Yew”] //배열 리터럴
var treeArray: [String] = [“Pine”, “Oak”, “Yew”] //타입 애너테이션
var priceArray = [Float]()
var nameArray = [String](repeating: “My String”, count: 10) //”My String”으로 초기화된 배열 10개를 생성let firstArray = [“Red”, “Green”, “Blue”]
let secondArray = [“Indigo”, “Violet”]
let thirdArray = firstArray + secondArray
- 배열로 작업하기
var treeArray = ["Pine", "Oak", "Yew"]
var itemCount = treeArray.count
if treeArray.isEmpty {
// 배열이 비어있는 경우
}
let shuffledTree = treeArray.shuffled() // 항목의 순서가 무작위로 섞인 새로운 배열이 반환된다.
let lrandomTree = treeArray.randomElement() // 배열의 항목을 무작위로 반환한다.
treeArray.append("Redwood")
treeArray += ["Redwood"] // append와 마찬가지로 배열에 항목을 추가한다.
treeArray.insert("Maple", at: 0) // 배열 맨 앞쪽에 항목을 추가한다.
treeArray.remove(at: 2) //배열의 특정 인덱스 위치에 있는 항목을 제거한다.
treeArray.removeLast() ///배열 마지막 항목을 삭제한다.
for tree in treeArray {
print(tree)
}
treeArray.forEach { tree in
print(tree)
}
treeArray.forEach {
print($0)
}
- 타입이 혼합된 배열 생성하기: 스위프트의 Any 타입은 특별한 타입으로 지정된 클래스 타입이 아닌 객체를 참조하는데 사용된다. 따라서 Any객체 타입을 포함하도록 선언된 배열을 여러 타입의 항목을 담을 수 있게 된다. 예를 들면 다음의 코드는 배열을 생성하고 String과 Int, Double형의 항목을 포함하도록 초기화하고 있다. Any 배열을 사용하게 되면 컴파일 오류는 발생하지 않을 수 있지만 런타임에서 충돌이 발생할 수도 있다.
let mixedArray: [Any] = ["A String", 432, 34.989]
- 딕셔너리 컬렉션: 키-값 쌍의 형태로 데이터를 저장하고 관리할 수 있다. 딕셔너리에 저장된 각 항목은 연관된 값을 참조하고 접근하는데 사용되는 유일한 키와 연결되어 있다. 현재는 String, Int, Double, Bool 데이터 타입만 키로 사용할 수 있다.
//딕셔너리 리터럴
var bookDict = ["100-432112" : "Wind in the Willows",
"200-532874" : "Tale of Two Cities",
"202-546549" : "Sense and Sensiblity",
"104-109834" : "Shutter Island"]
//타입 애너테이션을 이용한 배열 선언
var bookDict: [String: String] = ["100-432112" : "Wind in the Willows",
"200-532874" : "Tale of Two Cities",
"202-546549" : "Sense and Sensiblity",
"104-109834" : "Shutter Island"]
//정수형 키와 문자열 값을 저장하기 위해 설계된 빈 딕셔너리
var myDictionary = [Int: String]()
//시퀀스 기반의 딕셔너리 초기화
let keys = ["100-432112", "200-532874", "202-546549", "104-109834"]
let values = ["Wind in the Willows", "Tale of Two Cities",
"Sense and Sensiblity", "Shutter Island"]
let bookDict = Dictionary(uniqueKeysWithValues: zip(keys, values))
// 미리 정의된 키가 아닌 1부터 시작하는 숫자를 키로 지정
let values = ["Wind in the Willows", "Tale of Two Cities",
"Sense and Sensiblity", "Shutter Island"]
let bookDict = Dictionary(uniqueKeysWithValues: zip(1..., values))
print(bookDict["999-546547", default: "Book not found"])
bookDict["200-532874"] = "Sense and Sensibility"
bookDict.updateVaue("The Ruins", forkey: "200-532874") //위의 구문과 동일하게 동작
bookDict["300-898871"] = nil
bookdict.removeValue(forkey: "300-898871") //위 구문과 동일하게 동작(특정 항목 삭제)
for (bookid, title) in bookDict {
print("Book ID: \(bookid) Title: \(title)")
}
chap15. 에러핸들링 이해하기
- 스위프트에서 에러를 처리하는 데는 두 가지 단계가 있다. 첫 번째는 iOS앱의 메서드 내에서 원하는 결과가 나오지 않을 경우 에러를 발생하고, 두 번째는 메서드가 던진에러를 잡아서 처리하는 것이다.
- 에러를 던지는 구문은 throw구문과 guard구문을 결합하여 사용한다.
let connectionOk = true
let connectionSpeed = 30.00
let fileFound = false
enum FileTransferError: Error {
case noConnection
case lowBandWidth
case fileNotFound
}
func transferFile() throws {
guard connectionOK else {
throw FileTransferError.noConnection
}
guard connectionOK else {
throw FileTransferError.lowBandWidth
}
guard connectionOK else {
throw FileTransferError.fileNotFound
}
}
- 스로잉 메서드와 함수 호출하기
try fileTransfer()
func sendFile() -> String {
do {
try fileTransfer()
} catch FileTransferError.noConnection, FileTransferError.lowBandWidth {
return("Connection problem")
} catch FileTransferError.fileNotFound {
return("File not Found")
} catch {
return("Unknown error")
}
return("Successful transfer")
}
- 여러 객체에 접근하기
do {
try filemgr.createDirectory(atPath: newDir,
withIntermediateDirectories: true,
attributes: nil)
} catch let error {
print("Error: \(error.localizedDescription)")
}
- 에러 캐칭 비활성화하기: 다음과같이 try! 구문을 사용하면 do-catch 구문 내에서 메서드가 호출되도록 감싸지 않아도 스로잉 메서드가 강제로 실행된다.
try! fileTransfer
- defer 구문 사용하기: 에러의종류에 상관없이 제어권을 반환하기전에 어떠한 별도의 제어권을 반환하기 전에 임시파일들을 지원야 할 경우가 발생할 수 있다. 이것은 defer 구문을 이용하면 가능하다. defer 구문은 메서드가 결과를 반환하기 직전에 실행되어야 하는 일련의 코드를 지정할 수 있게 해 준다.
try fileTransfer()
func sendFile() -> String {
defer {
removeTmpFile()
closeConnection()
}
do {
try fileTransfer()
} catch FileTransferError.noConnection, FileTransferError.lowBandWidth {
return("Connection problem")
} catch FileTransferError.fileNotFound {
return("File not Found")
} catch {
return("Unknown error")
}
return("Successful transfer")
}


