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를 했는데... 어려웠다 ㅋㅋㅋㅋㅋㅋ 인스턴스 생성새ㅓ 뭐..하고
바다를 둘러싼 섬만드는 맵에서 시간이 다되서 끝을 냈다. 코슥머슥
'외부활동 > 멋사 앱스쿨 1기' 카테고리의 다른 글
[TIL] 22-10-04: Swift(Error Handling, Type, Property Observer, enum, Generic, Protocol Oriented) (2) | 2022.10.06 |
---|---|
[TIL] 22-09-30: Open Source, Git/GitHub (0) | 2022.10.03 |
[TIL] 22-09-28: Swift (Inheritance, Subclassing, Extension) (0) | 2022.10.01 |
[TIL] 22-09-27: Swift (객체지향 프로그래밍 기초) (0) | 2022.09.27 |
[TIL] 22-09-26: Swift (함수, 메서드, 클로저) (0) | 2022.09.26 |