16 minute read

  • 변수, 상수 선언
    1. Kotlin : var, val
    2. Swift : var, let
  • 상수는 선언과 동시에 초기화 해주는 것이 기본
  • 변수는 선언과 초기화를 분리가능, 분리할거면 선언할 때 자료형을 명시해야함.
  • 변수 선언과 초기화를 동시에 할 때는 타입 추론기에 의해 자료형 명시 하지 않아도 괜찮음.

  • null : Kotlin과 달리 nil이라는 키워드를 사용. null과 같이 값이 없음을 상징하는 특수 값. null은 진짜 없는거고 nil은 없는 값을 표현하는 있는 값임.

  • 네이밍 규칙
    1. Swift에서 한글, 특문, 이모티콘도 변수명에 사용가능 하나 가독성을 위해 알파벳, 숫자, 언더바만 사용할 것
    2. 연산자, 공백 사용 불가
    3. 예약어 사용 불가
    4. 숫자로 첫 글자는 불가
  • 자료형
    1. Int : 4byte로 고정된 코틀린과 다르게 CPU의 비트 수에 따라 크기가 결정됨 이 컴퓨터가 64비트이므로 Int는 거기에 맞춰 64비트의 크기를 가짐. Int8, Int16 등등 고정된 크기를 가진 서브타입이 있음. 코틀린에는 이런거 없음. SignedInteger를 구현한 구조체. 반면에 코틀린의 Int는 Number를 구현함.
    2. UInt : 부호없는 정수. 원리는 Int와 유사하므로 min이 0, max는 Int의 2배. 코틀린에도 있음
    3. Double, Float : 각각 64비트, 32비트 부동소수점 실수. 서브타입인 Float64는 그냥 Double을 의미
    4. Bool : true, false. 코틀린의 Boolean
    5. String : 문자열.
    6. Charactor : 단일 문자. String과 마찬가지로 큰 따옴표 사용하므로(더블 쿼우팅) 타입 어노테이션 명시. Kotlin에서는 작은 따옴표를 여전히 유지함
  • 타입 추론 : 타입 추론기가 초기화 값을 보고 알아서 타입을 유추해 주는 것. 선언과 초기화를 분리하거나, 기본적으로 정해지는 타입이 아닌 다른 타입으로 사용해야 하는 경우는 직업 타입 어노테이션을 지정해 줘야 함. Kotlin, Swift 모두 해당.

  • 문자열 템플릿 : String에 다른 타입의 값을 끼워 사용하려는 경우 Kotlin : “name : ${name}” Swift : “name : (name)”

  • 기본자료형의 타입 변환 : 타입별 생성자에 변환 할 객체를 전달해 새 객체를 만듬. val age : Int = 29 val ageStr : String = String(age) Kotlin은 각 자료형마다 다른 자료형으로 변환하기 위한 toXX()메서드가 구현되어 있음.

  • 멀티라인 쿼우팅 : 멀티 라인의 문자열을 +연산과 리턴 피드없이 평문처럼 사용할 수 있도록 한 것. “”” 내용 “”” 모양으로 사용(트리플 쿼우팅). Kotlin에도 해당함.

  • 연산자
    1. 산술 : +, -, *, /, %
    2. 비교 : <, >, <=, >=, ==, !=
    3. 논리 : !, &&,  
    4. 범위 : …(닫힌 범위, 코틀린은 ..), ..<(반 닫힌 범위, 코틀린의 until키워드)
    5. 대입 : =, +=, -=, *=, /=, %=, «=, »=, &=, ^=, |= Kotlin과 달리 Swift는 ++, –를 사용할 수 없음
  • 반복, 제어문 : 조건문에 괄호는 생략 가능
    1. for i in 1..<5, 루프 상수 i는 사용하지 않을 시 언더바로 대체할 수 있음. 코틀린에서는 for의 뒷부분은 괄호 안에 넣어야 함.
    2. while, repeat while(자바의 do while임, 코틀린에는 없고 동명의 인라인함수가 있음. 내부적으로는 그냥 for문임)
    3. if, else if, else
    4. guard 조건식 else {…} : 조건식이 true가 아닐때의 블럭을 실행한다. 주로 심각한 오류를 사전 방지하기 위해 필터링 용으로 사용함. kotlin에는 없음. ex) guard 조건문 else {…}
    5. #available구문 : OS버전별로 구문을 분리해야 할 때 조건식에 넣어서 사용한다. if #available(iOS 9, OSX 10.10, *) {…} else {…} 형식으로 사용 OS이름과 버전은 상수이며, 마지막에 * 붙여야 함. 안드로이드에서의 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)과 같은 의미라고 보면 됨.
    6. switch case : Kotlin에서는 when문으로 대체 됨. switch를 사용할 수 없음. break문 없이도 가장 먼저 조건에 맞는 문을 실행한 수 중지 됨. 여기까지는 Kotlin의 when문도 그러함. case 블록이 비어있으면 안되며, 굳이 다음 case로 내려가려면 fallthrough키워드를 사용해야 함. case에 모든 경우의 수가 들어가야 하며, 그렇지 못하다면 default문을 만들워 줘야함. 안그러면 switch fail case 1: case 2:… 대신 case 1, 2…이렇게 표기, Kotlin의 when문은 case키워드를 사용하지 않음 조건에 들어갈 변수가 튜플이면 그 중 일부만 일치하더라도 case문이 실행 됨. 일치하지 않는 부분은 변수나 상수로 사용가능 ex) var value = (2, 3) switch value { case let (x, 3) : print(x) case let (2, x) : print(x) … } case문에 (0..2, 3)처럼 범위 연산자도 사용 가능하며 where문을 추가해 더 복잡한 패턴으로 확장 가능 ex) var value = (2, 3) switch value { case let (x, y) where x==y : print(“같음”) case let (x, y) where x!=y : print(“다름”) … }
    7. fallthrough : switch case문에서 조건에 일치한 case문을 실행한 후 아래로 계속 진행
    8. return : 반환값과 함께 종료
    9. break
    10. continue
  • 구문 레이블 : 조건문이나 반복문이 여러겹 중첩되어 있는 경우 break, continue의 중단점을 유연하게 하기 어렵다. 그래서 각 반복문에 레이블을 붙여준 후 중단시 명시해 줌으로써 자유로운 컨트롤을 가능하게 하는 것이다. 코틀린에는 없는 듯. ex) outer : for i in 0…5 { inner : for j in 0…5 { … if (j == 3) { break outer // inner 내에서 사용했지만 outer를 종료 } } }

  • Collections : Array, Set, Tuple, Dictionary
    1. Array : Array과 같이 [Type]으로도 타입을 표현할 수 있다. 코틀린이 List, MutableList로 수정가능, 불가능한 클래스가 분리되어 있고 Array도 같이 존재하는 것과 달리 스위프트는 선언자가 var인지 let인지로 수정가능성을 구별함. 또한 Array와 List의 구별이 없음. 그냥 Array하나. let으로 선언하면 배열 객체는 물론 내용물도 수정할 수 없음. arr[0]과 같이 참조.
      • 생성
        1. var arr = [1, 2, 3]
        2. var arr = Array<타입>()
        3. var arr = 타입
      • 아이템 추가 : append(값이나 배열), insert(아이템, at:인덱스)
      • 부분배열 : arr[2…5]
      • 범위 인덱스를 이용해 배열 수정시, 기존 배열에서 범위만큼의 원소들이 사라지고 새로운 배열이 들어감. 이 때 수정 전 원소의 갯수와 수정 후 원소의 갯수가 달라도 가능함. ex) var arr = [1, 2, 3, 4, 5] arr[1…2] = [100, 200, 300, 400] 1~2번 자리에 값 4개가 끼어듬
    2. Set : 같은 타입의 중복을 허용하지 않음. 순서 없음. 해시를 이용.
      • 생성
        1. var set : Set = [1, 2, 3]
        2. var set = Set<타입>()
      • 아이템 추가 : insert
      • 아이템 삭제 : remove, 삭제 실패시 nil반환
      • 집합 연산
        1. set1.intersection(set2) : 교집합
        2. symmectricDifference : 배타적 합집합
        3. union : 합집합
        4. subtract : 차집합
          • 1,2,3은 새로운 Set객체를 만들어 반환하고 4는 기존의 배열에서 제거한 후 반환
    3. Tuple : 여러가지 타입을 한 번에 저장할 수 있는 컬렉션. 일단 선언되면 변경 불가.
      • var tuple : (Int, String,..) = (아이템1, 아이템2,…)
      • tuple[1]가 아닌 tuple.1과 같이 인덱스 사용
      • 원소 하나짜리 투플은 그 원소의 기본 자료형이 됨
      • 이걸 이용해 let (a,b,c,d,e) = (“사”, “랑”, “합”, “니”, “다”) 이렇게 바인딩도 가능
      • return 받고싶은 값이 여러개일 때 사용가능
      • 바인딩 시 받고싶지 않은 변수는 언더바처리하면 바인딩하지 않음(a,b,,,e)
      • Swift의 튜플은 갯수 제한이 없는데 비해 Kotlin에서는 같은 용도의 클래스인 Pair, Triple을 사용할 수 있다. 두개, 세개를 저장할 수 있는 데 네 개 부터는 지원하지 않는다.
      • 자주 사용할 만한 튜플을 typealias키워드를 이용해 사전에 정의해 둘 수 있다. typealias infoResult = (Int, Character, String) 함수의 리턴 타입등에 사용할 수 있음. 코틀린의 data class하고 비슷한 느낌인 듯.
    4. Dictionary : 해시맵, 기본적인 순서는 없으나 Key에는 순서가 있음, Key는 Hashable프로토콜을 구현해야함.
      • 생성
        1. var dic = [“kang” : 4, “Kim” : 9, “Park” : 10]
        2. var dic = Dictionary<타입, 타입>()
        3. var dic = String : Int
        4. var dic = [:]
      • 삽입, 수정 : dic[키] = 값, updateValue(값, forKey : 키)
      • 삭제 : updateValue(nil, forKey : 키), removeValue(forKey : 키)
      • 값을 꺼낼 때 있는 없는 키로 호출할 가능성이 있으므로 Swift에서는 Optional이라는 특수한 값으로 반환함.
      • 순회 : index를 사용할 수 없으므로 이터레이터 방식으로 사용, 각 원소는 (키, 값) 튜플로 떨어짐 for (key, value) in dic {…}
      • Swift가 Key:Value 모양으로, Kotlin이 Key to Value모양으로 초기화
  • Optional : 코틀린의 nullable타입과 같음. nil이 가능한 값임을 나타내는 타입. Kotlin과 마찬가지로 null check의 번거로움을 해결하기 위함.
  • 표기법은 코틀린과 같이 “타입명?”. 코틀린과 달리 사용하려면 옵셔널을 해제(Optional Unwrapping)해 주어야 한다.
  • 옵셔널 타입에 값을 대입할 때는 굳이 Wrapping해줄 필요는 없음.
  • 옵셔널 해제법
    1. 옵셔널 바인딩. if let intValue = Int(str) {nil이 아니면 실행} else {nil이면 실행} (여기서 Int(str)구문의 반환값은 Int?이지만 intValue로 대입되면서 해제되고 true를 반환, nil이라면 false를 반환.) if let 구문에 있는 변수가 nil이 아니면 구문 실행. nil이면 else문 실행 코틀린에서의 그냥 null체크와 비슷함. 또는 intValue?.let{…}
    2. guard let intValue = Int(str) else {nil이면 실행}로 nil인 경우를 처리
    3. let intValue = Int(str) ?? 0 코틀린의 val intValue = Int(str) ?: 0과 같은 의미
    4. 강제 해제. !사용. 코틀린의 !!와 같음. nil값으로 인한 오류 가능성이 생김.
    5. 단 비교연산은 해제하지 않더라도 가능.
    6. 타입 어노테이션 뒤에 ?대신 !를 붙이면 옵셔널타입이지만 자동해제시킬 수 있음 이 것은 형식적으로 옵셔널이지만 절대 nil일 가능성이 없을 때만 사용하도록 함. 묵시적 옵셔널 타입이라 칭함.
  • 함수
    • Swift vs Kotlin func swiftStyle(n1 : Int, n2 : Int) -> Int { return n1 + n2 }

      fun kotlinStyle(n1 : Int, n2 : Int) : Int { return n1 + n2 }

  • 함수의 호출 시 swiftStyle(n1 : 1, n2 : 3)과 같이 파라메터 레이블을 붙여줘야 함. Objective-C와의 호환성을 위한 특수한 방식. Objective-C가 메소드 이름과 파라메터 레이블을 합쳐서 메소드를 구분한다는 특성에 맞춰 Swift도 함수 이름 + 파라메터 레이블들을 합쳐서 함수에 식별자를 붙임 위의 함수의 식별자는 swiftStyle(n1:n2:)임 따라서 이렇게도 호출 할 수 있음. swiftStyle(n1:n2:)(10, 20)
  • 파라메터의 이름을 외부에서 접근할 때 쓸 이름, 내부에서 사용할 이름 두 가지를 사용할 수 있다. swiftStyle(major n1 : Int, minor n2 : Int)라고하면 밖에서 호출할 때는 major, minor를, 안에서 사용할 때는 n1, n2를 사용 외부 매개변수명을 사용하기 싫을 때는 외부 매개변수 명을 ‘_‘으로 사용하면 파라메터 레이블을 붙이지 않아도 된다.
  • 가변인자로 func swiftStyle(n1 : Int…) 사용 가능. 코틀린의 vararg키워드와 같음
  • 기본값 설정 가능 : func swiftStyle(n1 : Int = 5, n2 : Int). 기본값을 받지 않는 함수를 자동 생성하나 코틀린에서는 무조건 파라메터 순서대로 써야함.
  • 함수 내부로 전달받은 파라메터는 기본적으로 상수이므로 함수 내부에서 수정 불가. 이는 코틀린과 같음
  • 기본 자료형 파라메터 선언 시 inout키워드를 붙이면 참조에 의한 전달을 할 수 있음. ex) swiftStyle(n1 : inout Int), 호출할 때는 swiftStyle(n1 : &number)처럼 &를 붙여줘야 함. 포인터같은 느낌으로 사용

  • 일급 함수 : 아래 조건을 모두 만족하는 함수
    1. 런타임에 생성이 가능해야 함
    2. 인자값, 반환값으로 함수를 전달 가능해야 함
      • 함수의 타입 파라메터는 코틀린과 같음 (인풋타입) -> (리턴타입)
    3. 변수나 데이터 구조안에 저장 가능해야 함.
    4. 고유한 구별이 가능해야 함.
      • 자바에서 클래스가 일급 객체인 것과 비슷한 원리로 스위프트에서 함수도 일급의 지위를 갖는다. ex) 함수를 변수에 담는 예시 func increment(base: Int) { print(“결과값은 (base)입니다”) } let inc = increment(base:) inc(1) ex) 함수를 반환하는 예시 func desc() -> String { return “this is desc()” }

      func pass() -> () -> String { return desc }

      let p = pass() print(p()) * 함수의 인자값으로 함수를 넣음, 콜백함수와 같은 개념이 된다.

  • 클로저(Closer) : 일회용 익명함수, 람다와 비슷한 개념 {(파라메터) -> 리턴타입 in 실행문 }

    ex) 클로저 함수를 변수에 담아 호출 let f = {() -> Void in print(“안녕”)} f() ex) 클로저 함수를 바로 호출 ({() -> Void in print(“안녕”)})() ex) sort함수에 클로저 함수를 사용한 예시 value.sort(by: {(s1 : Int, s2 : Int) -> Bool in return s1 > s2 }) ex) 리턴 타입 생략 가능, 한줄로 요약 value.sort(by: {(s1 : Int, s2 : Int) in return s1 > s2}) ex) 파라메터 타입 생략 가능 value.sort(by: {(s1, s2) in return s1 > s2}) ex) 파라메터 명, in, return구문 생략 가능 value.sort(by: {$0 > $1})

  • 트레일링 클로저 : 함수의 마지막 인자값이 함수라면 괄호 밖으로 뺄 수 있다. 이 때 인자값이 유일하다면 함수명 뒤의 괄호도 생략 가능. ex) value.sort {$0 > $1}

  • @escaping
    • 함수의 인자값으로 전달 된 클로저는 기본적으로 함수 내에서 직접 실행을 위해서만 사용되어야 한다. 따라서 함수 내부나 외부 어디서든 인자값으로 전달된 클로저는 변수나 상수에 대입될 수 없다. 클로저가 바깥으로 탈출할 수 있기 때문인데 이 것은 메모리 관리 상의 효율을 위해 제약한다.
    • 함수 선언시 함수 인자를 받는 부분에 @escaping를 추가해 주면 이러한 제약이 사라진다. ex) var f: (() -> Void)? func callback(fn: @escaping () -> Void) {f = fn} callback { print(“ㅎㅇ”) } f?()
  • @autoclosure : 함수의 인자 값으로 클로저를 의미하는 전체 구문이 아닌 내용만 ()안에 넣을 수 있도록 함 클로저 구문보다 익숙하고 자연스러운 구문으로 사용하기 위함. ex) func condition(stmt: @autoclosure () -> Bool) { if stmt() == true { print(“트루”) } else { print(“뻘스”) } } condition(stmt: (4 < 2))
  • 클로저로서 함수1에 대입된 함수2는 함수 1이 실행되기 전까지는 실행되지 않는다. ex) addVars(fn: arrs.insert(“AS”, at: 0))라고 해서 arr.insert()가 실행되는게 아님

  • 구조체와 클래스
    • 스위프트에서는 기본 자료형들이 모두 구조체로 이루어 져 있다. 둘 다 카멜 표기법을 사용.
    • 구조체와 클래스의 공통점
      1. 프로퍼티를 가짐.
      2. 메소드를 가짐.
      3. 프로퍼티에 접근할 수 있도록 서브스크립트를 가짐.
      4. 초기화 블록을 가짐.
      5. 확장구문 사용 가능
      6. 프로토콜 구현 가능
    • 클래스만의 특성
      1. 상속 가능
      2. 타입 캐스팅 가능
      3. 소멸화 구문 사용 가능
      4. 참조에 의한 전달(구조체는 값 복사에 의한 전달)
    • 구조체 kotlin의 data class와 비슷하다. 그러나 코틀린의 data class는 결국 클래스이므로 참조에 의한 전달일 수 밖에 없다.
    • let으로 선언한 배열에 값을 추가할 수 없는 이유이기도 하다. Array와 같은 기본 자료형은 구조체이므로 값을 추가하거나 변경하게 되면 완전히 다른 인스턴스가 되어 값이 변하지 않는다는 let의 규칙에 어긋나게 된다.
    • 코틀린과 마찬가지로 new 키워드 없이 인스턴스 생성.
    • 구조체의 기본 초기화 구문
      1. Rect(), 이 경우는 프로퍼티 선언과 동시에 초기화가 되어 있어야 한다.
      2. Rect(x : 10, y : 20, width : 100, height : 150), 멤버와이즈 초기화 방법(기본 제공)
    • 클래스의 기본 초기화 구문 : 멤버와이즈 방식이 제공되지 않는다, 선언과 초기화를 동시에 하거나 옵셔널로 타입으로 선언해야함.
    • 자바의 GC개념과 비슷한 ARC(Auto Reference Counter)가 클래스의 인스턴스를 청소함. ARC객체는 인스턴스를 모니터링하며 어떤 객체가 변수 등으로 할당되면 카운트를 1씩 증가시키고, 할당 종료되면 1씩 줄임. 그러다가 카운트가 0이 되면 메모리에서 해제함.
    • ===, !== : 동일한 인스턴스 여부인지를 판단하는 연산자
    • 구조체를 사용하는 것이 권장되는 경우
      1. 몇 개의 기본 자료형 데이터를 묶는 것이 목적일 때
      2. 상속이 필요없을 때.
      3. 데이터를 전달하는 과정에서 참조보다는 복사하는 것이 합리적일 때
      4. 캡슐화된 원본 데이터를 보존해야 할 때
  • 프로퍼티
    • 저장 프로퍼티 : 입력을 저장, 저장을 출력. 구조체, 클래스의 멤버 필드. 열거형에서는 사용할 수 없음.
      • 클래스의 경우 선언과 동시에 초기화 하거나, 초기화 구문(init메소드, 참고로 코틀리의 this키워드를 스위프트에서는 self키워드로 사용)에서 초기화 하거나(이 단계까지 초기화 안할꺼면 옵셔널로 해야함). 반면 구조체의 경우 멤버와이즈 구문이 초기화 구문 역할을 하기 때문에 옵셔널로 지정하지 않아도 괜찮다.
      • 상수 구조체는 구조체 자체도, 그 프로퍼티도 변경할 수 없는 반면 상수 클래스는 내부의 프로퍼티가 변수라면 변경 가능하다.
      • 지연 저장 프로퍼티 : lazy키워드로 프로퍼티의 초기화를 해당 프로퍼티가 처음 호출 되는 타이밍으로 미룬다. 코틀린의 lateinit var과 비슷하지만 lateinit의 경우 선언문에 초기화 구문을 사용할 수 없고, 처음 접근할 때 직접 초기화 해 줘야 함. 선언문에 지연 초기화를 사용할 때는 val…by lazy{…}로 사용할 수 있음. 코틀린의 lateinit var와 val by lazy는 변수, 상수의 차이도 있지만 lateinit의 경우 약간의 제약이 더 있음 Swift의 lazy는 그냥 var에만 가능한 듯.
      • 클로저를 이용한 저장 프로퍼티 초기화. 초기화시 단 한번만 실행된다. lazy키워드로 클로저의 실행을 미루는 것도 가능. ex) var prop: Int = { return 15 }
    • 연산 프로퍼티 : 값을 저장하지 않고 연산을 통해 get, set. 저장 프로퍼티에 의존적인 경우가 많다. ex) var age: Int { get { return (self.thisYear - self.birth) + 1 } set(age) { … } }
    • 타입 프로퍼티 : 인스턴스가 아닌 클래스나 구조체 자체에 소속된 프로퍼티. Java의 static. kotlin의 companion object static키워드 사용 : 구조체, 클래스에서 저장, 연산 프로퍼티에 모두 사용가능 class키워드 사용 : 클래스의 연산 프로퍼티에만 사용가능, 상속시 오버라이드 가능.
    • 인스턴스 프로퍼티 : 각 인스턴스에 독립적으로 소속된 프로퍼티
    • 프로퍼티 옵저버 : 프로퍼티 값의 변화를 모니터링할 수 있도록 함. 직접 변경하든 시스템이 변경하든 상관없음. willSet은 값이 변경되기 전에, didSet은 변경된 후에 호출됨. 각각 newValue, oldValue사용. ex) var age: Int = 0 { willSet { print(“(newValue)로 변경 됨”) } didSet { print(“(oldValue)에서 변경 됨”) } }
    • 메소드
      • 구조체, 열거형 객체의 프로퍼티를 바꾸는 메소드는 mutating키워드를 붙여 정의해야 한다. ex) mutating func changeValue(newValue : Int)…
      • 구조체와 열거형 객체를 상수로 전달 받으면 mutating메소드를 호출할 수 없다.
      • 클래스의 경우 이러한 제약이 없음.
      • 타입 메소드 : static 메소드. static키워드를 사용하며 class키워드를 사용하면 오버라이드 할 수 있다.
    • 상속, 구현방식은 코틀린과 같음. :키워드를 사용하며 단일 클래스 상속, 다중 인터페이스(스위프트는 프로토콜) 구현
    • 상속시 저장/연산 프로퍼티는 모두 연산 프로퍼티로 오버라이드 되며, 저장 프로퍼티의 경우 get/set을 모두 제공해야 하고 연산 프로퍼티의 경우 최소 부모 클래스가 제공하는 것은 자식 클래스도 제공해야한다.
    • 두 언어 다 is로 타입을 비교하고, as로 타입 캐스팅함.
    • 불확실한 다운 캐스팅시 옵셔널타입을 반환하는 as?나 강제로 캐스팅하는 as!(실패하면 nil)를 이용할 수 있다.
    • 모든 클래스의 부모클래스는 AnyObject이다. 반면에 Any는 더 넓은 범위를 포괄한다. 기본 자료형, 구조체, 열거형, 함수도 포함하는 개념.

    • 옵셔널 체인 : 옵셔널 타입의 인스턴스가 옵셔널 타입의 인스턴스를 멤버로 갖고 있고, 또 옵셔널 타입이 그 멤버로 있고…중첩이 되어있을 경우 이를 모두 옵셔널 해제를 하면 번거로우므로 ?키워드로 연결해 한번에 해제하는 것. ex)a?.b?.c = 1, 이 경우 a와 b는 옵셔널임. b?.c는 b가 nil이 아닌경우에만 c에 접근하는 것. b!.c와는 대조적임.
  • Enum : 열거형 클래스. 요일, 월처럼 사용 가능한 값이 정해져있어야 할 때만 사용한다. 각 멤버들이 enum클래스의 인스턴스인 것 처럼 다룰 수 있다. ex) : Int가 붙어 있으므로 각 멤버들은 Int타입을 가짐. enum DayOfWeek: Int { case SUN, MON, TUE, WED, THU, FRI, SAT } let value: DayOfWeek = .FRI let value = DayOfWeek.FRI
    • case SUN = 100과 같이 각 멤버에 고유의 값을 줄 수 있고 인스턴스의 rawValue로 값에 접근할 수 있다. 값은 어떤 타입도 줄 수 있으며 만약 정수형인데 지정하지 않았다면 0에서 부터 1씩 자동 증가한다.
    • case SUN(Int, String)과 같이 연관 값을 줄 수도 있다.
    • 클래스처럼 연산 프로퍼티와 메소드를 정의할 수 있다.
  • Extension : 이미 존재하는 클래스, 구조체, 열거형 등 객체에 새로운 기능을 추가하여 확장하는 것. 수평 확장
    • 독립적인 객체가 아니고 기존 객체에 기능을 더해주는 것일 뿐임으로 타입으로 사용될 수 없다. ex) 기본적인 Double 구조체를 확장한다. 이후 확장된 Double객체를 사용할 수 있다. extension Double { var km: Double {return self * 1_000.0} var m: Double {return self} } let distance = 42.0.km + 195.0.m
    • 새로운 메소드를 작성할 수도 있다. 기존에 있던 메소드를 오버라이딩하는 것은 불가능.
    • 남용하면 객체의 정체성을 혼란시킬 수 있음. 꼭 필요한 곳에 사용하고, 전체적인 구조를 파악할 수 있는 위치에서 할 것.
    • 점프바에서의 트리구분을 위해 인위적으로 클래스를 extension으로 분리시키는 경우가 있음.
    • 또는 복잡하게 여러 프로토콜이 구현된 클래스를 분리시켜 구현하기 위해 사용.
  • Protocol : 코틀린의 인터페이스 개념. 구현해야할 명세만을 작성한다. 프로퍼티를 작성할 수 있다는 것이 차이.
    • 저장/연산 프로퍼티의 경우 var name: String {get set}과 같이 get/set의 사용가능여부만을 작성해 준다.
    • 메소드의 경우 구현부를 제외한 나머지 부분은 일반 메소드 작성법과 같다. 구현체에서는 내부 프로퍼티는 프로토콜과 다르게 작성 가능하다.
    • 구조체, 열거형의 경우 프로토콜에서 mutating을 명시하지 않은 메소드를 mutating할 수 없다. 따라서 해당 메소드에서 프로퍼티를 변경할 수도 없다.
    • 단 클래스에서는 프로퍼티를 자유롭게 변경 가능하다. 따라서 프로토콜에서 mutating을 붙이지 않는 경우는 프로퍼티 변경을 원치 않던지, 처음부터 클래스를 타겟으로한 프로토콜인 경우이다.
    • 프로토콜에서는 타입 프로퍼티를 선언할 때 class키워드는 사용할 수 없고 static만 사용가능하다. static 프로퍼티를 클래스에서 구현할 때는 class키워드로 바꿔도 된다.
    • 초기화 메소드도 다른 메소드와 같이 명세에 작성된 것과 일치해야하며 기본 제공되는 초기화 메소드라도 일단 프로토콜에 선언되어 있으면 직접 구현해야 한다.
    • 클래스에서 초기화 메소드를 구현할 때는 required를 명시해야 한다.
    • 만약 부모 클래스와 프로토콜 양쪽에서 같은 초기화 클래스를 물려받았다면 override required를 모두 붙여야 한다.
    • 여러 프로토콜을 구현한 구현체의 자료형을 각 프로토콜로 받을 수도 있고, A&B처럼 여러 프로토콜을 합쳐 자료형으로 사용할 수 있다.
    • 프로토콜을 이용해 Delegate패턴을 구현한다. 이는 안드로이드에서 Listener패턴을 구현하는 것과 같다. 객체A가 객체B를 가지고 있고, 기능 중 일부를 B에게 위임한다. B는 맡은 기능을 수행하며 필요한 시점에 A의 메소드를 호출한다. 통신을 위한 프로토콜을 정의하고, A가 이를 구현하고, B는 이 프로토콜을 참조해 A의 기능을 호출하는 것. ex) Car객체가 기능의 일부를 FuelPump에게 위임하고, FuelPump는 Car의 기능을 호출한다. protocol FuelPumpDelegate { func lackFuel() func fullFuel() }

        class FuelPump {
            var maxGage: Double = 100.0
            var delegate: FuelPumpDelegate? = nil
      
            var fuelGage: Double {
                didSet {
                    if oldValue < 10 {
                        self.delegate?.lackFuel?()
                    } else if oldValue == self.maxGage {
                        self.delegate?.fullFuel()
                    }
                }
            }
      
            init(fuelGage: Double = 0) {
                self.fuelGage = fuelGage
            }
      
            func startPump() {
                while (true) {
                    if (self.fuelGage > 0) {
                        self.jetFuel()
                    } else {
                        break
                    }
                }
            }
      
            func jetFuel()  {
                self.fuelGage -= 1
            }
        }
      
        class Car: FuelPumpDelegate {
            var fuelPump = FuelPump(fuelGage: 100)
      
            init() {
                self.fuelPump.delegate = self
            }
      
            func lackFuel() {
      
            }
      
            func fullFuel() {
      
            }
      
            func start() {
                fuelPump.startPump()
            }
        }
      
    • 클래스A가 프로토콜B를 구현하지 않더라도 클래스A의 익스텐션C는 B를 구현할 수 있다.
    • 프로토콜끼리도 상속을 할 수 있다. 이 경우 다중상속이 허용된다.
    • 프로토콜 정의시 이름 뒤에 :class를 붙이면 클래스 전용 프로토콜로 만들 수 있음.
    • 프로토콜 정의 시 optional 키워드를 붙여 필수 구현 사항이 아닌 선택 사항을 만들 수 있다.
    • 이런 프로토콜은 @objc를 표시해야 하는데 이 어노테이션이 붙은 프로토콜은 클래스만 구현할 수 있다. ex) 이 경우 lackFuel()메소드는 필수 구현하지 않아도 된다. 이 메소드를 호출할 때는 옵셔널을 의미하는 ?를 메소드 명 뒤에 붙인다. @objc protocol FuelPumpDelegate { @objc optional func lackFuel() func fullFuel() } … fuelPumlDelegate.lackFuel?()
  • 오류 처리
    • 오류 객체는 Error라는 이름의 프로토콜을 만들어 정의한다. 컴파일러는 Error 프로토콜을 구현한 열거형만을 오류 타입으로 인정하기 때문. Error프로토콜에는 아무 내용도 없어도 되며 enum에서 이를 구현하기만 하면 된다. ex) protocol Error {

        }
      
        enum DateParseError: Error {
            case overSizeString
            case underSizeString
        }
      
    • 오류를 throws해야할 메소드는 메소드 선언문에 throws를 명시해야 한다. 이는 클로저도 마찬가지다. 반환 타입을 명시하는 -> 앞에 작성한다. ex) enum의 프로퍼티를 던진다. func doSomething() throws -> String { … throw DateParseError.overSizeString }
    • 던져진 오류를 잡을 때에는 do try catch를 사용한다. do~catch문의 안에 try를 끼워넣은 모양새 ex) do { try doSomething() } catch DateParseError.overSizeString { … } catch DateParseError.underSizeString { … }
    • throws를 명시한 메소드를 사용할 때 do catch를 사용하지 않으면 오류가 발생한다.
    • try!를 사용하여 오류처리를 하지 않고 강제실행할 수 있지만 이 경우 오류가 발생하면 런타임 에러로 이어진다.

Categories:

Updated: