Search
Duplicate

Swift 기본 문법 (3)

생성일
2023/06/22 03:19
태그
Grammar

Swift 기본 문법 (3)

25) Type Casting (타입 캐스팅)

타입 캐스팅은 인스턴스의 타입을 확인하는 용도, 또는 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도로 사용한다.
is, as를 사용한다.
ex) 스위프트의 타입 캐스팅이 아닌 예
let someInt: Int = 100 let someDouble: Double = Double(someInt)
Swift
복사
→ 실질적으로 Swift에서는 타입 캐스팅이 아니다.
→ Double 타입에 인스턴스를 하나 더 생성해준 것일 뿐.
import Swift // 타입 캐스팅을 위한 클래스 정의 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("MT 가자") } } var yagom: Person = Person() var hana: Student = Student() var jason: UniversityStudent = UniversityStudent()
Swift
복사

타입 확인

is를 사용하여 타입을 확인한다.
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은 사람입니다") } // yagom은 사람입니다 switch jason { case is Person: print("jason은 사람입니다") case is Student: print("jason은 학생입니다") case is UniversityStudent: print("jason은 대학생입니다.") default: print("jason은 사람도, 학생도, 대학생도 아닙니다") } // jason은 사람입니다 switch jason { case is UniversityStudent: print("jason은 대학생입니다") case is Student: print("jason은 학생입니다") case is Person: print("jason은 사람입니다") default: print("jason은 사람도, 학생도, 대학생도 아닙니다") } // jason은 대학생입니다
Swift
복사

업 캐스팅

as 를 사용하여 부모 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 타입 정보를 전환해 준다.
Any 혹은 AnyObject로도 타입 정보를 변환할 수 있다.
암시적으로 처리되므로 생략해도 무방하다.
var mike: Person = UniversityStudent() as Person var jenny: Student = Student() // var jina: UniversityStudent = Person() as UniversityStudent // 컴파일 오류 var jina: Any = Person() // as Any 생략 가능
Swift
복사

다운 캐스팅

as? 또는 as! 를 사용하여 자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입 정보를 전환해 준다.

조건부 다운 캐스팅

as!
결과값이 옵셔널로 나오게 된다.
var optionalCasted: Student? optionalCasted = mike as? UniversityStudent // casting에 성공하면 옵셔널로 반환된다 -> 실패 가능성을 내포하기 떄문에 optionalCasted = jenny as? UniversityStudent // nil optionalCasted = jina as? UniversityStudent // nil optionalCasted = jina as? Student // nil
Swift
복사

강제 다운 캐스팅

as!
반환 타입이 옵셔널 타입이 아니고, 일반 타입이기 때문에 편하게 활용 가능
var forcedCasted: Student optionalCasted = mike as! UniversityStudent // optionalCasted = jenny as! UniverstiyStudent // 런타임 오류 // optionalCasted = jina as! UniversityStudent // 런타임 오류 // optionalCasted = jina as! Student // 런타임 오류
Swift
복사
활용
func doSomethingWithSwitch(someone: Person) { switch someone { case is UniversityStudent: (someone as! UniversityStudent).goToMT() case is Student: (someone as! Student).goToSchool() case is Person: (someone as! Person).breath() } } doSomethingWithSwitch(someone: mike as Person) // MT 가자! doSomethingWithSwitch(someone: mike) // MT 가자! doSomethingWithSwitch(someone: jenny) // 등교를 합니다 doSomethingWithSwitch(someone: yagom) // 숨을 쉽니다
Swift
복사
func doSomething(someone: Person) { if let universityStudent = someone as? UniversityStudent { universityStudent.goToMT() } else if let student = someone as? Student { student.goToSchool() } else if let person = someone as? Person { person.breath() } } doSomething(someone: mike as Person) // MT 가자! doSomething(someone: mkie) // MT 가자! doSomething(someone: jenny) // 등교를 합니다 doSomething(someone: yagom) // 숨을 쉽니다
Swift
복사
if-let 구문을 사용하면, 캐스팅과 동시에 인스턴스를 반환 받아서 unwrapping을 할 수 있다 → 옵셔널을 추출해서 쓸 수가 있다.

26) assert와 guard

애플리케이션이 동작 도중에 생성하는 다양한 결과값을 동적으로 확인하고 안전하게 처리할 수 있도록 확인하고 빠르게 처리할 수 있다.

Assertion

