Ch2. 스위프트 기본 데이터 구조의 활용

  • 스위프트의 표준 라이브러리는 데이터 타입, 컬렉션 타입, 함수와 메소드 그리고 다양한 목적에 부합하는 다수의 프로토콜 등을 제공
  • 2장 주요내용
    • 스위프트의 표준 라이브러리
    • 서브스크립트 구현 방식
    • immutability 데이터 타입의 이해
    • 스위프트와 Objective-C의 상호 관련성
    • 스위프트의 프로토콜 지향 프로그래밍

스위프트 표준 라이브러리의 활용

  • 스위프트의 표준 라이브러리는 스위프트 프로그래밍 언어와 구분되는 별개의 요소이자, 클래스, 구조체, 열거형, 함수, 프로토콜 등 스위프트 언어를 활용하기 위한 핵심 도구이다.

애플이 구조체를 사용하는 이유

  • 스위프트에 정의된 타입 대부분은 구조체(Value Type)

  • 구조체를 이용하여 클래스를 지원하도록 한 이유는

  • 상속, 초기화 해제객체, 레퍼런스 카운팅등 다양한 기능을 제공하는 클래스에 비해 훨씬 제한된 수의 기능을 제공하는 스위프트가 표준 라이브러리의 구성 요소로서 적합하기 때문

  • 또 구조체는 벨류 타입으로서 단 하나의 소유 객체만을 지니며, 새로운 변수에 할당하거나 함수에 전달할 때는 항상 복사해서 사용한다는 점도 중요한 이유

  • 스위프트의 구조체의 주요기능

    • 자동으로 생성되는 멤버 초기화 함수(memberwise initializer)외에, 커스텀 초기화 함수도 사용 가능
    • 메소드를 지닐 수 있음
    • 프로토콜을 구현할 수 있음
  • 아래 조건중 하나라도 해당된다면 클래스보다는 구조체를 사용하는 편이 낫다고 애플 가이드라인은 설명한다.

    • 특정 타입 생성의 가장 중요한 목적이 간단한 몇 개의 값을 캡슐화 하는것인 경우
    • 캠슐화한 값을 구조체의 인스턴스에 전달하거나 할당할 때 참조가 아닌 복사를 할 경우
    • 구조체에 의해 저장되는 프로퍼티를 참조가 아닌 복사를 위한 벨류 타입인 경우
    • 기존의 타입에서 가져온 프로퍼티나 각종 기능을 상속할 필요가 없는 경우

스위프트에서 배열 선언

  • 스위프트의 배열과 Objective-C의 배열의 몇가지 중요한 차이점

    • 반드시 동일 타입의 값만 저장해야 한다는 것
    • 클래스 타입이 있음(스위프트 배열은 generic type collections)
    • 클래스가 아닌 구조체로서 정의됨
  • 스위프트에는 다음과 같은 세가지 유형의 배열이 있음

    • Array
    • ContiguousArray
    • ArraySlice
  • 모든 Array 클래스는 배열에 포함된 배열 요소를 저장하기 위한 메모리 공간을 유지한다.

  • 배열 요소의 타입이 클래스 또는 @objc 프로토콜 타입이 아닌 경우, 배열의 메모리 영역은 인접 블록에 저장된다.

  • 배열 요소의 타입이 클래스 또는 @objc 프로토콜 타입인 경우, 배열의 메모리 영역은 인접 블록에 NSArray의 인스턴스 또는 NSArray의 서브클래스의 인스턴스로 저장된다.

  • 저장하려는 배열 요소가 클래스 또는 @objc 프로토콜인 경우 ContiguousArray 유형을 사용하면 좀더 효율적인 코드를 작성할 수 있다.

  • ContiguousArray가 Array와 다른 점은 Objective-C와의 브릿징을 지원하지 않는다는 것

  • ArraySlice 도 배열요소를 저장할 때 인접 메모리 공간을 사용하며, Objective-C와의 브릿징을 지원하지 않는다.

  • ArraySlice의 가장 큰 특징은 이미 존재하는 또 다른 배열 타입의 일부 그룹을 대표한다는 것

  • Array, ContiguousArray, ArraySlice의 인스턴스를 생성하면 해당 배열 요소를 저장하기 위한 추가 저장공간이 할당된다.

  • 스위프트의 배열은 기하급수적 증가전략을 따르며, 배열에 요소가 추가될 때마다 소진된 배열 용량을 자동으로 증가시킨다.

  • 배열 요소 추가작업을 여러차례 나눠서 반복적으로 진행할 경우, 각각의 추가 작업에는 일정한 시간이 소요된다.

  • 위 예제의 실행결과를 보면 500개의 용량을 예약 했지만 실제 할당된 공간은 그보다 크다는 사실을 알 수 있다.
  • 스위프트가 실행 성능을 고려해서 실제 요청한 양 이상을 할당한 것으로 볼수 있으며, 최소한 예약한 배열 용량만큼은 확보할 수 있음을 알 수 있다.

배열 초기화

  • 스위프트 표준 라이브러리는 세 가지 타입의 배열을 구현하기 위해 네 가지 초기화 메소드를 제공 

배열에 요소 추가 및 업데이트

  • 배열에 새로운 요소를 추가할 때는 append(_:) 메소드를 사용한다.
  • 이 메소드는 배열의 맨 마지막 부분에 새로운 요소를 추가한다.
  • 특정한 배열 인덱스 위치에 요소를 추가하려 할 경우 insert(newElement:at:) 메소드를 사용한다.
  • 이 메소드는 newElement를 인덱스 값이 i인 위치에 삽입한다.
  • 특정 인덱스 위치의 배열 요소를 교체하려 할 경우 서브스크립트 문법을 사용할 수 있다 

배열에서 요소 가져오기 및 삭제

  • 배열에서 특정 요소를 가져오기 위한 방법은 여러가지가 있다.
  • 해당 배열의 인덱스를 알고 있거나 인덱스의 범위를 알고 있는 경우 배열 서브스크립트 기법을 사용할 수 있다. 

딕셔너리 가져오기 및 초기화 하기

  • 딕셔너리는 동일한 데이터 타입이 키와 값 쌍으로 묶여 있는 무순위 컬렉션이며(unordered collection) 순위를 별도로 지정할 수 있는 방법은 없다. 각각의 값은 딕셔너리 내에서 해당 값의 이름표(identifier)와 같은 역할을 하는 키와 연결돼 있다.
  • 딕셔너리의 키 타입은 Hashtable 프로토콜에 부합해야만 한다.

딕셔너리 초기화하기

  • 딕셔너리 역시 배열과 마찬가지로 정식 선언 문법과 단축 선언 문법이 있다. 

키/값 쌍 추가, 변경, 삭제

  • 새로운 키/값 쌍을 추가하러가 기존의 쌍을 업데이트 하려고 할 경우, updateValue(_:forkey:) 메소드 또는 서브스크립트 문법을 사용할 수 있다
  • 혹시 키가 존재하지 않는 경우, 새로운 키/값 쌍이 추가되며, 기존의 새로운 값으로 업데이트 된다.
  • 서브스크립트 문법과 달리 updateValue(_:forkey:)메소드는 교체된 값을 반환하거나 새로운 키/값 쌍이 추가된 경우 nil을 반환한다.
  • 키/값 쌍을 삭제하려면 removeValue(forkey:) 메소드에서 삭제하려는 값의 키를 입력한다.
  • 서브스크립트 문법에서는 키에 nil을 전달하면 해당 대상이 삭제된다.
  • 서브스크립트 문법과 달리 removeValue(forkey:)메소드는 삭제된 값을 반환하거나, 키가 존재하지 않을 경우 nil을 반환한다. 

딕셔너리에서 값 가져오기

  • 서브스크립트 문법을 이용해서 딕셔너리에서 특정 키/값 쌍을 가져올 수 있다.

  • 이때 키는 서브스크립트의 대괄호 속에 넣어서 전달하는데, 해당 키가 딕셔너리 상에 존재하지 않을 경우, 서브스크립트 옵셔널을 반환한다.

  • 이때 옵셔널 바인딩 또는 강제 언래핑을 통해 해당 키/값 쌍을 가져오거나, 해당 키/값 쌍이 없다고 결론 지을수도 있다 

  • 특정 값을 가져오는 대신, 딕셔너리를 반복적으로 순회하며 명시적으로 키/값을 분할해서 사용할 수 있는 (key, value)튜플을 반환하도록 할 수도 있다 -딕셔너리의 출력결과는 데이터의 삽입순서와 일치하지 않을 수 있음. 딕셔너리는 무순위 컬렉션이며, 딕셔너리의 순회에 따라 반환되는 결과물은 반드시 삽입된 순서를 따르지 않는다

  • 딕셔너리의 키 또는 값만을 개별적으로 가져오고 싶다면, 딕셔너리의 keys 프로퍼티 또는 values프로퍼티를 사용

  • 이들 프로퍼티는 컬렉션에 대응하는 LazyMapColletion인스턴스를 반환한다.

  • 이렇게 반환된 딕셔너리 요소는 기본 요소에 포함된 변환 클로저 함수의 호출에 의해 정보를 읽을 때마다 지연처리되고, 이때의 키와 값은 각각 .0 멤버와 .1 멤버가 되어 딕셔너리의 키/값 쌍과 동일한 순서대로 나타난다.

 

  • 딕셔너리는 기본적으로 무순위 컬렉션이지만, 가끔은 딕셔너리를 순회하면서 순위 목록으로 정돈해야 할 경우도 있음
  • 이런경우 전역 메소드인 sort(_:)를 이용 

세트 선언

  • 세트는 서로 중복되지 않고 nil이 포함되지 않은 non-nil 순위를 정의할 수 없는 순위 컬렉션이다.
  • 세트는 형식상 hashable 프로토콜에 부합해야 하며, 스위프트의 모든 기본 타입은 기본적으로 hashable 프로토콜을 따른다. 연관 값을 사영하지 않는 열거형의 case값 역시 기본적으로 hashable 프로토콜을 따른다.
  • 커스텀 타입 저장시에도 해당 타입은 반드시 hashable 프로토콜과 equatable프로토콜에 부합해야만 한다
  • hashable프로토콜은 equatable프로토콜을 상속한 프로토콜이기도 하다.
  • 세트틑 배열에 비해 매우 효율적이며, 데이터 접근 속도 역시 훨씬 빠르다.

세트 초기화

  • 세트의 경우 다른 컬렉션 타입과 달리 그에 포함된 배열 요소의 타입을 스위프트가 추측하지 않으므로 개발자가 직접 Set 타입을 명시적으로 선언해야만 한다. 

세트요소 변경 및 가져오기

  • 세트에 새로운 요소를 추가하려면 insert(_:)메소드를 사용하고
  • 특정요소가 이미 세트에 포함돼 있는지 여부를 확인하려 할 때는 contains(_:)메소드를 사용한다.
  • 삭제하려는 요소의 인스턴스를 알고 있을 때는 remove(_:)메소드를 사용
  • 세트내 특정 요소의 인덱스값을 알고 있다면 remove(at:)메소드를 사용하여 인스턴스를 삭제할수 있다.
  • 세트내 모든 요소를 삭제하려 할 때는 removeAll() 메소드 또는 removeAll(keepCapacity)메소드를 사용할수 있다
  • keepCapacity가 차민 경우 현재의 세트 용량은 감소하지 않는다.

  • 배열이나 딕셔너리와 마찬가지로 for..in 순환문을 이용해서 세트 내 요소를 순회하며 각종 임무를 수행할 수 있다.
  • 스위프트의 세트 타입은 무순위 이지만 sort 메소드를 이용해 정렬가능하다

세트 연산자

  • 세트는 수학에서의 집합 개념을 기반으로 만든 타입
  • 수학의 집합 연산과 같이 두 개 세트의 비교를 위해 다양한 메소드를 제공하고, 두 개 세트의 멤버십 연산과 동등 연산 기법도 제공

세트의 비교연산

  • 스위프트의 세트 타입은 두 개 세트 간의 연산을 위해 합집합, 교집합 연산을 포함 네 개의 연산 메소드를 제공
  • union, formUnion(_:)메소드는 새로운 세트를 만들고, 두개 세트의 합집합으로 원본 세트를 업데이트
  • intersection, formIntersetion(_:) 메소드는 새로운 세트를 만들고 두 개 세트의 교집합으로 원본 세트를 업데이트
  • symmetricDifference, formSymmetricDifference(_:) 메소드는 새로운 세트를 만들고, 두 개 세트의 여집합 요소로 원본 세트를 업데이트
  • subtracting, subtract(_:)메소드는 새로운 세트를 만들고 두 개 세트의 차집합 요소로 원본 세트를 업데이트 

부분 집합 및 동등 연산자

  • 두 개의 세트에 속한 내부 요소가 서로 안전히 같을 경우, 두 세트는 동등하다고 표현한다.
  • 세트는 원래부터 무순위 컬렉션이므로 내부 요소의 순서는 동등 여부 판단에서 별 의미가 없다.
  • 두 개의 세트가 같음을 표현하기 위해 동등 연산자인 == 연산자를 사용
  • 세트에서 다음 메소드도 활용 가능
    • isSubset(of:) : 어떤 세트의 요소가 특정 세트에 모두 포함돼 있는지 확인
    • isStrictSubset(of:) : 어떤 세트의 요소가 특정 세트에 모두 포함돼 있지만, 동등 집합은 아님을 확인
    • isSuperset(of:) : 특정 세트의 모든 요소가 또 다른 세트에 모두 포함돼 있는지 확인
    • isStrictSuperset(of:) : 특정 세트의 모든 요소가 또 다른 세트에 모두 포함돼 있지만, 동등 집합은 아님을 확인
    • isDisjoint(with:) : 두 세트에 공통 요소가 포함돼 있는지 여부를 확인

