Array와 Collection Type, 그리고 Optional Chaining과 Generic을 통해 데이터를 처리하는 방법을 예제와 함께 알아볼게요.Optional Chaining은 객체의 속성이나 메서드에 접근할 때, 해당 속성이나 메서드가 존재하지 않더라도 오류를 발생시키지 않고 undefined 또는 null을 반환하는 문법이에요.
먼저 Optional Chaining 을 지원하는 언어부터 알아볼게요.
옵셔널 체이닝을 지원하는 언어는 JavaScript, TypeScript, Swift, Kotlin, C# 가 있어요.

각 언어에 대해서 예제와 함께 사용하는 방법을 정리하면 다음과 같아요.
JavaScript 에서는 ?. 연산자로 옵셔널 체이닝을 구현할 수 있어요.const user = {
name: "Alice",
address: {
city: "Seoul"
}
};
// 옵셔널 체이닝 사용
console.log(user?.address?.city); // "Seoul"
console.log(user?.contact?.phone); // undefined (오류 없이 반환)
TypeScript는 JavaScript의 상위 집합으로, ?.를 동일하게 지원해요.interface User {
name: string;
address?: {
city: string;
};
}
const user: User = { name: "Bob" };
console.log(user?.address?.city); // undefined
Swift에서는 ?. 연산자를 사용해 옵셔널 체이닝을 지원해요.struct User {
var address: Address?
}
struct Address {
var city: String
}
let user = User(address: nil)
print(user.address?.city) // nil
Kotlin은 안전 호출 연산자(?.)로 옵셔널 체이닝을 지원해요.data class Address(val city: String)
data class User(val address: Address?)
val user = User(null)
println(user?.address?.city) // null
C#에서는 ?. (Null-Conditional Operator)를 사용해요.class Address {
public string City { get; set; }
}
class User {
public Address Address { get; set; }
}
var user = new User();
Console.WriteLine(user?.Address?.City); // null
cell.textLabel!.text로 작성하면 옵셔널이 아닌 일반형으로 반환하여 편하지만 textLabel이 nil일 경우 크래시가 남
Swift에서는 optional을 사용하여 값의 유무를 전달함으로써 작업의 성공/실패 유무를 판단할 수 있지만 작업이 실패할 때 코드가 적절히 응답할 수 있도록 함으로써 오류의 원인을 이해하는 데 도움을 줄 수 있다.'존재하지 않는 파일', '읽기 권한 없음', '호환되는 형식이 아님' 등 다양Swift 2.0 이후부터는 error handling을 도입do-catch 구문try?)Error Propagation)throws 키워드를 사용하여 발생한 에러를 호출한 코드로 전파try!)throws라는 키워드가 있는 함수는 그냥 사용할 수 없고 error handing을 해야 함func can() throwsthrowing functionfunc canThrowErrors() throws -> Stringerror handing을 해야하는 함수func cannotThrowErrors() -> Stringerror handing할 수 없는 함수AVAudioPlayer(contentsOf: audioFile)do~try~catch로 error handing해야 함Call can throw, but it is not marked with 'try' and the error is not handled 오류가 발생do {
try <오류 발생 코드>
<오류가 발생하지 않으면 실행할 코드>
} catch <오류패턴1> {
<처리 코드>
} catch <오류패턴2> where <조건> {
<처리 코드>
} catch {
<처리 코드>
}

제네릭은 특정 타입에 의존하지 않고, 재사용 가능한 코드를 작성할 수 있도록 설계된 문법입니다. 함수, 구조체, 클래스, 열거형 등에서 사용되며, 타입을 플레이스홀더(예: T)로 추상화하여 다양한 타입에 동일한 로직을 적용할 수 있습니다.
<T>)를 정의해 임의의 타입을 나타냅니다.<>)를 통해 타입 파라미터를 명시합니다.기본적인 정의 방법을 활용하여 실제로 어떻게 적용되는지 확인해볼게요.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
// Int 사용
var x = 5
var y = 10
swapValues(&x, &y)
print(x, y) // 10, 5
// String 사용
var str1 = "Hello"
var str2 = "World"
swapValues(&str1, &str2)
print(str1, str2) // "World", "Hello"
<T>와 같이 정의되며, T는 임의의 이름(관례적으로 T, U 등 사용)코드를 작성하다보면 많은 내용을 간략화하거나 특정 영역으로 묶기 위해 괄호를 많이 사용해요.
이제는 그 괄호의 종류가 점점 늘어나다 보니 헷갈릴 수 있어요.
아래 이미지를 통해 좀 더 직관적으로 각 괄호의 역할과 사용법을 정리했어요.