assert(_:_:file:line:)
Swift
복사
함수 사용
assert 함수는 디버깅 모드에서만 동작한다.
배포하는 애플리케이션에서는 제외된다.
주로 디버깅 중 조건의 검증을 위하여 사용한다.
var someInt: Int = 0 assert(someInt == 0, "someInt != 0") someInt = 1 // assert(someInt == 0) // 동작중지, 검증실패 // assert(someInt == 0, "someInt != 0") // 동작중지, 검증실패 // assertion failed: someInt != 0: file guard_assert.swift, line 26 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) // 동작중지, 검증실패
Swift
복사

Early Exit

guard를 사용하여 잘못된 값의 전달 시, 특정 실행 구문을 빠르게 종료한다.
디버깅 모드 뿐만 아니라, 어떤 조건에서도 동작한다.
guard의 else 블럭 내부에는 특정 코드 블럭을 종료하는 지시어(return, break 등)가 꼭 있어야 한다.
타입 캐스팅, 옵셔널과도 자주 사용된다.
그 외, 단순 조건 판단 후, 빠르게 종료할 때도 용이하다.
func functionWithGuard(age: Int?) { guard let unwrappedAge = age, unwrappedAge < 130, unwrappedAge >= 0 else { print("나이값 입력이 잘못되었습니다") return } print("당신의 나이는 \(unwrappedAge)세 입니다") }
Swift
복사
guard let → 옵셔널 바인딩과 연결시켜 준다.
age가 nil이라면, 실행이 되지 않고 바로 return 된다.
guard unwrappedAge < 100 else { return }
Swift
복사
이것도 가능
var count = 1 while true { guard count < 3 else { break } print(count) count += 1 } // 1 // 2
Swift
복사
딕셔너리 같은 것들을 받아왔을 때도 굉장히 많이 활용!
func someFunction(info: [String: Any]) { guard let name = info["name"] as? String else { // 딕셔너리에서 나온 값들은 모두 옵셔널 -> Key에 해당되는 값이 없기 때문 // as? String -> 캐스팅 return } guard let age = info["age"] as? Int, age >= 0 else { return } print("\(name): \(age)") } someFunction(info: ["name": "jenny", "age": "10"]) // age가 String이기 때문에 실행X someFunction(info: ["name": "mike"]) // age가 없기 때문에, 바인딩X. 캐스팅X someFunction(info: ["name": "yagom", "age": 10]) // 정상적으로 실행 된다 // yagom: 10
Swift
복사

27) 프로토콜

프로토콜은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의한다.
구조체, 클래스, 열거형은 프로토콜을 채택(adopted)해서 프로토콜의 요구사항을 실제로 구현할 수 있다.
어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 ‘프로토콜을 준수한다(conform)’고 표현한다.
프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현하여야 한다.

정의

protocol 프로토콜 이름 { // 정의부 }
Swift
복사
protocol Talkable { // 프로퍼티 요구 // 프로퍼티 요구는 항상 var 키워드를 사용한다. // get은 읽기만 가능해도 상관없다는 뜻이며, // get과 set을 모두 명시하면, 읽기 쓰기 모두 가능한 프로퍼티여야 한다. var topic: String { get set } var language: String { get } // 메서드 요구 func talk() // 이니셜라이저 요구 init(topic: String, language: String) }
Swift
복사

프로토콜 채택 및 준수

// Person 구조체는 Talkable 프로토콜을 채택하였다 struct Person: Talkable { var topic: String let language: String /* // 읽기 전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능하다 var language: String { return "한국어" } // 물론 읽기 쓰기 프로퍼티도 연산 프로퍼티로 대체가 가능하다 var subject: String = "" var topic: String { set { self.subject = newValue } get { return self.subject } } */ func talk() { print("\(topic)에 대해 \(language)로 말합니다") } init(topic: String, language: String) { self.topic = topic self.language = language } }
Swift
복사

프로토콜 상속

프로토콜은 클래스와 다르게 다중 상속이 가능하다
protocol 프로토콜 이름: 부모 프로토콜 이름 목록 { // 정의부 }
Swift
복사
protocol Readable { func read() } protocol Writeable { func write() } protocol ReadSpeakable: Readable { // func read() // 써줄 필요 없다. 이미 상속 받았기 때문에 func speak() } protocol ReadWriteSpeakable: Readable, Writeable { // func read() // func write() func speak() } struct SomeType: ReadWriteSpeakable { func read() { print("Read") } func write() { print("Write") } func speak() { print("Speak") } } // 하나라도 구현하지 않으면 Error
Swift
복사

클래스 상속과 프로토콜

클래스에서 상속과 프로토콜 채택을 동시에 하려면, 상속 받으려는 클래스를 먼저 명시하고, 그 뒤에 채택할 프로토콜 목록을 작성한다.
class SuperClass: Readable { func read() { print("read") } } class SubClass: SuperClass, Writeable, ReadSpeakable { func write() { print("write") } func speak() { print("speak") } }
Swift
복사

프로토콜 준수 확인

인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있다.
is , as 사용
let sup: SuperClass = SuperClass() let sub: SubClass = SubClass() var someAny: Any = sup someAny is Readable // true someAny is ReadSpeakable // false someAny = sub someAny is Readable // true someAny is REadSpeakable // true someAny = sup if let someReadable: Readable = someAny as? Readable { someReadable.read() } // read if let someReadSpeakable: Readable = someAny as? Readable { someReadSpeakable.speak() } // 동작하지 않음 someAny = sub if let someReadable: Readable = someAny as? Readable { someReadble.read() } // read
Swift
복사

28) Extension (익스텐션)

익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능이다.
기능을 추가하려는 타입의 구현된 소스코드를 알지 못하거나 볼 수 없다해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수 있다.

익스텐션으로 추가할 수 있는 기능

연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
타입 메서드 / 인스턴스 메서드
이니셜라이저
서브스크립트
중첩 타입
특정 프로토콜을 준수할 수 있도록 기능 추가
→ 기존에 존재하는 기능을 재정의 할 수는 없다.

정의

extension 확장할 타입 이름 { // 타입에 추가될 새로운 기능 구현 }
Swift
복사
익스텐션은 기존에 존재하는 타입이 추가적이고 다른 프로토콜을 채택할 수 있도록 확장할 수도 있다.
extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3 { // 프로토콜 요구사항 구현 }
Swift
복사

익스텐션 구현

연산 프로퍼티 추가
extension Int { var isEven: Bool { return self % 2 == 0 } var isOdd: Bool { return self % 2 == 1 } } print(1.isEven) // false print(2.isEven) // true print(1.isOdd) // true print(2.isOdd) // false var number: Int = 3 print(number.isEven) // false print(number.isOdd) // true number = 2 print(number.isEven) // true print(number.isOdd) // false
Swift
복사

메서드 추가

extension Int { func multiply(by n: Int) -> Int { return self * n } } print(3.multiply(by: 2)) // 6 print(4.multiply(by: 5)) // 20 number = 3 print(number.multiply(by: 2)) // 6 print(number.multiply(by: 3)) // 9
Swift
복사

이니셜라이저 추가

extension String { init(intTypeNumber: Int) { self = "\(intTypeNumber)" } init(doubleTypeNumber: Double) { self = "\(doubleTypeNumber)" } } let stringFromInt: String = String(intTypeNumber: 100) // "100" let stringFromDouble: String = String(doubleTypeNumber: 100.0) // "100.0"
Swift
복사

29) Error Handling (오류 처리)

오류 표현

Error 프로토콜과 (주로) 열거형을 통해서 오류를 표현한다.
enum 오류종류 이름: Error { case 종류1 case 종류2 case 종류3 ... }
Swift
복사
ex) 자판기 동작 오류의 종류를 표현한 VendingmachineError 열거형
enum VendingMachineError: Error { case invalidInput case insufficientFunds(moneyNeeded: Int) case outOfStock }
Swift
복사

함수에서 발생한 오류 던지기

자판기 동작 도중 발생한 오류 던지기
오류 발생의 여지가 있는 메서드는 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 { // 원하는 아이템의 수량이 잘못 입력되었으면 오류를 던진다 guard numberOfItemsToVend > 0 else { throw VendingMachineError.invalidInput } // 구매하려는 수량보다 미리 넣어둔 돈이 적으면 오류를 던진다 guard numberOfItemsToVend * itemPrice <= deposited else { let moneyNeeded: Int moneyNeeded = numberOfItemsToVend * itemPrice - deposited throw VendingMachineError.insufficientFunds(moneyNeeded: money) } // 구매하려는 수량보다 요구하는 수량이 많으면 오류를 던진다 guard itemCount >= numberOfItemsToVend else { throw VendingMachineError.outOfStock } // 오류가 없으면 정상처리 let totalPrice = numberOfItemsToVend * itemPrice self.deposited -= totalPrice self.itemCount -= numberOfItemsToVend return "\(numberOfItemsToVend)개 제공함" } }
Swift
복사
// 자판기 인스턴스 let machine: VendingMachine = VendingMachine() // 판매 겨로가를 전달받을 변수 var result: String?
Swift
복사

오류 처리

오류 발생의 여지가 있는 throws 함수(메서드)는 try를 사용하여 호출한다.
try , try? , try!

do-catch

오류 발생의 여지가 있는 throws 함수(메서드)는 do-catch 구문을 활용하여 오류 발생에 대비한다.
do { try machine.receiveMoney(0) } catch VendingMachineError.invalidInput { print("입력이 잘못되었습니다") } catch VendingMachineError.insufficientFuncs(let moneyNeeded) { print("\(moneyNeeded)원이 부족합니다") } catch VendingMachineError.outOfStock { print("수량이 부족합니다") } // 입력이 잘못되었습니다
Swift
복사
do { try machine.receiveMoney(300) } catch // let error { switch error { case VendingMachineError.invalidInput: print("입력이 잘못되었습니다") case VendingMachineError.insufficientFunds(let moneyNeeded): print("\(moneyNeeded)원이 부족합니다") case VendingMachineError.outOfStock: print("수량이 부족합니다") default: print("알수없는 오류 \(error)") } }
Swift
복사
do { result = try machine.vend(numberOfItems: 4) } catch { print(error) } // insufficientFunds(100) do { result = try machine.vend(numberOfItems: 4) }
Swift
복사

try?

별도의 오류처리 결과를 통보 받지 않고, 오류가 발생했으면 결과값을 nil로 돌려 받을 수 있다.
정상 동작 후에는 옵셔널 타입으로 정상 반환값을 돌려 받는다.
result = try? machine.vend(numberOfItems: 2) result // Optional("2개 제공함") result = try? machine.vend(numberOfItems: 2) result // nil
Swift
복사

try!

오류가 발생하지 않을 것이라는 강력한 확신을 가질 때, try!를 사용하면 정상동작 후에 바로 결과값을 돌려 받는다.
오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지된다.
result = try! machine.vend(numberOfItems: 1) result // 1개 제공함 // result = try! machine.vend(numberOfItems: 1) // 죽어버림..
Swift
복사

30) 고차 함수

전달 인자로 함수를 전달 받거나, 함수 실행의 결과를 함수로 반환하는 함수
map, filter, reduce

map

컨테이너 내부의 기존 데이터를 변형(transform)하여 새로운 컨테이너 생성
let numbers: [Int] = [0, 1, 2, 3, 4] var doubledNumbers: [Int] var strings: [String] // for 구분 사용했을 때 doubledNumbers = [Int]() strings = [String]() for number in numbers { doubleNumbers.append(number * 2) strings.append("\(number)") } print(doubleNumbers) // [0, 2, 4, 6, 8] print(strings) // ["0", "1", "2", "3", "4"] // map 메서드 사용했을 때 // numbers의 각 요소를 2배하여 새로운 배열로 반환 doubleNumbers = numbers.map({ (number: Int) -> Int in return number * 2 }) // numbers의 각 요소를 문자열로 반환하여 새로운 배열로 반환 strings = numbers.map({ (number: Int) -> String in return "\(number)" }) print(doubledNumbers) // [0, 2, 4, 6, 8] print(strings) // ["0", "1", "2", "3", "4"] // 매개변수, 반환타입, 반환 키워드(return) 생략, 후행 클로저 doubledNumbers = numbers.map{ $0 * 2 } print(doubleNumbers) // [0, 2, 4, 6, 8]
Swift
복사

filter

컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출
// for 구문 사용했을 때 // 변수 사용에 주목 var filtered: [Int] = [Int]() for number in numbers { if number % 2 == 0 { filtered.append(number) } } print(filtered) // [0, 2, 4] // filter 메서드 사용 // numbers의 요소 중 짝수를 걸러내어 새로운 배열로 반환 let evenNumbers: [Int] = numbers.filter { (number: Int) -> Bool in return number % 2 == 0 } print(evenNumbers) // [0, 2, 4] // 매개변수, 반환타입, 반환 키워드(return) 생략, 후행 클로저 let oddNumbers: [Int] = numbers.filter{ $0 % 2 != 0 } print(oddNumbers) // [1, 3]
Swift
복사

reduce

컨테이너 내부의 컨텐츠를 하나로 통합
let someNumbers: [Int] = [2, 8, 15] // for 구문 사용했을 때 var result: Int = 0 // someNumbers의 모든 요소를 더한다. for (number in someNumbers { result += number } print(result) // 25 // reduce 메서드 사용했을 때 // 초기값이 0이고, someNumbers 내부의 모든 값을 더한다. let sum: Int = someNumbers.reduce(0, { (first: Int, second: Int) -> Int in return first + second }) print(sum) // 25 // 초기값이 0이고, someNumbers 내부의 모든 값을 뺀다. var subtract: Int = someNumbers.reduce(0, { (first: Int, second: Int) -> Int in return first - second }) print(subtract) // -25 print(someNumbers.reduce(1) { $0 * $1 }) // 240
Swift
복사