튜플의 특징

  • 튜플은 스위프트에는 있지만 Objective-C에는 없는 고급타입
  • 튜플은 컬렉션 타입은 아니지만, 컬렉션 타입과 매우 비슷한 특징을 지님
  • 튜플에는 하나 이상의 데이터 타입을 함께 담을수 있으며, 내부 요소들이 배열등 다른 컬렉션 타입과 달리 모두 같은 타입일 필요는 없다.
  • 튜플은 컬렉션이 아니기 때문에 SwquenceType프로토콜에 부합할 필요가 없으며, 내부 요소의 순회는 불가능 하다.
  • 튜플은 한 무리의 데이터를 저장하거나 전달하기 위한 목적으로 사용된다
  • 튜플은 구조체를 사용하지 않고도 함수에서 하나의 값으로 여러타입의 데이터를 반환 해야 하는 경우에 특히 유용

무기명 튜플

  • 튜플은 어떤 숫자 또는 어떤 데이터 타입을 조합해서 만들 수 있다.
  • 이때 타입에 대한 정보는 추측해서 판단하며, 튜플을 입력하면 컴파일러가 스스로 타입을 결정한다.
  • 리터럴 타입에 대한 판단을 컴파일러에게 맡기는 것을 원치 않는다면 직접 명시적으로 타입을 선언하면 된다.
  • 튜플의 개별 요소에 접근할 수 있는 방법은 인덱스 값을 쓰는 방법, 그리고 개별요소를 상수 또는 변수로 분할하는 방법 두가지이다. 

기명 튜플

  • 기명 튜플은 개별 요소에 이름을 붙일 수 있는 튜플이다.

  • 코드를 더 이해하기 쉽게 해주며, 메소드를 통해 튜플을 반환할 때 특정 인덱스 위치에 어떤 값이 있는지 알기 쉽게 도와준다.

  • 기명 튜플 역시 튜플 타입을 명시적으로 선언할 수 있다 

  • 튜플은 메소드를 통해 구조화된 값을 임시로 전달할 때 특히 유용한 타입이다.

  • 메소드에 의해 튜플을 반환할 때는 클래스를 정의할 때 필요한 정보 혹은 여러 타입의 값을 지니고 있는 딕셔너리를 이용할 때 필요한 정보와 같은 것을 추가적으로 전달할 수 있다. 

서브스크립팅 구현

  • 서브스크립트 기법으로도 클래스, 구조체, 그리고 열거형을 모두 정의할 수 있다.
  • 서브스크립트는 컬렉션, 리스트, 시퀀스 타입의 개별요소에 접근할 수 있는 지름길을 제공하며 문법은 좀 더 간소해질 수 있다.
  • 서브스크립트에서는 특정 인덱스를 지정하기 위해 set과 get요소를 사용한다.

서브스크립트 문법

  • 서브스크립트는 하나 혹은 그 이상의 입력 파라미터를 받을 수 있으며, 이때의 파라미터는 서로 다른 타입이어도 무방하고, 반환하는 값 역시 어떤 타입이든 가능하다.
  • 서브스크립트 정의에는 subscript키워드를 사용하며, 읽기전용 속성으로 정의하거나 특정요소에 접근하기 위한 getter 또는 setter를 설정할 수 있다. 

서브스크립트 옵션

  • 클래스와 구조체는 필요한 만큼 많은 서브스크립트를 반환할 수 있으먀, 이와 같은 다중 서브스크립트 지원방식을 서브스크립트 오버로딩이라 부른다.

수정 가능 속성과 수정 불가 속성의 이해

  • 스위프트에서는 타입을 정할 때 수정 가능, 수정 불가능 속성을 별도로 정할 필요가 없다.
  • var 키워드를 사용해서 배열, 세트, 딕셔너리 변수를 만들면, 이들 변수는 기본적으로 수정 가능 객체가 된다.
  • let 키워드를 사용해서 배열, 세트, 딕셔너리 등을 만든다면 이들은 상수인 객체가 된다.

컬렉션의 수정가능 속성

  • 컬렉션 타입으로 작업하려 할 때는 스위프트가 구조체와 클래스에서 수정 가능 속성을 어떻게 처리하는지 미리 알고 있어야 한다.
  • 구조체 인스턴스를 만들거나 이를 상수에 할당할 때는 이들이 변수로 선언됐다 하더라도 해당 인스턴스의 프로퍼티는 수정할 수 없다.
  • 클래스 인스턴스를 생성하고 이를 상수에 할당하면, 해당 인스턴스를 또 다른 변수에는 할당할 수 없지만, 해당 인스턴스의 프로퍼티는 수정 가능하다
  • 컬렉션을 변경할 필요가 없는 경우, 애플은 모든 컬렉션을 생성할 때 수정불가 속성으로 만들것을 권장, 이렇게 하면 컴파일러가 켈렉션과 관련된 코드를 처리할때 성능을 최적화 할 수 있다고 설명함

스위프트와 Objective-C의 상호 관련성

초기화 방식

  • 스위프트에서 Objective-C 프레임 워크를 사용하려면, 다음과 같이 코드 상단에 해당 프레임워크의 이름을 써서 임포트 하면 된다.
  • import Foundation

클래스 인스턴스를 메소드에 전달하는 방법

  • 스위프트 등에서 메소드라는 표현이 일반적이지만 Objective-C에서는 메시지 라는 개념을 사용한다.
  • Objective-C의 명령 실행 체계인 메시지는 리시버, 셀렉터, 파라미터 등의 요소로 구성된다.
  • 그 중 리시버는 메소드가 실행 결과를 받게 될 대상 객체이고 셀렉터는 메소드의 이름이며, 파라미터는 메소드에 전달되 실행될 객체를 의미한다.
  • Objective-C 메시지로 모델을 전달할 때는 보통의 컴파일 시점의 바인딩이 아닌 동적 바인딩 기법을 사용한다. 이렇게 하면 메시지를 미리 구현해 놓지 않고, 런타임에 해당 메시지를 구현 및 실행하는 것이 가능해진다.
  • 런타임에서 특정 객체가 즉각 메시지에 반응할 수 없는 경우에도 상속 연쇄는 해당 객체를 찾을 때까지 기다렸다가 메시지를 전달한다.
  • 하지만 결국 해당 객체를 찾을 수 없을 때는 nil을 반환하며, 이는 컴파일러 설정에서 변경할 수 있다.

스위프트 타입의 호환성

  • 스위프트 1.1부터 사용자가 직접 init메소드를 정의할 수 있는 실패가능 초기화(failable initialzation)기능을 추가
  • 이 패턴을 통해 이전보다 일관성 높은 구조 선언 문법을 제공
  • 초기화 객체 작성과 팩토리 메소드 생성과정의 혼돈과 불필요한 복제 가능성을 크게 줄일 수 있게 됐다.
  • 다음은 NSURLComponents클래스를 이용한 실패 가능 초기화 예제코드

  • 실패가능 초기화 기능은 Objective-C와 관련 프레임 워크를 이용해서 작업할 때 특히 유용
  • 다음은 스위프트에서 만든 타입에 실패 가능 초기화 기능을 사용해서는 안되는 이유를 보여주는 코드

  • 구조체와 클래스를 개발하고자 한다면 다음의 SOLID원칙을 준수해야함

    • 단일 책임 원칙 : 하나의 클래스는 오직 단 하나의 책임만 부담해야 한다.
    • 개방과 폐쇄의 원칙 : 소프트웨어는 확장이라는 측면에서는 개방되어 있어야 하고, 수정이라는 측면에서는 폐쇄되어 있어야 한다.
    • 리스코프 대체 원칙 : 특정 클래스에서 분화돼 나온 클래스는 원본 클래스로 대체 가능해야 한다.
    • 인터페이스 세분화 원칙 : 개별적인 목적에 대응할 수 있는 여러개의 인터페이스가 일반적인 목적에 대응할 수 있는 하나의 인터페이스보다 낫다.
    • 의존성 도치의 원칙 : 구체화가 아닌 추상화를 중시한다.
  • 접근자 함수를 이용해서 이미지 생성 및 NSImage와 관련된 오류를 구조체가 처리할 수 있도록 구현한 초기화 객체코드

컬렉션 클래스 브릿징

  • 스위프트는 파운데이션 컬렉션 타입인 NSArray, NSSet, NSDictionary를 스위프트의 배열, 세트, 딕셔너리 타입으로 브릿징 할 수 있도록 지원한다.

NSArray를 Array로 브릿징

  • NSArray를 파라미터화된 타입으로 브릿징하면 [ObjectType] 형식의 배열이 만들어지고, 별도의 파라미터화된 타입을 지정하지 않으면 [AnyObject]형식의 배열이 만들어진다.
  • [AnyObject]타입으로 브릿징된 NSArray를 활용해야 하는경우, 배열에 다른 타입이 포함되어 있을 때는 이들 인스턴스를 별도로 관리해야한다.
  • 스위프트는 이러한 것을 처리하기 위한 도구를 제공하는데 강제 언래핑과 타입캐스팅 연산자다
  • 만일 [AnyObject]배열에 서로 다른 타입이 포함되어 있는지 알지 못한다면 nil을 반환할 수 있는 타입 캐스팅 연산자를 사용해서 다른 타입의 인스턴스를 안전하게 관리할 수 있다.

NSSet을 set로 브릿징

  • 파라미터화된 타입으로 NSset을 브릿징하면 Set 타입의 세트가 만들어지고, 별도의 파라미터 타입을 지정하지 않고 브릿징 하면 Set타입의 세트가 만들어 진다.
  • 강제언래핑, 타입캐스팅연산자를 이용하여 서로 다른 타입이 포함된 세트를 안전하게 관리할 수 있다.

NSDictionary를 dictionary로 브릿징

  • 파라미터화된 타입으로 NSDictionary를 브릿징하면[ObjectType] 타입의 딕셔너리가 만들어지고, 별도의 파라미터 타입을 지정하지 않고 브릿징하면 [NSObject:AnyObject]딕셔너리가 만들어진다.

스위프트 프로토콜 지향 프로그래밍

명령 전달을 위한 디스패치 기법

  • 스위프트의 프로토콜은 Objective-C 프로토콜의 슈퍼세트이다.
  • Objective-C 에서 모든 메소드는 런타임시 메시지를 이용해서 다이나믹 디스패치 또는 동적 명령 전달 기법을 사용
  • 스위프트에서 메소드는 명령 전달을 위한 다양한 기법을 제공한다.
  • 스위프트는 기본적으로는 특정 클래스에서 사용 가능한 메소드 목록을 담은 vtable기법을 사용하는데 컴파일시 생성된 vtable은 인덱스 값으로 접근할 수 있는 함수 포인터를 포함한다.
  • 애플의 세번째 명령 전달 기법은 정적 디스패치이다.
  • 처리하려는 메소드와 관련된 정보가 충분하지 않을 경우, 컴파일러는 정적으로 명령을 전달하게 된다. 이 방식은 메소드 전체를 직접 호출하거나 해당 메소드를 아예 학제한다.

프로토콜 작성 문법

  • 프로토콜 선언 문법은 클래스, 구조체 선언과 매우 유사하다.

protocol Particle {
    var name: String { get }
    func particalAsImage() -> NSImage
}
  • 프로토콜을 선언할 때는 protocol 키워드 뒤에 프로토콜의 이름을 입력한다. 프로토콜 프로퍼티를 정의할 때는 get 속성인지, set속성인지, 혹은 둘 다인지 구체적으로 밝혀야 한다. 그리고 메소드를 정의할 때는 이름, 파라미터, 반환타입을 명시한다.
  • 메소드에서 구조체의 멤버변수가 변경될 수 있으면 메소드 선언시 mutating 키워드를 추가해야 한다.
  • 프로토콜은 하나 혹은 그 이상의 다른 프로토콜을 상속할 수 있다.
  • 또 여러개의 프로토콜을 프로토콜 컴포지션으로 묶어서 하나처럼 사용할 수 있다. 프로토콜 컴포지션을 만들 때는 여러개의 프로토콜 이름을 & 기호로 연결하면 된다. 하지만 이는 새로운 프로토콜을 정의한 것이 아니며, 한데 묶인 프로토콜의 모든 규칙과 요구사항을 임시로 적용하기 위한 방법일 뿐이다.

타입으로서의 프로토콜

  • 프로토콜을 이용해서 뭔가를 직접 구현하지 않더라도, 타입이 필요한 곳에 프로토콜을 하나의 타입으로서 사용할 수 있다. 대략적인 용도는 다음과 같다.
    • 함수, 메소드, 초기화 객체에서 반환타입 또는 파라미터 타입
    • 배열, 세트, 딕셔너리에서 개별 아이템 타입
    • 변수, 상수, 프로퍼티의 타입

프로토콜 익스텐션

  • 프로토콜 익스텐션을 이용하면 소스 코드 작성자가 아니더라도 기존 프로토콜의 기능을 확장할 수 있다.
  • 익스텐션을 통해 기존 프로토콜에 새로운 메소드, 프로퍼티, 서브스크립트를 추가할 수 있다.

컬렉션에서 활용하기 위한 프로코콜의 검증

Array 리터럴 문법

  • ExpressibleByArrayLiteral 프로토콜은 배열 형식의 문법을 이용해서 구조체, 클래스, 열거형을 초기화할 수 있도록한다.

열거형 배열 만들기

  • Sequence프로토콜과 IteratorProtocol 은 for...in 문법을 이용해서 컬렉션을 순환하는 것과 같은 기능을 제공한다.
  • 이때 사용되는 데이터 타입은 컬렉션의 순회 기능을 사용하기 위해 열거형을 지원해야 한다.

Sequence/IteratorProtocol

  • 이들 두개의 컬렉션 프로토콜은 서로 뗄 수 없는 사이
  • 시퀀스는 일련의 연속된 값을 나타내고, 반복기 Iterator는 시퀀스에서 이들 값을 한번에 하나씩, 순서대로 사용할 수 있는 방법을 제공
  • Sequence 프로토콜에는 단 하나의 규칙이 있는데 모든 시퀀스는 makeIterator() 메소드로 만들 반복기만 지원해야 한다는 것


블로그 이미지

百見 이 不如一打 요 , 百打 가 不如一作 이다.

,

Ch.1 플레이그라운드 살펴보기

