옵셔널 체이닝 (Optional Chaining)
옵셔널을 언래핑하는 여러가지 방법
Forced Unwrapping (강제 언래핑): 위험
var x: String? = "Hi"
x! // 만약 x가 nil이면 런타임 에러 발생
Optional Binding (옵셔널 바인딩): 안전
if let a = x {
print(a) // x가 nil이 아닐 경우 a에 값이 할당됨
}
Nil Coalescing Operator (nil 병합 연산자): 안전
let c = x ?? "" // x가 nil이면 빈 문자열이 할당됨
Optional Chaining (옵셔널 체이닝): 안전
let b = x?.count // x가 nil이면 b는 nil
안전하고 간결한 코드의 비밀, 옵셔널 체이닝 (Optional Chaining)
안녕하세요! Swift 개발자라면 누구나 마주치게 되는 Optional. 그리고 이 옵셔널을 더욱 우아하고 안전하게 다룰 수 있게 해주는 옵셔널 체이닝(Optional Chaining)에 대해 알아보겠습니다. 옵셔널 체이닝은 Swift 언어의 강력하고 독특한 기능 중 하나로, 코드의 가독성과 안전성을 크게 향상시켜 줍니다.
🤔 옵셔널 체이닝, 왜 필요할까요? (The Problem)
Swift는 'nil' 값으로 인한 런타임 오류를 방지하기 위해 '옵셔널'이라는 개념을 도입했습니다. 하지만 옵셔널 타입의 값 안에 있는 프로퍼티나 메서드에 접근하려면, 해당 옵셔널이 nil인지 아닌지 먼저 확인해야 하죠.
만약 옵셔널 체이닝이 없다면, 우리는 다음과 같이 중첩된 if let 구문이나 guard let 구문을 사용해야 할 수 있습니다.
class Person {
var job: Job?
}
class Job {
var title: String?
func printTitle() {
if let title = title {
print("직업명: $$title)")
} else {
print("직업명이 없습니다.")
}
}
}
let person = Person()
// let person.job = Job() // 만약 job이 있다면...
// let person.job.title = "Developer" // 만약 title이 있다면...
// 옵셔널 체이닝 없이 접근하려면?
if let job = person.job {
if let title = job.title {
// 드디어 title에 접근!
print("직업명(if let): $$title)")
job.printTitle() // 메서드 호출도 확인 필요
} else {
print("직업명이 없습니다.")
}
} else {
print("직업이 없습니다.")
}
위 예시처럼, 단 한 줄로 여러 단계의 옵셔널 값을 안전하게 탐색할 수 있습니다. 코드가 훨씬 깔끔해졌죠? 👍
🌟 옵셔널 체이닝의 주요 특징 및 장점
- 안전성(Safety):
nil값에 접근하여 발생하는 런타임 에러(Fatal Error)를 원천적으로 방지합니다. - 간결성(Conciseness): 중첩된
if let이나guard let구문을 줄여 코드를 짧고 명확하게 만듭니다. - 가독성(Readability): 코드가 어떤 값에 순차적으로 접근하는지 쉽게 파악할 수 있습니다.
- 결과는 항상 옵셔널: 체이닝의 결과는 성공하든 실패하든 항상 옵셔널 타입으로 반환되어, 후속 처리도 일관성 있게 할 수 있습니다.
🆚 옵셔널 체이닝(?) vs 강제 언래핑(!)
가끔 옵셔널 값을 다룰 때 느낌표(!)를 사용하는 강제 언래핑(Force Unwrapping)과 헷갈릴 수 있습니다.
- 옵셔널 체이닝 (
?): "혹시 값이 있다면 계속 진행하고, 없다면nil을 줘." (안전함) - 강제 언래핑 (
!): "여기엔 반드시 값이 있어! 그냥 꺼내줘!" (값이nil이면 앱 충돌!)
⚠️ 경고: 강제 언래핑(
!)은 해당 옵셔널 변수에 절대로nil이 없다고 100% 확신할 수 있는 경우에만 제한적으로 사용해야 합니다. 일반적으로는 옵셔널 체이닝이나if let,guard let을 사용하는 것이 훨씬 안전합니다.
마무리
Swift의 옵셔널 체이닝(?)은 단순히 문법 설탕(syntactic sugar)을 넘어, Swift가 추구하는 안전성과 표현력을 잘 보여주는 핵심 기능입니다. 옵셔널을 다룰 때 옵셔널 체이닝을 적극적으로 활용하여 더 깔끔하고, 더 안전하며, 더 Swift스러운 코드를 작성해 보세요! 😊
// 1. 옵셔널 변수 선언 및 초기화
// 'String?' 타입은 문자열(String) 또는 아무 값도 없음(nil)을 가질 수 있는 '옵셔널' 타입입니다.
// 여기서는 초기값으로 "Hi"라는 문자열을 할당했습니다.
// 만약 "Hi"를 지우고 `var x : String?` 로만 두거나 `var x : String? = nil` 로 하면 x는 nil 값을 갖게 됩니다.
var x : String? = "Hi"
// 2. 옵셔널 값과 강제 언래핑(!) 값 출력
// print(x): 옵셔널 변수 x를 그대로 출력합니다.
// - x가 "Hi"일 때: "Optional("Hi")" 라고 출력됩니다. (옵셔널 값임을 명시적으로 보여줌)
// - x가 nil일 때: "nil" 이라고 출력됩니다.
// print(x!): 옵셔널 변수 x를 강제로 언래핑(force unwrapping)하여 출력합니다.
// - '!'는 "x 안에 값이 반드시 있다고 확신하니, 그 값을 꺼내줘!" 라는 의미입니다.
// - x가 "Hi"일 때: 옵셔널 포장을 벗기고 실제 값인 "Hi"가 출력됩니다.
// - x가 nil일 때: 값이 없는데 강제로 꺼내려고 했으므로, **런타임 에러(Fatal error: Unexpectedly found nil while unwrapping an Optional value)**가 발생하며 앱이 **강제 종료**됩니다. 매우 위험하므로 꼭 필요하고 값이 있음을 100% 확신할 때만 사용해야 합니다.
print("옵셔널 그대로 출력:", x)
print("강제 언래핑(!) 출력:", x!) // 만약 x가 nil이라면 이 줄에서 앱이 종료됩니다!
// 3. 옵셔널 바인딩 (if let) - 안전한 값 추출
// 'if let'은 옵셔널 변수 안에 값이 있는지 **안전하게** 확인하고, 값이 있다면 그 값을 임시 상수(여기서는 'a')에 담아 { } 블록 안에서 사용할 수 있게 해줍니다.
// - x가 "Hi"일 때: x 안에 값이 있으므로, 그 값 "Hi"가 상수 'a'에 할당되고 { } 안의 코드가 실행됩니다. 'a'의 타입은 옵셔널이 아닌 그냥 String 입니다.
// - x가 nil일 때: x 안에 값이 없으므로, { } 안의 코드는 실행되지 않고 건너<0xEB><0x9A><0xB0>니다. 에러 없이 안전하게 넘어갑니다.
if let a = x {
print("옵셔널 바인딩(if let) 성공:", a) // a는 옵셔널이 아닌 String 타입
} else {
print("옵셔널 바인딩(if let) 실패: x는 nil입니다.")
}
// 4. 옵셔널 체이닝 (?.) - 안전한 프로퍼티 접근
// 'x?.count'는 옵셔널 체이닝을 사용합니다.
// - '?'는 "x 안에 값이 있다면, 그 값의 count 프로퍼티에 접근하고, 없다면 그냥 nil을 반환해줘" 라는 의미입니다.
// - x가 "Hi"일 때: x 안에 "Hi"가 있으므로, "Hi".count (문자열 길이)인 2에 접근합니다. 옵셔널 체이닝의 결과는 항상 옵셔널이므로, b에는 'Optional(2)'가 저장됩니다.
// - x가 nil일 때: x가 nil이므로, 뒤의 .count 접근을 시도하지 않고 즉시 nil을 반환합니다. b에는 'nil'이 저장됩니다.
// 따라서 b의 타입은 결과값이 정수(Int) 또는 nil일 수 있으므로 'Int?' (옵셔널 Int)가 됩니다.
let b = x?.count
// 5. 옵셔널 체이닝 결과의 타입과 값 출력
// type(of: b): 변수 b의 타입을 출력합니다. 위 설명대로 'Optional<Int>' 또는 'Int?' 라고 출력됩니다.
// b: 변수 b의 값을 출력합니다.
// - x가 "Hi" 였을 때: Optional(2)
// - x가 nil 였을 때: nil
print("옵셔널 체이닝 결과 타입:", type(of:b), "/ 결과 값:", b)
// 6. 옵셔널 체이닝 결과 값 + 강제 언래핑 (!)
// b1은 위 4번의 b와 동일하게 옵셔널 체이닝으로 값을 얻습니다. (타입은 Int?)
let b1 = x?.count
// b1!: 옵셔널 Int인 b1을 강제로 언래핑합니다.
// - x가 "Hi" 였을 때: b1은 Optional(2) 이므로, 강제 언래핑하면 실제 값인 2가 나옵니다.
// - x가 nil 였을 때: b1은 nil 이므로, 강제 언래핑하면 **런타임 에러**가 발생하며 앱이 **강제 종료**됩니다. 역시 위험합니다.
print("b1 타입:", type(of:b1), "/ b1 값:", b1, "/ b1 강제 언래핑(!):", b1!) // 만약 x가 nil이라면 이 줄에서 앱이 종료됩니다!
// 7. nil 병합 연산자 (??) - nil일 경우 기본값 사용
// 'x ?? ""' 는 nil 병합 연산자를 사용합니다.
// - '??'는 "왼쪽의 옵셔널 값(x)을 확인해서, 값이 있으면 그 값을 사용하고, 만약 nil이면 오른쪽의 기본값("")을 사용해줘" 라는 의미입니다.
// - x가 "Hi"일 때: x에 값이 있으므로, 그 값 "Hi"가 c에 할당됩니다.
// - x가 nil일 때: x가 nil이므로, ?? 오른쪽의 기본값인 "" (빈 문자열)이 c에 할당됩니다.
// 이 연산자의 결과는 옵셔널이 아닙니다. x가 String?이고 기본값이 String이므로, c의 타입은 'String'이 됩니다.
let c = x ?? ""
// 8. nil 병합 연산자 결과 출력
// c의 값을 출력합니다.
// - x가 "Hi" 였을 때: "Hi"
// - x가 nil 였을 때: "" (빈 문자열)
print("nil 병합 연산자(??) 결과:", c)
// --- 실습 요약 ---
// x = "Hi" 일 때 결과 예상:
// 옵셔널 그대로 출력: Optional("Hi")
// 강제 언래핑(!) 출력: Hi
// 옵셔널 바인딩(if let) 성공: Hi
// 옵셔널 체이닝 결과 타입: Optional<Int> / 결과 값: Optional(2)
// b1 타입: Optional<Int> / b1 값: Optional(2) / b1 강제 언래핑(!): 2
// nil 병합 연산자(??) 결과: Hi
// x = nil 일 때 결과 예상:
// 옵셔널 그대로 출력: nil
// 강제 언래핑(!) 출력: (앱 충돌 - Fatal Error) -> 이 줄 실행 전에 멈춤
// (만약 위 라인을 주석처리 한다면)
// 옵셔널 바인딩(if let) 실패: x는 nil입니다.
// 옵셔널 체이닝 결과 타입: Optional<Int> / 결과 값: nil
// b1 타입: Optional<Int> / b1 값: nil / b1 강제 언래핑(!): (앱 충돌 - Fatal Error) -> 이 줄 실행 전에 멈춤
// (만약 위 라인도 주석처리 한다면)
// nil 병합 연산자(??) 결과: ""

