DADAHAE's Log
비싼 장난감 가지고 노는 중 (❁´▽`❁)*✲゚*
[TIL] 22-09-29: Swift (Struct, Property Wrapper, Collection - Array, Dictionary)
22-09-29 목요일
3주차

 

 

Swift

구조체

 

객체는 3가지로 만들 수 있다.

  • Class
  • Struct
  • Enum (Next Week)

class, struct의 차이가 무엇이 있는지 꼭 알도록 하자.

 

목표

  • 앞서 배운 것
    • 서브클래싱
    • 상속
    • 익스텐션의 개념 (method ok - what is self?, property no - only computed)
  • 이번에 배울 것
    • 구조체는 클래스와 비슷해보이지만, 어떤 것을 사용해야 할지 결정할 때 이해해야 할 중요한 차이점들이 있다.
    • 구조체를 어떻게 선언하고 사용해야 하는지?
    • 구조체와 클래스의 차이점
    • 값 타입(call by value)참조 타입(call by reference)에 대한 개념
      → 구조체, 클래스로 만드는 이유들이 보통 타입 때문이다..

 


 

✔️ Struct

  • 클래스처럼 구조체도 객체지향 프로그래밍의 기초 형성
  • 데이터와 기능을 재사용할 수 있는 객체로 캡슐화하는 방법을 제공한다.

 

1️⃣ Definition Syntax

  • class ↔ struct (키워드만 다르다)
struct SampleStruct {

	var name: String

	init(name: String) {
		self.name = name
	}
}
class SampleStruct {

	var name: String

	init(name: String) {
		self.name = name
	}
}
  • class와 struct의 선언부는 키워드만 제외하고 모두 동일하다.
  • struct는 class가 하는 일 중 몇가지만 제외하고 할 수 있음 → 그러려면 왜 함? → 이유가 있음 잘들어봥잉

 

2️⃣ Structure and Class Instances

  • 인스턴스 생성에도 class와 struct는 동일한 문구를 사용한다.
let myStruct = SampleStruct(name: "Mark")

let myClass = SampleStruct(name: "Mark")

나중에 프로젝트에서 클래스, 구조체 위에 마우스 커서 올리면 설명창 뜨면서 구분할 수 있다.

  • String은 struct 이다. 그래서 상속받을 수 없다!!

 

🦁 공식문서를 늘 끼고 다니자.

 

  • 클래스와 마찬가지로 “구조체 확장 + 프로토콜을 채택 + 초기화” 가능
  • 클래스, 구조체는 공통점이 많다. 그러므로 서로가 어떻게 다른지 이해하는 것이 중요하다.
    • call-by-value
    • call-by-reference

 

3️⃣ call by value vs. call by reference

값 타입 vs. 참조 타입
  • 겉보기에는 구조체와 클래스는 비슷하나, 구조체의 인스턴스와 클래스의 인스턴스가 ‘복사’되거나 메서드 또는 함수에 인자가 ‘전달’될 때 발생하는 동작의 큰 차이가 있다.

 

(1) 

let age: Int = 9
let myAge = age

myAge = 30
// age? 는 어케 되나여? 이런 차이를 알아볼 것.

퍼가요~

Copy

  • 구조체 인스턴스 → 값 타입(value type)
    • 구조체 인스턴스가 복사되거나 메서드에 전달될 때… 인스턴스의 실제 복사본이 생성되면서 원본 객체가 가지고 있던 모든 데이터틑 그대로 ‘복사’해서 가진다.
    • 복사본원본 구조체 인스턴스와 별개인 ‘자신만의 데이터’를 가진다는 의미이다.
    • 구조체 인스턴스에 대한 복사본은 여러개 존재 가능, 각각 자신만의 데이터 가진다.
      • 인스턴스를 변경해도 그 복사본들에게 영향 x

📌 위 코드에서 myAge는 age와 같은 값을 복사해서 가지고, myAge 값을 변경해도 age에 영향을 주지 않는다.

 

 

 

(2)

var account: BankAccount = BankAccount()

var myAccount = account 
// myAccount? 는 어케 될까요

어이어이 같이 씁시다!!!!

Shared

  • 클래스 인스턴스 → 참조 타입(reference type)
    • 클래스 인스턴스가 복사되거나 인자로 전달되면… 해당 클래스 인스턴스가 있는 ‘메모리의 위치’에 대한 참조체가 만들어지거나 전달된다.
    • 참조체를 변경하면 원본 인스턴스에도 동일한 작업이 수행된다.
    • 하나의 클래스 인스턴스 - 인스턴스를 가리키는 여러개의 참조체
      • 참조체들 중 하나를 이용하여 인스턴스 데이터를 변경하면.. 모든 참조체의 데이터가 변경된다.

📌 위 코드에서 myAccount는 account의 메모리 위치에 대한 참조체가 된다. (주소값을 가지고 있음)

 

 

 

 

 

과거에는 call-by-value가 무시당했다. 그냥 다 class로 다 하자! 메모리 차지하는 것도 줄이고 좋네~ 했다.

그러나 10-20년전에 비해 같은 값으로 살 수 있는 메모리가 엄청 늘어났다. 이제는 메모리 공간이 적지 않으니까 값 타입으로 해도 되겠어! 하고 다시 살아난 것이다.

 

 

 

코드로 이해해보자.

struct SampleStruct {

	var name: String
	
	init(name: String) {
		self.name = name
	}
}

let myStruct1 = SampleStruct(name: "Mark")  // (1)
print(myStruct1.name)

var myStruct2 = myStruct1  // (2)
myStruct2.name = "David"   // (3)

print(myStruct1.name)      // Mark
print(myStruct2.name)      // David
  • (1) : “Mark”로 SampleStruct의 name이 초기화된다.
  • (2) : myStcut1 인스턴스의 복사본(struct - myStruct2)을 만들고,
  • (3) : name 프로퍼티를 변경하였다.

 

myStruct1, myStruct2의 name 프로퍼티는 각각 Mark, David로 다르게 출력된다. (당연하쥐)

  • myStruct2는 myStruct1의 복사본이므로 아래와 같이 자신만의 데이터를 가지게 된다.
  • 그러므로 (3)에서 myStruct2의 name만 변경되었다.

 

 

class SampleClass {

	var name: String
	
	init(name: String) {
		self.name = name
	}
}

let myClass1 = SampleClass(name: "Mark")  // (1)
print(myClass1.name)

var myClasst2 = myClass1  // (2)
myClass2.name = "David"   // (3)

print(myClass1.name)      // David
print(myClass2.name)      // David
  • (1) : myClass1 인스턴스 생성
  • (2) : myClass2는 myClass1이 가리키는 메모리 주소를 가진다. 같은 공간을 공유한다.
  • (3) : name 프로퍼티를 변경한 것이 myClass1과 myClass2 모두에 영향을 미쳤다.
    • 동일한 클래스 인스턴스에 대한 참조제들이기 때문이다.

 

 

정리: 구조체, 클래스의 차이

  • 값 타입 - 참조 타입
  • 구조체는 클래스에 있던 ‘상속’, ‘하위클래스’ 지원x
  • 구조체는 소멸자 메서드(deinit) 포함x
  • 런타임시에 클래스 인스턴스의 유형을 식별할 수 있지만, 구조체는 그렇지 않다.
    • 클래스  : 상속 + protocol(option), 상속을 고려해서 짜는 경우가 많다.
    • 구조체  : 상속x, protocol, 어떤 프로토콜을 채택할지 고려하여 짠다.
  • 상속관계가 없다보니 구분이 힘들다. 클래스는 내부에서 구분이 가능하다. 구조체는 근본적으로 프로토콜을 많이 응용하여 확장한다.

 

 

4️⃣ 구조체와 클래스는 언제 사용하는가

  • 구조체가 클래스보다 효율적이고 멀티스레드 코드를 사용하는데 더 안정적
    • 가능하다면 구조체를 권장 (apple의 철학 중 하나. 그래도 class base도 많다)
    • 만들 수 있다면 구조체를 먼저 생각해달라 는게 swift의 기본 입장
  • But, 클래스 사용이 필요한 경우
    • 상속이 필요하거나 데이터가 캡슐화된 하나의 인스턴스가 필요할 때는 클래스를 사용해야 한다.
    • 또는 deinit을 써야할 때도 클래스를 사용해야 한다.

 

5️⃣ Struct Property, method

struct Sample {
	
	// 가변 프로퍼티
	var mutableProperty: Int = 100

	// 불변 프로퍼티
	let immutableProperty: Int = 100

	// 타입 프로퍼티
	static var typeProperty: Int = 100

	// 인스턴스 메서드
	func instanceMethod() {
		print("instance method")
	}

	// 타입 메서드
	static func typeMethod() {
		print("type method")
	}
}

 

// 가변 인스턴스 생성
var mutable: Sample = Sample()

mutable.mutableProperty = 200

// 불변 프로퍼티는 인스턴스 생성 후 수정할 수 없습니다.
// 컴파일 오류 
//mutable.immutableProperty = 200

// 불변 인스턴스
let immutable: Sample = Sample()

// 불변 인스턴스는 아무리 가변 프로퍼티라도
// 인스턴스 생성 후에 수정할 수 없습니다.
// 컴파일 오류 
//immutable.mutableProperty = 200
//immutable.immutableProperty = 200

// 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod() // type method

// 인스턴스에서는 타입 프로퍼티나 타입 메서드를 사용할 수 없습니다.
// 컴파일 오류 발생
//mutable.typeProperty = 400
//mutable.typeMethod()

 

 

 

📒 요약

Struct

구조체 ↔ 클래스

  • 공통점
    • 프로퍼티 정의, 값 저장
    • 메서드를 정의할 수 있는 개체 생성 메커니즘 제공
  • 차이점
    • 구조체 인스턴스가 복사되거나 메서드로 전달되면 완전히 새로운 복사본이 생성되고, 복사본 자신의 데이터를 가진다.
    • 클래스만 가지는 고유한 기능은 상속+소멸자를 지원한다는 것 + 런타임에서 클래스 타입을 식별할 수 있다는 것
    • 클래스만의 기능이 필요하지 않다면 일반적으로 클래스 대신에 구조체를 사용해야 한다.

 

 

Swift

Property Wrapper

목표

  • 앞서 배운 것
    • 클래스
    • 구조체
  • 이번에 배울 것
    • 프로퍼티 래퍼(property wrapper) 형태
    • Swift 5.1부터 나온 프로퍼티 래퍼는 클래스와 구조체 구현부에 getter, setter computed property 코드의 중복을 줄이는 방법을 제공한다.

뻔한 게터 세터는 모아두고 정리해보자잇. 별을 달아두고 필요할 때마다 필요한 곳에 그 별만 가져와서 달자. 로 생각하면 쉽다.

 

 

 


 

✔️ Property wrapper

멀티 스레드

동시에 여러가지가 작동되는 경우: 로그인할때 로그인 중 스핀 돌면서 서버와 통신할 때

Data 복제품을 날려주면 서로 건드려도 영향을 안준다. struct로 만들고 value 기반의 copy

동시에 데이터를 가져와서 여기저기서 쓸 수 있따굿.

 

수업들으면서 뭐라 적어놨는데 제대로 된 문장이 아니어서 신경쓰지말고 넘어가십셔.

 

1️⃣ 개념

  • 클래스,구조체 인스턴스에 있는 프로퍼티에 값을 할당하거나 접근할 때 저장하거나 읽어내기 전에 변환하거나 유효성 검사를 해야할 경우가 있다 → 이 작업은 연산 프로퍼티로 구현
  • getter, setter를 이용해왔다.
  • 근데 여러 클래스나 구조체에서 생성한 연산 프로퍼티들이 유사한 패턴을 갖는 경우가 빈번하게 발생한다.
    → 비슷~한 애들이 많다는 거임
  • 비슷한 로직을 공유하는 방법은 코드 복-붙하는 것밖에 없었음
    • 너무 비효율적이고 수정이 필요하면 일일이 다 찾아 들어가야했음
  • 이런 단점을 개선하기 위해 Propery wrapper 기능 도입.
    • 기본적으로 연산 프로퍼티 기능을 개별

 

2️⃣ 언제 프로퍼티 래퍼를 사용할까?

프로퍼티 예제를 이해하는 가장 좋은 방법 == 간단한 예제

 

1. 도시 이름을 저장하는 Sting 구조체가 있다고 하자.

struct Address {
	private var cityname: String
}

 

사용자가 도시 이름을 어떻게 입력했는지 상관없이 대문자로 저장되어야 한다면, 다음과 같이 연산 프로퍼티를 구조체에 추가할 수 있다.

struct Address {
	private var cityname: String

	var city: String {
		get { cityname }
		set { cityname = newValue.uppercased() }
	}
}

var address = Address()
address.city = "London"
print(address.city)      // LONDON

도시 이름이 프로퍼티에 할당되면 연산 프로퍼티의 세터가 cityname 변수에 값을 저장하기 전에 대문자로 변환하게 된다.

 

2. 연산 프로퍼티를 사용하는 대신, 이 로직을 프로퍼티 래퍼로 구현할 수 있다.

예) 다음의 선언부에 문자열을 대문자로 변환하도록 설계된 FixCase라는 프로퍼티 래퍼를 구현한다.

@propertyWrapper
struct FixCase {
	private(set) var value: String = ""
	
	var wrappedValue: String {
		get { value }
		set { value = newValue.uppercased() }
	}

	init(wrappedValue initalValue: String) {
		self.wrappedValue = initalValue
	}
}

 

 

3️⃣ 프로퍼티 래퍼의 특징

프로퍼티 래퍼는

  • @propertyWrapper 지시자(annotaion @)를 이용하여 선언
  • 클래스구조체 안에서 구현된다. (구조체를 더 많이 볼 것)

 

  • 모든 프로퍼티 래퍼는 값을 변경하거나 유효성을 검사하는 게터와 세터 코드가 포함된 wrappedValue 프로퍼티를 가져야 한다.
  • 초기값이 전달되는 초기화 메서드는 선택사항으로 포함될 수 있다.
  • 앞의 코드에서는 초기값을 문자열을 대문자로 변환하고 private 변수에 저장하는 프로퍼티에 할당한다.

 

이와 동일한 동작이 필요한 다른 프로퍼티 변수에 적용하여 재사용할 수 있다.

  • 이 동작이 필요한 클래스, 구조체의 선언부에 있는 프로퍼티 선언 앞에 @FixCase 지시자 를 붙이면 된다.
struct Contact {
	@FixCase var name: String
	@FixCase var city: String
	@FixCase var country: String
}

var contact = Contact(name: "John Smith", city: "London", country: "United Kingdom")
print("\\(contact.name), \\(contact.city), \\(contact.country)")

// JOHN SMITH, LONDON, UNITED KINGDOM

 

 

4️⃣ 여러 변수와 타입 지원하기

앞의 예제에서 프로퍼티 래퍼는 래핑되는 프로퍼티에 할당되는 값의 형태로 단 하나의 값을 받았다.

어떤 작업을 수행할 때 사용될 여러 값을 받도록 좀 더 복잡한 프로퍼티 래퍼를 구현할 수도 있다.

추가되는 값들은 프로퍼티 래퍼 이름 다음의 괄호 안에 둔다.

 

지정된 값으로 사용하도록 설계된 프로퍼티 래퍼는 다음의 형태와 같다.

struct Demo {
	@MinMaxVal(min: 10, max: 150) var valud: Int = 100
}

@MinMaxVal 이 어떤 프로퍼티 래퍼인지 짐작해보자.

값의 범위가 적절히 바뀔 수도 있으니 값의 범위를 지정해두고 확인하는 것으로 보인다.

 

다음은 앞의 MinMaxVal 프로퍼티 래퍼를 구현하는 코드이다.

@propertyWrapper
struct MinMaxVal {
	var value: Int
	let max: Int
	let min: Int

	init(wrappedValue: Int, min: Int, max: Int) {
		value = wrappedValue
		self.min = min
		self.max = max
	}

	var wrappedValue: Int {
		get { return value }
		set {
			if newValue > max {
				value = max
			} else if newValue < min {
				value = min
			} else {
				value = newValue
			}
		}
	}

}
  • init() 메서드
    • 래퍼 값에 추가된 min, max 값을 받아서 구현된다.
  • wrappedValue setter
    • 값이 특정 범위 안에 있는지를 검사하여 그 값을 min 또는 max에 할당한다.

 

앞의 프로퍼티 래퍼는 아래의 코드로 테스트할 수 있다.

struct Demo {
	@MinMaxVal(min: 100, max: 200) var value: Int = 100
}

var demo = Demo()
demo.value = 150
print(demo.value)  // 150

demo.value = 250
print(demo.value)  // 200
  • 150 출력
    • 150은 허용범위 안에 들어오기 때문에 150이 잘 출력된다.
  • 200 출력
    • 프로퍼티 래퍼가 값을 200으로 제한하므로 250은 잘리고 200이 출력된다.

 

 

그런데 잘 살펴보면, 현재 구현된 프로퍼티 래퍼는 정수형(Int)값만 가지고 작업한다.

  • Integer말고 다른 타입도 사용할 수 있으면 더 유용하지 않을까?
    • 다른 타입과 작업할 수 있긴하다!
  • 위에 작성된 프로퍼티 래퍼(MinMaxVal)의 목적 → 비교 작업
    • Foundation 프레임워크에 포함된 Comparable 프로토콜을 따르는 모든 데이터 타입을 지원하도록 수정해야 한다.
    • Comparable 프로토콜을 따르는 타입은 값이 같은지, 더 큰지, 더 작은지 비교하는데 사용될 수 있다.
    • String, Int, Date, DateInterval, 그리고 Character 같은 다양한 타입이 이 프로토콜을 따른다.

 

 

Comparable 프로토콜을 따르는 모든 타입에 사용될 수 있는 프로퍼티 래퍼를 구현하기 위해서 기존의 MinMaxVal 구조체를 아래와 같이 수정할 수 있다.

@propertyWrapper
struct MinMaxVal<V: Comparable> {
	var value: V
	let max: V
	let min: V

	init(wrappedValue: V, min: V, max: V) {
		value = wrappedValue
		self.min = min
		self.max = max
	}

	var wrappedValue: V {
		get { return value }
		set {
			if newValue > max {
				value = max
			} else if newValue < min {
				value = min
			} else {
				value = newValue
			}
		}
	}

}

이렇게 수정하고 나면 Int값으로도 동작할 수 있고 Comparable 프로토콜을 따르는 다른 모든 타입에도 사용할 수 있다.

 

 

  • String

예) 문자열 값이 알파벳 관점에서 최솟값과 최댓값 범위 안에 들어오는지를 판단하는 코드이다.

struct Demo {
	@MinMaxVal(min: "Apple", max: "Orange") var value: String = ""
}

var demo = Demo()
demo.value = "Banana"  // Banana는 주어진 알파벳 범위 내에 있어서 저장이 된다.
print(demo.value)  // Banana

demo.value = "Pear"   // Pear는 주어진 알파벳 범위 밖이므로 지정한 최댓값으로 대체된다.
print(demo.value)  // Orange

 

  • Date 객체로도 동작한다.

예) 현재 날짜와 한달 후 날짜 사이의 데이터로 제한하는 코드이다.

struct DateDemo {
	@MinMaxVal(min: Date(), max: Calendar.current.date(byAdding: .month, value: 1, to: Date())!) var value: Date = Date()
}

var dateDemo = DateDemo()
// default로 현재 날짜가 프로퍼티에 설정되었다.
print(dateDemo.value)   // 2022-08-23 20:05:13 +0000

// 프로퍼티에 10일 후의 날짜를 설정한다: 유효범위 내에 있으므로 프로퍼티에 저장된다.
dateDemo.value = Calendar.current.date(byAdding: .day, value: 10, to: Date())!)
print(dateDemo.value)   // 2022-09-22 20:05:13 +0000

// 프로퍼티에 2달 후의 날짜를 설정한다: 유효범위 밖이므로 프로퍼티에는 최댓값(1달)이 저장된다.
dateDemo.value = Calendar.current.date(byAdding: .month, value: 2, to: Date())!)
print(dateDemo.value)   // 2022-09-23 20:08:54 +0000

 

 

📒 요약

  • Swift 5.1 에서 도입된 프로퍼티 래퍼!
  • 클래스 및 구조 선언 내에서 코드의 중복을 피하면서 앱 프로젝트의 코드를 통해 재사용되는 프로퍼티 게터와 세터의 구현체를 사용할 수 있게 한다.
  • 프로퍼티 래퍼의 선언
    • @propertyWrapper 지시자를 이용하여
    • 구조체 형태로 선언 (클래스도 가능)
  • 강력한 스위프트 기능으로, 우리가 만든 동작(작업)을 Swift 코드에 추가할 수 있게 한다.
  • iOS SDK로 작업하다 보면 이런 프로퍼티 래퍼를 접할 수 있다.
  • 실제로 미리 정의된 프로퍼티 래퍼는 나중에 설명할 SwiftUI 작업을 할 때 광범위하게 사용된다.

 

 

 


 

Swift

Colletion - Array, Dictionary
  • 앞서 배운 것
    • 클래스, 상속
    • 구조체
    • 프로퍼티 래퍼
  • 이번에 배울 것
    • Swift의 배열(array)딕셔너리(dictionary)는 다른 객체들의 집합을 담을 수 있는 객체다.
    • 배열과 딕셔너리로 작업하는 기본적인 방법

 

 


 

✔️ Collections

  • class, struct → type, 타입() → instance → array, dictionary

배열도 하나의 타입이라고 볼 수 있다. 근데 모든 배열, 딕셔너리가 타입인 것은 아니고...(??)

 

가변형 컬렉션과 불변형 컬렉션

Collection - Array, Dictionary set
  • Swift Collection
    • mutable, immutable

변경 가능 여부에 따라 나뉜다.

 

 


 

 

1️⃣ Array

배열은 하나의 순서있는 컬렉션에 여러 값을 담기 위하여 특별하게 설계된 데이터 타입이다.
  • 엄밀하게 하나의 스위프트 배열 → 동일한 타입의 값들만 저장 가능
  • 그러나! 여러 타입이 혼합된 배열 생성 가능
  • 배열의 타입
    • type annotation : 구체적으로 지정
    • type inference : 컴파일러가 식별

 

배열 초기화

아래의 구문으로 배열을 생성할 때 값들을 가지도록 초기화할 수 있다.

  • 배열 리터럴(array literal)
var 변수명: [타입] = [값1, 값2, 값3]

타입의 인스턴스들이 담기는 배열의 타입

 

 

아래의 코드를 보자.

이 인스턴스는 Swift 컴파일러는 타입 추론을 이용하여 배열이 String 타입의 값을 담고 있다고 판단하며, 앱 코드 어디에서든지 다른 타입의 값이 배열에 추가되지 않도록 막을 것이다.

var treeArray = ["Pine", "Oak", "Yew"]
// Swift 컴파일러는 위의 코드를 [String] 타입으로 추론할 것이다.

var treeArray: [String] = ["Pine", "Oak", "Yew"]

 

 

Creating an Empty Array

  • 배열을 생성할 때 반드시 값을 할당할 필요는 없다. 빈 배열을 생성할 때 사용해보자.
var 변수명 = [타입]()

var priceArray = [Float]()

 

 

Creating an Array with a Default Value

  • 배열의 각 항목마다 지정된 디폴트 값으로 미리 설정하여 배열이 특정 크기로 초기화하도록 할 수 있다.
var nameArray = [String](repeating: "My String", count: 10)

 

MyStrin이라는 문자열로 배열의 각 항목이 초기화된다. 즉, nameArray는 “My String”을 10개를 가진 배열이다.

 

 

Creating an Array by Adding Two Arrays Together

  • 기존의 배열 2개를 합하여(배열 모두 동일한 타입의 값을 가지고 있을 때) 새로운 배열 생성 가능
let arr1 = ["Red", "Green", "Blue"]
let arr2 = ["Indigo", "Violet"]

let arr3 = arr1 + arr2

 

 

배열로 작업하기

배열 항목 개수

  • 하나의 배열에 들어있는 항목들의 개수는 배열의 count 프로퍼티에 접근하여 얻을 수 있다.
var treeArray = ["Pine", "Oak", "Yew"]
var itemCount = treeArray.count

print(itemCount)   // 3

 

  • 다음과 같이 불리언 타입인 isEmpty 프로퍼티를 이용하면 배열이 비었는지 알 수 있다.
var treeArray = ["Pine", "Oak", "Yew"]

if treeArray.isEmpty {
	// 배열이 비어있다.
}
if treeArray.count < 1 { ... }
if treeARray.count == 0 { ... }

// 위 둘다 가능, 근데 첫번째가 더 낫긴하다! 
// swift에서는 isEmpty가 있으니까 그걸 쓰자. isEmpty가 더 빠르다.

 

 

배열 항목 접근하기

  • 인덱스 첨자(index subscripting)라 불리는 기술을 이용하여 배열 인덱스의 항목 위치를 참조하여 배열의 특정 항목에 접근하거나 수정할 수 있다.
var treeArray = ["Pine", "Oak", "Yew"]

print(treeArray[2])    // Yew

 

 

배열 항목 섞기와 무작위로 가져오기

  • 배열 객체의 shuffled() 메서드를 호출하면 항목의 순서가 무작위로 섞인 새로운 버전의 배열이 반환된다.
let shuffledTrees = treeArray.shuffled()
  • 배열의 항목을 무작위로 선택하여 접근하려면 randomElement() 메서드를 호출하면 된다.
let shuffledTree = treeArray.randomElement()

 

 

배열에 항목 추가하기

  • 배열의 항목은 append 메서드나 +, += 연산자로 추가한다.
treeArray.append("Redwood")
treeArray += ["Redwood"]
treeArray += ["Redwood", "Maple", "Birch"]

 

 

항목 삽입하기

  • 배열에 삽입될 새로운 항목 → insert(at:) → 특정 인덱스 위치에 삽입 가능
  • 삽입을 하면 새로운 항목이 삽입되는 인덱스 위치를 포함하여 그 뒤에 있던 기존 항목들은 오른쪽으로 한칸씩 이동하게 된다.
treeArray.insert("Maple", at: 0)

 

 

항목 삭제하기

  • 배열의 특정 인덱스 위치에 있는 항목 → remove(at:)로 삭제 가능
treeArray.remove(at:2)

// 더 안전하게...
if treeArray.count > 2 {
	treeArray.remove(at:2)
}
  • 배열의 마지막 항목을 삭제하려면 → removeLast()
treeArray.removeLast()

 

 

 

배열 반복하기

  • for-in 반복문 이용
let treeArray = ["Six eggs","Milk","Flour","Baking Powder","Bananas"]

for item in shoppingList {
    print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas

shoppingList에서 하나씩 꺼낸다 → 꺼내진 것의 이름을 tree라고 한다 → print 출력

 

 

 

타입이 혼합된 배열 생성하기: Any

피하자.

타입이 혼합된 배열 → 서로 다른 클래스 타입의 항목들을 담을 수 있는 배열

물론 String 타입을 받도록 선언된 배열이라면 String 클래스 객체가 아닌 인스턴스는 담을 수 없다.

그러나 Any 타입을 쓴다면?

  • Swift의 Any
    • 특별한 타입, 지정된 클래스 타입이 아닌 객체를 참조하는데 사용
    • 그러므로 Any 객체 타입을 포함하도록 선언된 배열은 여러 타입의 항목을 담을 수 있다.
let treeAarry:[Any] = [45, 1, 3, "Hello", 2]

 

  • Any 타입은 주의해서 사용해야한다.
    • 코드상에서의 프로그래머 실수로 인한 오류가 발생할 수 있다. (Any를 사용하며 Swift는 올바른 타입의 요소들이 뱅려에 포함되었다고 간주하므로)
    • Any 배열을 사용하게 된다면 → 배열의 요소에서 요소를 사용할 때 올바른 타입으로 형변환을 해야하는 경우가 종종 생긴다.
    • 올바르지 않은 타입으로 형 변환을 할 경우 → 컴파일 오류는 발생하지 않겠지만.. 런타임에서 충돌이 발생할 것이다.

 

let mixedArray: [Any] = [1, 2, 45, "Hello"]

for object in mixedArray {
	print(object * 10)
}

위의 코드를 실행하면 어떻게 될까?

Any타입과 Int 타입의 곱셈 연산이 불가능하다는 구문 오류가 발생할 것이다.

  • 오류를 없애기 위해 배열의 요소를 Int 타입으로 다운캐스팅(downcast)해야 한다.
  • Any타입과 Int타입의 곱셈연산? → 구문 오류

 

 

그럼 아래의 코드는 어떨까.

for object in mixedArray {
	print(object as! Int * 10)
}

// Cloud not cast value of type 'switf.string' to 'swift.Int'

mixedArray의 요소들 중 처음부터 1, 2, 45 까지는 문제없이 계산이 될 것이다. Int 형 이므로 곱하기 10이 계산이 된다.

하지만 “Hello”를 만난다면 어떻게 될까?

Int 요소들까지는 아무렇지 않다가 String 요소에 다다랐을 때 에러메세지와 함께 충돌이 발생하게 된다.

그러므로 위 코드는 배열에 있는 각 항목의 특정 타입을 식별하도록 수정되어야 한다.

 

  • Any 타입 사용은 가급적 자제하자.
  • 그래도 실제 프로젝트에서 쓰는 경우도 있다. 네트워크 통신을 할 때는 어쩔 수 없이 Any로 받아서 따로 처리하기도 한다.

 

 


 

2️⃣ Dictionary

Key-Value 쌍의 형태로 데이터를 저장하고 관리할 수 있게 해준다.
  • 딕셔너리는 배열과 비슷한 목적의 작업을 실시하지만….
  • 딕셔너리에 저장된 각 항목은 연관된 값을 참조하고 접근하는 데 사용되는 유일한 키(정확히 말하면 키는 특정 딕셔너리 객체에서 유일하다)와 연결되어 있다는 점이 다르다.
  • 딕셔너리의 키로 사용할 수 있는 타입
    • String
    • Int
    • Double
    • Bool

 

Creating a Dictionary with a Dictionary Literal

딕셔너리 초기화

순서가 없는 단일 컬렉션에 여러 값을 담기 위해 설계된 특별한 타입
  • key와 value의 데이터 타입은..
    • type annotaion : 구체적으로 지정
    • type inference : 컴파일러가 식별
  • dictionary literal
    • 새로운 딕셔너리는 다음의 구문을 이용하여 생성 시에 값들의 컬렉션으로 초기화할 수 있다.
var 변수명: [키타입,값타입] = [키1:값1, 키2:값2]

 

  • 배열처럼 빈 딕셔너리를 생성할 수 있다.
var 변수명 = [키 타입: 값 타입]()

 

  • 다음의 코드는 정수형 키와 문자열 값을 저장하기 위하여 설계된 빈 딕셔너리를 생성한다.
var myDictionary = [Int: String]()

 

 

Creating an Empty Dictionary

시퀀스 기반의 딕셔너리 초기화
  • 딕셔너리는 키들과 값들을 나타내는 시퀀스를 이용하여 초기화될 수도 있다.
  • 이것은 키들과 값들을 Swift의 zip()함수에 전달하면 된다.

 

두 개의 배열을 이용하여 딕셔너리를 생성해보자.

var keys = ["100-432112", "200-532874", "202-546549", "104-109834"]
var values = ["Wind in the Willows", "Tale of Two Cites", "Sense and Sesibility", "Shutter Island"]

var bootDict = Dictionary(uniqueKeysWithValues: zip(keys, values))

 

미리 정해진 키 배열이 아니라 1부터 시작하는 숫자를 키로 지정할 수 있다.

var values = ["Wind in the Willows", "Tale of Two Cites", "Sense and Sesibility", "Shutter Island"]

var bootDict = Dictionary(uniqueKeysWithValues: zip(1..., values))

 

 

딕셔너리 항목의 개수

  • count 프로퍼티
print(bootDict.count)   // 4

 

 

딕셔너리 항목 접근하기

  • key를 이용하여 value에 접근할 수 있다.
print(bootDict["100-432112"])

 

  • 딕셔너리 항목에 접근할 때도 지정된 키에 해당하는 값이 없는 경우, 사용될 디폴트 값을 선언할 수 있다.
  • 우리가 만든 딕셔너리에는 지정된 키에 대한 항목이 없기 때문에 아래 코드는 default 텍스트를 출력할 것이다
print( bootDict[""], default: "Book not found" )

 

 

딕셔너리 항목 갱신하기

  • 특정 키와 연결된 값을 갱신할 수 있다.
bootDict["200-532874"] = "Sense and Sensibility"
  • 변경될 값과 해당 키를 전달하여 updateValue(forKey:) 메서드를 호출해도 같은 동작을 한다.
bootDict.updateValue("The Rain", forkey: "200-532874")

 

 

딕셔너리 항목 추가하기, 제거하기

  • 어떤 키-값 쌍을 딕셔너리에서 제거할 때는 해당 항목에 nil 값을 할당하거나 딕셔너리 인스턴스의 removeValue(forKey:)메서드를 호출하면 된다.
bookDict["300-898871"] = nil
bookDict.removeValue(forKey: "300-898871")

 

 

딕셔너리 반복

  • 배열과 마찬가지로 for-in반복문을 이용하여 딕셔너리의 항목들을 반복할 수 있다.
for (bookId, title) in bookDict {
	print("Book ID : \\(bookId) Title : \\(title)")
}

/*
BookID: 100-432112 Title: Wind in the Willows
BookID: 200-532874 Title: The Ruins
BookID: 104-109734 Title: Shutter Island
BookID: 202-546549 Title: Sens and Sesibility
*/

 

 


 

 

📒 요약

배열과 딕셔너리 컬렉션으로 작업하기
  • Swift의 컬렉션은 딕셔너리(Dictionary), 세트(Set) 또는 배열(Array)의 형태를 취한다.
    • 이들 모두 하나의 객체에 여러 항목을 담을 수 있는 방법 제공!
  • Array
    • 항목들을 순서대로(index)로 담을 수 있는 방법 제공
    • 인덱스로 항목에 접근함
  • Dictionary
    • key-value

 

 

 

 



 

 

마지막 남은 시간동안 Playground를 했는데... 어려웠다 ㅋㅋㅋㅋㅋㅋ 인스턴스 생성새ㅓ 뭐..하고 

바다를 둘러싼 섬만드는 맵에서 시간이 다되서 끝을 냈다. 코슥머슥

 

 

 

 

  Comments,     Trackbacks