데이터 구조의 중요성

  • 데이터 구조는 프로그래밍에 있어 효율적이며, 확장가능하고 유지 보수성 높은 시스템을 만들기 위한 주요요소중 하나로서, 시스템에서 데이터의 공유, 유지, 정렬, 검색등 데이터 활용을 위한 데이터의 체계화 방법이다.

데이터 구조 + 알고리즘 = 프로그램

  • 데이터의 추상화(abstraction)기법은 데이터가 지닌 복잡성을 관리하기 위한 기술이다.
  • 데이터 구조를 디자인할 때 데이터 추상화 기법을 사용하는데, 이는 개발자가 애플리케이션을 만들려고 할 때 내부의 상세한 구현 방식을 몰라도 되도록 하기 위함이다.
  • 내부의 복잡한 구현 방식을 드러내지 않음으로써 개발자는 알고리즘이 제공하는 인터페이스의 활용에 더욱 집중할 수 있으며, 이 때문에 데이터 구조는 프로그램 내부에서 구현하게 된다.

기본적인 데이터 구조

  • 데이터 구조의 가장 근원적인 형태는 사실상 배열과 포인터 두가지 타입이며, 다른 데이터 구조는 여기서 파생된다고 할 수 있다.
    • 인접 데이터 구조(Contiguous Data Structures) : 데이터를 메모리 영역 중 인접한 부분에 저장한다
      • Arrays, Heaps, Matrices, Hash Tables 등이 있다.
    • 연결 데이터 구조(Linked Data Structures) : 서로 명확히 구분되는 메모리 영역을 차지하되, 포인터라는 주소 체계로 연결, 관리되는 구조이다
      • Lists, Trees, Graphs 등이 있다.

인접 데이터 구조

[1] 배열(Array)

  • 일차원배열(선형배열)
    • swift에서 배열의 인덱스 값은 0부터 시작하며, 데이터 간에 순서가 있으먀, 임의로 특정 요소에 접근할 수 있는 데이터 집합의 성질을 지닌다.
  • 다차원배열
    • 행렬은 다차원 배열의 대표적인 형식

[2] 배열 선언

  • var myIntArray: Array = [1,3,5,7,9] / 기본형
  • var myIntArray: [Int] = [1,3,5,7,9] / 축약형
  • var myIntArray = [1,3,5,7,9] / 타입추측
  • var my2Darray =[[Int]] = [[1,2],[10,11],[20,30]] / 다중 배열

[3] 배열 요소 가져오기

6> var myIntArray: [Int] = [1,3,5,7,9]
myIntArray: [Int] = 5 values {
  [0] = 1
  [1] = 3
  [2] = 5
  [3] = 7
  [4] = 9
}
  7> var someNumber = myIntArray[2]
someNumber: Int = 5
  8> for elemebt in myIntArray {
  9.     print(elemebt)
 10. }
1
3
5
7
9
 11> var someSubset = myIntArray[2...4]
someSubset: ArraySlice<Int> = 3 values {
  [2] = 5
  [3] = 7
  [4] = 9
}

12> var my2dArray: [[Int]] = [[1,2],[10,11],[20,30]]
my2dArray: [[Int]] = 3 values {
  [0] = 2 values {
    [0] = 1
    [1] = 2
  }
  [1] = 2 values {
    [0] = 10
    [1] = 11
  }
  [2] = 2 values {
    [0] = 20
    [1] = 30
  }
}
 13> var element = my2dArray[0][0]
element: Int = 1
 14> element = my2dArray[1][1]
 15> print(element)
11

[4] 배열 요소 추가

  • 기존 배열의 맨 끝 부분에 요소를 추가하는 방법
16> var myIntArray = [1,3,5,7,9]
myIntArray: [Int] = 5 values {
  [0] = 1
  [1] = 3
  [2] = 5
  [3] = 7
  [4] = 9
}
 17> myIntArray.append(10)
 18> print(myIntArray)
[1, 3, 5, 7, 9, 10]
  • 기존 배열의 특정 인덱스 위치에 요소를 삽입하는 방법
19> var myIntArray: [Int] = [1,3,5,7,9]
myIntArray: [Int] = 5 values {
  [0] = 1
  [1] = 3
  [2] = 5
  [3] = 7
  [4] = 9
}
 20> myIntArray.insert(4, at:2)
 21> print(myIntArray)
[1, 3, 4, 5, 7, 9]

[5] 배열 요소 삭제

  • 배열의 맨 끝 부분에 있는 요소를 삭제 하는 방법
22> var myIntArray: [Int] = [1,3]
myIntArray: [Int] = 2 values {
  [0] = 1
  [1] = 3
}
 23> myIntArray.removeLast()
$R2: Int = 3
 24> print(myIntArray)
[1]
  • 특정 인덱스 위치의 요소를 삭제하는 방법
25> var myIntArray: [Int] = [1,3,5,7,9]
myIntArray: [Int] = 5 values {
  [0] = 1
  [1] = 3
  [2] = 5
  [3] = 7
  [4] = 9
}
 26> myIntArray.remove(at: 3)
$R3: Int = 7
 27> print(myIntArray)
[1, 3, 5, 9]

연결 데이터 구조

  • 연결 데이터 구조는 데이터 타입과 이를 다른 데이터와 묶어주는 포인터로 구성된다. 여기서 포인터(pointer)란 메모리상의 위치 주소를 의미한다.
  • 스위프트는 직접적으로 포인터에 접근하지 않으먀, 포인터를 활용할 수 있는 별도의 추상 체계를 제공한다.
  • 연결 리스트(linked list)
    • 일련의 노드로 구성되며, 이들 노드는 링크 필드를 통해 서로 연결되어 있다.
    • 가장 간단한 형태의 연결 리스트는 데이터와 다음 노드에 연결할 수 있는 레퍼런스(또는 링크)정보를 포함한다.
    • 좀 더 복잡한 형태의 경우, 추가 링크 정보를 통해 연결된 데이터에서 앞 또는 뒤로 이동할 수 있다.
    • 연결 리스트에서 추가 노드를 삽입하거나 삭제하는 일은 매우 간단하다.

단일 연결 리스트

28> class LinkedList<T> {
 29.     var item: T?
 30.     var next: LinkedList<T>?
 31. }
  • 자세한 연결 리스트 구현은 3장으로..

데이터 구조의 종류와 단점

알고리즘 개요

  • 대부분의 데이터 구조에서 다음과 같은 내용은 필수적으로 파악하고 있어야 한다.

    • 새로운 데이터 아이템을 삽입하는 방법
    • 데이터 아이템을 삭제하는 방법
    • 특정 데이터 아이템을 찾는 방법
    • 모든 데이터 아이템을 순회하는 방법
    • 데이터 아이템을 정렬하는 방법

스위프트에서의 데이터 타입

벨류 타입과 레퍼런스 타입

  • 스위프트의 기본 데이터 타입은 벨류 타입과 레퍼런스 타입 두가지이다.
  • 벨류 타입은 오직 하나의 소유 객체만을 지니며, 해당 타입의 데이터가 변수 또는 상수에 할당됐을 때 혹은 함수에 전달됐을 때, 지니고 있던 값을 복사한다
  • 벨류 타입에는 다시 구조체와 열거형, 두 가지 유형이 있으며, 스위프트의 모든 데이터 타입은 기본적으로 구조체이다.
  • 반면, 레퍼런스 타입은 벨류 타입과 달리 값을 복사하지 않고 공유한다.
  • 즉, 레퍼런스 타입은 변수에 할당하거나 함수에 전달할 때 값을 복사해서 제공하는 대신 동일한 인스턴스를 참조값으로 활용한다.
  • 레퍼런스 타입은 여러 개의 소유 객체가 참조라는 방식으로 공유할 수 있다.

기명 타입과 복합 타입

  • 스위프트의 또 다른 데이터 타입 분류 체계는 기명 타입(named type) 과 복합 타입(compound type)이다.
  • 기명 타입은 사용자가 정의할 수 있는 데이터 타입이자, 해당 타입이 정의될 당시 특정한 이름을 부여할 수 있는 타입이다.
  • 기명 타입에는 클래스, 구조체, 열거형, 프로토타입이 있다
  • 사용자 정의 기명 타입 외에 스위프트 라이브러리에는 배열, 딕셔너리, 세트, 옵셔널 값을 나타낼 수 있는 기명 타입이 별도로 마련되어 있다.
  • 또한 기명 타입은 익스텐션 선언을 통해 동작 범위를 확장할 수 있다.
  • 복합 타입은 별도의 이름이 붙여지지 않은 타입이며, 스위프트에서는 function 타입과 tuple 타입 두개의 복합 타입이 정의 되어 있다.

타입 에일리어스

  • 타입 에일리어스는 기존의 타입을 또 다른 이름으로 부를 수 있는 방법을 제공한다
32> typealias TCPPacket = UInt16
 33> var maxTCPPacketSize = TCPPacket.max
maxTCPPacketSize: UInt16 = 65535

스위프트 표준 라이브러리 컬렌션 타입

  • 스위프트는 배열, 딕셔너리, 세트 등 세가지의 컬렉션 타입을 제공한다.
  • 이외에도 정식 컬렉션 타입은 아니지만, 복합적인 값을 한꺼번에 묶어서 사용할 수 있는 튜플도 있다.

점근적 분석

데이터 크기 분석 방법

func insertionSort(alist: inout [Int]){
    for i in 1..<alist.count {
        let tmp = alist[i]
        var j = i-1
        while (j >= 0 && alist[j] > tmp) {
            alist[j+1] = alist[j]
            j = j-1
        }
        alist[j+1] = tmp
    }
}

정리


블로그 이미지

百見 이 不如一打 요 , 百打 가 不如一作 이다.

,

MVP 패턴

  • MVC형태에서 View와 Model 간의 커플링을 줄이는 것을 목표로함

View

  • 콘텐츠를 사용자에게 표시
  • 사용자의 행동에 따른 이벤트를 Presenter에게 전달하는 역할

Presenter

  • 사용자 이벤트에 대한 조작의 처리와 Model을 다루고 상태변화를 View에게 알리는 역할
  • Presenter에는 레이아웃 관련 코드가 없고 오직 View의 데이터와 상태를 갱신하는 역할만 가진다.

Model

  • 비지니스로직의 처리
  • Application Data를 다룸

 

그림출처: http://steveyoon.tistory.com/188

과정

  1. 입력이 View에서 처리됨
  2. Presenter은 View와 Model의 instance를 가지고 있음 ( View와 Model 사이의 다리역할을 함)
  3. View 에서 이벤트 발생시 Presenter에게 전달
  4. Presenter은 해당 이벤트에 따른 Model을 조작하고 그 결과를 Binding을 통해 View에게 통보하여 View를 업데이트

MVP의 특징

  • Distribution : Presenter 과 Model의 책임을 거의 분리했고 View는 빈껍데기가 됨.
  • Testability : View의 재사용가능 덕분에 대부분의 비지니스 로직을 테스트 할 수 있다.
  • Easy of Use : MVC에 비해 코드의 양이 많지만 익숙하지 않은 개발자가 쉽게 관리 할 수 있음

MVP의 장단점

  • Presenter을 통해 Model과 View를 완벽하게 분리해주기 때문에 View는 Model을 따로 알고 있지 않아도 된다는 장점을 가지고 있다
  • View와 1대 1의 관계이기 때문에 View와의 의존성이 매우 강함

MVC와의 비교

  • MVC의 경우 View는 사용자 동작에 대한 통신을 Controller와 수행하지만 변경에 대한 알림은 모델로 부터 받는다. 그리고 업데이트된 데이터를 가져오고 자체를 새로 고치기 위해 Model에 대한 세부적인 정보를 활용해야 한다. 이는 View와 Model 간의 커플링을 높이게 된다

MVP 패턴의 구현

  • MVP패턴을 구현하기 위해서는 다음과 같은 구조를 클래스와 인터페이스를 이용해야 한다.

MVP의 다른 버전(MVP Supervising Controller)

  • 이 다른 버전의 MVP는 Presenter(Supervising Controller)가 View를 바꾸고 View로부터 받은 액션을 핸들링 하는동안 View와 Model의 바인딩을 포함함


블로그 이미지

百見 이 不如一打 요 , 百打 가 不如一作 이다.

,

Ch.19 테이블 뷰를 이용한 데이터 목록 구현

  • 테이블 뷰 컨트롤러는 뷰 컨트롤러를 바탕으로 만들어진 특수한 컨트롤러
  • 테이블 뷰 컨트롤러에는 테이블 뷰가 루트뷰로 정의되어있는데 이 테이블뷰는 목록형식의 데이터를 화면에 표현하는데에 사용됨

테이블 뷰의 계층구조
1.테이블 뷰 컨트롤러
2.테이블 뷰
3.테이블 뷰 셀
4.콘텐츠 뷰

  • 테이블 뷰는 목록을 구성하기 위한 객체이므로 여러개의 행을 가질수 있음
  • 이 여러개의 행을 테이블뷰 셀 이라고함
  • 테이블 뷰 셀은 다시 내부에 콘텐츠 뷰를 가짐
  • 실제로 우리가 보는 화면에서 목록 각 행의 내용은 저마다의 콘텐츠가 콘텐츠뷰 내부에 적절히 배치된 결과물
  • 섹션은 하나의 테이블 뷰 내에서 셀들을 그룹으로 묶을 수 있는 단위
  • 테이블 뷰 컨트롤러는 UIKit 프레임워크에 UITableViewController 클래스로 구현되어 있음. 이 클래스는 델리게이트 패턴에 기반한 다양한 메소드가 정의되어 있음. 이들의 역할은 데이터를 이용하여 목록을 구성하고 목록에 대한 사용자와의 상호 반응을 지원하는 것

19.1 프로토타입 셀

  • 프로토타입 셀은 테이블 뷰의 셀을 원하는 대로 쉽게 디자인할 수 있도록 해주는 객체로 테이블 뷰를 설계하는 데에 걸리는 시간과 노력을 대폭 줄여줌
  • 테이블 뷰가 화면에 표현될 때 셀의 구성을 미리 보여주기 위한 가상 틀에 불과함

