[CH4.2 ~ 4.4.1] 데이터 타입 고급

Tabber·2021년 9월 8일
0
post-thumbnail

4.2 타입 별칭

스위프트에서 기본으로 제공하는 데이터 타입이든, 사용자가 임의로 만든 데이터 타입이든 이미 존재하는 데이터 타입에 임의로 다른 이름(별칭)을 부여할 수 있다.

그런 다임 기본 타입 이름과 이후에 추가한 별칭을 모두 사용할 수 있다.

import Foundation

// MARK: 타입 별칭
typealias MyInt = Int
typealias YourInt = Int
typealias MyDouble = Double

var age: MyInt = 100        // MyInt 는 Int의 또 다른 이름이다.
var year: YourInt = 2080    // YourInt 도 Int의 또 다른 이름이다.

// MyInt, YourInt 다 Int이기 때문에 같은 타입으로 취급한다.
year = age

let month: Int = 7              // 기존 Int 도 사용가능하다.
let percentage: MyDouble = 99.9 // Int 외에 다른 자료형도 모두 별칭 사용이 가능하다.

4.3 튜플(Tuple)

튜플(Tuple)은 타입의 이름이 따로 지정되어 있지 않은, 프로그래머 마음대로 만드는 타입이다.
'지정된 데이터의 묶음'이라고 표현할 수 있다. C언어를 예로 들면 원시 구조체의 형태와 가깝다.

스위프트의 튜플은 파이썬의 튜플과 유사하다. 튜플은 타입 이름이 따로 없으므로 일정 타입의 나열만으로 튜플 타입을 생성해줄 수 있다. 튜플의 포함될 데이터의 개수는 자유롭게 정할 수 있다. 하나가 될수도 있고, 여러개가 될 수도 있다.

import Foundation

// MARK: 튜플
// String, Int, Double 타입을 갖는 튜플
var person: (String, Int, Double) = ("yagom", 100, 182.5)

// 인덱스를 통해 값을 빼 올 수 있다.
print("이름: \(person.0), 나이: \(person.1), 신장: \(person.2)")

person.1 = 99 // 인덱스틑 통해 값을 할당할 수 있다.
person.2 = 178.5

print("이름: \(person.0), 나이: \(person.1), 신장: \(person.2)")

이렇게 튜플의 각 요소를 이름 대신 숫자로 표현하기 때문에 간편해 보일 수 있다.

하지만 나중에 다른 프로그래머가 이 코드를 보았을 때 어떤것인지 유추하기가 어렵다.
이름 없이 인덱스만으로 각 요소의 데이터가 무엇을 나타내는지 쉽게 파악하기가 어렵기 때문이다.

그래서 튜플의 요소마다 이름을 붙여줄 수도 있다.
다음 코드는 튜플의 요소마다 이름을 붙여봤다.

import Foundation

// MARK: 튜플
// String, Int, Double 타입을 갖는 튜플
var person: (name: String, age: Int, height: Double) = ("yagom", 100, 182.5)

// 이름요소를 통해 값을 빼 올 수 있다.
print("이름: \(person.name), 나이: \(person.age), 신장: \(person.height)")

person.age = 99 // 요소 이름을 통해 값을 할당할 수 있다.
person.2 = 178.5 // 인덱스를 통해서도 값을 할당할 수 있다.

// 기존처럼 인덱스를 이용하여 값을 빼 올 수도 있다.
print("이름: \(person.0), 나이: \(person.1), 신장: \(person.2)")

또, 튜플에는 타입 이름에 해당하는 키워드가 따로 없다 보니 사용에 불편함을 겪기도 한다.
매번 같은 모양의 튜플을 사용하고 싶은데 선언해줄 때마다 긴 튜플 타입을 모두 써줘야 하는 불편함이 생길 수 있기 때문이다.

이럴때는 타입 별칭을 사용하여 조금 더 깔끔하고 안전하게 코드를 작성할 수 있다.

import Foundation

// MARK: 튜플
typealias PersonTuple = (name: String, age: Int, height: Double)

let itLearning: PersonTuple = ("ITlearning", 100, 178.5)
let eric: PersonTuple = ("eric", 150, 183.5)

print("이름: \(itLearning.name), 나이: \(itLearning.age), 신장: \(itLearning.height)")
print("이름: \(eric.name), 나이: \(eric.age), 신장: \(eric.height)")

