[Swift] 클로저, 고차 함수, 배열과 딕셔너리 활용 정리
- 1. 모든 함수는 클로저(Closure)다
- 2. 고차 함수 (Higher-order functions)
filter
sorted
- 3. 문법 최적화
- 단축 인자 이름 사용 (Shorthand Argument Names)
- 암시적 반환 (Implicit Return)
- 후행 클로저 (Trailing Closure)
- 4. 함수 파라미터로 클로저 사용
- 매개변수 순서 최적화
- 5. 리스트에서 조건에 맞는 값 찾기
- 최소값/최대값 찾기
- 6. 배열의 변형 (map, compactMap, reduce)
map
compactMap
reduce
- 7. 문자열 처리 (split, joined)
- Set (집합) 이란?
- 1. Set 기본 사용법
- 2. 포함 여부 검사 (Membership Test)
- 3. 요소 추가 및 삭제
- 4. Set 사용이 적합한 경우
- 5. 도전 과제: 로또 번호 생성기
- Set vs Array vs Dictionary 정리표
- Dictionary란?
- 2. 기본 선언 방식들
- 3. 접근 & 조회
- 4. 딕셔너리 특징
- 5. 추가 / 수정 / 삭제
- 6. 기타 속성
- 7. 딕셔너리에서 불가능한 것들
- 8. 실제 사용 예
- 요약 정리
- 1. Unnamed Tuple (이름 없는 튜플)
- 2. Named Tuple (이름 있는 튜플)
- 3. Tuple Decomposition (튜플 분해)
- 4. 튜플의 특징 요약
- 튜플은 언제 사용할까?
- 클래스와 구조체 개념 정리
- 튜플 (Tuple)
1. 모든 함수는 클로저(Closure)다
개념
Swift에서 함수는 모두 클로저라고 볼 수 있어. 즉, 함수는 이름을 가진 클로저(named closure)로 이해할 수 있음.
클로저는 이름이 있는 클로저(함수)와 이름이 없는 클로저(익명 함수)로 구분할 수 있음.
예제
let c = { print("Hello") }
c() // 출력: Hello
let c2 = { (str: String) -> String in
return "Hello \(str)"
}
print(c2("World")) // 출력: Hello World
위 예제에서 c
는 이름 없는 클로저로, c2
는 매개변수를 받는 클로저임.
2. 고차 함수 (Higher-order functions)
개념
고차 함수는 다른 함수를 파라미터로 받거나, 함수를 리턴하는 함수임. Swift에서는 배열, 딕셔너리 등에서 여러 고차 함수를 제공함.
filter
예제
let products = [
"MacBook Air", "MacBook Pro",
"iMac", "Mac Studio", "Mac Pro", "Mac mini",
"iPad Pro", "iPad", "iPad mini",
"iPhone 16", "iPhone 16 Pro Max", "iPhone 14", "iPhone SE",
"AirPods",
"Apple Watch Series 10", "Apple Watch Ultra 2"
]
let result = products.filter({ (name: String) -> Bool in
return name.contains("Pro")
})
print(result) // ["MacBook Pro", "iPad Pro", "iPhone 16 Pro Max", "Mac Pro"]
filter
는 배열에서 조건에 맞는 요소만 골라내는 함수임.
sorted
예제
let sorted = result.sorted(by: {(lhs: String, rhs: String) -> Bool in
return lhs.caseInsensitiveCompare(rhs) == .orderedAscending
})
print(sorted) // ["iPhone 16 Pro Max", "MacBook Pro", "Mac Pro", "iPad Pro"]
sorted
는 배열을 정렬하는 함수로, by
파라미터로 정렬 기준을 설정할 수 있음.
3. 문법 최적화
개념
Swift에서는 코드의 가독성과 간결함을 위해 클로저 문법을 최적화할 수 있음. 컴파일러가 추론할 수 있는 부분은 생략 가능하고, 이를 통해 코드가 간결해짐.
단축 인자 이름 사용 (Shorthand Argument Names)
예제
products.filter { $0.contains("Pro") } // 더 간단한 문법
$0
, $1
, $2
등으로 인자 이름을 줄여서 사용할 수 있음.
암시적 반환 (Implicit Return)
예제
products.filter { $0.contains("Pro") }
리턴 키워드를 생략하고 표현할 수 있음.
후행 클로저 (Trailing Closure)
예제
products.filter { $0.contains("Pro") }
클로저가 함수의 마지막 파라미터일 경우, 괄호를 생략하고 클로저를 함수 호출 바깥에 작성할 수 있음.
4. 함수 파라미터로 클로저 사용
개념
함수에서 클로저를 파라미터로 받을 수 있음. 이를 통해 더욱 유연한 함수 호출이 가능해짐.
예제
func multi(first: () -> (), second: () -> (), third: () -> ()) {
first()
second()
third()
}
multi {
print("First")
} second: {
print("Second")
} third: {
print("Third")
}
// 출력:
// First
// Second
// Third
위 예제에서 multi
함수는 세 개의 클로저를 파라미터로 받고, 이를 호출함.
매개변수 순서 최적화
예제
func mixed(first: () -> (), second: Int, third: () -> ()) {
first()
print(second)
third()
}
mixed(first: {
print("First")
}, second: 123) {
print("Third")
}
// 출력:
// First
// 123
// Third
second
와 같은 일반 파라미터가 있을 경우, 이를 맨 앞에 배치해 코드가 더 간결하고 직관적이게 만듦.
5. 리스트에서 조건에 맞는 값 찾기
예제
let randomNumbers = [1, 2, 2, 1, 4, 5, 2, 6, 7, 5, 0]
randomNumbers.first { $0 % 2 == 0 } // 첫 번째 짝수
배열에서 특정 조건을 만족하는 첫 번째 값을 찾을 때 사용할 수 있음.
최소값/최대값 찾기
예제
randomNumbers.min() // 0
randomNumbers.max() // 7
배열에서 최소값과 최대값을 쉽게 구할 수 있음.
6. 배열의 변형 (map, compactMap, reduce)
map
예제
let evenNumbers = [2, 4, 6, 8, 10]
evenNumbers.map { $0 * 2 } // [4, 8, 12, 16, 20]
map
은 배열의 각 요소를 변형해 새로운 배열을 반환하는 함수임.
compactMap
예제
let optionalNums = [1, 2, 3, nil, 4, 5, 6, 7, nil]
let result = optionalNums.compactMap { $0 } // [1, 2, 3, 4, 5, 6, 7]
compactMap
은 배열에서 nil
을 제거하고 새로운 배열을 만들어 줌.
reduce
예제
let sum = evenNumbers.reduce(0) { $0 + $1 } // 30
reduce
는 배열의 모든 요소를 하나의 값으로 합칠 때 사용함.
7. 문자열 처리 (split, joined)
예제
let multiline = """
Hello, Swift
Hello, iOS
Goodbye, Objective-C
"""
let lines = multiline.split(separator: "\n")
let joinedString = lines.joined(separator: ", ") // "Hello, Swift, Hello, iOS, Goodbye, Objective-C"
split
은 문자열을 구분자로 나누고, joined
는 나누어진 문자열을 다시 합칠 때 사용함.
Set (집합) 이란?
개념
- 중복을 허용하지 않는 요소들의 모음임
- 요소들의 순서가 없으며, 유일한 값만 저장함
- 요소의 중복 제거, 포함 여부 검사에 효율적임
1. Set 기본 사용법
let set: Set<Int> = [1, 2, 2, 3, 3, 4, 4, 5, 5, 6]
print(set) // [2, 4, 5, 6, 3, 1] 등 순서 보장 없음
print(set.count) // 6
print(set.isEmpty) // false
- 중복된 값은 자동으로 제거됨
- 선언 시 배열 형태처럼 보이지만, 실제 타입은
Set
임 - 순서를 보장하지 않음
2. 포함 여부 검사 (Membership Test)
set.contains(1) // true
Set
은 Hashing 기반 구조라서Array
보다 빠르게 포함 여부를 검사할 수 있음
let arr = [1, 2, 3]
arr.contains(1) // true, 하지만 성능은 Set보다 느림
3. 요소 추가 및 삭제
var words = Set<String>()
words.insert("Swift")
words.insert("Swift") // 중복이라 무시됨
words.remove("Swift") // 제거됨
words.remove("iOS") // 없는 값은 무시됨
insert(_:)
: 요소를 추가함remove(_:)
: 요소를 제거함- 없는 값을 제거해도 에러는 발생하지 않음
4. Set 사용이 적합한 경우
- 중복을 허용하지 않아야 하는 경우
- 빠른 검색이 필요한 경우
- 데이터의 존재 여부만 중요할 때
5. 도전 과제: 로또 번호 생성기
조건
- 1부터 45까지의 숫자 중
- 중복 없이 6개의 숫자
- 정렬된 배열로 반환
구현
func genLottoNumber() -> [Int] {
var lotto: Set<Int> = []
while lotto.count < 6 {
lotto.insert(Int.random(in: 1...45))
}
return lotto.sorted()
}
Set
을 사용해 중복 없이 랜덤 숫자를 생성함Set
을sorted()
로 정렬해Array
로 반환함
예시 출력
genLottoNumber() // [3, 8, 19, 22, 34, 41]
Set vs Array vs Dictionary 정리표
자료구조 | 중복 허용 | 순서 유지 | 검색 속도 | 특징 |
---|---|---|---|---|
Array | ✅ | ✅ | 느림 | 일반적인 리스트 |
Set | ❌ | ❌ | 빠름 | 중복 제거, 빠른 검색 |
Dictionary | ❌ (Key) | ❌ | 빠름 | Key-Value 구조 |
Dictionary란?
개념
Dictionary
는 키(Key)와 값(Value)의 쌍으로 데이터를 저장하는 컬렉션임Set
,Array
와는 다르게 키를 통해 값을 찾는 방식임- 키는 유일해야 하고, 값은 중복 가능함
예제
var dict = ["A": "Apple", "B": "Banana"]
2. 기본 선언 방식들
var dict = ["A": "Apple", "B": "Banana"]
var dict2: Dictionary<String, String>
var emptyDict = [String: Int]()
3. 접근 & 조회
dict["A"] // "Apple"
dict["z"] // nil
dict["Z", default: "missing"] // "missing"
dict.keys // ["A", "B"]
dict.values // ["Apple", "Banana"]
- 없는 키로 접근하면
nil
반환함 default:
파라미터로 기본값 설정 가능함
4. 딕셔너리 특징
- 키는 유일해야 함 → 중복되면 덮어쓰기 됨
- 순서가 보장되지 않음 → 반복문 돌릴 때마다 순서가 바뀔 수 있음
for key in dict.keys {
print(key)
}
for key in dict.keys.sorted() {
print(key)
}
5. 추가 / 수정 / 삭제
추가 & 수정 (Upsert)
var mutableDict = [String: String]()
mutableDict["A"] = "Apple" // insert
mutableDict["A"] = "Application" // update
mutableDict.updateValue("Banana", forKey: "B") // insert
mutableDict.updateValue("Ball", forKey: "B") // update
updateValue(_:forKey:)
→ 있으면 업데이트, 없으면 삽입- 이걸 Upsert라고 부름
삭제
mutableDict.removeValue(forKey: "B")
mutableDict["A"] = nil
- 둘 다 같은 의미로, 해당 키-값 쌍을 제거함
6. 기타 속성
words.count // 요소 수
words.isEmpty // 비어있는지 여부
7. 딕셔너리에서 불가능한 것들
- 정렬 ❌ (순서 없음)
- 값으로 키 찾기 ❌ (역방향 탐색 불가)
- 값 기반 비교/검색 제한적
8. 실제 사용 예
let users = [
"001": "Alice",
"002": "Bob",
"003": "Charlie"
]
if let name = users["002"] {
print("User 002 is \(name)")
} else {
print("User not found")
}
요약 정리
항목 | 설명 |
---|---|
선언 방식 | ["Key": "Value"] , Dictionary<Key, Value>() |
값 조회 | dict["Key"] , dict["Key", default: "값"] |
추가/수정 | dict["Key"] = "Value" , updateValue(_:forKey:) |
삭제 | removeValue(forKey:) , dict["Key"] = nil |
순서 보장 여부 | ❌ 없음 |
키 중복 가능 여부 | ❌ 불가 (덮어씀) |
값 중복 가능 여부 | ✅ 가능 |
1. Unnamed Tuple (이름 없는 튜플)
let b = (123, 456)
- 두 개의
Int
값을 튜플로 묶은 형태임 - 각각의 요소는
.0
,.1
처럼 인덱스로 접근함
예제
var data = ("<html>", 200, "ok", 12.34)
print(data.0) // "<html>"
print(data.1) // 200
print(data.2) // "ok"
print(data.3) // 12.34
data.1 = 404 // 수정 가능함
2. Named Tuple (이름 있는 튜플)
var named = (body: "<html>", statusCode: 200, message: "ok", size: 12.34)
named.body // "<html>"
named.statusCode // 200
named.message // "ok"
named.size // 12.34
- 각 요소에 이름을 붙여 더 읽기 쉽고 명확하게 사용할 수 있음
- 이름을 사용하면 구조체처럼 읽는 느낌이지만, 튜플은 단순한 묶음임
3. Tuple Decomposition (튜플 분해)
개념
- 튜플의 각 요소를 한 번에 변수로 분해해서 바인딩할 수 있음
- 사용하지 않을 값은
_
와일드카드로 무시할 수 있음
let (body, code, msg, _) = named
print(body) // "<html>"
print(code) // 200
print(msg) // "ok"
// size는 무시함
4. 튜플의 특징 요약
항목 | 설명 |
---|---|
타입 | (Int, String) , (String, Int, Double) 등 |
요소 접근 방법 | .0 , .1 혹은 이름(body , statusCode ) |
값 수정 가능 여부 | 변수로 선언 시 가능 (var 사용) |
크기 변경 가능 여부 | ❌ 불가능, 선언 시 고정됨 |
반복문 순회 | ❌ 불가능 (배열과 다름) |
튜플은 언제 사용할까?
- 간단하게 여러 값을 한 번에 반환하거나,
- 임시 데이터 묶음이 필요할 때 사용함
- 복잡하거나 구조화가 필요한 경우는
struct
사용이 더 적합함
클래스와 구조체 개념 정리
클래스 (Class)
- 참조 타입 (Reference Type)임
- 힙에 저장되며, 복사하면 참조가 공유됨
- 상속이 가능함
- 생성자(
init
)를 직접 정의해야 함
구조체 (Struct)
- 값 타입 (Value Type)임
- 스택에 저장되며, 복사 시 값 자체가 복사됨
- 상속 불가
- 생성자를 자동으로 제공함
항목 | Class | Struct |
---|---|---|
타입 | 참조 타입 (Reference) | 값 타입 (Value) |
메모리 저장 위치 | 힙 (Heap) | 스택 (Stack) |
복사 방식 | 참조 복사 | 값 복사 |
상속 | 가능 | 불가능 |
생성자 제공 | 기본 생성자 없음 | 기본 생성자 자동 제공 |
주 용도 | 상태 공유가 필요한 객체 | 간단한 데이터 묶음, 불변 모델 |
튜플 (Tuple)
개념
- 튜플(Tuple)은 여러 값을 하나의 변수로 묶어서 저장할 수 있는 데이터 타입임.
- 스칼라 값이 아니라, 여러 값이 하나의 묶음으로 다뤄짐.
- 튜플은 순서가 중요하며, 각 값은 서로 다른 타입일 수 있음.
- 튜플의 값에 이름을 붙일 수도 있고, 이름 없이 사용할 수도 있음.
1. 이름 없는 튜플 (Unnamed Tuple)
- 이름 없이 값만 나열하는 방식.
- 인덱스로 접근 가능함.
let b = (123, 456) // 튜플
let data = ("<html>", 200, "ok", 12.34)
data.0 // "<html>"
data.1 // 200
data.2 // "ok"
data.3 // 12.34
data.1 = 404 // 값을 변경할 수 있음
data.0
처럼 인덱스를 사용해 각 값을 호출함.
2. 이름이 있는 튜플 (Named Tuple)
- 각 값에 이름을 붙여, 더 직관적으로 사용할 수 있음.
var named = (body: "<html>", statusCode: 200, message: "ok", size: 12.34)
named.body // "<html>"
named.statusCode // 200
named.message // "ok"
named.size // 12.34
- 각 값을
named.body
,named.statusCode
와 같이 이름으로 접근할 수 있음.
3. 튜플 분해 (Tuple Decomposition)
- 튜플을 분해하여 각 값을 별도의 변수에 바인딩할 수 있음.
- 바인딩할 값이 필요 없는 경우, 와일드카드 (
_
)를 사용하여 무시할 수 있음.
let (body, code, msg, _) = named
print(body) // "<html>"
print(code) // 200
print(msg) // "ok"
let (body, code, msg, _) = named
는 튜플의 값을 각각body
,code
,msg
에 할당하고, 네 번째 값인size
는_
로 무시함.
- 튜플은 여러 값을 묶어서 처리할 수 있는 자료형이며, 이름 있는 튜플은 각각의 값에 이름을 붙여 사용할 수 있고, 이름 없는 튜플은 인덱스로 값에 접근함.
- 튜플 분해는 튜플의 값을 개별 변수로 나누어 간편하게 접근할 수 있는 방법임.
_
를 사용하여 특정 값은 무시할 수도 있음.