오늘의 학습 컨텐츠
오늘의 학습 목표
정리
네이밍 룰
- 기본적으로 Camel Case 를 따른다.
- Lower Camel Case : function, method, variable, constant
- Upper Camel Case : type (class, struct, enum, extension ...)
- 대소문자를 구분한다
Console Log
- Print
- Dump
- 인스턴스에 대한 자세한 설명 (description 프로퍼티) 까지 출력
문자열 보간법
- String interpolation
- 프로그램 실행 중 문자열 내에 변수 또는 상수의 실질적인 값을 표현하기 위해 사용
- \()
let age : Int = 10
print("안녕하세요 저는 \(age + 5)살입니다.")
상수와 변수
- Swift 는 함수형 프로그래밍 페러다임을 채용했기 때문에 불변객체에 대한 내용이 굉장히 많다.
- 상수 선언 키워드 -> let
- 변수 선언 키워드 -> var
- 스위프트는 띄워쓰기에도 굉장히 민감한 언어기 때문에 띄워쓰기도 신경써줘야 한다.
// 상수 선언
let 이름: 타입 = 값
// 변수 선언
var 이름: 타입 = 값
// 나중에 할당하려고 하는 상수나 변수는 타입을 꼭 명시해주어야 한다.
let sum: Int
let inputA: Int = 100
let inputB: Int = 200
// 선언 후 첫 할당
sum = inputA + inputB
sum = 1 // error
- 타입 추론이 있긴 하지만 타입은 항상 명확히 쓰도록 하자.
- 초기화가 되지 않은 상태에서 사용하려고 하면 컴파일러가 미리 에러를 잡아준다
스위프트의 기본 데이터 타입
// Bool
var someBool: Bool = true
someBool = false // 가능
someBool = 0 // C언어 같은 이딴거 안됨
// Int
var someInt: Int = -100
someInt = 100.1 // Error
// UInt
var someUInt: UInt = 100
someUInt = -100 // Error
someUInt = someInt // Error
// Float (32bit 부동 소수점)
var someFloat: Float = 3.14
someFloat = 3 // 가능
// Double
var someDouble: Double = 3.14
someDouble = someFloat // Error
someDouble = 3 // 가능
// Character (UniCode)
var someChar: Character = "ㅁ" // 가능
someChar = "123" // Error (String type)
// String
var someString: String = "ggg"
someString = someString + "aaa" // 가능
someString = someChar // Error
- 스위프트는 Type 에 굉장히 민감한 언어이다.
- 암시적으로 Data Type 을 변경하는 것은 거의 불가능하다고 생각하면 된다. (또한 해서 좋을게 하나도 없다)
Any, AnyObject, nil
- Any - Swift 의 모든 타입을 지칭하는 키워드
- AnyObject - 모든 클레스타입을 지칭하는 프로토콜
- nil - 없음을 의미하는 키워드
var someAny: Any = 100
someAny = "어떤 타입도 가능합니다."
someAny = 123.12
let someDouble: Double = someAny // Error -> Cannot convert value of type
---
class SomeClass {}
var someAnyObject: AnyObject = SomeClass()
someAnyObject = 123.12 // Error
---
someAny = nil // Error (어떤 데이터 타입도 가능하지만 없음은 안된다.)
someObject = nil // Error
// nil 은 다른 언어의 null 과 매우 유사하다
컬렉션 타입 (Array, Dictionary, Set)
// Array - 순서가 있는 리스트 컬렉션
// Dictionary - 키와 값의 쌍으로 이루어진 컬렉션 (해쉬맵과 흡사)
// Set - 순서가 없고, 맴버가 유일한 컬렉션
// 빈 Int Array 생성
var integers: Array<Int> = Array<Int>()
integers.append(1)
integers.append(100)
integers.append(101.1) // Error
integers.contains(100) // true
integers.contains(99) // false
integers.remove(at: 0) // remove value ( 1 )
integers.removeLast()
integers.removeAll()
integers.count // 0
// Array 를 표현하는 다양한 방법들
var doubles: Array<Double> = [Double]()
var strings: [String] = [String]()
var chars: [Charactor] = []
let immutableArray = [1, 2, 3]
immutableArray.append(4) // Error
---
//
var anyDictionary: Dictionary<String, Any> = [String: Any]()
anyDictionary["someKey"] = "value"
anyDictionary["anotherKey"] = 100
anyDictionary.removeValue(forKey: "anotherKey")
andDictionary["someKey"] = nil
// 뒤 두개는 같은 표현
let emptyDicionary: [String: String] = [:]
let initializedDictionary: [String: String] = ["name": "yagom", "gender": "male"]
let someValue: String = initializedDictionary["name"] // Error -> name = String?
---
// Set 는 축약문법이 없다.
var integerSet: Set<Int> = Set<int>()
integerSet.insert(1)
integerSet.insert(100)
integerSet.insert(99)
integerSet.insert(99)
integerSet.insert(99)
integerSet.count // 3
let setA: Set<Int> = [1, 2, 3, 4, 5]
let setB: Set<Int> = [3, 4, 5, 6, 7]
let union: Set<Int> = setA.union(setB) // 2, 3, 5, 6, 7, 4, 1
let sortedUnion: [Int] = union.sorted() // 1, 2, 3, 4, 5, 6, 7
let intersection: Set<Int> = setA.intersection(setB) // 5, 4, 3
let subtracting: Set<Int> = setA.subtracting(setB) // 2, 1
함수 기본
// 함수 선언 기본 형태
func 함수이름(매개변수1이름: 매개변수1타입, 매개변수2이름: 매개변수2타입 ...) -> 반환타입 {
함수 구현부
return 반환값
}
func sum(a: Int, b: Int) -> Int {
return a + b
}
// 반환값이 없는 경우
func printMyName(name: String) -> Void {
print(name)
}
func printYourName(name: String) {
print(name)
}
// 매개변수가 없는 함수
func maxIntValue() -> Int {
return Int.max
}
// 매개변수와 반환값이 없는 함수
func hello() -> Void { print("hello") }
함수 고급
// 함수의 다양한 모습
// 기본값을 갖는 매개변수는 매개변수 목록 중 뒤쪽에 위치하는 것이 좋다.
func 함수이름(매개변수1이름: 매개변수1타입, 매개변수2이름: 매개변수2타입 = 매개변수기본값 ...) -> 반환타입 {
함수 구현부
return 반환값
}
func greeting(friend: String, me: String = "yagom") {
print("Hello \(friend)! I'm \(me)")
}
// 매개변수 기본값을 가지는 매개변수는 생략할 수 있습니다.
greeting(friend: "hana") // Hello hana! I'm yagom
greeting(friend: "john", me: "eric") // Hello john! I'm eric
// 전달인자 레이블
// 전달인자 레이블은 함수를 호출 할 때
// 매개변수의 역할을 좀더 명확하게 하거나
// 함수 사용자의 입장에서 표현하고자 할 때 사용합니다.
// 함수 내부에서 전달인자를 사용 할 때에는 매개변수 이름을 사용합니다.
func greeting(to friend: String, from me: String) {
print("Hello \(friend)! I'm \(me)")
}
// 함수를 호출 할 때에는 전달인자 레이블을 사용해야 합니다.
greeting(to: "hana", from: "yagom")
// 가변 매개변수
// 전달 받을 값의 개수를 알기 어려울 때 사용할 수 있다.
// 가변 매개변수는 함수당 하나만 가질 수 있다.
func sayHelloToFriends(me: String, friends: String...) -> String {
return "Hello \(friends)! I'm \(me)!"
}
// 함수의 타입 표현
var someFunc: (String, String) -> Void = greeting(to: from:)
someFunc("eric", "yagom")
someFunc = sayHelloToFriends(me: friends:) // Error
// 함수타입을 매개변수 타입으로 지정도 가능
func runAnother(function: (String, String) -> Void) {
function("jenny", "mike")
}
runAnother(function: greeting(friend:me:))
runAnother(function: someFunction)
- 함수형 프로그래밍 페러다임을 채용했으니 당연히 함수는 일급객체이다.
조건문
// if - else
if someInteger < 100 {
print("100 미만")
} else if someInteger > 100 {
print("100 초과")
} else {
print("100")
}
// 스위프트는 조건에 항상 Bool 타입이 들어와야 합니다
// switch
// 범위 연산자를 활용하면 더욱 쉽고 유용합니다.
switch someInteger {
case 0:
print("zero")
case 1..<100:
print("1 ~ 99")
default:
print("unknown")
}
// 정수 외 대부분의 기본 타입을 사용 할 수 있습니다.
switch "yagom" {
case "jake":
print("jake")
case "mina":
print("mina")
case "yagom":
print("yagom!!")
default:
print("unknown")
}
- 스위프트의 if 문은 조건절에 () 를 생략 할 수 있다.
- 스위프트의 switch 문은 break 가 없어도 암시적으로 break 를 한다.
- 스위프트의 조건문은 범위 연산자를 활용할 수 있다.
- 스위프트의 switch 문은 정수 외 대부분의 기본타입을 다 사용 할 수 있다.
반복문
var integers = [1, 2, 3]
let people = ["yagom": 10, "eric": 15, "mike": 12]
for integer in integers {
print(integer)
}
// Dictionary 의 item 은 key와 value 로 구성된 튜플타입 입니다.
for {name, age} in people {
print("\(name): \(age)")
}
옵셔널
- nil 의 가능성을 문서화 하지 않아도, 코드만으로 충분히 표현 가능
- 전달 받은 값이 옵셔널이 아니라면 nil 체크를 하지 않더라도 안심하고 사용
// 스위프트 핵심개념중 하나
// Optional => 값이 있을수도 있고, 없을 수도 있다.
func someFunc(someOptionalParam: Int?) {
//
}
func someFunc(someParam: Int) {
}
someFunc(someOptionalParam: nil)
someFunc(someParam: nil) // Error
---
// 옵셔널은 열거형과 제너럴의 합작이다.
enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
}
let optionalValue: Optional<Int> = nil
let optionalValue: Int? = nil
---
// 옵셔널에서 암시적 추출 (!)
var optionalValue: Int! = 100
switch optionalValue {
case .none:
print("nil")
case .some(let value):
print("Value is \(value)")
}
---
/// ?
var optionalValue: Int? = 100
// nil 할당가능
optionalValue = nil
// 기존 변수처럼 사용은 불가능함. 옵셔널과 일반값은 다른 타입이므로 연산불가
옵셔널 값 추출
// if - let
var myName: String? = "yagom"
var yourName: String? = nil
if let name = myName, let friend = yourName {
print("...")
}
// yourName 이 nil 이기 때문에 실행되지 않습니다.
yourName = "hana"
if let name = myName, let friend = yourName {
print("...")
}
// ...
구조체
// 스위프트에서는 대부분의 타입이 구조체이다.
struct 이름 {
구현부
}
struct Sample {
var mutableProperty: Int = 100 // 가변
let immutableProperty: Int = 100 // 불변
static var typeProperty: Int = 100 // 타입 프로퍼티
// 인스턴스 메서드
func instanceMethod() {
print("instance method")
}
// 타입 메서드
static func typeMethod() {
print("type method")
}
}
// 구조체 사용
// 가변 인스턴스
var mutable: Sample = Sample()
mutable.mutableProperty = 200 // 가능
mutable.immutableProperty = 200 // Error
// 불변 인스턴스
let immutable: Sample = Sample()
immutable.mutableProperty = 200 // Error
immutable.immutableProperty = 200 // Error
// 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod() // type method
mutable.typeProperty = 300 // Error
- 다른 언어의 Class 와 굉장히 흡사한데 Class 가 또 있네.. 왜그럴까
클레스
// 구조체는 값타입인 반면에 Class 는 참조 타입
class 이름 {
// 구현부
}
class Sample {
var mutableProperty: Int = 100
var immutableProperty: Int = 100
// 타입 메서드
// 재정의 불가 타입 메서드 - static
static func typeMethod() {
print("type method - static")
}
// 재정의 가능 타입 메서드 - class
class func typeMethod() {
print("type method - class")
}
}
var mutableReferrence: Sample = Sample()
mutableReference.mutableProperty = 200
let immutableReference: Sample = Sample()
immutableReference.mutableProperty = 200 // 가능!
- Struct 은 값타입, Class 는 참조타입이다.
열거형
// enum 은 타입이므로 대문자 카멜케이스를 사용
// 각 case 는 소문자 카멜케이스로 정의
// 각 case 는 자체가 고유 값
// enum 이름 {
// case 이름1
// case 이름2
// case 이름3, 이름4, 이름5
// ...
// }
enum Weekday {
case mon
case tue
case wed
case thu, fri, sat, sun
}
var day: Weekday = Weekday.mon
day = .tue // 축약표현
switch day {
case .mon, .tue, .wed, .thu:
print("평일")
case Weekday.fri:
print("불금!")
case .sat, .sun
print("주말!")
}
// 원시값
// C언어의 enum 처럼 정수값을 가질수도 있다.
// rawValue 를 사용
// case 별로 각각 다른 값을 가져야 한다.
enum Fruit: Int {
case apple = 0
case grape = 1
case peach // 자동으로 2가 들어감
}
print(Fruit.apple.rawValue)
// 원시값을 통한 초기화
// rawValue 를 통해 초기화 할 수 있다.
// rawValue가 case에 해당 하지 않을 수 있다.
// rawValue를 통해 초기화한 인스턴스는 옵셔널이다.
let apple: Fruit? = Fruit(rawValue : 0)
if let orange: Fruit = Fruit(rawValue: 5) {
print("rawValue 5에 해당하는 케이스는 \(orange)입니다")
}
// enum method
enum Month {
case dec, jan, feb
case mar, apr, may
case jun, jul, aug
case sep, oct, nov
func printMessage() {
switch self {
case .mar, .apr, .may:
print("봄")
default:
print("not 봄")
}
}
}
- 다른 언어의 열거형보다 훨씬 많은 기능들이 있다.
값타입과 참조타입
- Class
- 전통적인 OOP 관점에서의 클레스
- 단일 상속
- (인스턴스/타입) 메서드
- (인스턴스/타입) 프로퍼티
- 참조타입!
- Apple 프레임워크의 대부분의 큰 뼈대는 모두 클레스로 구성
- Struct
- C언어 등의 구조체보다 다양한 기능
- 상속 불가
- (인스턴스/타입) 메서드
- (인스턴스/타입) 프러퍼티
- 값 타입!
- Swift 의 대부분의 큰 뼈대는 모두 구조체로 구성
- Enum
- 다른 언어의 열거형과는 많이 다른 존재
- 상속 불가
- (인스턴스/타입) 메서드
- (인스턴스/타입) 프러퍼티
- 값타입
- 유사한 종류의 여러 값을 유의미한 일므으로 한 곳에 모아 정의
- 열거형 자체가 하나의 데이터 타입
- 열거형의 case 하나하나 전부 하나의 유의미한 값으로 취급
- 구조체는 언제 사용하나?
- 참조가 아닌, 복사를 원할 때!! (값타입이니까)
- 상속 관계가 필요 없을 때
- Apple 프레임워크에서 개발할땐 주로 클레스를 사용함
- Value vs Reference
- Value : 데이터를 전달할때 값을 복사하여 전달
- Reference : 데이터를 전달할때 참조 메모리 주소를 전달
struct ValueType {
var property = 1
}
class ReferenceType {
var property = 1
}
let firstStructInstacne = ValueType()
var secondStructInstance = firstStructInstacne
secondStructInstance.property = 2
firstStructInstacne.property // 1
secondStructInstance.property // 2
let firstClassReference = ReferenceType()
var secondClassReference = firstClassReference
secondClassReference.property = 2
firstClassReference.property // 1
secondClassReference.property // 2
- 스위프트는 구조체, 열거형 사용을 선호
- Apple 프레임워크는 대부분 클레스 사용
- Apple 프레임워크는 사용 시 구조체/클래스 선택은 우리의 몫
클로저
// 클로저 - 코드의 블럭
// 일급시민 (first-citizen)
{ (매개변수 목록) -> 반환 타입 in
실행 코드
}
// 함수를 사용한다면
func sumFunc(a: Int, b: Int) -> Int {
return a + b
}
var sumResult: Int = sumFunc(a: 1, b: 2)
// 클로저 사용
var sum: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
return a + b
}
// 함수는 클로저의 일종이므로
// sum 변수에는 당연히 함수도 할당 할 수 있다.
sum = sumFunc(a:b:)
sumResult = sum(1, 2)
// 함수의 전달인자로서 클로저
let add: (Int, Int) -> Int
add = { (a: Int, b: Int) -> Int in
return a + b
}
func calc(a: Int, b: Int, method (Int, Int) -> Int) -> Int {
return method(a, b)
}
var calculated: Int = calc(a: 50, b: 10, method: add)
클로저 고급
// 후행 클로저
// 반환타입 생략
// 단축 인자이름
// 암시적 반환 표현
func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
return method(a, b)
}
var result: Int
// 후행 클로저
// 클로저가 함수의 마지막 전달인자라면,
// 마지막 매개변수 이름을 생략한 후
// 함수 소괄호 외부에 클로저를 구현 할 수 있다.
result = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
return left + right
}
// 반환타입 생략
// calculate 함수의 method 매개변수는 Int 타입을 반환할 것이라는 사실을 컴파일러도 알기 떄문에
// 굳이 클로저에서 반환타입을 명시해 주지 않아도 됩니다. 대신 in 키워드는 생략할 수 없습니다.
result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in
return left + right
})
// 후행 클로저와 함께 사용 가능
result = calculate(a: 10, b: 10) { (left: Int, right: Int) in
return left + right
}
// 단축 인자 이름
// 클로저의 매개변수 이름이 굳이 불필요하다면 단축인자 이름을 활용할 수 있습니다.
// 단축인자 이름은 클로저의 매개변수의 순서대로 $0, $1... 처럼 표현합니다.
result = calculate(a: 10, b: 10, method: {
return $0 + $1
}
// 당연히 후행 클로저와 함께 사용 할 수 있ㅅ브니다.
result = calculate(a: 10, b: 10) {
return $0 + $1
}
// 암시적 반환
// 클로저가 반환 값이 있다면
// 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급합니다.
result = calculate(a: 10, b: 10) {
$0 + $1
}
// 간결하게 표현
result = calculate(a: 10, b: 10) {$0 + $1}
// 축약하지 않은 표현
result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in
return left + right
})
프로퍼티
// 프로퍼티
// 저장 프로퍼티
// 연산 프로퍼티
// 인스턴스 프로퍼티
// 타입 프로퍼티
// 프로퍼티는 구조체, 클레스, 열거형 내부에 구현 할 수 있습니다.
// 다만 열거형 내부에는 연산 프로퍼티만 구현 할수 있습니다.
// 연산 프로퍼티는 var 로만 선언할 수 있습니다.
struct Student {
// 인스턴스 저장 프로퍼티
var name: String = ""
var `class`: String = "swift"
var koreanAge: Int = 0
// 인스턴스 연산 프로퍼티
var westernAge: Int {
get {
return koreanAge - 1
}
set(inputValue) {
koreanAge = inputValue + 1
}
}
// 타입 저장 프로퍼티
static var typeDescription: String = "학생"
// 읽기 전용 인스턴스 연산 프로퍼티
var selfIntroduction: String {
get {
return "저는 \(self.class)반 \(name)입니다"
}
}
}
프로퍼티 감시자
// 프로퍼티 감시자를 사용하면 프로퍼티 값이 변경될 때 원하는 동작을 수행 할 수 있습니다.
struct Money {
var currencyRate: Double = 1100 {
willSet(newRate) {
// 바뀌기 직전 호출
print("\(currencyRate)에서 \(newRate)로 변경될 예정입니다")
}
didSet(oldRate) {
// 바뀐 다음 호출
print("\(oldRate)에서 \(currencyRate)로 변경되었습니다.")
}
}
}
// 연산 프로퍼티와 프로퍼티 감시자는 함께 사용 할 수 없습니다.
상속
// 상속
// 스위프트의 상속은 클래스, 프로토콜 등에서 가능합니다.
// 열거형, 구조체는 상속이 불가능
// 스위프트는 다중상속을 지원하지 않습니다.
class 이름: 상속받을 클래스 이름 {
// 구현부
}
class Person {
var name: String = ""
func selfIntroduce() {
print("저는 \(name) 입니다")
}
// final 키워드를 사용하여 재정의를 방지 할 수 있습니다.
final func sayHello() {
print("hello")
}
// 타입 메서드
// 재정의 불가 타입 메서드 - static
static func typeMethod() {
print("type method)
}
// 클레스 메서드
// 재정의 가능!
class func classMethod() {
print("class method)
}
// 재정의 가능한 class 메서드라도 final 키워드가 붙으면 재정의 불가능합니다.
// final class = static
}
class Student: Person {
var name: String = "" // Error (이미 상속받은 프로퍼티 저장 프로퍼티는 오버라이딩 불가)
var major: String = ""
override func selfIntroduce() {
print("저는 \(name)이고, 전공은 \(major)입니다")
super.selfIntroduce()
}
override class func classMethod() {
print("...")
}
override func sayHello() {} // Error 재정의 불가
}
인스턴스 생성과 소멸
// 이니셜라이저와 디이니셜라이저
// init, deinit
// 스위프트의 모든 인스턴스는 초기화와 동시에
// 모든 프로퍼티에 유효한 값이 할당되어 있어야 합니다.
// 프로퍼티에 미리 기본값을 할당해두면
// 인스턴스가 생성됨과 동시에 초기값을 지니게 됩니다.
class PersonA {
// 모든 저장 프로퍼티에 기본 값 할당
var name: String = "unknown"
var age: Int = 0
var nickName: String = "nick"
}
let jason: PersonA = PersonA()
// 프로퍼티 기본값을 지정하기 어려운 경우에는
// 이니셜라이저를 통해
// 인스턴스가 가져야할 초기값을 전달 할 수 있습니다.
class PersonB {
var name: String
var age: Int
var nickName: String
init(name: String, age: Int, nickName: String) {
self.name = name
self.age = age
self.nickName = nickName
}
}
let hana: PersonB = PersonB(name: "hana", age: 20, nickName: "하나")
// 프로퍼티의 초기값이 꼭 필요 없을 때
// 옵셔널을 사용
class PersonC {
var name: String
var age: Int
var nickName: String?
// 자신의 initializer 를 사용하려면 앞에 convenience 키워드를 넣어줘야함.
convenience init(name: String, age: Int, nickName: String) {
self.init(name: name, age: age)
self.nickName = nickName
}
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// 실패가능한 이니셜라이저
// 이니셜라이저 매개변수로 전달되는 초기값이 잘못된 경우
class PersonD {
var name: String
var age: Int
var nickName: String?
init?(name: String, age: Int) {
if (0...120).contains(age) == false {
return nil
}
if name.characters.count == 0 {
return nil
}
self.name = name
self.age = age
}
}
let john: PersonD = personD(name: "john", age: 23) // Error
let john: PersonD? = personD(name: "john", age: 23)
// 디이니셜라이저
// deinit은 클레스의 인스턴스가 메모리에서 해제된느 시점에 호출됩니다
class PersonE {
var name: String
var pet: Puppy?
var child: PersonC
// deinit 은 매개변수를 가질 수 없다.
deinit {
if let petName = pet?.name {
print("\(name)가 \(child.name)에게 \(petName)를 인도합니다.")
self.pet?.owner = child
}
}
}
옵셔널 체이닝과 nil 병합 연산자
// 옵셔널 체이닝은 옵셔널 요소 내부의 프로퍼티로
// 또 다시 옵셔널이 연속적으로 연결되는 경우 유용하게 사용 할 수 있습ㄴ디ㅏ.
class Person {
var name: String
var job: String?
var home: Apartment?
init(name: String) {
self.name = name
}
}
class Apartment {
var buildingNumber: String
var roomeNumber: String
var `guard`: Person?
var owner: Person?
init(dong: String, ho: String) {
buildingNumber = dong
roomNumber = ho
}
}
let yagom: Person? = Person(name: "yagom")
let apart: Apartment? = Apartment(dong: "101", ho: "1")
let superman: Person? = Person(name: "superman")
// 만약 우리집의 경비원의 직업이 궁금하다면?
// 옵셔널 체이닝을 사용하지 않는다면
func guardJob(owner: Person?) {
if let owner = owner {
if let home = owner.home {
if let ...
}
}
}
// 옵셔널 체이닝을 사용하면
func guardJob(owner: Person?) {
if let guardJob = owner?.home?.guard?.job {
print("우리집 경비원의 직업은 \(guardJob) 입니다")
} else {
print("우리집 경비원은 직업이 없어요")
}
}
// nil 병합 연산자
var guardJob : String
guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
print(guardJob) // 슈퍼맨
타입캐스팅
// 스위프트의 타입캐스팅은
// 인스턴스의 타입을 확인하는 용도
// 또는 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용 할 수 있는지
// 확인하는 용도로 사용합니다.
// is, as 를 사용합니다.
// 기존의 다른언어에서 타입 캐스팅은 아래와 같다
let someInt: Int = 100
let someDouble: Double = Double(someInt) // 이것은 실질적으로 타입캐스팅이 아니라, Double type 의 인스턴스가 하나 더 생성된다.
class Person {
var name: String = ""
func breath() {
print("하 하")
}
}
class Student: Person {
var school: String = ""
func goToSchool() {
print("등교")
}
}
class UniversityStudent: Student {
var major: String = ""
func goToMT() {
print("멤버쉽 트레이닝을 갑니다!")
}
}
var yagom: Person = Person()
var hana: Student = Student()
var jason: UniversityStudent = UniversitiyStudent()
var result: Bool
result = yagom is Person // true
result = yagom is Student // false
result = yagom is UniversityStudent // false
result = hana is Person // true
result = hana is Student // true
result = hana is UniversityStudent // false
result = jason is Person // true
result = jason is Student // true
result = jason is UniversityStudent // true
if yagom is UniversityStudent {
print("yagom은 대학생입니다")
} else if yagom is Student {
print("yagom은 학생입니다")
} else if yagom is Person {
print("yagom은 사람입니다")
}
switch jason {
case is UniversityStudent:
...
case is Student:
...
case is Person:
...
default:
...
}
// 업캐스팅
// as를 사용해서 부모클래스의 인스턴스로 사용 할 수 있도록 컴파일러에게 가르쳐 주는 것
var mike: Person = UniversityStudent() as Person
// 다운캐스팅
// as? 또는 as! 를 사용하여
// 자식 클래스의 인스턴스로서 사용 할 수 있도록 컴파일러에게 가르쳐 주는 것
// 다른 언어들과 마찬가지로 사실상 잘 사용되지 않는다.....
Assert 와 Guard
// 어플리케이션의 동작 도중 생성하는 다양한 결과값을
// 동적으로 확인하고 안전하게 처리 할 수 있도록
// 확인하고 처리 할 수 있다.
// assert(_:_:file:line:) 함수를 사용합니다.
// assert 함수는 디버깅 모드에서만 동작합니다.
// 배포하는 어플리케이션에서는 제외됩니다.
// 주로 디버깅 중 조건을 검증하기 위해 사용합니다.
var someInt: Int = 0
assert(someInt == 0, "someInt != 0") // 참이면 지나감, 거짓이면 메시지를 출력하고 정지함
someInt = 1
assert(someInt == 0) // 동작 중지, 검증 실패
func functionWithAssert(age: Int?) {
assert(age != nil, "age == nil)
assert((age! >= 0) && (age! <= 130), "나이값 입력이 잘못되었습니다")
print("당신의 나이는 \(age!)세 입니다")
}
functionWithAssert(age: 50)
functionWithAssert(age: -1) // 동작 중지
functionWithAssert(age: nil) // 동작 중지
---
// guard 를 사용하여 잘못된 값의 전달 시
// 특정 실행 구문을 빠르게 종료합니다
// 디버깅 모드 뿐만 아니라 어떤 조건에서도 동작합니다
// guard 의 else 블럭 내부에는
// 특정 코드블럭을 종료하는 지시어 (return, break) 가 꼭 있어야 합니다
// 타입 캐스팅, 옵셔널과도 자주 사용됩니다.
// 그 외 단순 조건 판ㄷ나 후 빠르게 종료할때도 용이합니다.
func functionWithGuard(age: Int?) {
guard let unwrappedAge = age,
unwrappedAge < 130,
unwrappedAge >= 0 else {
print("나이값 입력이 잘못되었습니다.)
return
}
guard unwrappedAge < 100 else {
return
}
// guard 는 블럭을 넘어서도 unwrappedAge 를 사용 할 수 있게 해준다.
print("당신의 나이는 \(unwrappedAge)세 입니다")
}
var count = 1
while true {
guard count < 3 else {
break
}
print(count)
count += 1
}
func someFunc(info: [String: Any]) {
guard let name = info["name"] as? String else {
return
}
guard let age = info["age"] as? Int, age >= 0 else {
return
}
print("\n(name): \(age)")
}
someFunc(info: ["name": "jenny", "age": "10"]) // guard - return
someFunc(info: ["name": "mike"]) // guard - return
someFunc(info: ["name": "jenny", "age": 10]) // yagom : 10
- Guard 를 잘써야 스위프트를 잘할수있을것 같은 느낌이 든다...
프로토콜
// 프로토콜
// 프로토콜은 특정 역할을 수행하기 위한
// 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의합니다.
// 구조체, 클레스, 열거형은 프로토콜을 채택(Adopted) 해서
// 프로토콜의 요구사항을 실제로 구현 할 수 있습니다.
// 어떤 프로토콜의 요구사항을 모두 따르는 타입은
// 그 '프로토콜을 준수한다(Conform)' 고 표현합니다.
protocol 프로토콜 이름 {
// 정의부
}
protocol Talkable {
// 프로퍼티 요구
// 프로퍼티 요구는 항상 var 키워드를 사용합니다.
// get 은 읽기만 가능해도 산과없다는 뜻이며
// get과 set을 모두 명시하면
// 읽기 쓰기 모두 가능한 프로퍼티여야 합니다.
var topic: String {get set}
var language: String {get}
// 메서드 요구
func talk()
// 이니셜라이저 요구
init(topic: String, language: String)
}
// Person 구조체는 Talkable 프로토콜을 채택했습니다
struct Person: Talkable {
var topic: String
let language: String
func talk() {
...
}
init(topic: String, language: String) {
...
}
}
// 읽기 전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
// var language: String { get { return "한국어" } }
// 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체 할 수 있습니다.
//
---
// 프로토콜은 다중상속이 가능하다
// 프로토콜도 프로토콜의 상속이 가능하다
// 프로토콜을 채택하면 반드시 모두 구현해야한다.
// 클레스 상속과 프로토콜
// 클래스에서 상속과 프로토콜 채택을 동시에 하려면
// 상속받으려는 클래스를 먼저 명시하고,
// 그 뒤에 채택할 프로토콜 목록을 작성하면 된다.
// 인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있습니다.
// is, as 연산자 이용
let sup: SuperClass = SuperClass()
var someAny: Any = sup
someAny is Readable // true or false ...
if let someReadable: Readable = someAny as? Readable {
someReadable.read()
}
- 다른언어의 Interface 와 매우 흡사하다.
- 사용하는 사람 입장에서는 이 타입은 이 프로토콜을 준수하고 있기 때문에, ~기능을 수행 할 수 있구나 라는 것을 쉽게 파악 할 수 있다.
익스텐션
// 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에
// 새로운 기능을 추가할수 있는 기능입니다.
// 기능을 추가하려는 타입의 구현된 소스 코드를
// 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장 할 수 도 있습니다.
// 익스텐션으로 추가 할 수 있는 기능
// 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
// 타입 메서드 / 인스턴스 메서드
// 이니셜라이저
// 서브스크립트
// 중첩타입
// 특정 프로토콜을 준수 할 수 있도록 기능 추가
// 기존에 존재하는 기능을 재정의할 수는 없습니다.
extension 확장할타입이름 {
// 타입에 추가될 새로운 기능 구현
}
// 익스텐션은 기존에 존재하는 타입이
// 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있습니다.
extension String: 프로토콜1, 프로토콜2, 프로토콜3 ... {
// 프로토콜 요구사항 구현
}
extension Int {
var isEven: Bool {
return self % 2 == 0
}
var isOdd: Bool {
return self % 2 == 1
}
}
print(1.isEven) // false
print(2.isEven) // true
var number: Int = 3
print(number.isOdd) // true
extension Int {
func multiply(by n: Int) -> {
return self * n
}
}
print(3.multiply(by: 2)) // 6
// 이니셜라이저 추가
extension String {
init(intTypeNumber: Int){
self = "\(intTypeNumber)"
}
}
let stringFromInt: String = String(intTypeNumber: 100)
- Dart 의 extension 과 완전히 동일하다 ....
오류 처리
// Error 프로토콜과 (주로) 열거형을 통해서 오류를 표현합니다.
enum 오류종류이름: Error {
case 종류1
case 종류2
case 종류3
// ...
}
// 자판기 동작 오류를 표현
enum VendingMachineError: Error {
case invalidInput
case insufficientFuncs(moneyNeeded: Int)
case outOfStock
}
// 자판기 동작 도중 발생한 오류 던지기
// 오류 발생의 여지가 있는 메서드는 throws를 사용하여
// 오류를 내포하는 함수임을 표현합니다.
class VendingMachine {
let itemPrice: Int = 100
var itemCount: Int = 5
var deposited: Int = 0
// 돈 받기 메서드
func receiveMoney(_ money: Int) throws {
// 입력한 돈이 0이하면 오류를 던집니다
guard money > 0 else {
throw VendingMachineError.invalidInput
}
// 오류가 없으면 정상처리를 합니다.
self.deposited += money
print("\(money)원 받음")
}
// 물건을 파는 메서드
func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {
...
}
}
// 자판기 인스턴스
let machine: VendingMachine = VendingMachine()
// 판매 결과를 전달받을 변수
var result: String?
// 오류발생의 여지가 있는 throws 함수는
// try를 사용하여 호출해야 합니다.
// try, try?, try!
// 오류 발생의 여지가 있는 함수는 do - catch 구문을 활용하여 try 와 함께 사용
do {
try machine.receiveMoney(0)
} catch VendingMachineError.invalidInput {
print("입력이 잘못되었습니다.")
} catch ... {
...
} catch ... {
...
}
// switch 로도 표현함
do {
try machine.receiveMoney(300)
} catch (let error) { // } catch { 이렇게 생략해도 됨. 암시적으로 표현됨
switch error {
case ...
}
}
do {
result = try machine.vend(numberOfItems: 4)
} catch {
print(error)
}
do {
result = try machine.vend(numberOfItems: 2)
}
// try?
// 별도의 오류처리 결과를 통보받지 않고
// 오류가 발생했으면 결과값을 nil로 받는다.
// 정상동작 후에는 옵셔널 타입으로 정상반환값을 돌려받습니다.
result = try? machine.vend(numberOfItems: 2)
result // Optional or nil
// try!
// 오류가 발생하지 않을 것이라는 강력한 확신을 가질 때
// try!를 사용하면 정상동작 후에 바로 결과값을 돌려받습니다
// 오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지됩ㄴ디ㅏ
result = try! machine.vend(numberOfItems: 1)
result // 1개 제공함
- try - catch 가 아닌 do - try - catch 라니
고차함수
// 전달이낮로 함수를 전달받거나
// 함수실행의 결과를 함수로 반환하는 함수
// map. filter, reduce
// 컨테이너 내부의 기존 데이터를 변형(transform) 하여 새로운 컨테이너 생성
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int]
var strings: [String]
// for구문 사용
doubledNumbers = [Int]()
strings = [String]()
for number in numbers {
doubledNumbers.append(number * 2)
strings.append("\(number)")
}
print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]
// map 메서드 사용
// numbers 의 각 요소를 2배 하여 새로운 배열 반환
doubledNumbers = numbers.map({ (number: Int) -> Int in
return number * 2
})
// numbers 의 각 요소를 문자열로 변환하여 새로운 배열 반환
strings = numbers.map({ (number: Int) -> String in
return "\(number)"
})
doubledNumbers = numbers.map { $0 * 2 }
var filtered: [Int] = [Int]()
let evenNumbers: [Int] = numbers.filter {
(number: Int) -> Bool in
return number % 2 == 0
}
// 매개변수, 반환타입, 반환키워드 생략, 후행 클로저
let oddNumbers: [Int] = numbers.filter { $0 % 2 != 0 }
// reduce
let sumFormThree = someNumbers.reduce(3) { $0 + $1 }
- 확실히 함수형 프로그래밍 부분은 Javascript 의 향이 난다
다음에 알아 볼 것
- 제네릭(Generics)
- 서브스크립트(Subscript)
- 접근수준(Access Control)
- ARC(Automatic Reference Counting)
- 중첩타입(Nested Types)
- 사용자정의 연산자(Custom Operators)