4.4 컬렉션형

스위프트는 튜플 외에도 많은 수의 데이터를 묶어서 저장하고 관리할 수 있는 컬렉션 타입을 제공한다.

컬렉션 타입에는 배열(Array), 딕셔너리(Dictionary), 세트(Set) 등이 있다.

4.4.1 배열

배열은 같은 타입의 데이터를 일렬로 나열한 후 순서대로 저장하는 형태컬렉션 타입이다.
각기 다른 위치에 같은 값이 들어갈 수도 있음을 알아두자.

배열 타입을 선언해줄 방법은 다양하다. let 키워드를 사용해 상수로 선언하면 변경할 수 없는 배열이 되고,

var 키워드를 사용해 변수로 선언해주면 변경 가능한 배열이 된다.

실제로 배열을 사용할 때는 Array 라는 키워드와 타입 이름의 조합으로 사용한다. 또, 대괄호로 값을 묶어 Array 타입임으로 표현할 수도 있다.

빈 배열은 이니셜라이저 또는 리터럴 문법을 통해 생성해줄 수 있는데, isEmpty 프로퍼티로 비어있는 배열인지 확인해 볼 수 있다.

참고로 isEmpty 의 시간복잡도는 O(1)이다. 왜 O(1)일까?


Apple 공식 문서에서의 isEmpty 설명

isEmpty - 인스턴스 프로퍼티

A Boolean value indicating whether the collection is empty.

컬렉션이 비어있는지에 대한 여부를 나타내는 Bool 값이다.

Discussion

When you need to check whether your collection is empty, use the isEmpty property instead of checking that the count property is equal to zero. For collections that don’t conform to RandomAccessCollection, accessing the count property iterates through the elements of the collection.

컬렉션이 비어있는지 확인해야 하는 경우, count 프로퍼티가 0인지 확인하는 대신, isEmpty 프로퍼티를 사용한다.

RandomAccessCollection을 준수하지 않는 컬렉션의 경우, count 프로퍼티를 사용하면 된다.

그럼 RandomAccessCollection은 또 뭘까


RandomAccessCollection - 랜덤 접근 컬렉션

랜덤 접근 컬렉션은 인덱스 접근 등에 있어서 효율적인 랜덤접근을 지원하는 컬렉션이다.

랜덤 접근 컬렌션은 프로토콜로 되어있다.

Declaration - 선언

protocol RandomAccessCollection where Self.Indices : RandomAccessCollection,
Self.SubSequence : RandomAccessCollection

앞서 설명했듯이, RandomAccessCollection은 프로토콜로 되어있으며 해당 프로토콜을 채택한 객체는 Indices , SubSequence 에 대하여 RandomAccessCollection의 형태를 준수해주어야 한다.

잠깐만 조금 또 새서 Indices , SubSequence 에 대해 알아보자.

Indices : 컬렉션의 유효한 인덱스를 오름차순으로 나타내는 유형이다.

SubSequence: 컬렉션 요소의(안에 존재하는 것들) 연속된 하위 범위를 나타내는 시퀀스이다.
아마도 이건 컬렉션 안에 또 다른 컬렉션들이 존재할 때 하위로 점점 내려가는 형식의 시퀀스인듯 하다.

자 다시 RandomAccessCollection으로 돌아가자.

따라서 유효한 인덱스의 유형과 하위 컬렉션들을 수행할 수 있는 컬렉션이면서 RAC의 형태를 준수해야 하는 것 같다.

OverView

랜덤 접근 컬렉션은 어느 위치에 있던, 임의의 인덱스 접근을 단 O(1)의 시간복잡도 만으로 수행한다.

단적인 예로, 스위프트의 기본적인 배열에서 사용하는 랜덤 접근 컬렉션과, String 타입 등에서 사용하는 양방향 접근 컬렉션의 근본적 차이는 이러한 접근 첨자로 이동하는 인덱스의 이동 방식에 따라 달라진다. 이로 인해, 양방향 접근 컬렉션 은 첨자 접근 시 해당 인덱스에 이동하는 동안 모든 위치를 순회해야 하므로 복잡도가 O(N)인 반면, 랜덤 접근 컬렉션은 복잡도가 O(1)이 된다.

양방향 접근 컬렉션

RandomAccessCollection 프로토콜 사용하기