프로토타입 셀 영역


1.Cell Content : 셀에 표현될 콘텐츠
2.Accessory View : 콘테츠의 부가 정보 여부를 암시

  • 일반적으로 프로토타입 셀을 이용하여 콘텐츠를 표현할 때의 작업 대부분은 Cell Content영역에서 이루어짐
  • 모바일 디바이스에 따라 가로너비가 달라질 경우 Accessory View영역은 너비가 고정값을 유지하는 반면 Cell Content영역은 가변적인 너비값으로 처리됨(가로길이 변화에 따른 주의가 필요함)

Cell Content


그림이나 사진등 이미지 콘텐츠를 표현하는 Image영역과 텍스트를 표현하는 Text영역으로 나누어짐

표준 편집 인터페이스


목록에서 단순히 행을 삭제하기 위한 목적이면 오른쪽의 Reordering Control영역에 ㅈ공되는 삭제버튼만 사용하면 되지만 복합적인 편집 기능을 제공해야 할 때는 위 그림과 같은 표준 인터페이스를 모두 사용함
행을 추가, 삭제하는 기능은 Editing Control여역에 빨간색 마이너스 아이콘이나 초록색 플러스 버트으로 제공되며, Reordering Control 영역에는 셀의 순서를 재배치 하는 컨트롤이 제공됨

19.1.1 프로토타입 셀을 이용한 테이블 뷰 실습

19.2 데이터 소스

  • 일반적으로 테이블 뷰를 이용하여 화면에 콘텐츠를 표현하는 방법에는 두 가지가 있음
  • 정적인 방법과 동적인 방법
  • 정적인 방법은 테이블 뷰 셀 각각을 프로그래밍으로 구성하지 않고 스토리보드에서 직접 구성한 것을 말함
  • 테이블 뷰 컨트롤러를 처음 생성하면 테이블 뷰의 속성은 동적 타입으로 지정되어 있지만 우리가 임의로 정적 타입으로 변경할수 있음
  • Content속성에서 Static Cells가 정적 타입의 테이블 뷰를 의미함
  • 테이블 뷰를 정적 타입으로 바꾸면 기존의 프로토타입 셀은 사라지고 그 자리를 정적인 형식의 셀이 채우게 됨. 이 셀들은 프로토타입 셀과 달리 화면에 직접 표현되므로 내가 필요한 콘텐츠를 직접 올려서 구현할 수 있음
  • 반면 고정되지 않고 매번 갱신되는 내용을 표현하려면 테이블 뷰 셀을 프로그래밍적으로 구성해 주어야 하는데 이를 위해 데이터소스가 필요함
  • 테이블 뷰의 각 행마다 대응할 수 있도록 배열 형태이기만 하면 데이터 소스가 됨
  • 이렇게 만들어진 데이터 소스를 테이블 뷰 각 행에 연결하는 과정을 데이터 바인딩이라 함

19.2.1 데이터 소스 만들기

19.2.2 테이블 뷰와 데이터 소스 연동

  • 데이터 소스와 테이블 뷰를 연동하는 과정은 UITableViewDataSource라는 프로토콜에 의존하여 이루어짐. 테이블 뷰 컨트롤러는 이 프로토콜을 참고하여 지정된 메소드를 호출함으로써 데이터 소스와 테이블 뷰를 연동함. UITableViewController 클래스가 이미 해당 프로토콜을 상속 받고 있음

데이터 소스 연동을 위한 핵심 메소드
테이블 뷰와 데이터 소스를 연동하는 데 필요한 기본 메소드는 다음과 같음
tableView(:numberOfROwsInSection:)
tableView(
:cellForRowAt:)

  • 이 메소드들은 iOS시스템이 필요에 의해 호출하는 메소드들. 일종의 델리게이트 패턴을 따르고 있음. 동작이나 이벤트에 관한 메소드가 아니기 때문에 델리게이트라는 접미어를 븉이지는 않지만 델리게이트 패턴과 동일한 방식으로 동작함
  • 이 메소드들은 이미 UITableView 클래스에서 구현이 되어 있음

tableView(_:numberOfRowsInSection:)
이 메소드는 테이블 뷰가 생성해야 할 행(row)의 개수를 반환. 이 메소드는 iOS시스템이 테이블 뷰를 구성하기 위해 먼저 호출하는 메소드. 이 메소드는 우리가 사용하기 위한 것이 아니라 시스템이 사용하기 위한 메소드임
이 메소드에 의해 테이블 뷰의 행 수가 결정되는 것임

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { }
첫번째 인자값은 이 메소드를 호출한 테이블 뷰 객체에 대한 정보
두번째 인자값은 섹션에 대한 정보

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { }
이 메소드는 각 행이 화면에 표현해야 할 내용을 구성하는데에 사용. 이 메소드가 반환하는 값은 전체 테이블 뷰의 목록이 아니라 하나하나의 개별적인 테이블 셀 객체인데 이는 화면에 표현해야할 목록의 수만큼 이 메소드가 반복적으로 호출된다는 것을 의미함
첫번째 매개변수를 통해 테이블 뷰가 특정되면 두번째 매개변수인 indexPath를 통해 몇번째 행을 구성하기 위한 호출인지를 구분할 수 있음.
IndexPath 객체 타입으로 정의된 이 매개변수는 선택된 행에 대한 관련 속성들을 모두 제공함. 그중에서도 .row는 가장 많이 사용되는 속성으로 행의 번호를 알려주는 역할을 함
0부터 시작하는 이 행 번호는 배열로 이루어진 데이터 소스의 아이템 인덱스와 대부분의 경우 일치하므로 이 속성을 사용하면 데이터 소스의 필요한 부분을 편리하게 읽어 들일 수 있음.

사용자의 액션 처리를 위한 핵심 메소드
tableView(:didSelectRowAt:)
UITableViewDelegate 프로토콜에 정의된 이 메소드는 사용자가 목록중에서 특정 행을 선택 했을때 호출됨
override func tableView(
 tableView: UITableView, didSelectRowAt indexPath: IndexPath) { }
이 메소드는 델리게이트 메소드이기 때문에 앞의 두 메소드처럼 적절한 시점에 맟추어 자동으로 호출됨. 앞의 두 메소드는 테이블 뷰를 화면에 구현할 때 호출되는데 반해 이 메소드는 사용자의 액션이 있을 때 호출됨
첫번째 인자값이 사용자가 터치한 테이블 뷰에 대한 참조값
두번째 인자값이 터치된 행에 대한 정보

메소드 구현 실습

 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.list.count
    }
  • 생성해야 할 행의 개수를 반환 하는 메소드
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let row = self.list[indexPath.row]
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell")!
        
        cell.textLabel?.text = row.title
        
        return cell
        
    }
  • 이 메소드는 개별 행을 만들어내는 역할을 함. 위의 메소드가 반환하는 값만큼 이 메소드가 반복 호출됨. 이 메소드가 한번 호출 될때 마다 하나의 행이 만들어진다고 생각하면 됨
  • 몇번째 행을 구성해야 하는지 알려주기 위해 IndexPath 타입의 객체가 인자값으로 전달됨. 행 번호를 알고자 할 경우 indexPath.row 속성을 사용하면 됨
  • 이 속성은 배열과 마찬가지로 0부터 시작
  • dequeueReusableCell(withIdentifier:) 메소드는 인자값으로 입력받은 아이디를 이용하여 스토리보드에 정의된 프로토타입 셀을 찾고 이를 인스턴스로 생성하여 제공함

개선 및 업그레이드

19.3 커스텀 프로토타입 셀

19.3.1 커스텀 프로토타입 셀 구현하기

  • 커스텀 타입의 프로토 타입 셀에 디자인된 객체를 소스코드에서 읽어오기 위해서 viewWithTag(_:) 메소드를 사용하는데 이때 객체마다 각각의 태그값이 필요
  • 다양한 타입의 뷰 객체(이미지버튼, 스위치버튼 등) 을 모두 메소드 하나로 읽어오는 만큼 반환타입은 UIView 임. 뷰를 상속받은 모든 객체 타입을 포괄할 수 있는 UIView 타입의 객체로 넘겨 받은 다음 필요에 따라 적절한 구체적 타입으로 캐스팅하는것임
  • 입력되지 않은 잘못된 태그값을 인자로 호출할 경우를 대비하여 viewWithTag(_:)메소드의 반환값은 옵셔널 타입으로 정의됨
  • 커스텀 타입의 프로토타입 셀에 디자인된 객체들은 다음의 과정을 통해 소스코드에서 참조 할 수 있음.

19.3.2 커스텀 클래스로 프로토타입 셀의 객체 제어하기

  • 프로토타입 셀에 디자인된 객체를 제어하는 또 다른 방법은 커스텀 클래스를 만들어 사용하는 것
  • 프로토타입 셀 자체를 커스텀 클래스와 연결한 다음, 셀위에 올려진 객체를 아룰렛 변수로 연결해서 참조하는 것
  • 아울렛 변수를 뷰 컨트롤러에 직접 정의하면 셀 내부 객체들이 정적인 객체가 되므로 사용하는데 문제가 생기지만, 프로토타입 셀을 연결한 커스텀 클래스에 아울렛 변수를 정의하면 이는 동적으로 사용할 수 있는 형태의 객체가 되기 때문에 아울렛 변수를 통해 객체를 관리할 수 있음.
  • 따라서 태그 속성 사용시 단점으로 꼽혔던 객체관리 문제나 잘못된 태그값을 호출하는 문제로부터 자유로워 질 수 있으며, 유지보수도 무척 편리해짐
  • 셀 커스텀하기 위한 클래스는 UITableViewCell을 서브클래싱함
  • 프로토타입 셀에 새로운 객체를 추가하고자 할 경우 다음과 같은 순서로 처리

19.3.3 프로토타입 셀에 섬네일 이미지 추가하기

  • 이미지뷰를 구현하는 기본 클래스는 UIImageView
  • 이미지 객체를 화면에 표현해주기 위한 각종 기능과 속성 구현을 담당
  • 이미지뷰는 .image 라는 속성을 가지고 있으며 UIImage타입으로 작성된 이미지 객체를 속성에 대입받아 화면에 표현함.
  • 이미지뷰의 하위 속성으로 정의되어 있는 UIImage는 이미지 데이터를 저장하는 객체
  • 이미지뷰는 이미지를 화면에 표현해주는 기능을 구현한 뷰 이지만 UIImage는 이미지 데이터 자체를 iOS에 맞게 다듬은 객체
  • 이미지를 앱 화면에 표시하려면 먼저 이미지를 담아 UIImage객체를 만들고 이 객체를 다시 UIImageView 객체의 .image속성에 대입하는 과정을 거쳐야 함

var img = UIImage(named: <프로젝트 내 파일 경로>)

  • UIImage(named:) 방식으로 생성한 이미지 객체는 한번 읽어온 이미지를 메모리에 저장해둔 다음 두번째 호출부터는 메모리에 저장된 이미지를 가져옴(캐싱함)
  • 이미지 객체로 인한 메모리 점유가 걱정되는 경우 UIImage(contentsOfFile:)생성자를 사용해 이미지 객체 생성 -> 캐싱되지 않음.

UIImage(contentsOfFile:<프로젝트 내 파일 경로>)

  • 커스텀 클래스를 이용하여 프로토타입 셀을 제어하는 과정정리

19.4 테이블뷰의 행 높이를 결정하는 방식

  • 코코아 터치 프레임워크에서는 기본적으로 테이블 뷰의 행 높이를 결정하는 두가지 방식을 제공
  • 하나는 모두 동일한 높이를 갖는 방식이고 또다른 하나는 각 셀마다 다른 높이를 갖는 방식
  • 내부 콘텐츠에 따라 동적으로 셀의 높이가 늘어나거나 줄어드는 방식으로 셀을 구현하는 방식도 제공됨

19.4.1 tableView(_:estimatedHeightForRowAt:)

  • 이 메소드는 테이블 뷰에서 특정 행의 높이를 설정하고 싶을때 사용하는 메소드
  • UITableViewDelegate 프로토콜에 정의되어 있으며 UITableView 클래스에서 이미구현되어 있으므로 커스텀 클래스에서는 override키워드를 붙여 재정의 하는 방식으로 사용하여야 함.
  • 행의 높이를 결정하는 것은 UITableView객체의 rowHeight속성 -> 테이블 뷰 내의 모든 셀에 공통으로 적용되어 모두 동일한 높이를 갖는 행으로 만들어줌
  • self.tableView.rowHeight = <원하는 행 높이>
  • tableView(_:estimatedHeightForRowAt:) 메소드가 구현되면 UITableView 객체의 rowHeight속성은 더이상 행의 높이값으로서 역할을 하지 못함.
  • 메소드에서 반환하는 값이 높이값으로 대신 사용이 됨
  • 코코아터치 프레임 워크는 행에 대한 정보를 indexPath 매개변수에 담아 tableView(_:estimatedHeightForRowAt:) 메소드를 호출하고 그 결과값을 받아 셀의 높이를 결정함.
  • 이점 : 개별적인 행 높이를 제어 할 수 있음.
  • 이 메소드가 호출될때 두번째 인자값으로 IndexPath 타입의 행 정보가 함께 전달되기 때문에 이를 이용하여 행 정보를 얻고 그에 따라 알맞은 높이값을 반환하면 됨

A ?? B
두개의 물음표로 이어진 ?? 는 nil-Coalescing Operator라는 의미의 연산자
-> 만약 A가 nil이 아닐경우 옵셔널을 해제하고, nil일 경우 대신 B값을 사용하라!
이 연산자를 사용하면 옵셔널 타입이 해제된다
이 연산자의 앞쪽에는 옵셔널 값이, 뒤쪽에는 일반값이 위치한다
이 연산자의 뒤쪽에 위치한 일반 값의 타입은 앞쪽 옵셔널 값에서 옵셔널을 해제한 타입 과 일치해야 한다

  • 옵셔널 타입을 해제하면서 동시에 대체 값을 제공해주는 아주 편리한 연산자임
  • 변수에 옵셔널 타입을 해제한 값을 할당하는 과정에서 해당 값이 nil일 경우를 대비하여 기본 값을 주고자 할 때 매우 유용하게 사용할 수 있음
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell() -> "cell"아이디를 가진 셀을 읽어와 옵셔널을 해제하되, 만약 그값이 nil일 경우 UITableViewCell 인스턴스를 새로 생성한다.