프로그래밍 언어에서 Collection Type은 여러 개의 값을 하나로 묶어서 다룰 수 있는 자료형을 의미해요.
하나의 변수로 여러 데이터를 저장하고, 쉽게 반복하거나 검색, 수정할 수 있게 도와주는 역할을 해요.
| 특성 | 타입 | 순서 있음 | 중복 허용 | 키-값 구조 |
|---|---|---|---|---|
| Array | 배열 | 예 (인덱스 순서 유지) | 예 (값 중복 가능) | 아니오 (인덱스 기반) |
| Dictionary | 딕셔너리 | 아니오 (Swift 4.2부터 삽입 순서 유지 가능) | 예 (값 중복 가능, 키는 고유) | 예 (키로 값 접근) |
| Set | 집합 | 아니오 | 아니오 (고유성 보장) | 아니오 (값만 저장) |
let arr = [1, 2, 3]
print(arr) // [1, 2, 3]
let arr = [1, 2, 3]
print(arr[1]) // 2
let arr = [1, 1, 2]
print(arr) // [1, 1, 2]
let arr = [1, 2]
print(arr[0]) // 1
let dict = ["a": 1]
print(dict) // ["a": 1]
let dict = ["a": 1, "b": 2]
print(dict) // ["b": 2, "a": 1] (Swift 4.1 이하에서는 순서 무작위 가능)
let dict = ["a": 1, "b": 1]
print(dict) // ["a": 1, "b": 1]
let dict = ["a": 1]
print(dict["a"]) // Optional(1)
let set = Set([1, 2])
print(set) // [2, 1] (순서 무관)
let set = Set([1, 2])
print(set) // [2, 1] (순서 보장되지 않음)
let set = Set([1, 1, 2])
print(set) // [1, 2]
let set = Set([1, 2])
print(set.contains(1)) // true
var x : [Int] = [] //빈 배열
var y = [Int]()
var z : Array<Int> = []
var a : [Int] = [1,2,3,4]
var b : Array<Int> = [1,2,3,4]
var c : Array<Double> = [1.2,2.3,3.5,4.1]
let으로 만들 수 있지만 초기값 설정 이후 변경 불가하여 배열의 의미가 없어요.// 가변형(mutable)
var animal = ["dog", "cat","cow"]
// 불변형 (immutable)
// 초기화 후 변경 불가
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:)를 이용해서 초기화하는 방법이 있어요.
일반적인 배열 선언 방법과 달리 클래스를 선언하는 방식으로 배열을 초기화할 수 있어요.
Array(repeating:count:)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 문법 사용in 키워드로 하나씩 가져와서 처리할 수 있어요.let colors = ["red", "green", "blue"]
print(colors)
for color in colors {
print(color)
}
count와 isEmptycount와 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와 lastfirst와 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
}
subscript)로 항목 접근var num = [1, 2, 3, 4]
print(num[0], num[3]) // 출력: 1 4
print(num.first!) // 배열의 첫 번째 요소를 출력: 1 (first는 !로 강제 언래핑)
for i in 0...num.count-1 { // 0부터 배열 길이-1(0...3)까지 반복
print(num[i]) // 출력: 1, 2, 3, 4
}
print(num[1...2]) // 배열의 1부터 2까지 부분을 출력: [2, 3]
num[0...2] = [10, 20, 30] // 배열의 0부터 2까지를 [10, 20, 30]으로 교체
print(num) // 출력: [10, 20, 30, 4]
append(_:)var numbers = [1, 2, 3]
numbers.append(4)
print(numbers) // 출력: [1, 2, 3, 4]
append(contentsOf:)var numbers = [1, 2, 3]
numbers.append(contentsOf: [4, 5, 6])
print(numbers) // 출력: [1, 2, 3, 4, 5, 6]
insert(_:at:)var numbers = [1, 2, 3]
numbers.insert(0, at: 0)
print(numbers) // 출력: [0, 1, 2, 3]
numbers.insert(10, at: 2)
print(numbers) // 출력: [0, 1, 10, 2, 3]
remove(at:)var numbers = [1, 2, 3, 4]
let removed = numbers.remove(at: 1)
print(removed) // 출력: 2
print(numbers) // 출력: [1, 3, 4]
removeFirst()와 removeLast()var numbers = [1, 2, 3, 4]
let first = numbers.removeFirst()
print(first) // 출력: 1
print(numbers) // 출력: [2, 3, 4]
let last = numbers.removeLast()
print(last) // 출력: 4
print(numbers) // 출력: [2, 3]
removeAll()var numbers = [1, 2, 3, 4]
numbers.removeAll()
print(numbers) // 출력: []
removeFirst()나 removeLast()를 호출하면 런타임 에러가 발생하니, 배열이 비어 있는지 확인 후 사용하세요.var emptyArray = [Int]()
if !emptyArray.isEmpty {
emptyArray.removeFirst() // 빈 배열이므로 실행되지 않음
}
print(emptyArray) // 출력: []
배열의 요소는 배열에 저장된 개별 값들을 의미하며, Swift에서는 배열이 제네릭 타입이므로 모든 요소가 동일한 타입이어야 합니다. 요소에 접근하거나 수정하는 방법은 위에서 다룬 첨자(subscript)나 프로퍼티(first, last)를 활용할 수 있습니다.
var fruits = ["apple", "banana", "orange"]
fruits[1] = "grape"
print(fruits) // 출력: ["apple", "grape", "orange"]
var numbers = [1, 2, 3, 4, 5]
numbers[1...3] = [20, 30]
print(numbers) // 출력: [1, 20, 30, 5]
count로 요소 개수를 확인하고, for-in으로 요소를 순회할 수 있습니다.let animals = ["dog", "cat", "bird"]
print(animals.count) // 출력: 3
for animal in animals {
print(animal) // 출력: dog, cat, bird
}
var dynamicArray = [1, 2]
print(dynamicArray.count) // 출력: 2
dynamicArray.append(3)
print(dynamicArray.count) // 출력: 3
dynamicArray.remove(at: 0)
print(dynamicArray.count) // 출력: 2
print(dynamicArray) // 출력: [2, 3]
Swift에서 접근 제어는 코드의 접근 가능 범위를 제어하는 메커니즘으로, 모듈과 소스 파일 간의 접근 권한을 설정합니다. 이를 통해 코드의 캡슐화를 강화하고, 의도치 않은 접근을 방지할 수 있습니다.
Swift는 open, public, package, internal, fileprivate, private 총 6가지 접근 수준을 제공하며, 접근 권한은 넓은 순서부터 좁은 순서로 정렬됩니다.