(Conforming to the RandomAccessCollection Protocol)

랜덤 접근 컬렉션을 준수하기 위해서는 Indices, SubSequence 타입에 관련된 제약을 충족해야 한다.

그러니까 유효한 인덱스를 가지고, 하위 컬렉션 접근이 가능한 것의 대한 제약이다.

그와 반대로 양방향 접근 컬렉션(Bidirectional Access Collection)은 이러한 제약이 필요하지 않다.

그러나 만약 랜덤 접근 컬렉션의 이점을 취하기 위해서는 Inices, SubSequence에 대한 제약을 지켜야 한다.
또한, 랜덤 접근 컬렉션의 인덱스 접근 성능을 보장받기 위해서는 해당 컬렉션을 충족할 커스텀 타입은 Strideable 프로토콜을 반드시 준수하거나 , index() , distance() 메서드를 O(1)의 시간복잡도로 구현해야 한다.

Strideable 프로토콜이란?

1차원 값에 대한 연속적인 측정 및 표현이 가능한 타입을 정의한다.

정리해보자면, RandomAccessCollection 프로토콜은 어느 위치에 있던 임의의 인덱스 접근을 O(1)의 시간 복잡도만으로 수행이 가능하다.

제약조건이 존재하는데, 랜덤 접근 컬렉션을 준수하기 위해서는 유효한 인덱스를 가지고(Indices), 하위 컬렉션 접근이 가능한(SubSequence) 컬렉션이던지, 1차원 값에 대한 연속적인 측정 및 표현이 가능한 타입(Strideable) 을 준수할 경우 RandomAccessCollection 프로토콜을 사용할 수 있다.


다시 배열로 돌아와!

다시 배열로 돌아오자.. 너무 많이 샜다..

🎨 NOTE_스위프트의 Array

스위프트의 Array는 C언어의 배열처럼 버퍼(Buffer)이다. 단, C 언어처럼 한 번 선언하면 크기가 고정되던 버퍼가 아니라, 필요에 따라 자동으로 버퍼의 크기를 조정해주므로 요소의 삽입 및 삭제가 자유롭다.
스위프트는 이런 리스트 타입을 Array, 즉 배열이라고 표현한다.

import Foundation

// MARK:  배열의 선언과 생성

// 대괄호를 사용하여 배열임을 표현한다.
var names: Array<String> = ["ITlearning", "Chulsoo", "YoungHee", "ITlearning"]

// 위 선언과 정확히 동일한 표현이다. [String]은 Array<String> 의 축약 표현이다.
var names_small: [String] = ["ITlearning", "Chulsoo", "YoungHee", "ITlearning"]

var emptyArray: [Any] = [Any]() // Any 데이터를 요소로 갖는 빈 배열을 생성한다.
var emptyAnotherArray: [Any] = Array<Any>() // 위 선언과 정확히 같은 동작을 하는 코드.

// 배열의 타입을 정확히 명시해줬다면, [] 만으로도 빈 배열을 생성할 수 있다.
var array: [Any] = []

print(emptyArray.isEmpty) // true
print(names.count) // 4

배열은 각 요소에 인덱스를 통해 접근할 수 있다. 인덱스는 0부터 시작하고, 잘못된 인덱스로 접근하려고 하면 익셉션 오류(Exception Error) 가 발생한다. 또, 맨 처음맨 마지막 요소firstlast 프로퍼티를 통해 가져올 수 있다.

index(of:) 메서드를 사용하면 해당 요소의 인덱스를 알아낼 수도 있다.
만약 중복된 요소가 있다면 제일 먼저 발견된 요소의 인덱스를 반환한다. 맨 뒤에 요소를 추가하고 싶다면 append(_:) 메서드를 사용한다.

중간에 요소를 삽입하고 싶다면 insert(_:at:) 메서드를 사용하면 된다. 요소를 삭제하고 싶다면 remove(_:) 메서드를 사용하게 되는데, 메서드를 사용하면 해당 요소가 삭제된 후 반환된다.

...위의 코드 연속...

print(names[2]) // YoungHee
names[2] = "Jenny"
print(names[2]) // Jenny
//print(names[4]) // Excepiton Error