셀프 사이징 셀
estimatedRowHeight 프로퍼티
UITableViewAutomaticDimension 객체
위 두가지만 이해하고 있으면 됨!

  • estimatedRowHeight 프로퍼티는 셀 전체의 높이를 결정하기 전에 임시로 사용할 셀의 높이값을 나타냄 -> 테이블 뷰는 이 값을 바탕으로 아직 내부 사이즈가 결정되지 않은 셀들을 임시배치하고 그 안에 콘텐츠를 구성함
  • UITableViewAutomaticDimension 은 테이블 뷰의 rowHeight속성에 대입되어 높이값이 동적으로 설정될 것을 테이블 뷰에 알려주는 역할 -> 테이블 뷰의 rowHeight속성이 해당 값으로 설정되면 테이블 뷰는 전체 목록이 모두 만들어진 시점에서 셀 내부의 콘텐츠 레이아웃을 계산하고, 그에 따라 셀마다 높이값을 재설정함.
  • 위 코드는 viewWillAppear(_:)메소드와 적절한 시점에 넣어서 구현해 주면 됨


블로그 이미지

百見 이 不如一打 요 , 百打 가 不如一作 이다.

,

Ch.15 화면전환

  • iOS에서 화면을 전환하는 방법에는 크게 두가지가 있음
  • 하나는 소스 코드를 통해 전환하는 방식이고 (동적으로 화면전환)
  • 또 다른 하나는 스토리보드가 제공하는 기능을 이용하여 전환하는 방식(정적으로 화면전환)
  • 동적인 방식은 특정 상황에 대응할 수 있지만 조금 복잡하고 어려움
  • 정적인 방식은 일괄적으로 적용되는 것이라 특정 상황에 대응하기 어렵지만 그만큼 구현하기 쉽다는 장점이 있음

15.1 iOS에서의 화면 전환 개념

  • iOS에서 화면 전환 방식은 분류기준에 따라 크게 4가지로 나누어 볼 수 있음
1. 뷰 컨트롤러의 뷰 위에 다른 뷰를 가져와 바꿔치기 하기
2. 뷰 컨트롤러에서 다른 뷰 컨트롤러를 호출하여 화면 전환하기
3. 내비게이션 컨트롤러를 사용하여 화면 전환하기
4. 화면 전환용 객체 세그웨이를 사용하여 화면 전환하기
  • 위 1은 특수한 상황에서 제한적으로 사용하는 방법임. 일부 뷰 컨트롤러들은 콘텐츠를 직접 배치하여 화면을 보여주는 역할 대신 다른 뷰 컨트롤러를 구조화 하는 역할을 하는데 이때 화면을 구조화하는 방식이 이것임
  • 이같은 뷰 컨트롤러를 컨테이너 뷰 컨트롤러라고 함
  • 1 을 제외한 나머지 대부분의 화면 전환은 모두 뷰 컨트롤러를 호출하는 방식으로 이루어짐. 전환할 화면을 담당하는 뷰 컨트롤러의 인스턴스를 생성하고, 이를 불러들여서 기존의 화면 위에 덮으면 화면이 전환된다는 뜻. 이에 따라 현재의 화면이 다른 화면으로 완전히 교체되는 것이 아니라 현재 화면이 있는 상태에서 그 위에 새로운 화면이 얹어지는 모양새가 됨
  • 이같은 특성 때문에 기존 화면과 새로운 화면 사이에는 서로 참조 관계가 성립함. 화면이 전환되는 방식에 따라 이들은 서로 직접 참조할수 있거나 또는 화면전환을 관리하는 전담 객체를 통해 간접적으로 참조하기도 함

iOS에서 화면 전환은 다음 두가지 특성을 가짐

  1. 다음 화면으로 이동하는 방법과 이전 화면으로 되돌아가는 방법이 다름
  2. 화면 전환방식에 따라 이전 화면으로 되돌아 가는 방법이 다름

15.2 화면 전환 기법1 : 뷰를 이용한 화면 전환

  • 하나의 뷰 컨트롤러 안에 두 개의 루트뷰를 준비한 다음 상태에 따라 뷰를 적절히 교체해 주는 것
  • 이 방식은 하나의 뷰 컨트롤러가 두개의 루트뷰를 관리해야 하므로 그리 좋은 방법은 아님
  • iOS에서는 하나의 뷰 컨트롤러 아래에 하나의 루트뷰를 관리하는 MVC패턴을 기본으로 하는데 위에방식은 이같은 구조를 완전히 거스르는 방식
  • 또 다른 방식은 다른 뷰 컨트롤러에 올려진 루트 뷰를 가져와 표시하는 방식
  • 이 방법 역시 뷰 컨트롤러 내부에 있어야 할 뷰가 다른 뷰 컨트롤러로 옮겨가 버리므로 뷰를 제어할 책임을 지는 컨트롤러가 모호해진다는 단점이 있음

15.3 화면 전환 기법2 : 뷰 컨트롤러 직접 호출에 의한 화면 전환

  • 현재의 뷰 컨트롤러에서 이동할 대상 뷰 컨트롤러를 직접 호출해서 화면을 표시하는 방식

present(<새로운 뷰 컨트롤러 인스턴스>, animated:<애니메이션 여부>)
present(_:animated:completion:)
completion -> 화면 전환이 완전히 끝난후에 호출해주는 역할

  • 프레젠테이션 방식으로 화면을 전환했을 때 iOS시스템은 화면에 표시된 뷰 컨트롤러(VC2)와 표시하고 있는 뷰 컨트롤러(VC1) 사이에 참조할 수 있는 포인터를 생성하여 서로 참조할 수 있게 해줌
  • VC1은 presentedViewController 속성에다 자신이 표시하고 있는 새로운 뷰 컨트롤러의 포인터를 저장하고, 새로운 뷰 컨트롤러인 VC2에는 presentingViewController 속성에다 자신을 표시한 뷰 컨트롤러의 포인터를 저장
  • VC1 에서는 presentViewController속성을 이용하여 VC2를 참조 할 수 있고 VC2에서는 presentingViewController속성을 이용하여 VC1을 참조할 수 있다는 뜻
  • 이전 화면으로 복귀할 때는 다음과 같은 복귀 메소드를 사용함

dismiss(animated:)

  • 이전 화면으로 돌아가는 기능이기 때문에 뷰 컨트롤러의 인스턴스를 인자값으로 받지는 않음. 화면 복귀시 애니메이션을 적용할지 말지를 결정하는 값만 전달해주면 됨

dismiss(animated:completion:)
completion -> 화면 복귀가 완전히 처리되었을 때 실행할 구문을 인자값으로 입력받는 매개변수

  • 두 메소드는 기존화면이 새로운 화면 위로 올라오는 것이 아니라 기존 화면을 덮고 있던 새 화면을 걷어내는 것. 걷어낸 화면의 뷰 컨트롤러 객체는 운영체제에 의해 곧 메모리에서 해제됨
  • iOS에서 화면이 사라지게 처리하는 것은 사라질 화면의 뷰 컨트롤러 자신이 아니라 자신을 띄우고 있는 이전 뷰 컨트롤러임
  • 즉 VC1 이 VC2 를 호출하여 화면에 표시해주었다면 반대로 VC2를 화면에서 사라지게 하는것도 VC1

self.presentingViewController?.dismiss(animated:)

15.3.1 화면전환 실습

//
//  ViewController.swift
//  Scene-Trans01
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    @IBAction func moveNext(_ sender: Any) {
        
        // 여러개의 스토리 보드가 있을경우 이렇게 사용..Main은 스토리보드 파일명
        
        // let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        // let uvc = storyboard.instantiateViewController(withIdentifier: "SecondVC")
        
        
        // let uvc = storyboard!.instantiateViewController(withIdentifier: "SecondVC")
        // self.storyboard 값은 옵셔널 타입, 경우에 따라 nil값이 될수도 있음
        // 이값을 nil검사 없이 바로 ! 연산자를 사용하여 강제 해제하였으므로 만약 self.storyboard 값이 nil이라면 오류가 발생. 이를 옵셔널 체인과 옵셔널 바인딩으로 보강하면 다음과 같음
        
        /* if let uvc = storyboard?.instantiateViewController(withIdentifier: "SecondVC"){
                uvc.modalTransitionStyle = UIModalTransitionStyle.coverVertical
        
                self.present(uvc, animated: true)
            }
        */
        
        //뷰 컨트롤러 인스턴스는 moveNext메소드 전체 실행에서 비어있어서는 안되는 필수조건이기 때문에 guard 조건문으로 필터링
        //안전한 코드를 위해 self.storyboard를 옵셔널 체인으로 처리하여 instantiateViewController(withIdentifier:)메소드를 호출하여 뷰 컨트롤러의 인스턴스를 받아온 다음 옵셔널 타입을 해제하기 위해 상수 uvc에 옵셔널 바인딩, 만약 바인딩에 실패하면 메소드의 실행은 중지됨.
        guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC") else {
            return
        }
        
        // 화면 전환할 때의 애니메이션 타입
        
        uvc.modalTransitionStyle = UIModalTransitionStyle.coverVertical
        
        // 인자값으로 뷰컨트롤러 인스턴스를 넣고 프레젠트 메소드 호출
        
        self.present(uvc, animated: true)
        
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
//
//  SecondViewController.swift
//  Scene-Trans01
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class SecondViewController: UIViewController {

    //self.dismiss가 아님!!
	  //vc2(secondViewController를 사라지게 하는건 vc1 임

    @IBAction func dismiss(_ sender: Any) {
        self.presentingViewController?.dismiss(animated: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

15.4 네비게이션 컨트롤러를 이용한 화면 전환

  • 네비게이션 컨트롤러는 뷰 컨트롤러의 특별한 종류로 계층적인 성격을 띠는 콘텐츠 구조를 관리하기 위한 컨트롤러(내비게이션 바가 내장되어 있음)
  • 이 컨트롤러가 제어하는 모든 뷰 컨트롤러에 내비게이션 바를 생성하는 특징이 있음
  • 루트뷰 컨트롤러는 내비게이션 컨트롤러에 직접 연결된 컨트롤러이므로 화면 UI상단에 내비게이션 바가 표시됨
  • 내비게이션 컨트롤러는 화면에 현재 표시되고 있는 뷰 컨트롤러들을 내비게이션 스택을 이용하여 관리함
  • 내비게이션 컨트롤러 최상위 뷰컨트롤러(마지막컨트롤러), 최하위 컨트롤러(루트뷰 컨트롤러)
  • 최상위 뷰 컨트롤러에 추가할 때는 pushViewController(animated:) -> 새로운화면표시
  • 최상위 뷰 컨트롤러에서 제거할 때는 popViewController(animated:) -> 이전화면으로 돌아올때
//
//  ViewController.swift
//  Scene-Trans02
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func moveByNavi(_ sender: Any) {
        
        // 
        guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVc") else {
            return
        }
        self.navigationController?.pushViewController(uvc, animated: true)
    }
    @IBAction func movePresent(_ sender: Any) {
        guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVc") else {
            return
        }
        self.present(uvc, animated: true)
    }

}
//
//  SecondViewController.swift
//  Scene-Trans02
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class SecondViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func back(_ sender: Any) {
        self.presentingViewController?.dismiss(animated: true)
        
    }
    @IBAction func back2(_ sender: Any) {
        self.navigationController?.popViewController(animated: true)
    }
}

 

15.5 세그웨이를 이용한 화면 전환

  • 세그웨이를 이용하면 뷰 컨트롤러에 대한 정보가 없어도 됨.
  • 또한 뷰 컨트롤러의 객체를 생성할 필요도 없음. 세그웨이가 스토리보드상의 연결정보를 이용하여 대상 뷰 컨트롤러의 인스턴스를 자동으로 만들어줌
  • 출발점이 뷰 컨트롤러 자체인 경우를 메뉴얼세그, 컨트롤이 출발점인 경우를 액션세그 또는 트리거세그라고 나누어 부르기도 함.
  • 메뉴얼세그를 실행하려면 UIKit프레임워크에 정의된 performSegue(withIdentifier:sender:)메소드를 사용

15.5.1 액션 세그웨이

  • 액션세그는 트리거와 세그웨이가 직접 연결된 것을 의미. 터치 또는 클릭 이벤트를 발생시켜 세그웨이를 실행할 수 있는 요소를 말함
  • 액션세그는 화면 전환을 위해 프로그래밍 코드가 필요없고, 스토리보드에 구현된 객체를 트리거로 지정만 하면 되므로 전체적인 구성이 굉장히 단순해짐
  • PresentModally 는 화면전환 메소드 중 present(_:animated:)메소드를 이용한 화면 전환과 같은 기능을 함
  • 즉, 뷰 컨트롤러 자신이 새로운 화면을 불러들이도록 처리하는것
  • 내비게이션 컨트롤러가 추가되어 있는 상태에서 Show 타입의 세그웨이를 생성하면 내비게이션 컨트롤러를 통한 화면 이동이 발생. 즉, 모든 화면 이동의 결과는 내비게이션 컨트롤러의 통제하에 있게됨. 따라서 모든 뷰 컨트롤러에는 내비게이션 바가 추가됨
  • 내비게이션 컨트롤러가 없을 때는 세그웨이를 Show타입으로 생성하였더라도 PresentModally방식으로 실행됨
  • 뷰컨트롤러에 임베디드 인 하여 네비게이션 컨트롤러 추가한후에 새로운 뷰 컨트롤러를 추가하고 세그웨이로 연결한 경우 내비게이션 아이템을 추가해야 타이틀 입력 또는 바버튼을 추가 할 수 있음.