실행문에서 ! vs ? 비교

cell.textLabel?.text
- textLabel은 UILabel?형
- 옵셔널 언래핑을 하고 text에 접근해야 함
- 성공하면 옵셔널 값, 실패하면 nil
- 만약 cell.textLabel!.text로 작성하면 옵셔널이 아닌 일반형으로 반환하여 편하지만 textLabel이 nil일 경우 크래시가 남
// Person 클래스를 정의합니다.
class Person {
var name: String // 이름을 저장할 변수
var age: Int // 나이를 저장할 변수
// 초기화 메서드
init(name: String, age: Int) {
self.name = name // 매개변수 name을 인스턴스 변수 name에 할당
self.age = age // 매개변수 age를 인스턴스 변수 age에 할당
}
}
// Person 클래스의 인스턴스를 생성합니다.
let kim: Person = Person(name: "Kim", age: 20)
// kim의 나이를 출력합니다.
print(kim.age) // 20이 출력됩니다.
// 옵셔널 타입의 Person 인스턴스를 생성합니다.
let han: Person? = Person(name: "Han", age: 25)
// 강제 언래핑을 통해 han의 나이를 출력합니다.
print(han!.age) // 25가 출력됩니다. (han이 nil이 아닐 경우)
// 옵셔널 체이닝을 통해 han의 나이를 출력합니다.
print(han?.age) // Optional(25), han이 nil이 아닐 경우 나이를 감싼 옵셔널로 출력됩니다.
// 옵셔널 체이닝 후 강제 언래핑하여 나이를 출력합니다.
print((han?.age)!) // 25가 출력됩니다. (han이 nil이 아닐 경우)
// 옵셔널 바인딩을 사용하여 han의 나이를 안전하게 추출합니다.
if let hanAge = han?.age {
print(hanAge) // 25가 출력됩니다.
} else {
print("nil") // han이 nil일 경우 "nil"이 출력됩니다.
}
// Company 클래스를 정의합니다.
class Company {
var ceo: Person? // CEO를 저장할 옵셔널 타입의 변수
}
// Person 클래스를 정의합니다.
class Person {
var name: String // 이름을 저장할 변수
// 초기화 메서드
init(name: String) {
self.name = name // 매개변수 name을 인스턴스 변수 name에 할당
}
}
// Company 클래스의 인스턴스를 생성합니다.
let apple = Company()
// apple의 ceo 속성에 Person 인스턴스를 할당합니다.
apple.ceo = Person(name: "Kim")
// 아래 코드는 주석 처리되어 있습니다.
// print(apple.ceo.name) // 오류 발생: ceo가 옵셔널이므로 직접 접근할 수 없음
// 옵셔널 체이닝 없이 ceo에 안전하게 접근하는 방법:
if let ceo = apple.ceo {
print(ceo.name) // "Kim"이 출력됩니다. (ceo가 nil이 아닐 경우)
}
// 강제 언래핑을 통해 ceo의 이름을 출력합니다.
print(apple.ceo!.name) // "Kim"이 출력됩니다. (ceo가 nil이 아닐 경우)
// 옵셔널 체이닝을 사용하여 ceo의 이름을 출력합니다.
print(apple.ceo?.name) // Optional("Kim")이 출력됩니다. (ceo가 nil이 아닐 경우)
// 옵셔널 체이닝을 사용하여 안전하게 name을 추출합니다.
if let name = apple.ceo?.name {
print(name) // "Kim"이 출력됩니다.
}
// nil 병합 연산자를 사용하여 ceo의 이름을 출력하거나 기본값을 제공합니다.
print(apple.ceo?.name ?? "CEO가 없습니다") // "Kim"이 출력됩니다. (ceo가 nil이 아닐 경우)