//names[4] = "elsa" // Excepiton Error
names.append("elsa") // 마지막에 elsa 추가.
names.append(contentsOf: ["John", "Max"]) // 맨 마지막에 John과 Max가 추가.
names.insert("Happy", at: 2) // Index 2에 삽입된다.
// Index 5의 위치에 JinHee와 Minsoo가 삽입된다.
names.insert(contentsOf: ["JinHee", "Minsoo"], at: 5)

print(names[4]) // ITlearning
// firstIndex : 지정한 값이 컬렉션에 나타나는 첫 번째 인덱스를 반환합니다.
// 책에서는index를 사용했는데, Swift 문법이 바뀌면서 사용하지 않는 문법으로 바뀌었나보다.
// index에 밑줄이 그어있다.
print(names.firstIndex(of: "ITlearning")!) // 0
print(names.firstIndex(of: "christal")!) // nil
print(names.first!) // ITlearning
print(names.last!) // Max

let firstItem: String = names.removeFirst() // 앞에꺼 뽑기
let lastItem: String = names.removeLast() // 뒤에꺼 뽑기
let indexZeroItem: String = names.remove(at: 0) // 입력한 인덱스의 원소 제거

print(firstItem) // ITlearning
print(lastItem) // Max
print(indexZeroItem) // Chulsoo
print(names[1...3]) // ["Jenny", "ITlearning", "JinHee"]

코드의 맨 아래 줄 names[1...3] 표현은 범위 연산자를 사용하여 names 배열의 일부만 가져온 것이다.
코드처럼 읽기만 가능한 것이 아니라 names[1...3] = ["A", "B","C"] 처럼 범위에 맞게 요소를 바꾸는 것도 가능하다.

스위프트의 배열을 비롯한 컬렉션 타입을 활용할 때 서브스크립트(Subscript) 기능을 많이 사용한다.

서브스크립트는 나중에 더 자세히 살펴보자.
간단하게 보면 "콜렉션, 리스트, 시퀀스 등 집합의 특정 member elements에 간단하게 접근할 수 있는 문법" 이라고한다.

오늘 공부 정리

오늘은 타입 별칭, 튜플, 컬렉션형의 배열 을 알아보았다.

타입별칭

  • 타입 별칭은 타입의 다른 이름을 지정해주는 것으로 이미 존재하는 데이터에 임의로 다른 이름을 지정할 수 있다.

튜플

  • 튜플은 타입의 이름이 따로 정해진 것이 아닌,마음대로 만들 수 있는 타입이다.
  • 튜플은 '지정된 데이터의 묶음'이라고 표현할 수 있다.
  • 여러개의 타입을 하나로 모아줄 수 있다.
  • 튜플에 타입별칭을 지정해 인덱스로 접근하는 것이 아닌 이름으로 접근할 수 있게 할 수 있다.

컬렉션 - 배열

  • 컬렉션의 종류에는 배열, 딕셔너리, 세트 등이 있다.
  • 배열은 같은 타입의 데이터를 일렬로 나열한 후 순서대로 저장하는 컬렉션 타입이다.
  • 배열의 isEmpty 프로퍼티의 시간복잡도는 O(1)이다.
  • 그 이유는 isEmpty는 RandomAccessCollection 을 준수하기 때문이다.

배열 - RandomAccessCollection

  • RandomAccessCollection은 점차 접근등에 있어서 효율적으로 랜덤접근을 지원하는 컬렉션이다.
  • 따라서 임의의 인덱스에 접근하는 RAC로 O(1)에 접근할 수 있다.
  • 이와 반대되는 개념은 BidirectionalAccessCollection 이다.
  • RandomAccessCollection 은 제약이 존재하고 ,BidirectionalAccessCollection 은 제약이 존재하지 않는다. 단, BidirectionalAccessCollection의 시간복잡도는 O(N)이다.

다시 배열

  • 스위프트의 배열은 C언어와 같이 버퍼이다. 그러나 고정적인 버퍼가 아닌 유동적인 버퍼라 요소의 삽입과 삭제가 자유롭다.
  • first, last , index(), append() 등의 다양한 메서드가 존재한다.
  • 스위프트의 배열을 비롯한 컬렉션 타입을 활용할 때 서브스크립트 기능을 많이 사용한다.
  • 간단하게 설명하자면 콜렉션, 리스트, 시퀀스 등 집합의 특정 member elements에 간단하게 접근할 수 있는 문법 이다.

오늘 정리 끝.

profile
iOS 정복중인 Tabber 입니다.

0개의 댓글