open과 public 접근 수준은 모듈 외부에서도 접근이 가능하도록 설정합니다. 주로 앱, 프레임워크, UI킷, 혹은 라이브러리에서 사용됩니다.
open: 상속과 오버라이드가 허용됩니다. 외부 모듈에서 해당 요소를 사용할 수 있으며, 서브클래싱도 가능합니다.public: 외부 모듈에서 접근은 가능하지만, 상속과 오버라이드는 허용되지 않습니다.import Alamofire // 외부 모듈 임포트
open class SomeOpenClass {
open var someOpenVariable = 0
public var somePublicVariable = 0
}
public class SomePublicClass {
public var somePublicVariable = 0
// open var someOpenVariable = 0 // public 클래스 내에서는 open 사용 불가
}
// 사용 예시
let openInstance = SomeOpenClass()
print(openInstance.someOpenVariable) // 출력: 0
print(openInstance.somePublicVariable) // 출력: 0
let publicInstance = SomePublicClass()
print(publicInstance.somePublicVariable) // 출력: 0
package 접근 수준은 Swift 6.0부터 도입된 접근 제어로, 패키지 내부의 모든 소스 파일에서 접근 가능하지만, 패키지 외부의 소스 파일에서는 접근할 수 없습니다. 이는 모듈 내부에서만 공유해야 하는 코드를 정의할 때 유용합니다.
package class SomePackageClass {
package var somePackageVariable = 0
}
// 같은 패키지 내에서 접근 가능
let packageInstance = SomePackageClass()
print(packageInstance.somePackageVariable) // 출력: 0
// 패키지 외부에서는 접근 불가
internal 접근 수준은 기본 접근 수준으로, 해당 모듈 내부의 모든 소스 파일에서 접근 가능하지만, 외부 모듈에서는 접근이 차단됩니다. 앱이나 프레임워크에서 내부 구조를 정의할 때 주로 사용됩니다.
internal class SomeInternalClass {
internal let someInternalConstant = 0
internal func someInternalFunction() {
print("Internal function called")
}
}
// 같은 모듈 내에서 접근 가능
let internalInstance = SomeInternalClass()
print(internalInstance.someInternalConstant) // 출력: 0
internalInstance.someInternalFunction() // 출력: Internal function called
// 모듈 외부에서는 접근 불가
fileprivate 접근 수준은 해당 소스 파일 내에서만 접근이 가능합니다. 같은 파일 내에서는 사용 가능하지만, 다른 파일에서는 접근할 수 없습니다. 이는 특정 파일 내에서만 공유해야 하는 코드를 보호할 때 유용합니다.
fileprivate class SomeFilePrivateClass {
fileprivate func someFilePrivateFunction() {
print("Fileprivate function called")
}
}
let filePrivateInstance = SomeFilePrivateClass()
filePrivateInstance.someFilePrivateFunction() // 출력: Fileprivate function called
// 다른 파일에서는 접근 불가
private 접근 수준은 가장 제한적인 접근 수준으로, 클래스나 구조체 내부에서만 접근이 가능합니다. 외부에서 접근하거나 상속된 클래스에서도 접근할 수 없습니다. 확장(extension)에서도 접근이 불가능합니다.
class SomeClass {
private var somePrivateVariable = 0
private func somePrivateFunction() {
print("Private function called")
}
func accessPrivate() {
print(somePrivateVariable) // 내부에서 접근 가능
somePrivateFunction() // 내부에서 호출 가능
}
}
let instance = SomeClass()
instance.accessPrivate() // 출력: 0 \n Private function called
// print(instance.somePrivateVariable) // 오류: private 변수는 외부에서 접근 불가
Swift에서 많이 사용되는 기능들을 알아봤어요.Array와 Collection Type, 그리고 Optional Chaining과 Generic의 내용을 실습 코드와 함께 확인해볼 수 있었어요.