15.5.2 메뉴얼 세그

  • 메뉴얼세그는 해당 이벤트만 발생하면 자동적으로 실행되는 액션세그와 달리 뷰 컨트롤러와 뷰 컨트롤러 사이에 연결되는 수동 실행 세그웨이임.
  • 액션세그는 트리거의 터치에 의해 실행되므로 별도의 처리코드가 전혀 필요없지만, 메뉴얼세그는 트리거 없이 수동으로 실행해야 하므로 소스 코드에서 세그웨이를 실행할 메소드를 호출해야함

performSegue(withIdentifier :<세그웨이 식별자>, sender :<세그웨이 실행 객체>)

  • 두개의 인자값은 세그웨이가 여러 개일 경우를 대비한 세그웨이 식별자와 세그웨이를 실행하는 객체정보임.
  • 필요한 시점에서 세그웨이 식별자를 통해 특정 세그웨이를 지정하고 위 메소드를 호출하면 세그웨이가 실행되면서 화면이 전환되는 구조

15.5.3 Unwind - 화면 복귀

  • 새로운 화면으로 전환하는 것은 Wind라고 한다면 Unwind는 wind작업을 해제 한다는 의미. 다시 말해 새로운 화면을 해제하고 본래의 화면으로 돌아간다는 해석이 됨
  • 세그웨이는 목적지가 되는 뷰 컨트롤러의 인스턴스를 자동으로 생성. 따라서 두번째 뷰 컨트롤러에서 첫번째 뷰 컨트롤러로 세그웨이를 연결하면 자동으로 첫번째 뷰 컨트롤러의 인스턴스가 만들어짐. 하지만 이미 첫번째 뷰 컨트롤러의 인스턴스가 존재하는 상황
  • 역방향 세그웨이를 다시 생성한다는 것은 이미 존재하는 뷰 컨트롤러의 인스턴스를 또다시 만들어 낸다는 의미.
  • 뷰 컨트롤러의 인스턴스는 하나 이상 존재해서는 안됨

프레젠테이션 방식으로 이동했을 때 dismiss(animated:)
내비게이션 컨트롤러를 이용하여 이동했을 때 popViewController(animated:)

  • 다른 방법은 세그웨이 레벨에서 제공하는 것으로 이른바 Unwind Segue를 이용
  • 뷰 컨트롤러 도크바 Exit는 Unwind Segue를 구현할 수 있도록 지원함
  • 버튼을 두번째 뷰 컨트롤러에 Present Modally로 연결 후 첫번째 뷰 컨트롤러 클래스에 다음과 같이 소스 코드 작성
  • @IBAction func unwindToVC(_ segue : UIStoryboardSegue){
  • 이경우 반드시 UIStoryboardSegue타입을 인자값으로 입력받도록 정의해야함

Unwind Segue를 이용하여 한꺼번에 여러 페이지 복귀하기
중간페이지로 돌아가기

  • 되돌아갈 화면의 뷰 컨트롤러 클래스에 UIStoryboardSegue 객체를 인자값으로 받는 메소드를 구현해두기만 하면 이를 시스템에서 Unwind 메소드로 인식함
  • 따라서 Exit 아이콘에서 해당 메소드를 연결하는 것으로 Unwind Segue를 손쉽게 구현
  • 이 때문에 호출될 메소드의 이름은 앱 프로젝트 영역에서 구분되는 이름이어야 하며, 각 뷰컨트롤러를 대표할 수 있는 이름으로 만드는 것이 좋음

15.5.4 커스텀 세그

  • 지금까지 사용한 세그웨이 객체는 UIKit 프레임 워크에서 제공하는 UIStoryboardSegue 클래스를 통해 구현된 객체
  • 앱 개발시 기본적인 기능의 세그웨이로는 원하는 기능을 구현하기 힘든 경우 발생
  • 이럴때 대비하여 UIKit 프레임 워크는 UIStoryboardSegue 클래스를 서브 클래싱 하여 새로운 기능을 갖춘 세그웨이 객체를 정의 할 수 있도록 지원함
  • 이를 커스텀 세그라고 부름
  • UIStoryboardSegue를 상속받음
  • 세그웨이 실행을 처리하는 메소드는 perform()이기 때문에 커스텀 세그에서 원하는 화면 전환기능을 구현하기 위해서는 이를 오버라이드해야함
class NewSegue : UIStoryboardSegue {
    
    override func perform() {
        //세그웨이 출발지 뷰 컨트롤러
        let srcUVC = self.source
        
        //세그웨이 도착지 뷰 컨트롤러
        let destUVC = self.destination
        
        UIView.transition(from: srcUVC.view, to: destUVC.view, duration: 2, options: .transitionCurlDown)
        
    }
}

15.5.5 전처리 메소드의 활용

  • 세그웨이를 이용하여 화면을 전환하는 과정에서 뭔가 특별한 처리를 해 주어야 할 때에는 어떻게?
  • 코코아 터치 프레임워크는 세그웨이가 실행되기 전에 특정한 메소드를 호출하도록 정해져 있기 때문에 이를 이용하면 화면을 전환하기 전에 필요한 처리를 해줄 수 있음
  • 이를 전처리 메소드라고 함
  • 세그웨이를 실행하기전에 값을 저장해둘 필요가 있거나 혹은 경고창을 띄워주는 등의 처리를 해야할 경우 전처리 메소드에 해당 내용을 작성해 놓으면 그 내용이 세그웨이가 실행되기 전에 자동으로 실행됨

prepare(for segue : UIStoryboardSegue, sender : Any?) {...}

  • 주의! 이 메소드의 호출주체는 우리가 아니라 시스템이 호출하는 방식
  • 우리가 호출하고 싶을때 임의로 호출하지 못함
  • 일반적으로 뷰 컨트롤러에 연결된 세그웨이는 여러개가 될 수 있는데 이들 세그웨이는 모두 실행전에 전처리 메소드를 공통적으로 호출함
  • 하나의 전처리 메소드를 여러 세그웨이가 공유하는 방식
  • 이 때문에 전처리 메소드는 어느 세그웨이가 자신을 호출할 것인지를 알고 구분해 주어야할 필요가 있는데 그에 대한 정보가 첫번째 매개변수를 통해 전달
  • 이 매개변수를 사용하여 어느 세그웨이가 실행되는 것인지 알 수 있으므로 이를 이용하여 세그웨이에 따른 조건별 작업을 처리하면 됨 (segue.identifier)
  • 전처리 메소드의 두번째 매개변수는 세그웨이를 실행하는 트리거에 대한 정보
  • 하나의 세그웨이는 여러개의 트리거를 가질수 있음. 화면 내에 있는 여러요소가 동일한 세그웨이를 실행할 수 있다는 뜻
  • 만약 액션세그라면 버튼이나 테이블셀 혹은 제스쳐등의 객체가 주 대상이 되고
  • 메뉴얼세그라면 뷰 컨트롤러 자신이 인자값으로 전달될것임
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "custom_segue") {
            NSLog("커스텀세그 실행")
        }else if (segue.identifier == "action_segue"){
            NSLog("액션세그 실행")
        }else {
            NSLog("알수없는 세그 실행")
        }
        
    }
}


블로그 이미지

百見 이 不如一打 요 , 百打 가 不如一作 이다.

,

Ch.8 구조체와 클래스

  • 구조체와 클래스 내부에 정의된 변수와 상수는 프로퍼티라는 이름을 가짐
  • 구조체와 클래스 내부에 정의된 함수는 메소드라고 함
  • 프로퍼티와 메소드를 구조체나 클래스의 멤버라고 함

8.1 구조체와 클래스의 기본개념

8.1.1 정의 구문

  • 구조체는 struct, 클래스는 class 키워드를 사용하여 정의함
  • 구조체와 클래스 이름의 첫글자는 대문자, 나머지는 소문자로 작성하는것이 원칙

8.1.2 메소드와 프로퍼티

  • 구조체와 클래스 내부에서 정의된 변수나 상수를 프로퍼티 또는 속성이라고 함

8.1.3 인스턴스

  • 타입의 설계도를 사용하여 메모리공간을 할당받은 것이 인스턴스
  • 프로퍼티에 접근하려면 반드시 인스턴스를 먼저 생성하여야 함
struct Resolution {
    var width = 0
    var height = 0
    
    func desc() -> String {
        return "Resolution 구조체"
    }
}

class VideoMode {
    var interlaced = false
    var frameRate = 0.0
    var name : String?
    
    func desc() -> String {
        return "VideoMode 클래스"
    }
    
    var res = Resolution()
}

//인스턴스 생성
let inRes = Resolution()
let insVMode = VideoMode()

//인스턴스 속성에 접금
let width = inRes.width
print("insRes인스턴스의 width값은 \(width)입니다")

//인스턴스 속성에 접근
let vMode = VideoMode()
print("vMode 인스턴스의 width 값은 \(vMode.res.width)입니다")

//프로퍼티에 값 대입
vMode.name = "Sample"
vMode.res.width = 1280

print("\(vMode.name!)인스턴스의 width값은 \(vMode.res.width)입니다")
---
insRes인스턴스의 width값은 0입니다
vMode 인스턴스의 width 값은 0입니다
Sample인스턴스의 width값은 1280입니다

8.1.4 초기화

8.1.5 구조체의 값 전달방식 : 복사에 의한 전달

8.1.6 클래스의 값 전달방식 : 참조에 의한 전달


8.2 프로퍼티

  • 클래스와 구조체 내에서 정의된 변수나 상수
  • 저장프로퍼티와 연산프로퍼티는 대체로 클래스나 구조체를 바탕으로 만들어진 개별 인스턴스에 소속되어 값을 저장하거나 연산 처리하는 역할
  • 예외적으로 일부 프로퍼티는 클래스와 구조체 자체에 소속되어 값을 가지기도함. 이런 프로퍼티들을 타입 프로퍼티라고함
  • 프로퍼티는 클래스의 내부에 메소드의 외부에 정의해야함. (메소드안에서 정의 할 경우 지역변수에 불과함)

8.2.1 저장 프로퍼티

  • 클래스내에서 선언된 변수나 상수를 부르는 이름
  • 선언시 초기값을 할당할수 있지만 반드시 선언하는 시점에서 초기값을 할당해야 하는 것은 아님(초기화 구문에서 초기값을 설정해도 됨)
  • But! 클래스에서 프로퍼티를 선언할때 초기값을 함께 할당해주지 않으면 주의가 필요함 -> 반드시 옵셔널 타입으로 선언해 주어야함. 클래스의 프로퍼티에 값이 비어있는경우 인스턴스를 생성할 때 무조건 nil 값으로 초기화 되기 때문
  • 옵셔널 타입으로 프로퍼티를 선언할 때에는 일반 옵셔널 타입과 묵시적 옵셔널 해제 타입중에서 선택해서 정의할 수 있음 -> 묵시적 옵셔널 해제 타입으로 지정해두면 이값을 사용할 때 옵셔널 해제 처리할 필요없이 일반변수처럼 쓸수 있기때문에 편리함
  • 구조체의 경우 초기값을 할당하지 않고 선언만 하더라도 프로퍼티의 타입을 옵셔널로 지정해 주지 않아도 됨 -> 멤버와이즈 초기화 구문이 제공 되기 때문임

저장프로퍼티의 분류
1.var 로 정의되는 변수형 저장 프로퍼티
2.let으로 정의되는 상수형 저장 프로퍼티

* 구조체 인스턴스를 변수에 할당할 경우
	* 변수 프로퍼티의 값 변경 가능
	* 상수 프로퍼트의 값 변경 불가
* 구조체 인스턴스를 상수에  할당할 경우 
	* 변수 프로퍼티의 값 변경 불가
	* 상수 프로퍼티의 값 변경 불가
* 클래스 인스턴스를 변수에 할당할 경우
	* 변수 프로퍼티의 값 변경 가능
	* 상수 프로퍼트의 값 변경 불가
* 클래스 인스턴스를 상수에 할당할 경우
	* 변수 프로퍼티의 값 변경 가능
	* 상수 프로퍼트의 값 변경 불가

위와 같은 차이는 구조체는 값에 의한 전달, 클래스는 참조에 의한 전달 방식이기 때문

지연 저장 프로퍼티

  • 저장 프로퍼티의 정의 앞에 lazy라는 키워드를 붙임
  • 클래스 인스턴스가 생성되어 모든 저장 프로퍼티가 만들어지더라도 lazy키워드가 붙은 프로퍼티는 선언만 될뿐 초기화 되지 앟고 계속 대기하고 있다가 프로퍼티가 호출되는 순간에 초기화가 됨

클로저를 이용한 저장 프로퍼티 초기화
--------------------421페이지 추가

8.2.2 연산 프로퍼티

  • 연산프로퍼티는 필요한 값을 제공한다는 점에서 저장 프로퍼티와 같지만, 실제 값을 저장했다가 반환하지는 않고 대신 다른 프로퍼티의 값을 연산 처리하여 간접적으로 값을 제공함
  • 이때 프로퍼티의 값을 참조하기 위해 내부적으로 사용하는 구문이 get구문 -> 내부적으로 return키워드를 사용하여 값을 반환하는데 이 값이 프로퍼티가 제공하는 값임
  • 연산프로퍼티는 선택적으로 set구문을 추가할 수도 있음 -> 연산 프로퍼티에 값을 할당하거나 변경하고자 할 때 실행되는 구문 -> 연산의 중요한 요소로 사용됨
  • set구문이 생략되면 외부에서 연산 프로퍼티에 값을 할당 할 수 없으며, 내부적인 연산처리를 통해 값을 제공받는 읽기 전용 프로퍼티가 만들어짐
  • get구문은 연산 프로퍼티에 필수요소! -> 생략시 연산 프로퍼티가 값을 반환하는 기능 자체를 갖지 못함
  • 연산 프로퍼티는 항상 클래스나 구조체 또는 열거형 내부에서만 사용할 수 있음
  • 연산 프로퍼티는 다른 프로퍼티에 의존적이거나, 혹은 특정 연산을 통해 얻을 수 있는 값을 정의 할 때 사용됨
  • set구문에서 매개변수명이 생략된다면 "newValue"라는 기본 인자명이 사용됨

