-
Swift 문법 기초 2편iOS/Swift 기초문법 2022. 3. 27. 13:20
<타입 캐스팅>
인스턴스의 타입을 확인하거나 어떠한 클래스의 인스턴스를
해당 클래스 계층구조의 슈퍼 클래스나 서브 클래스로 취급하는 방법
class MediaItem { var name: String init(name: String){ self.name = name } } class Movie: MediaItem { var director: String init(name: String, director: String) { self.director = director super.init(name: name) } } class Song: MediaItem { var artist: String init(name: String, artist: String) { self.artist = artist super.init(name: name) } } // library는 타입추론을 통해 super class인 MediaItem으로 형변환됨 let library = [ Movie(name: "기생충", director: "봉준호"), Song(name: "Butter", artist: "BTS"), Movie(name: "올드보이", director: "박찬욱"), Song(name: "Wonderfull", artist: "Oasis"), Song(name: "Rain", artist: "이적") ] var movieCount = 0 var songCount = 0 // is 연산자를 통해 인스턴스의 클래스 파악 - moviCount는 2, songCount는 3 for item in library { if item is Movie { movieCount += 1 } else if item is Song { songCount += 1 } // as? - 다운캐스팅 (옵셔널 값 반환) // library 내 인스턴스 들은 Movie class or Song class // Movie or Song으로 다운캐스팅이 될수 있으면 해당 내용을 출력 for item in library { if let movie = item as? Movie { print("Movie: \(movie.name"), dir. \(movie.director)") } else if let song = item as? Song { print("Song: \(song.name"), dir. \(song.artist)") } } // as! - 강제 언래핑 (항상 성공할 것이라는 확신이 있을 때 사용)
< assert문 / guard문 >
- assert문
- 특정 조건을 체크, 조건이 성립되지 않으면 메세지와 함께 에러를 발생시킴
- 디버깅 모드에서만 동작. 디버깅 중 조건의 검증을 위하여 사용 - guard문
- 함수 내부에서 조건식으로 뭔가를 검사하여 그 다음에 오는 코드를 실행할지 말지 결정
- gaurd문에서 주어진 조건문이 거짓일 때 구문이 실행
- eary exit: 특정 조건문을 만족하지 않으면 이후 코드를 실행하지 않도록 방어코드로 활용
(잘못된 값이 함수에 들어오지 않기 위한 방어)
// assert함수 var value = 0 assert(value == 0) value = 2 assert(value == 0, "값이 0이 아닙니다") // 런타임 에러. console창에 "Assertion faild: 값이 0이 아닙니다" 출력
guard문을 사용하면 옵셔널 바인딩 된 상수를 조건문 범위 밖에서도 사용 가능
func guardTest(value: Int) { guard value == 0 else {return} print("안녕하세요") } guardTest(value: 2) // guard문에 막혀서 아무것도 출력되지 않음 (함수 종료) guardTest(value: 0) // guard문을 통과해서 그 다음 구문인 "안녕하세요"가 출력됨 // guard문을 통한 옵셔널 바인딩 func guardTest2(value: Int?) { guard let value = value else {return} // value에 값이 들어가 있으면 옵셔널 바인딩 해서 value상수에 넣기 print(value) } guardTest2(value: 2) // 2가 출력됨 guardTest2(value: nil) // guard문에 막혀서 함수가 종료
<프로토콜>
기본 형식
protocol SomeProtocol { <#requirements#> } protocol SomeProtocol2 { <#requirements#> } // 구조체가 두개의 프로토콜을 따르도록 지정 struct SomeStruct: SomeProtocol, SomeProtocol2 { }
// 1. 프로퍼티 요구 // 특정 프로토콜을 재택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있음 protocol FirstProtocol { var name: Int {get set} // 읽기,쓰기 가능 var age: Int {get} // 읽기 전용 } // 프로토콜에서 "타입 프로퍼티"를 요구하려면 항상 static 키워드를 붙여야 함 protocol AnotherProtocol { static var someTypeProperty: Int { get set } } // 예시) struct가 프로토콜을 준수 protocol FullyNames { var fullName: String { get set } func printFullName() // 몸통부는 없어도 되지만 매개변수의 기본값 지정은 불가 } // FullyNames 프로토콜이 요구하는 사항을 모두 충족하면 에러메시지 사라짐 struct Person: FullyNames { var fullName: String func printFullName() { print(fullName) } }
// 2. initialize 요구 // 특정 프로토콜을 재택한 타입이 생성자도 준수하도록 요구 가능 protocol SomeProtocol { init(someParameter: Int) } protocol SomeProtocol2 { init() } // struct에서는 생성자 요구사항을 준수할때 required 식별자 필요X // class에서는 생성자 요구사항을 준수할때 required 식별자가 필요 class SomeClass: SomeProtocol2 { required init() { } }
< extension >
- swift의 강력한 기능 중 하나
- 기존의 클래스, 구조체, 열거형, 프로토콜에 새로운 기능을 추가하는 기능
1) 타입 프로퍼티 / 연산 인스턴스 프로퍼티
2) 타입 메서드 / 인스턴스 메서드
3) 이니셜라이저
4) 서브스크립트
5) 중첩 타입
6) 특정 프로토콜을 준수하도록 기능 추가
* 새로운 기능을 추가할수는 있으나 기존 기능을 오버라이딩 하는건 불가
(저장 프로퍼티나 감시자는 추가 X)
// 예시1) int형 타입에 연산 프로퍼티 추가 extension Int { var isEven: Bool { // 짝수인지 판단 return self % 2 == 0 } var isOdd: Bool { return self % 2 == 1 } } var number = 3 number.isOdd // true number.isEven // false // 예시2) 메소드 기능 추가 : 문자열을 int형으로 바꿔주는 기능 extension String { func convertToInt() -> Int? { return Int(self) } } var string = "0" string.convertToInt() // "0"문자열이 int형 0 으로 변환됨
< 열거형 >
- 연관성이 있는 값들을 모아놓은 것
(ex. 봄/여름/가을/겨울 -> 계절)
enum CompassPoint { case north case south case east case west // case north, south, east, west 이렇게도 사용 가능 } var direction = CompassPoint.east // 값 변경 시 .만 입력해도 관련 변수들 등장 // 컴파일러가 direction변수는 CompassPoint열거형 타입이라는 것을 추론하기 때문 // 열거형 이름을 생략하고 내부 항목 이름만으로도 사용 가능 direction = .west // 활용예시) switch함수 switch direction { case .north: print("north") case .south: print("south") case .east: print("east") case .west: print("west") }
// 각 항목이 특정 타입의 값을 원시값으로 갖도록 지정 가능 // CompassPoint2 열거형의 각 항목이 string형의 초기값을 갖도록 지정 enum CompassPoint2: String { case north = "북" case south = "남" case east = "동" case west = "서" } var direction2 = CompassPoint2.north // 원시값을 가져올땐 rawValue사용 switch direction2 { case .north: print(direction2.rawValue) case .south: print(direction2.rawValue) case .east: print(direction2.rawValue) case .west: print(direction2.rawValue) }
// 각 항목이 특정 타입의 값을 원시값으로 갖도록 지정 가능 // CompassPoint2 열거형의 각 항목이 string형의 초기값을 갖도록 지정 enum CompassPoint2: String { case north = "북" case south = "남" case east = "동" case west = "서" } // 1) 원시값을 가지고 열거형 반환하기 let direction3 = CompassPoint2(rawValue: "남") // direction3 상수에 south가 들어감 // 2) 열거형 항목에 연관값 추가 enum PhoneError { case unKnown case batteryLow(String) } let error = PhoneError.batteryLow("배터리가 곧 방전됨") // batteryLow("배터리가 곧 방전됨") 출력 // 3) 연관값만 추출하려면 switch문 활용 switch error { case .batteryLow(let message): print(message) case .unKnown: print("알 수 없는 에러입니다.") } // "배터리가 곧 방전됨" 만 출력됨
< 옵셔널 체이닝 >
옵셔널에 속해 있는 nil 일지도 모르는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정
- 옵셔널에 값이 있으면 프로퍼티, 메소드 등 호출 O
- 옵셔널이 nil이면 프로퍼티, 메소드는 nil 반환
struct Developer { let name: String } struct Company { let name: String let developer: Developer? // 옵셔널값 } var company = Company(name:"gunter", developer: nil) print(company.developer) // nil 출력 var developer = Developer(name: "yang") var company2 = Company(name: "gunter", developer: developer) print(company2.developer) // optional에 감싸진 developer 출력 print(company2.developer.name) // 에러 발생: optional에 감싸져있기 때문에 내부프로퍼티 name에 접근 불가 -> 옵셔널을 벗겨야함(옵셔널바인딩 or 옵셔널 체이닝) // 옵셔널 바인딩 말고 옵셔널 체이닝을 통해 값 벗겨내기 print(company2.developer?.name) // optional에 감싸진 yang print(company2.developer!.name) // yang (강제언래핑 하므로)
< try catch 문 >
- 프로그램 내에서 에러가 발생한 상황에 대해 대응하고 복구하는 과정
- swift에서는 런타임 에러 발생한 경우 이를 처리하기 위한 4가지 일급클래스를 제공
1) 발생(throwing)
2) 감지(catching)
3) 전파(propagation)
4) 조작(manipulating)
- swift에서 error는 Error프로토콜을 따르는 타입의 값으로 표현됨
- Error프로토콜은 요구사항이 없는 빈 프로토콜이지만 오류를 표현하기 위해서는 이 프로토콜을 채택해야 함
// 오류가 발생했을 때 정상적으로 실행할 수 없다는 것을 나타내기 위해 throw 구문 사용 // 에러가 발생할 것 같은 지점에 throw를 던져 에러를 발생시킴 throw PhoneError.batteryLow(batteryLevel: 20)
- 오류 처리 방법
1) 함수에서 발생한 오류를 해당 함수를 호출한 코드에 전파
2) do-catch 구문
3) try?를 통해 오류를 optional값 으로 처리
4) 오류가 발생하지 않을것이라고 확신
// 오류가 던져진 것에 대비하여, 던져진 오류를 처리하기 위한 대비 코드 // 오류 처리 4가지 방법 // 1) 함수에서 발생한 오류를 해당 함수를 호출한 코드에 전파 // 오류가 발생할 수 있음을 나타내기 위해 함수(매개변수) 뒤에 throws func checkPhoneBatteryStatus(batteryLevel: Int) throws -> String { // 오류가 throwing 되면 String 변수 반환 guard batteryLevel != -1 else { throw PhoneError.unknown } guard batteryLevel <= 20 else { throw PhoneError.batteryLow(batteryLevel: 20)} return "배터리 상태가 정상입니다." }
// 2) do-catch문으로 에러를 처리 /* do { try 오류 발생 가능 코드 } catch 오류 패턴 { 처리 코드 } */ // checkPhoneBatteryStatus메소드가 오류를 발생시켜도 catch문이 오류를 잡아 적절한 예외 처리 가능 do { try checkPhoneBatteryStatus(batteryLevel: -1) } catch PhoneError.unknown { print("알 수 없는 에러입니다.") } catch PhoneError.batteryLow(let batteryLevel) { print("배터리 전원 부족, 남은 배터리: \(batteryLevel)") } catch { print("그 외 오류 발생: \(error)") }
// 2) try? 옵셔널 값 변경 let status = try? checkPhoneBatteryStatus(batteryLevel: -1) print(status) // 메소드가 unknown을 반환하므로 status는 nil // 메소드가 에러를 던져주지 않는다면 optional값이 출력됨 let status2 = try? checkPhoneBatteryStatus(batteryLevel: 30) print(status2) // Optional("배터리 상태가 정상입니다.") // try! : 오류가 절대 발생하지 않을것이라고 확신할때 // 메소드가 오류를 발생하면 런타임에러, 프로그램 종료되므로 유의해서 사용 let status3 = try! checkPhoneBatteryStatus(batteryLevel: 30)
< 클로저 >
- 코드에서 전달 및 사용할 수 있는 독립 기능 블록이며, 일급 객체의 역할을 할 수 있음
* 일급객체란? 전달 인자로 보낼 수 있고, 변수/상수 등으로 저장하거나 전달할 수 있으며 함수의 반환값이 될 수도 있음
- 보통 unnamed closure를 칭함
- unnamed라서 func표현식 사용
- closure head와 closure body를 in 키워드로 구분
let hello = { () -> () in print("hello") } // 클로저 호출 hello() let hello2 = { (name: String) -> String in return "Hello, \(name)" } // 전달인자 레이블을 넣지 않고 바로 파라미터 값을 전달해야함 hello2(name: "Gunter") // 에러 hello2("Gunter") // 클로저를 함수 파라미터로 전달 func doSomething(closure:() -> ()) { closure() } doSomething(closure: { () -> () in print("hello") }) // 클로저를 함수 반환값으로 사용 func doSomething2() -> () -> () { return { () -> () in print("hello4") } } doSomething2()()
- 클로저를 간단하게 표현하는 법
// 후행 클로저를 통해 읽기 쉽게 변환 // int형 3개를 매개변수로 받고 int형을 반환하는 클로저 func doSomething(closure: (Int, Int, Int) -> Int) { closure(1,2,3) } doSomething(closure: { (a, b, c) in return a+b+c }) doSomething(closure: { return $0+$1+$2 }) // 단일 return문은 return도 생략 가능 doSomething() { $0+$1+$2 } // 단 하나의 클로저만 매개변수로 전달하면 ()도 생략 가능 doSomething { $0+$1+$2 }
< 고차함수 >
swift가 일급객체 함수라서 가능한 기능 중 하나
- 다른 함수를 전달 인자로 받거나, 함수 실행 결과를 함수로 반환하는 함수
- map, filter, reduce
// 1) map : 컨테이너 내부의 기존 데이터를 변형하여 새로운 컨테이너 생성 let numbers = [0,1,2,3] let mapArray = numbers.map { (number) -> Int in return number * 2 } print(mapArray) // [0,2,4,6] // 2) filter : 컨테이너 내부의 데이터를 걸러서 새로운 컨테이너로 추출 let intArray = [10,5,20,13,4] let filterArray = intArray.filter{ $0 > 5 } print(filterArray) // [10,20,13] // 3) reduce : 컨테이너 내부 데이터를 하나로 통합 // result: 누적 값, element: 각 요소 let someArray = [1,2,3,4,5] let reduceResult = someArray.reduce(0) { // 괄호안은 result의 초기값 (result: Int, element: Int) -> Int in print("\(result) + \(element)") return result + element } /* 0+1 1+2 3+3 6+4 10+5
'iOS > Swift 기초문법' 카테고리의 다른 글
Swift - 일급객체 (0) 2022.03.25 Swift 문법 기초 (0) 2022.03.25 - assert문