티스토리 뷰
INDEX
1. 옵셔널(Optional) 타입?
2. ⭐️Optional Unwrapping 4가지 방법
3. ⭐️옵셔널 체이닝(optional chaining)⭐️
4. IUO 타입(Implicitly Unwrapped Optional)
5. 함수의 파라미터로 옵셔널 타입
6. 마무리
1. 옵셔널(Optional) 타입?
1-1. Optional 타입과 nil에 대한 기본적인 설명
옵셔널(Optional) 타입이란(직역: 선택적인) 말 그대로 값이 있을 수도 있고 없을 수도(nil) 있음을 의미한다.
옵셔널을 이해하기 위해서는 먼저 스위프트라는 언어의 특징중 하나인 안정성(safety)과 nil에 대해 알 필요가 있다.
(다른 언어에서는 값이 없음을 NULL이라고 하지만) 스위프트에서는 값이 없음을 nil이라고 표현한다.
스위프트는 앱 개발을 위해 만들어진 언어인 만큼, 안정성을 중시하여, 오류에 있어 민감하기에
nil값을 가질 수 있는 타입(Optional Type)과 nil값을 가질 수 없는 타입(Non-Optional Type)을 구분지어 놓았다.
쉽게 말하면, 옵셔널타입은 오류가 발생할 가능성(값이 없을 수 있는 가능성, nil일 가능성)을 갖고 있는 타입인 것이다.
일반적인 타입(Int, Double, String...)은 nil값을 가질 수 없다(이를 Non-Optional Type이라고 한다).
하지만 옵셔널 타입은(Int?, Double?, String?)은 Int, Double, String도 가질 수 있고 nil값 역시 에러없이 가질 수 있다(동일 풀이: 당연히 값이 없을 수도 있는 것이다).
이를 통해, 옵셔널 타입이 Non-Optional type보다 더 넓은 범위에 속한다!는 걸 알 수 있다.
따라서 하나의 데이터를 옵셔널 타입으로 정의하면 그 값이 없더라도, 에러없이 nil값으로 반환을 하기 때문에, 스위프트에서는 오류가 발생할 가능성이 조금이라도 있다면 모두 옵셔널타입으로 정의하고자 한다.
간단하게 정리하면, Optional이란 선언하고자 하는 타입에다가 값이 없을 수도 있는(nil일수도 있는) 가능성을 가지도록 하는 타입으로 swift언어의 안정성을 높이기 위해 만들어진 타입이라고 볼 수 있다. 옵셔널 타입에 대한 기본적인 설명이 길었다. 하지만 옵셔널타입을 정확히 이해하기 위해서는 이러한 타입이 왜 필요하고, 일반적인 타입과 왜 구분해서 사용해야 하는지는 명확히 알 필요가 있다. 아래의 코드를 보면 더 쉽게 이해할 수 있을 것이다.
1-2. 옵셔널 타입의 선언
정식문법: Optional<type>
단축문법: 타입옆에 ? 붙임
let num1: Optional<Int> = 0 // 정식문법
let num2: Int? = 2 // 간편표기
1-3. 옵셔널 타입 선언의 규칙
nil 대입이 가능하고, 값을 초기화하지 않을시, 자동적으로 nil값이 대입된다.
// 예제코드
var yourAge: Int? //Type Annotation으로 타입만 명시
var yourGrade: Double? //Type Annotation으로 타입만 명시
var myAge: Int //Type Annotation으로 타입만 명시 (반면 Non-Optional 타입 Int)
//
////1) nil 대입 가능
yourAge = nil
//myAge = nil //error. Non-Optional 타입은 nil 대입 불가능
//
////2) 자동 초기화 (값을 넣지 않을 시에 nil로 초기화) ⭐️
print(yourGrade) //nil
//print(myAge) //error. 값에 대한 초기화 X
1-4. Optional과 Non-Optional은 엄연히 다른 타입(Optional이 더 포괄적인 개념)
//예제코드
var a: Int? = 7 // Optional Int
var b: Int = 0 // Non-Optional Int
a = b //Int를 Int?에 대입가능 Optional은 Non-Optional을 담을 수 있지만,
//b = a // error. Int를 Int?에 대입 불가능. Non-Optional은 Optional을 담을 수 없다
var c: Int?
//a + b // error. Optional타입과 Non-Optioanl타입간 계산불가
//a + b //error (옵셔널 타입 상태로는 계산 불가. unwrapping필요)
//a + c //error (옵셔널 타입 상태로는 계산 불가. unwrapping필요)
print(a) // Optional(7)
위 예제 코드에서 우리는 Optional(7)말고, 7이 출력되고, 옵셔널 타입이어도 연산이 가능하기를 기대할 것이다. 하지만 앞서 말했듯 옵셔널 타입은 값이 없을(nil) 수도 있는 경우를 포함하는(wrapping) 있는 임시적 타입이기에 옵셔널 값을 사용하기 위해서는 nil로 한 껍질 감싸고 있는 상태(wrapping상태)의 옵셔널 타입을 unwrapping하는 방법에 대해 알아야 한다.
2. Optional Unwrapping 4가지 방법
2-1. Forced Unwrapping(강제 값 추출)
2-2. if문으로 nil 아닌지 확인후, !붙여서 Unwrapping
var num: Int?
var str: String? = "안녕하세요"
//2-1. Forced Unwrapping: 강제 추출 연산자 !붙이기 (값이 있다고 확신할때 사용한다)
print(str!) //안녕하세요
//2-2. if문으로 nil아닌지 확인후 ! Unwrapping
if str != nil {
print(str!)
}
2-3. ⭐️옵셔널 바인딩(if let 바인딩과 guard let 바인딩)⭐️
// 2-3 if let 바인딩
var optionalName: String? = "홍길동"
if let name = optionalName { //Optional타입 optionalName 이 상수 name에 담기면?
print(name) //name Unwrapping 성공)
}
//Non-Optional 타입인 상수 name에 대입이 가능하다는건, 값이 있음을 (nil이 아님을) 뜻하기 때문이다.
guard let 바인딩을 하기 위해서는 먼저 guard문에 대한 설명이 필요하다.
guard문은 언제 사용할까?
guard문은 if문의 중복 조건 검사의 단점을 보완하기 위해 나왔으며 else문을 먼저 배치하여 조기종료 시킬때 사용한다. 조기종료 시킨다는 점에서, 함수, 반복문내에서만 사용이 가능하다.
guard문의 조건을 만족하면 코드는 guard문을 지나 다음줄로 넘어가서 계속 실행되며 guard문에서 선언된 변수, 상수는 아래문장에서도 사용가능하다(guard let 바인딩이 가능한 이유)
Guard문 기본 문법: guard 조건문 else { code (종료 코드 포함) }
//guard문 예시
func check(words: String) -> Bool {
guard words.count >= 5 else {
print("5글자 미만입니다.")
return false // 종료 조건 - 함수 내에서 return / throw / break
}
print("\(words.count)글자입니다.")
return true
}
check(words: "안녕하세") // 5글자 미만입니다.
check(words: "안녕하세요") // 5글자 입니다.
아래는 guard let바인딩의 예시코드로 실제로 앱을 만들때 자주 사용된다.
//2-3 guard let 바인딩 예시
func doSomething(name: String?) { //함수의 파라미터로 옵셔널 타입
guard let n = name else { return } //Optional타입 name이 상수 n에 담겨?
print(n) // n값 출력
}
doSomething(name: "hello") //hello
2-4. 닐 코어레싱(nil-coalesce) 연산자 사용
??로 옵셔널 타입에 디폴트(기본)값을 제시하여 nil일 가능성을 배제시킨다. 코드로 쉽게 이해가능하다.
//2-4 닐 코얼레싱(nil-coalesce) 예시
var serverName: String? = "홍길동"
var serverName2: String?
var userName = serverName ?? "미인증사용자" // String타입
print(userName) //홍길동
var userName2 = serverName2 ?? "미인증사용자"
print(userName2) //미인증사용자
//위 코드를 이해했다면, 아래로 넘어가자
var str2: String? = "안녕하세요"
str2 = nil
print(str2 ?? "Say, Hi") //str2가 nil이므로, Say, Hi를 그대로 값으로 받아 출력한다.
위에서는 길게 설명을 한 것 같지만, 옵셔널이 무엇을 의미하는지와 옵셔널을 Unwarapping 하는 방법밖에 다루지 않았다. 다음으로 소개할 부분은 옵셔널 체이닝(Optional Chaining)이다.
옵셔널 체이닝은 여러값이 중첩된 형태를 띠고있고, 다소 헷갈리게 느껴질 수 있다는점에서 이해하기 어려울 수 있으나, 옵셔널 체이닝 문법을 알지 못한다면 옵셔널을 편리하게 다루지 못할 것이다. 옵셔널 체이닝에는 차후에 포스팅할 class와 struct, Dictionary가 섞여있다. 원래는 Allen님의 강의, 야곰님의 Swift 프로그래밍 등 (대부분의 학습 자료)에서는 이러한 class와 같은 문법을 먼저 알고 그 다음 옵셔널 체이닝도 공부하게끔 학습 단원이 구성되어있다. 또한 아직 열거형에 대한 소개도 하지 않았다는 점에서(옵셔널타입 자체가 열거형을 사용하여 만들어진 타입으로 열거형 단원을 공부하면 옵셔널에 대한 이해도가 더 높아질 것이다)분명 이 포스팅만으로 옵셔널 체이닝을 완벽하게 다루기에는 한계점을 지닌다.
하지만 필자는 옵셔널에 관해서는 이 포스팅에 최대한 다 담고싶기에, 따로 나누지 않고, 한번에 묶어 정리하였다. 물론 옵셔널체이닝은 차후에 한번 더 심화된 예제를 통해 다루고자 한다.
3. ⭐️옵셔널 체이닝(optional chaining)⭐️
옵셔널 체이닝이란 옵셔널 타입일지도 모르는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정이다.
3-1 옵셔널 체이닝을 통한 기본적인 프로퍼티 접근
먼저 옵셔널 체이닝에 대한 모델클레스(Dog클레스와, Human클레스)를 정의하였다. 옵셔널 체이닝은 ?를 붙이고 안붙이고가 헷갈리고 어렵다는 점에서 case별로 나누어 코드로 예시를 들고 주석을 통해 자세히 적어두었다.
(다소 복잡할 수 있으나 꼼꼼히 알고싶다면 볼 필요가 있다..)
class Dog {
var name: String?
var weight: Int
init(name: String, weight: Int) {
self.name = name
self.weight = weight
}
func sit() { print("\(self.name)가 앉았습니다.")}
func layDown() { print("누웠습니다.") }
func suck() -> Int { return 30 }
}
class Human { var dog: Dog? }
//1. 옵셔널 체이닝을 통해 choco의 name값에 접근해보고 name도 수정해보기
var choco: Dog? = Dog(name: "초코", weight: 15) //choco를 옵셔널 타입으로 정의
//choco.name //error. choco는 옵셔널 타입으로 선언되어있음.(언제든지 nil로 바뀔수도 있음)
choco?.name //초코 ⭐️옵셔널 체이닝됨
choco?.sit() //Optional("초코")가 앉았습니다.
choco?.name = "초코얌"
choco?.sit() // Optional("초코얌")가 앉았습니다.
//2. 옵셔널 체이닝을 통해 human의 dog의 name값에 접근해보고 name도 수정해보기
var human2: Human? = Human() //human2도 마찬가지로 옵셔널 타입으로 정의
human2?.dog = choco
//human2?.dog?는 잘못된 접근.옵셔널 체이닝의 마지막 표현식은 옵셔널이든 아니든 ?를 생략함.(실수할 가능성을 낮춰줌)
human2?.dog?.name //초코얌
print(human2?.dog?.name) // Optional("초코얌")
//옵셔널 체이닝의 기본 사항 3가지
// 1.⭐️옵셔널체이닝이 되었다면,그 타입은 무조건 옵셔널 타입이다(nil값을 가질 수 있는 타입).
// 2.⭐️옵셔널체이닝의 표현식중 하나라도 nil리턴시, ?뒤에 이어지는 건 평가하지않고 nil을 리턴한다.
// 3.⭐️옵셔널 체이닝의 마지막 표현식은 옵셔널이든 아니든 ?를 생략해야 한다.
//1.⭐️옵셔널체이닝이 되었다면, (?와 .로 연결된 각각의 타입과 상관없이) 그 타입은 무조건 옵셔널 타입이다(nil값을 가질 수 있는 타입).
type(of: choco) //Optional<Dog>.Type
type(of: choco?.weight) //Optional<Int>.Type
type(of: human2?.dog?.weight) //Optional<Int>.Type
type(of: choco?.name) //당연히, Optional<String>.Type
//(choco = nil은 이해를 위한 코드임. 실행후 다시 주석 처리하기)
//choco = nil
//type(of: choco?.weight) //Optional<Int>.Type
//
//물론 옵셔널 체이닝된 값을 실제 사용시에는 Unwrapping 필요하다.
//옵셔널 체이닝의 마지막 표현식은 옵셔널이든 아니든 ?를 생략한다고 했지만
//unwrapping시에는 !를 붙여줘야한다(-당연한 것이지만 워낙 헷갈릴 여지가 있기 때문)
print(human2!.dog!.name) // Optional("초코얌") // name 자체가 옵셔널타입. 벗겨줘야한다.
print(human2!.dog!.name!) // 초코얌 //값이 존재한다고 확신하기에 강제추출
print(human2!.dog!.weight) //15 // weight 자체는 Int.
//2. ⭐️ 옵셔널체이닝의 표현식중 중 하나라도 nil리턴시, ?뒤에 이어지는 건 평가하지않고(프로퍼티든 메서드든 뭐든) nil을 리턴한다.
//(아래4줄은 이해를 위한 코드. 실행만 한후 다시 주석 처리하기 하지않으면 아래 코드들이 정상실행되지 않을 것임)
//choco = nil
//choco?.name //nil. name은 볼것도 없이 choco가 nil이므로, nil리턴.
//human2 = nil
//human2?.dog?.name //nil, human2이 nil이므로 nil리턴
// if let 바인딩과 닐코얼레싱(Nil-Coalescing)방법을 이용해 Unwrapping해서 출력해보기
// if let
if let name = human2?.dog?.name { // Optional("초코얌")
print(name) // 초코얌
}
// Nil-Coalescing 연산자
var defaultName = human2?.dog?.name ?? "멍탱구리"
print(defaultName) //멍탱구리
//3.옵셔널 체이닝의 마지막 표현식은 옵셔널이든 아니든 ?를 생략해야한다.(위에서 언급한 것 구체적 설명) -> 상당히 편리함
var human3: Human? = Human()
human3?.dog = choco //choco는 Dog?타입의 변수였음
human3?.dog?.name = "말티즈" //name은 옵셔널 타입이지만 name은 마지막 표현식이므로 ?를 생략
human3?.dog?.name //말티즈
human3?.dog?.weight = 30 //weight은 Non-Optional타입이기에 당연히 생략
print(human3!.dog!.name!)//말티즈 //물론 출력시에는 Unwrapping해야하므로 !로 추출해줘야한다.
3-2 옵셔널 체이닝을 통한 함수 호출
위의 코드들이 이해가 되었다면 옵셔널 체이닝으로 프로퍼티에 접근하는 방법은 잘 알고 있을 것이다. 다음은 옵셔널 체이닝이 함수와 함께 표기된 경우다. 헷갈리게 했던 ?는 함수의 앞뒤에도 붙을 수 있고 형태로 나누면 ?(), ()?, ?()? 3개의 형태로 나눌 수 있다. 공식문서 예제는 class와 계산프로퍼티를 모델로 하는데 이렇게 정의된 모델로 옵셔널체이닝을 처음에 이해하기엔 상당히 버거웠다. 이보다 단순한 개념인 struct를 통해 최대한 쉽게 설명하고자 한다. 아래 코드는 Student구조체에 bookInfo라는 함수를 만들고, 이 함수를 통해 book의 bookName 변수에 접근하고자 한다.
아래의 구조체 모델로 <?(), ()?, ?()?> 3가지 경우에 대해 코드를 통해 설명했다.
struct Book{
var bookName: String
var bookNumber: [String: String]
}
struct Student{
var name: String
var book: Book
init(name: String,bookName: String, bookNumber: String) {
self.name = name
book = Book(bookName: bookName, bookNumber: ["index" : bookNumber])
}
func bookInfo() -> Book{ // return type: Non-Optional
return book
}
func bookInfo2() -> Book? { // return type: Optional
return book
}
}
//옵셔널 Student타입의 me 변수 선언
let me: Student? = Student(name: "이도형", bookName: "Swift", bookNumber: "2023")
//1. ()? : 함수의 리턴 타입이 옵셔널 타입일때
type(of: me) // Optional<Student>.Type
me?.bookInfo2()?.bookName //swift
//me는 옵셔널타입 이므로 당연히 ? 표기, bookInfo2함수의 리턴형도 옵셔널 타입이므로 ()뒤에 ?표기
// ❗️위의 me와 a,b를 헷갈려서는 안된다.
// ❗️ me는 구조체?로 타입이 정의되어있고, a와 b는 Optional<Student>.Type의 func로 타입이 정의
//2. ?() : 함수를 담는 상수(변수)가 옵셔널 함수 타입이나, 그 함수의 리턴 타입은 옵셔널타입이 아닐때
let a = me?.bookInfo //bookInfo의 리턴타입은 Non-Optional 타입
type(of: a) //Optional<() -> Book>.Type a는 옵셔널 타입
a?().bookName //swift
//3. ?()?: 함수를 담는 상수(변수)도 옵셔널 함수 타입이고, 함수의 리턴 타입도 옵셔널 타입일때
let b = me?.bookInfo2 //bookInfo2의 리턴타입은 Optional 타입
type(of: b) //Optional<() -> Optional<Book>>.Type
b?()?.bookName //swift
결론적으로 옵셔널 체이닝하려는 변수나 상수의 타입이 무엇인지, 함수의 리턴 타입이 Optional / Non-Optional인지에 따라 ?의 위치는 결정된다.
3-3 옵셔널 체이닝과 딕셔너리( [ ]?, ?[]? )
(딕셔너리 개념을 알고 보아야 할 것)
위의 옵셔널 체이닝을 통한 함수호출까지 이해가 되었다면 딕셔너리와 함께 표기된 경우는 정말 쉽게 이해할 수 있다.
볼 수 있는 형태는 [ ]? 와 ?[]? 두가지다. 첫번째 [ ]?는 딕셔너리의 기본적인 특성으로 이해할 수 있다.
딕셔너리는 key와 value값으로 이루어져있고, key값을 통해 value를 얻고 싶지만, 그 key에 맞는 값(value)자체가 존재하지 않을 가능성도 높다. 따라서 딕셔너리의 값(value)에 접근 하고 싶다면, 최소 []?로 접근해야한다. 아래의 코드를 보자
//1. []?
var dic: [String: String] = ["name" : "scott", "age" : "23"]
dic["name"]?.description //scott
dic["age"]?.count // 2
//
//2. ?[]?
//두번째 ?[]?는 딕셔너리 타입자체가 옵셔널타입으로 정의된 경우다. 위의 예제를 조금 변형시키면
var dic2: [String: String]? = ["name" : "scott", "age" : "23"]
//아래 nil을 대입하는 2줄은 이해를 위한 코드. 해보고 주석처리
//dic = nil //error (dic은 Non-Optional)
//dic2 = nil //nil. (dic2 optional)
//이는 딕셔너리 자체가 없을수도 있음을 의미하고, 딕셔너리가 있다하더라도 key에 맞는 value값이 없을 수 있음을 의미한다. 따라서 이럴때에는 dic2자체가 nil이 아닌지 먼저 체크해준뒤, value값도 nil이 아닌지 확인해줘야한다.
dic2?["name"]?.description
dic2?["age"]?.description
//아래는 딕셔너리 with 옵셔널체이닝의 예제로 한번 풀어보는 것을 추천한다.
class Person {
var name: String?
}
class Library1 {
var books: [String: Person]?
}
var person1 = Person()
person1.name = "Jobs"
var person2 = Person()
person2.name = "Musk"
var library = Library1()
library.books = ["Apple": person1, "Tesla": person2]
//1. library를 통해 person1과 person2 name변수에 접근해보기
library.books?["Apple"]?.name
library.books?["Tesla"]?.name
//2. if let바인딩을 통하여, 이름: Jobs 형태로 출력해보기
// 실제로 사용하려면? ===> 옵셔널 벗기고 사용해야함
if let name = library.books?["Apple"]?.name {
print("이름:", name)
}
4. IUO 타입(Implicitly Unwrapped Optional)
IUO : 암시적 추출 옵셔널타입으로, nil값을 할당할 수도 있지만, 매번 값을 추출하기가 귀찮고, 로직상 항상 값이 있다는 것이 확신이 들때 사용하는 타입이다. 타입선언시 !가 붙어있다.(Int!)
IUO타입은 벗겨질 준비가 되어있는 타입이지 벗겨져 있는 타입이 아니며 엄연한 optional타입이기에 nil로도 초기화 가능하다. 코드를 보면 이해하기 쉽다.
var number: Int! = 5 //IUO 타입
print(number) //Optional(5)
var number2: Int //Non-Optional 타입
number2 = number //자동으로 Unwrapping되어 5저장
print(number2) //5
var number3: Int!
print(number3) // IUO타입으로 Type Annotation만 한다면, nil로 초기화 되어있음.
하지만 실제로 직접 정의해서 쓸 일은 없으며 이와 같은 강제추출방식은 안전하지 못하다. 그렇다면 Why 알아야할까?
@IBOutlet weak var tableView: UITableView!
1. 스토리보드에서 IBOutlet연결시, 위와 같은 타입으로 자동선언된다.
=> 옵셔널 체이닝을 사용하지 않아도 자동으로 추출되기때문에, 개발자가 실수할 가능성을 낮춰준다
2. API에서 IUO타입으로 리턴하는 경우
(IUO타입은 실제로 자주 사용하기보다는 Apple이 이미 만들어놓은 것을 읽고 이해하기 위한 문법 개념중 하나라고 생각하면 편하다.)
5. 함수의 파라미터로 옵셔널 타입
옵셔널타입의 기본값은 nil로 설정(실제 애플이 미리 만들어 놓은 함수의 옵셔널타입 파라미터도 nil로 설정된게 많다)
사용이유: 기본값으로 nil이 가능하기에, 옵셔널타입은 argument의 생략도 가능하여 함수의 사용이 유연해진다.
// 옵셔널 파라미터 사용 함수의 정의
func doSomePrint(with label: String, name: String? = nil) {
if let x = name{ // if let 바인딩
print("\(label): \(x)")
return
}
print("\(label): \(name)")
}
// 함수의 실행
//doSomePrint(with: String, name: String) //자동생성
doSomePrint(with: "레이블", name: "스티브 잡스") // 레이블: 스티브 잡스
doSomePrint(with: "레이블", name: nil) //레이블: nil
doSomePrint(with: "레이블") //레이블: nil // 옵셔널 타입은 argument의 생략O
6. 마무리
옵셔널은 Swift만이 가지는 타입이었기에, 처음 보면 낯설다. 하지만 옵셔널을 공부하다보면, nil에 대해 자연스럽게 이해하게 되고, 옵셔널 타입이 가지는 장점을 알게되면서 swift가 안정성을 중시하는 언어라는걸 다시 한번 알 수 있었다. 옵셔널은 여러 개념들이 접목되어있기에 한번에 공부하기는 벅찬게 사실이다. 옵셔널 단원은 공부하고 정리하기 위해서는 훨씬 더 많은 노력이 필요했다. 하지만 옵셔널 개념이 들어가 있는것을 최대한으로 묶어서 하나로 정리한다면, 나에게 있어서 개념적으로 더 와닿을 거라 생각했고, 시간이 지나서도 금방 되새길 수 있을거라 생각했다. 물론 열거형에서도 다시 다룰 것이고, 옵셔널타입 계속해서 등장할 것이지만 오늘 정리해둔 것들을 제대로 이해하고 있다면, 옵셔널 타입을 더 편하게 사용할 수 있을 것이다.
Index별로 기억에 남아야될 것은
옵셔널 타입이 무엇을 의미하는지,
옵셔널값을 Unwrapping하는 방법(4가지),
지금까지 배운 것 중 가장 어렵게 느껴졌을 옵셔널 체이닝,
IUO 타입,
옵셔널 파라미터가 어떤건지 안다면, PASS
출처
Inflearn Allen님의 강의 노트
Swift 공식문서
야곰님의 스위프트 프로그래밍 3판
개발자 소들님의 블로그의 옵셔널체이닝을 통한 함수 호출 예제를 모델링 했습니다. ( 쉽게 설명되어, 큰 도움이 되었습니다.)
'Swift' 카테고리의 다른 글
Swift [열거형(Enumerations)] (0) | 2023.02.12 |
---|---|
Swift [Collection Types] (0) | 2023.02.10 |
Swift [Functions] (0) | 2023.02.08 |
Swift [Tuples/Loops] (0) | 2023.02.07 |
Swift [연산자(Operators)와 조건문(if문/switch문)] (0) | 2023.02.05 |