8.2.3 프로퍼티 옵저버

  • 프로퍼티의 값을 직접 변경하거나 시스템에 의해 자동으로 변경하는 경우에 상관없이 일단 프로퍼티의 값이 설정되면 무조건 호출됨
  • 종류

willSet
프로퍼티의 값이 변경되기 직전에 호출되는 옵저버 didSet
프로퍼티의 값이 변경된 직후에 호출되는 옵저버

8.2.4 타입 프로퍼티

  • 인스턴스를 생성하지 않고 클래스나 구조체 자체에 값을 저장하게 되는 프로퍼티
  • 타입 프로퍼티는 클래스나 구조체의 인스턴스에 속하는 값이 아니라 클래스나 구조체 자체에 속하는 값이므로 인스턴스를 생성하지 않고 클래스나 구조체 자체에 저장하게 되며, 저장된 값은 모든 인스턴스가 공통으로 사용할 수 있음
  • 타입 프로퍼티의 값은 복사된 것이 아니라 실제로 하나의 값이므로 하나의 인스턴스에서 타입 프로퍼티의 값을 변경하면 나머지 인스턴스들이 일괄적으로 변경된 값을 적용받음. 이런 특성때문에 타입 프로퍼티는 특정 클래스나 구조체, 그리고 열거형에서 모든 인스턴스들이 공유해야 하는 값을 정의할 때 유용
  • 타입 프로퍼티를 선언하는 요령은 클래스와 구조체 모두에서 같음
  • 타입 프로퍼티로 사용할 프로퍼티 앞에 static 키워드만 추가해주면 됨
  • class 키워드를 사용하여 타입 프로퍼티를 선언하면 상속받은 하위 클래스에서 재정의할 수 있는 타입 프로퍼티가 됨
  • 이를 이용하여 정의한 저장 프로퍼티를 타입 프로퍼티로 선언할 때에는 초기값을 반드시 할당해야 함. 타입 프로퍼티는 인스턴스와 상관없기 때문에 인스턴스 생성 과정에서 초기값을 할당할 수 없기 때문
struct Foo {
    // 타입 저장 프로퍼티
    static var sFoo = "구조체 타입 프로퍼티값"
    // 타입 연산 프로퍼티
    static var cFoo : Int {
        return 1
    }
}

class Boo {
    //타입 저장 프로퍼티
    static var sFoo = "클래스 타입 프로퍼티값"
    //타입 연산 프로퍼티
    static var cFoo : Int {
        return 10
    }
    //재정의가 가능한 타입 연산 프로퍼티
    class var oFoo : Int { // Boo클래스를 상속받는 하위 클래스에서 재정의할 수 있는 타입 프로퍼티
        return 100
    }
}

print(Foo.sFoo)
Foo.sFoo = "새로운 값"
print(Foo.sFoo)

print(Boo.sFoo)
print(Boo.cFoo)

8.3 메소드

  • 메소드는 일종의 함수로서, 클래스나 구조체, 열거형과 같은 객체 내에서 함수가 선언될 경우 이를 메소드라고 통칭함. 즉 메소드는 특정 타입의 객체 내부에서 사용하는 함수라고 할 수 있음
  • 함수와 메소드의 차이점은 구현 목적이 가지는 독립성과 연관성에 있음

인스턴스 메소드
객체의 인스턴스를 생성해야 사용할 수 있는 메소드 타입메소드
객체의 인스턴스를 생성하지 않아도 사용할 수 있는 메소드

8.3.1 인스턴스 메소드

  • 인스턴스 메소드는 클래스, 구조체 또는 열거형과 같은 객체타입이 만들어 내는 인스턴스에 소속된 함수
  • 객체의 인스턴스에 대한 기능적인 측면을 제공함
  • 객체 타입 내부에 선언된다는 점을 제외하고는 일반 함수와 선언하는 형식이 완전히 동일
  • 구조체나 클래스, 열거형 등의 객체 타입을 인스턴스화 한 후 이 인스턴스를 통하여 호출하게 됨
struct Resolution {
    var width = 0
    var height = 0
    
    func desc() -> String {
        let desc = "이 해상도는 가로\(self.width) X \(self.height)로 구성됨"
        return desc
    }
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate : Double = 0.0
    var name : String?
    
    func desc() -> String {
        if self.name != nil {
            let desc = "이\(self.name!) 비디오 모드는 \(self.frameRate)의 프레임 비율로 표시됨"
            return desc
        } else {
            let desc = "이 비디오모드는 \(self.frameRate)의 프레임 비율로 표시됨"
            return desc
        }
        
    }
    
}
  • 인스턴스 메소드 내에서 프로퍼티를 읽어올 경우 다음 형식으로 참조해야함

self.프로퍼티명

  • 프로퍼티 앞에 붙은 self키워드는 클래스나 구조체의 인스턴스 자신을 가리킴
  • 구조체나 열거형의 인스턴스 메소드 내부에서 프로퍼티의 값을 수정할 때에는 반드시 메소드 앞에 mutating이라는 키워드를 추가해야함
  • 구조체나 열거형 인스턴스를 상수로 할당받으면 mutating 메소드를 호출할 수 없음( let aaa = struct or enum () )

8.3.2 타입 메소드

  • 인스턴스를 생성하지 않고도 객체 타입 자체에서 호출할 수 있는 메소드
  • 구조체나 열거형, 클래스 모두 타입 메소드를 선언할 때에는 static 키워드를 사용
  • 반면 하위 클래스에서 재정의 가능한 타입 메소드를 선언할 때는 class키워드를 사용(클래스 타입에서만 사용 가능)
  • 인스턴스 메소드는 객체 타입의 인스턴스에 대해 호출하는 것이지만 타입 메소드는 객체 자체에 대해 호출
class Foo {
    class func fooTypeMethod(){
        
    }
}
let f = Foo()
Foo.fooTypeMethod() // 객체자체에 대해 호출해야함
f.fooTypeMethod() // 오류
  • 타입 메소드를 사용하여 객체의 값을 변경하면 해당 객체 타입을 사용하는 모든 곳에서 변경된 값이 적용됨
  • 인스턴스 메소드는 두개의 인스턴스를 생성하여 메소드를 실행하면 메소드에 의해 값이 변하더라도 해당 인스턴스에만 국한되어 값이 변하고 나머지 인스턴스에는 영향을 미치지 않음
  • 그러니 타입 메소드는 객체 타입 전체에 영향을 미침
  • 타입 메소드에서 사용할수 있는 프로퍼티는 오직 타입 프로퍼티뿐임

8.4 상속(클래스만을 위함)

  • 한 클래스가 다른 클래스에서 정의된 프로퍼티나 메소드를 물려받아 사용하는 것
 class A {
    var name = "Class A"
    
    var description : String { //읽기전용
        return "이 클래스의 이름은 \(self.name)"
    }
    
    func foo() { //읽기전용
        print("\(self.name)의 메소드 foo는 호출됨")
    }
}

let a = A()
a.name
print(a.description)
a.foo()

8.4.1 서브클래싱

  • 클래스 A를 상속받아 새로운 클래스를 정의(서브클래싱)
  • 기존에 있는 클래스를 기반으로 하여 새로운 클래스를 작성하는 과정
  • swift는 단일상속만 지원됨(클래스는 하나의 클래스만 상속받을 수 있음)
class A {
    var name = "Class A"
    
    var description : String { //읽기전용
        return "이 클래스의 이름은 \(self.name)"
    }
    
    func foo() { //읽기전용
        print("\(self.name)의 메소드 foo는 호출됨")
    }
}

let a = A()
a.name
print(a.description)
a.foo()

class B : A {
    var prop = "Class B"
    
    func boo() -> String {
        return "Class B prop = \(self.prop)"
    }
}

let b = B()
print(b.prop)
b.boo()
print(b.name)
b.foo

b.name = "Class C"
b.foo()
---
이 클래스의 이름은 Class A
Class A의 메소드 foo는 호출됨
Class B
Class A
Class C의 메소드 foo는 호출됨
class Vehicle {
    var currentSpeed = 0.0
    
    var description : String {
        return "시간당 \(self.currentSpeed)의 속도로 이동"
    }
    func makeNoise(){
        
    }
}

let baseVehicle = Vehicle()
baseVehicle.description
print(baseVehicle.description)

class Bicycle : Vehicle {
    var hasBasket = false
}

let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 20.0
print("자전거 : \(bicycle.description)")

class Tandem : Bicycle {
    var passengers = 0
}

let tandem = Tandem()
tandem.hasBasket = true
tandem.passengers = 2
tandem.currentSpeed = 14.0

print("tandem: \(tandem.description)")
---
시간당 0.0의 속도로 이동
자전거 : 시간당 20.0의 속도로 이동
tandem: 시간당 14.0의 속도로 이동

8.4.2 오버라이딩

  • 자식클래스에서 재정의된 메소드나 프로퍼티는 부모 클래스로부터 물려받은 내용을 덮어쓰게 되는데 이 과정을 오버라이딩이라고함
  • 오버라이딩한 내용은 자기 자신 또는 자신을 서브클래싱한 하위 클래스에만 적용됨
  • 오버라이딩하려는 메소드나 프로퍼티의 선언 앞에 override 키워드를 붙여야함
  • override 키워드는 부모 클래스나 그 이상의 상위 클래스에서 반드시 선언되어 있고 이를 재정의할 때만 붙일 수 있는 키워드
  • 프로퍼티를 오버라이딩할 때에는 상위 클래스에서 저장 프로퍼티였건, 연산 프로퍼티였건 관계없이 연산 프로퍼티의 형태로 오버라이딩해야함.
  • 본래 저장 프로퍼티는 읽고 쓰기가 모두 허용되는 만큼 연산 프로퍼티로 오버라이딩할 경우 get, set 구문을 모두 제공해야함
  • 저장 프로퍼티를 읽기전용 연산 프로퍼티로 오버라이딩 할 수는 없음
  • get, set 이 모두 제공되던 연산 프로퍼티를 오버라이딩할 때에도 역시 get, set 구문을 모두 제공해야함

  • 프로퍼티 오버라이딩은 상위 클래스의 기능을 하위 클래스가 확장, 또는 변경하는 방식으로 진행되어야지, 제한하는 방식으로 진행되어서는 안됨
class Vehicle {
    var currentSpeed = 0.0
    
    var description : String {
        return "시간당 \(self.currentSpeed)의 속도로 이동"
    }
    func makeNoise(){
        
    }
}

class Bicycle : Vehicle {
    var hasBasket = false
}

class Tandem : Bicycle {
    var passengers = 0
}

class Car : Vehicle {
    var gear = 0
    var engineLevel = 0
    
    override var currentSpeed: Double { //부모클래스의 저장프로퍼티 -> 연산프로퍼티
        get {
            return Double(self.engineLevel * 50)
        }
        set {
            //비워둠
        }
    }
    override var description: String { //부모클래스에서 읽기전용 프로퍼티 -> 읽고 쓰기 가능한 프로퍼티로 변경
        get {
            return "Car : engineLevel = \(self.engineLevel), so currentSpeed = \(self.currentSpeed)"
        }
        set {
            print("new value is \(newValue)")
        }
    }
}


let c = Car()
c.engineLevel = 5
print(c.currentSpeed)
c.description = "New Class Car" //변경

print(c.description) // get, set 구문 출력

--- 프로퍼티 옵저버
class AutomaticCar : Car {
    override var currentSpeed: Double {
        didSet {
            self.gear = Int(currentSpeed / 10.0) + 1  //현재속도가 변할때 그에 따라 기어수도 함께 변경될수 있도록 didSet옵저버 추가
        }
    }
}

  • 메소드 오버라이딩은 오버라이딩 대상이 되는 메소드의 매겨변수 개수나 타입 그리고 반환타입을 변경할 수 없음.(매개변수의 순서도 변경할수 없음)
  • 매개변수 타입이나 반환 타입은 반드시 그래도 유지해야함
  • 같은 메소드 이름이지만 매개변수의 변화만으로 새로운 메소드를 만들어 적재할 수 있도록 지원하는 문법은 오버로딩
  • 오버라이딩 하는 대상 메소드의 매개변수 타입이 달라지거나 매개변수의 개수가 달라지면 오버로딩 문법에 의해 새로운 메소드로 인식하므로 이는 오버라이딩 대상에 포함되지 않음
  • 상속받은 부모 클래스의 인스턴스를 참조할 수 있도록 super라는 객체를 제공하는데 이 객체를 이용하여 점 구문을 함께 사용하면 부모클래스의 프로퍼티나 메소드를 호출할 수 있음
  • 상위 클래스에서 정의한 메소드나 프로퍼티가 하위 클래스에서 오버라이딩되는 것을 차단할 수 있도록 final 키워드를 지원
  • final 키워드는 클래스 자체에도 붙일 수도 있음, 이경우 상속자체가 차단됨

class Vehicle {
    final var currentSpeed = 0.0
    
   final var description : String {
        return "시간당 \(self.currentSpeed)의 속도로 이동"
    }
    final func makeNoise(){
        
    }
}

8.5 타입 캐스팅

  • 부모 클래스로부터 상속된 자식 클래스는 자기 자신의 타입이기도 하면서, 동시에 부모 클래스의 타입이기도 함. 이는 부모 클래스의 특성을 물려받았기 때문
  • 자식 클래스는 본래의 타입 대신 부모 클래스 타비으로 선언하여 사용할 수 있음
class Vehicle {
    var currentSpeed = 0.0
    
    func accelerate() {
        self.currentSpeed += 1
    }
}

class Var : Vehicle {
    var gear : Int{
        return Int(self.currentSpeed / 20) + 1
    }
    func wiper(){
        
    }
}

