ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

    댓글

Designed by Tistory.