예외 처리를 하는 방법에는 try와 try! 를 쓰는 방법이 있어요
용도: 에러를 발생시킬 수 있는 함수나 메서드를 호출할 때 사용합니다.
특징: 호출한 함수가 에러를 발생시키면, 해당 에러를 처리하는 do-catch 블록을 사용해야 합니다.
안전성: 에러가 발생할 경우, 프로그램이 크래시되지 않고 에러를 처리할 수 있습니다.
enum MyError: Error {
case somethingWrong
}
func riskyFunction() throws {
throw MyError.somethingWrong
}
do {
try riskyFunction() // 에러가 발생할 수 있는 함수 호출
} catch {
print("에러 발생: \(error)") // 에러가 발생했을 때의 처리
}
함수에 throws가 있다면 반드시 예외처리를 해야 사용할 수 가 있습니다
iOS에서 가장 많이사용하는 throws funtion
- URLSession:
URLSession.shared.data(for:delegate:)
설명: 네트워크 요청을 수행하고 데이터를 반환합니다. - FileManager:
FileManager.default.contentsOfDirectory(at:includingPropertiesForKeys:options:)
설명: 지정한 디렉토리의 파일 목록을 가져옵니다. - JSONDecoder:
JSONDecoder().decode(_:from:)
설명: JSON 데이터를 특정 타입으로 디코딩합니다. - JSONEncoder:
JSONEncoder().encode(_:)
설명: 특정 타입을 JSON 데이터로 인코딩합니다. - Data(contentsOf:):
설명: 주어진 URL에서 데이터를 가져옵니다. - Data.init(contentsOf:):
설명: 주어진 URL에서 데이터를 가져오고, 에러 발생 가능성을 내포합니다. - NSKeyedUnarchiver:
NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(_:)
설명: 데이터를 복원하여 객체를 생성합니다. - NSKeyedArchiver:
NSKeyedArchiver.archivedData(withRootObject:)
설명: 객체를 데이터로 변환합니다. - AVAsset:
AVAsset(url:)
설명: 주어진 URL에서 비디오 또는 오디오 자산을 생성합니다. - Core Data:
NSManagedObjectContext.save()
설명: Core Data의 변경 사항을 저장합니다. - FileHandle:
FileHandle(forReadingFrom:)
설명: 파일에서 데이터를 읽기 위한 핸들을 생성합니다. - CSVParser (타사 라이브러리):
다양한 CSV 파서에서 데이터를 파싱할 때 발생할 수 있는 에러.