let trans : Vehicle = Car()

  • 상속을 거듭해 갈수록 하위 클래스는 상위 클래스보다 점차 구체화 되어 가며 상대적으로 상위 클래스는 하위 클래스보다 추상화 되어감.
  • 함수나 메소드의 인자값을 정의할 때 하위 클래스 타입으로 선언한는 것보다 상위 클래스 타입으로 선언하면 인자값으로 사용할 수 있는 객체의 범위가 훨씬 넓어짐

func move(param : SUV {
param.accelerate()
}

  • SUV클래스 이거나 적어도 이 클래스를 상속받은 하위 클래스의 인스턴스만 인자값으로 사용할수 있음

func move(param : Vehicle) {
param.accelerate()
}

  • Vehicle 클래스나 이를 상속받은 모든 클래스의 인스턴스를 인자값으로 사용할 수 있게됨
  • 배열과 딕셔너리도 마찬가지임

var list = SUV
list.append(SUV)

  • 만약 상위 클래스인 Vehicle을 아이템 타입으로 사용한다면 다음 모두 가능

var list = Vehicle
list.append(Vehicle)
list.append(Car)
list.append(SUV)

8.5.1 타입 비교 연산

  • 스위프트는 타입 비교 연산자 is 를 지원함. 변수나 상수 또는 인스턴스에 이 연산자를 사용하면 할당된 값을 비교하는 것이 아니라 타입이 일치하는지 여부를 비교하여 그 결과를 Bool형태로 돌려줌

인스턴스(또는 변수, 상수) is 비교대상 타입

  • 타입을 비교 연산할 때 연산자 왼쪽에 인스턴스가 아니라 인스턴스가 할당된 변수가 사용될 경우 다소 주의해야함. 변수가 선언된 타입을 기준으로 비교하는 것이 아니라 변수에 할당된 실제 인스턴스를 기준으로 타입을 비교
let myCar : Vehicle = SUV()

if myCar is SUV {
    print("myCar는 SUV타입입니다")
}else {
    print("myCar는 SUV타입이 아닙니다")
}

let newCar : Vehicle = Car()

if newCar is SUV {
    print("newCar는 SUV타입입니다")
}else {
    print("newCar는 SUV타입이 아닙니다")
}
---
myCar는 SUV타입입니다
newCar는 SUV타입이 아닙니다

8.5.2 타입 캐스팅 연산

let someCar : Vehicle = SUV()

  • someCar는 Vehicle타입 이므로 Vehicle 클래스에 선언되지 않은 프로퍼티나 메소드를 사용할수 없음.
  • someCar상수를 이용하여 SUV() 클래스에 선언된 프로퍼티를 사용하고 싶거나 SUV타입을 인자값으로 받는 함수에 사용하려면 타입캐스팅이 필요
  • 일반적으로 타입 캐스팅은 상속관계에 있는 타입들 사이에서 허용됨
  • 타입캐스팅은 캐스팅 전 타입과 캐스팅 후 타입의 상위/하위 관계에 따라 업 캐스팅 과 다운 캐스팅으로 나누어짐
  • 업 캐스팅은 일반적으로 캐스팅 과정에서 오류가 발생할 가능성이 없음
  • 다운 캐스팅은 상위 클래스 타입의 객체를 하위 클래스 타입으로 캐스팅하는 것을 의미

  • 다운 캐스팅 과정에서 오류가 발상하면 nil이 반환됨. 이같은 결과값을 고려하여 다운 캐스팅은 옵셔널 타입을 반환하는 옵셔널 캐스팅과 반드시 캐스팅에 성공한다는 전제하에 일반 타입으로 반환하는 강제 캐스팅으로 나누어짐
  • 타입 캐스팅을 위한 연산자는 as

업 캐스팅

객체 as 변환할 타입
다운 캐스팅
객체 as? 변환할 타입(결과는 옵셔널 타입)
객체 as! 변환할 타입(결과는 일반 타입)

업 케스팅
let anyCar : Car = SUV()
let anyVehicle = anyCar as Vehicle

다운 캐스팅
let anySUV = anyCar as? SUV
if anySUV != nil {
    print("캐스팅이성공")
}

8.5.3 Aay, AnyObject

* 상속관계에 있지 않아도 타입 캐스팅 할 수 있는 예외가 있는데 바로 Any와 AnyObject 타입을 사용할 때임
* 무엇이든 다 받아들일 수 있는 일종의 범용 타입
* AnyObject는 클래스의 일종으로 모든 종류의 클래스 타입을 저장할 수 있는 범용타입의 클래스
* 모든 클래스의 인스턴스는 AnyObject 클래스 타입으로 선언된 변수나 상수에 할당할 수 있음
* 모든 클래스의 인스턴스는 AnyObject 타입으로 선언된 함수나 메소드의 인자값으로 사용될 수도 있으먀, AnyObject 타입을 반환하는 함수나 메소드는 모든 종류의 클래스를 반환할 수 있다는 의미로 해석 되기도함
* 고정된 하나의 타입만을 저장할 수 있는 배열이나 딕셔너리, 집합에서도 AnyObject 타입을 사용할수 있는데 이는 모든 클래스를 저장할 수 있다는 뜻임
* AnyObject는 클래스 특성상 항상 다운 캐스팅만 수행됨
* 구조체나 열거형은 AnyObject 타입으로 정의를 허용하지 않음
* Any 는 클래스 뿐만 아니라 원시자료형, 구조체, 열거형, 심지어 함수까지 허용됨. 즉, 어떤 변수의 타입이 Any로 선언되었다면 이 변수는 종류에 상관없이 모든 타입의 객체를 저장할 수 있음
* Any타입에 할당된 객체가 사용할 수 있는 프로퍼티나 메소드는 아예 제공되지 않음. 모든값을 제한없이 할당받을 수 있지만, 그 값을 이용하여서 할 수 있는것은 거의 없어지는 셈

8.6 초기화 구문

  • 구조체나 클래스는 모두 정의된 내용을 그대로 사용할 수는 없음.
  • 항상 인스턴스를 생성해서 메모리 공간을 할당받은 다음에 사용해야함
  • 이를 초기화라고 함
  • 초기화 과정에서 가장 중요한 것은 저장 프로퍼티, 모든 저장 프로퍼티는 인스턴스 생성 과정에서 초기화되어야 하며 이를 위해서 반드시 초기값이 지정되어 있어야 함

8.6.1 init 초기화 메소드

  1. 초기화 메소드의 이름은 init 으로 통일된다
    2.매개변수의 개수, 이름, 타입은 임의로 정의할 수 있다
    3.매개변수의 이름과 개수, 타입이 서로 다른 여러 개의 초기화 메소드를 정의할 수 있다.
  2. 정의된 초기화 메소드는 직접 호출되기도 하지만, 대부분 인스턴스 생성시 간접적으로 호출된다.
  • init 메소드가 작성되고 나면 작성된 init메소드가 어떤 인자값 형식을 갖는가에 상관없이 그 객체의 기본 초기화 구문은 더는 제공되지 않음
  • init 역시 메소드 이므로 매개변수에 기본값을 지정할 수 있음
  • 기본값이 지정된 메소드에서는 인자값을 생략할 수 있으며, 이때 생략된 인자값 대신 기본값이 인자값으로 사용됨
struct Resolution {
    var width = 0
    var height = 0
    
    init(width : Int){
        self.width = width
    }
}

class VideoMode {
    var resolution = Resolution(width : 2048)
    var interlaced = false
    var frameRate = 0.0
    var name : String?
    
    init(interlaced : Bool, frameRate : Double) {
        self.interlaced = interlaced
        self.frameRate = frameRate
    }
    init(name : String) {
        self.name = name
    }
    init(interlaced : Bool){
        self.interlaced = interlaced
    }
    init(interlaced : Bool, frameRate : Double, name : String) {
        self.interlaced = interlaced
        self.frameRate = frameRate
        self.name = name
    }
}

//인스턴스 생성
let resolution = Resolution.init(width: 4096)
let videoMode = VideoMode.init(interlaced: true, frameRate: 40.0)

let nameVideoMode = videoMode("이재성")
let simpleVideoMode = VideoMode(interlaced: true)
let doubleVideoMode = VideoMode(interlaced: true, frameRate: 40.0)
let tripleVideoMode = VideoMode(interlaced: true, frameRate: 50.0, name: "이재성")

class videoMode {
    var name : String?
    
    init(name : String = ""){
        self.name = name
    }
}
//이경우 2가지 초기화 가능
let defaultVideoMode = videoMode()
let defaultVideoMode = VideoMode(name: "이재성")

8.6.2 초기화 구문의 오버라이딩

  • 클래스에서는 초기화 구문도 일종의 메소드 이므로 자식 클래스에서 오버라이딩 할 수 있음
  • 기본초기화 구문 init()은 부모클래스에서 명시적으로 선언된 적이 없더라도 이를 상속받은 자식 클래스에서는 반드시 오버라이딩 형식으로 작성해야함
  • 초기화 구문을 오버라이딩 하면 더 이상 부모클래스에서 정의한 초기화 구문이 실행되지 않음.
  • 초기화 구문을 오버라이딩 할 경우 부모 클래스에서 정의된 초기화 구문을 내부적으로 호출해야 하는데, 오버라이딩된 초기화 구문 내부에 super.init 구문을 작성하면 됨

초기화 구문 델리게이션
기본 초기화 구문을 제외한 나머지 초기화 구문을 오버라이딩할 때는 반드시 부모 클래스의 초기화 구문을 호출함으로써 델리게이션 처리를 해 주어야 함
기본 초기화 구문의 경우는 주어진 상황별로 조금씩 다름
부모클래스에 기본 초기화 구문만 정의되어 있거나 기본 초기화 구문이 아예 명시적으로 정의 되어 있지 않은 상태에서 자식 클래스가 오버라이딩 할 때에는 super.init()구문을 호출해주지 않아도 자동으로 부모 클래스의 초기화 구문이 호출됨

class Base {
    var baseValue : Double
    
    init() {
        self.baseValue = 0.0
        print("Base init")
    }
}

class ExBase : Base {
    override init(){ ~// 부모클래스가 기본초기화 구문만 있으므로 super.init 호출하지 않아도 됨~
        print("ExBase init")
    }
}

let ex = ExBase()
class Base {
    var baseValue : Double
    
    init() {
        self.baseValue = 0.0
        print("Base init")
    }
    init(baseValue : Double){ ~//다른 형식의 초기화 구문 추가시~
        self.baseValue = baseValue
    }
}

class ExBase : Base {
    override init(){
        super.init() ~//명시적으로 부모클래스의 기본 초기화구문 호출해야함~
        print("ExBase init")
    }
}

let ex = ExBase()

8.7 옵셔널 체인

8.7.1 옵셔널 타입의 문제점

  • 옵셔널 타입은 항상 nil 여부를 검사하여 정상적이 값이 저장된 것을 확인한 후에 사용하는 것이 안전하므로 if 구문을 통해 옵셔널 타입을 처리하는 경우가 많음(옵셔널에 대한 이슈)
  • 클래스나 구조체가 옵셔널 타입과 관련되었을때 문제가 발생. 구조체나 클래스의 인스턴스가 옵셔널 타입으로 선언될 경우 프로퍼티와 메소드를 호출하기 위해서는 매번 if 구문을 통해 옵셔널 인스턴스의 정상값 여부를 검사해야함
struct Human {
    var name : String?
    var man : Bool = true
}

struct Company {
    var ceo : Human?
    var companyName : String?
}

var startup : Company? = Company(ceo : Human(name : "나대표", man : false), companyName: "루비페이퍼")
//startup 옵셔널 해제
if let company = startup {
    if let ceo = company.ceo {
        if let name = ceo.name {
            print("대표이사의 이름은 \(name)입니다")
        }
    }
}

8.7.2 옵셔널 체인

  • 옵셔널 체인은 옵셔널 타입으로 정의된 값이 하위 프로퍼티나 메소드를 가지고 있을 때, 이 요소들을 if구문을 쓰지 않고도 간결하게 사용할 수 있는 코드를 작성하기 위해 도입
  • 객체가 nil인 상황에서 안전성 검사를 하지 않고 메소드나 프로퍼티를 호출하더라도 오류가 발생하지 않을 수 있는 문법을 옵셔널 스타일을 이용하여 구현
  • 옵셔널 체인으로 처리할 수 있는 것은 하위 속성이나 메소드를 호출해야 할 떄임

startup?.ceo?.name

  • 마지막 name 값은 하위속성이나 메소드를 호출하는 것이 아니라 직접 사용해야 하는 값이므로 옵셔널에 대한 검사가 필요함
  • 하지만 값을 참조하는것이 아니라 할당해야 한다면 옵셔널 체인을 이용하여 다음과 같이 간편하게 구문을 작성할 수 있음

startup?.ceo?.name = “이재성천재”

  • 옵셔널 체인의 특징
    • 옵셔널 체인으로 참조된 값은 무조건 옵셔널 타입으로 반환된다
    • 옵셔널 체인 과정에서 옵셔널 타입들이 여러 번 겹쳐 있더라도 중첩되지 않고 한번만 처리된다.
  • 옵셔널 체인 구문에서 마지막에 오는 값이 옵셔널 타입이 아닌 일반 값일지라도 옵셔널 체인을 통해 참조 했다면 이 값은 옵셔널 타입으로 변경됨
//옵셔널
struct Human {
    var name : String?
    var man : Bool = true
}

struct Company {
    var ceo : Human?
    var companyName : String?
    func getCEO() -> Human? {
        return self.ceo
    }
}

var startup : Company? = Company(ceo : Human(name : "나대표", man : false), companyName: "루비페이퍼")
//startup 옵셔널 해제
if let company = startup {
    if let ceo = company.ceo {
        if let name = ceo.name {
            print("대표이사의 이름은 \(name)입니다")
        }
    }
}
//옵셔널 체인
if let name = startup?.ceo?.name {
    print("대표이사의 이름은 \(name)입니다")
}

let name = startup?.getCEO()?.name
if name != nil {
    print("대표이사의 이름은 \(name!)입니다")
}


블로그 이미지

百見 이 不如一打 요 , 百打 가 不如一作 이다.

,