제너릭
// 제네릭 함수 myPrint 정의
func myPrint<T>(a: T, b: T) {
// 두 인자를 b, a 순서로 출력
print(b, a)
}
// 주석 처리된 함수는 특정 타입(Double)에 대해 정의된 myPrint의 오버로드 버전
// func myPrint(a: Double, b: Double) {
// print(b, a)
// }
// myPrint 함수 호출 (정수 타입)
myPrint(a: 1, b: 2) // 출력: 2 1
// myPrint 함수 호출 (실수 타입)
myPrint(a: 2.5, b: 3.5) // 출력: 3.5 2.5
// myPrint 함수 호출 (문자열 타입)
myPrint(a: "HI", b: "Hello") // 출력: Hello HI
일반 class vs generic class
컬렉션 타입(Collection Type)
프로그래밍 언어에서 컬렉션 타입(Collection Type)은 여러 개의 값을 하나의 데이터 구조에 저장할 수 있는 타입입니다. 컬렉션 타입은 일반적으로 다음과 같은 특징을 가지고 있습니다:
1. 다양한 데이터 저장
컬렉션은 여러 개의 데이터를 저장할 수 있으며, 각 데이터는 동일한 타입이거나 다른 타입일 수 있습니다.
2. 데이터 구조의 종류
컬렉션 타입은 일반적으로 다음과 같은 주요 데이터 구조로 구분됩니다:
- 배열(Array): 순서가 있는 데이터의 집합으로, 인덱스를 통해 각 요소에 접근할 수 있습니다.
- 예:
[1, 2, 3, 4]
- 예:
- 딕셔너리(Dictionary): 키-값 쌍으로 데이터를 저장하며, 각 키를 사용하여 관련된 값을 빠르게 검색할 수 있습니다.
- 예:
["name": "Alice", "age": 30]
- 예:
- 집합(Set): 순서가 없고 중복되지 않는 데이터의 집합입니다. 주로 특정 값의 존재 여부를 확인하는 데 사용됩니다.
- 예:
[1, 2, 3](중복된 값 없음)
- 예:
3. 사용 예
컬렉션 타입은 데이터 집합을 효율적으로 관리하고 조작하는 데 매우 유용합니다. 예를 들어:
- 배열은 일정한 순서가 필요한 데이터를 저장할 때 사용합니다.
- 딕셔너리는 특정 키를 통해 값을 빠르게 검색해야 할 때 유용합니다.
- 집합은 중복된 데이터를 허용하지 않으므로 고유한 값들을 저장할 때 사용합니다.
4. 언어별 차이
각 프로그래밍 언어마다 컬렉션 타입의 구현과 사용법이 다를 수 있습니다. 예를 들어, Swift, Java, Python 등에서 배열, 리스트, 딕셔너리, 집합 등을 지원합니다.
5. 성능 및 효율성
컬렉션 타입은 데이터의 추가, 삭제, 검색 등의 작업에 대해 최적화된 성능을 제공하는 경우가 많습니다. 각 컬렉션 타입은 특정 작업에 대해 효율적인 성능을 제공하기 위해 내부적으로 다른 구조를 사용할 수 있습니다.
빈 배열과 방 만들기
빈 배열은 마치 방을 만드는 것과 같습니다. 방이 없으면 물건을 놓을 곳이 없듯이, 빈 배열도 요소를 저장할 공간이 필요합니다.
방을 만드는 과정
- 빈 배열 생성:
- 먼저 빈 배열을 생성하여 사용할 공간을 만들어야 합니다.
- 예:
var emptyArray: [Int] = []
- 요소 추가하기:
- 방이 만들어지면, 그 안에 물건을 놓듯이 배열에 요소를 추가할 수 있습니다.
- 예:
emptyArray.append(1) emptyArray.append(2)
- 배열 사용하기:
- 이제 배열이 비어 있지 않으므로, 필요한 작업을 수행할 수 있습니다.
- 예:
print(emptyArray) // 출력: [1, 2]
빈 배열은 방을 만든 다음에 가지고 놀아야 한다는 것을 기억하세요. 방이 없으면 물건을 놓을 수 없듯이, 빈 배열도 요소를 추가한 후에야 유용하게 사용할 수 있습니다.
값이 정해지는 불변형은
let으로 배열을 작성해주는 것이 좋아요 ex) let animal1 = ["dog", "cat", "cow"]
var number : [Int] = []
//number[0]=1 //crash, 방을 만든 후 사용하라!
number.append(1)
print(number)
number[0]=10
print(number)
Array(repeating:count:)
var x = [0,0,0,0,0]
print(x)
var x1 = Array(repeating: 0, count: 5)
print(x1)
var x2 = [Int](repeating: 1, count: 3)
print(x2)
var x3 = [String](repeating: "A", count: 4)
print(x3)
for~in으로 배열 항목 접근 //배열들의 항목들을 가져오는 방법
let colors = ["red", "green", "blue"]
print(colors)
for color in colors {
print(color)
}
항목이 몇 개인지(count), 비어있는지(isEmpty) 알아내기
let num = [1, 2, 3, 4]
var x = [Int]()
print(num.isEmpty) //배열이 비어있나? false
print(x.isEmpty)
if num.isEmpty {
print("비어 있습니다")
}
else {
print(num.count) //배열 항목의 개수
}
first와 last 프로퍼티
배열의 first나 last는 값이 옵셔널로 나와요
let num = [1, 2, 3, 4]
let num1 = [Int]()
print(num.first, num.last)//Optional(1) Optional(4)
print(num1.first, num1.last)//nil nil
if let f = num.first, let l = num.last {
print(f,l) //1 4
}
var num = [1, 2, 3, 4]
print(num[0], num[3]) //1 4
print(num.first!) //1
for i in 0...num.count-1{
print(num[i]) // 1 2 3 4
}
print(num[1...2]) // [2,3]
num[0...2] = [10,20,30]
print(num) // [10, 20, 30, 4]
Array: 추가/제거
var num = [1, 2, 3]
print(num) // 출력: [1, 2, 3]
num.append(4)
print(num) // 출력: [1, 2, 3, 4]
num.append(contentsOf: [6, 7, 8])
print(num) // 출력: [1, 2, 3, 4, 6, 7, 8]
num.insert(5, at: 4)
print(num) // 출력: [1, 2, 3, 4, 5, 6, 7, 8]
num.remove(at: 3)
print(num) // 출력: [1, 2, 3, 5, 6, 7, 8]
num.removeLast()
print(num) // 출력: [1, 2, 3, 5, 6, 7]
print(num.firstIndex(of: 2)) // Optional(1), 2가 처음으로 나오는 첨자
if let i = num.firstIndex(of: 2) {
// 2가 처음으로 저장된 방의 값을 20으로 바꿈
num[i] = 20 // num[1] = 20
}
print(num) // 출력: [1, 20, 3, 5, 6, 7]
num = num + num
print(num) // 출력: [1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7]
num += [8, 9]
print(num) // 출력: [1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7, 8, 9]
num.removeAll()
print(num) // 출력: []
Array 요소의 최댓값 최솟값 :max(), min()
배열에서 max와 min함수를 쓸려면 옵셔널 이므로 언래핑을 해줘야합니다 (빈배열의 경우가 있을 수 있으므로)
Array 요소의 정렬
var num = [1,5,3,2,4]
num.sort() //오름차순 정렬하여 원본 변경
print(num) //[1, 2, 3, 4, 5]
num[0...4] = [2,3,4,5,1]
num.sort(by:>) //내림차순 정렬하여 원본 변경
print(num) //[5, 4, 3, 2, 1]
num[0...4] = [2,3,4,5,1]
num.reverse() //반대로 정렬하여 원본 변경
print(num) //[1, 5, 4, 3, 2]
print(num.sorted()) //오름차순 정렬 결과를 리턴하고, 원본은 그대로, var x = num.sorted()
//[1, 2, 3, 4, 5]
print(num) //[1, 5, 4, 3, 2]
print(num.sorted(by:>)) //내림차순 정렬 결과를 리턴하고, 원본은 그대로
//[5, 4, 3, 2, 1]
print(num)//[1, 5, 4, 3, 2]

swift의 접근제어 에서는 대부분 internal이 생략되어있어요
모듈 내 접근:
internal로 선언된 속성, 메서드, 클래스, 구조체 등은 같은 모듈 내에서 접근할 수 있습니다. 모듈은 일반적으로 하나의 프로젝트 또는 프레임워크를 의미합니다.
다른 모듈에서는 접근 불가:
internal로 선언된 요소는 다른 모듈에서는 접근할 수 없습니다. 이는 모듈 간의 캡슐화를 제공합니다.
기본값:
접근 제어 수준을 명시하지 않으면 기본적으로 internal로 설정됩니다. 따라서, 개발자는 별도로 접근 제어를 지정하지 않아도 내부적으로 사용할 수 있습니다.
출처 - Smile Han