-
고차함수 for SwiftApple🍎/Swift 2025. 2. 25. 00:14
함수의 경우에는 값을 받아 일정한 연산을 수행한 뒤 다시 값을 반환합니다.
이에 비해 고차함수(Higher Order Functions)는 다음 특성 중 하나 이상을 가진 함수를 말합니다.
- 하나 이상의 함수를 매개변수로 받는 함수
- 함수를 결과로 반환하는 함수
Swift의 컬렉션 타입(Array, Dictionary, Set 등)에는 여러 유용한 고차함수가 내장되어 있어 데이터 처리를 위한 선언적 프로그래밍 방식을 지원합니다.
Swift 주요 고차 함수
map : 컬렉션의 각 요소를 변환하여 새로운 컬렉션을 생성합니다.
// 기본 사용법 let numbers = [1, 2, 3, 4, 5] let doubled = numbers.map { $0 * 2 } // 결과: [2, 4, 6, 8, 10] // 객체 배열에서 특정 속성만 추출하기 struct User { let name: String let age: Int } let users = [ User(name: "철수", age: 25), User(name: "영희", age: 28), User(name: "민수", age: 22) ] let names = users.map { $0.name } // 결과: ["철수", "영희", "민수"]
filter : 주어진 조건을 만족하는 요소만 포함하는 새 컬렉션을 반환합니다.
// 기본 사용법 let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] let evenNumbers = numbers.filter { $0 % 2 == 0 } // 결과: [2, 4, 6, 8, 10] // 객체 배열에서 조건에 맞는 항목만 필터링하기 let adults = users.filter { $0.age >= 25 } // 결과: [User(name: "철수", age: 25), User(name: "영희", age: 28)]
reduce : 컬렉션의 모든 값을 하나로 결합합니다.
// 기본 사용법 - 합계 구하기 let numbers = [1, 2, 3, 4, 5] let sum = numbers.reduce(0) { $0 + $1 } // 또는 간단하게: let sum = numbers.reduce(0, +) // 결과: 15 // 문자열 결합 let words = ["Swift", "는", "참", "재미있다"] let sentence = words.reduce("") { $0 + " " + $1 }.trimmingCharacters(in: .whitespaces) // 결과: "Swift 는 참 재미있다" // 복잡한 reduce 사용 예 - 통계 계산 let totalAge = users.reduce(0) { $0 + $1.age } let averageAge = Double(totalAge) / Double(users.count) // 결과: 25.0
flatMap : 중첩된 컬렉션을 평면화합니다.
// 중첩 배열 평면화 let nestedNumbers = [[1, 2, 3], [4, 5], [6, 7, 8, 9]] let flattened = nestedNumbers.flatMap { $0 } // 결과: [1, 2, 3, 4, 5, 6, 7, 8, 9] // Swift 4.1 이전에는 옵셔널 제거에도 사용했지만, 지금은 compactMap 사용
compactMap : nil을 제거하고 옵셔널을 언래핑합니다.
// nil 제거 및 옵셔널 언래핑 let possibleNumbers = ["1", "2", "three", "4", "five"] let validNumbers = possibleNumbers.compactMap { Int($0) } // 결과: [1, 2, 4] // URL 배열 생성 예제 let urlStrings = ["https://apple.com", "invalid-url", "https://swift.org"] let validURLs = urlStrings.compactMap { URL(string: $0) } // 결과: [https://apple.com, https://swift.org]
forEach : 각 요소에 대해 주어진 작업을 수행하지만, 새 컬렉션을 반환하지 않습니다.
let numbers = [1, 2, 3, 4, 5] numbers.forEach { print("숫자: \($0)") } // 출력: // 숫자: 1 // 숫자: 2 // ...
고차 함수 조합 : 고차함수는 체인을 이용해 연산을 파이프라인화 할 수 있습니다.
// 사용자 데이터 가공 예제 let result = users .filter { $0.age > 20 } // 20세 초과 사용자만 필터링 .map { $0.name } // 이름만 추출 .sorted() // 이름 알파벳순 정렬 .joined(separator: ", ") // 쉼표로 구분된 문자열로 변환 // 결과: "민수, 영희, 철수" // 중첩 배열에서 조건에 맞는 요소만 추출하기 let schoolClasses = [ ["철수", "영희", "민수"], ["정아", "윤호", "지민"], ["성준", "수빈", "준영"] ] let studentsWithJ = schoolClasses.flatMap { $0 } .filter { $0.hasPrefix("지") || $0.hasPrefix("준") } // 결과: ["지민", "준영"]
실제 프로젝트에서 활용 방법
1. 네트워크 응답 처리
// JSON 데이터를 가져와 파싱하고 필터링하는 과정 func fetchUsers(completion: @escaping ([User]) -> Void) { apiClient.getUsers { result in switch result { case .success(let data): let allUsers = try? JSONDecoder().decode([UserDTO].self, from: data) // 고차함수를 활용한 데이터 변환 파이프라인 let activeAdminUsers = allUsers? .compactMap { User(dto: $0) } // DTO를 도메인 모델로 변환 .filter { $0.isActive && $0.role == .admin } // 활성 관리자만 필터링 .sorted { $0.name < $1.name } // 이름순으로 정렬 completion(activeAdminUsers ?? []) case .failure: completion([]) } } }
2. 데이터 분석 및 통계
struct SalesRecord { let product: String let amount: Double let date: Date } // 판매 데이터 분석 func analyzeSales(_ records: [SalesRecord]) -> [String: Double] { let calendar = Calendar.current let currentYear = calendar.component(.year, from: Date()) // 올해 판매 기록만 필터링 let thisYearRecords = records.filter { calendar.component(.year, from: $0.date) == currentYear } // 제품별 총 판매액 계산 return thisYearRecords.reduce(into: [:]) { result, record in result[record.product, default: 0] += record.amount } }
3. UI 데이터 바인딩 (SwiftUI)
struct ContentView: View { @State private var tasks = [ Task(title: "앱 개발하기", isCompleted: false), Task(title: "운동하기", isCompleted: true), Task(title: "책 읽기", isCompleted: false) ] var body: some View { List { Section(header: Text("완료된 작업")) { ForEach(tasks.filter { $0.isCompleted }) { task in Text(task.title) } } Section(header: Text("미완료 작업")) { ForEach(tasks.filter { !$0.isCompleted }) { task in Text(task.title) } } } } }
성능 고려사항
고차함수는 가독성과 유지 보수성을 높이지만, 일부 상황에서는 성능에 영향을 줄 수 있습니다.
1. 체이닝 시 중간 배열 생성: 여러 고차함수를 체이닝할 때마다 새로운 배열이 생성됩니다.
// 비효율적인 방식 - 중간 배열 3개 생성 let result = numbers .filter { $0 % 2 == 0 } .map { $0 * 2 } .reduce(0, +) // 효율적인 방식 - 배열 순회를 한 번만 수행 let result = numbers.reduce(0) { sum, number in number % 2 == 0 ? sum + (number * 2) : sum }
2. 대용량 데이터: 매우 큰 데이터셋의 경우, 필요에 따라 전통적인 for 루프나 lazy 컬렉션을 고려하세요.
// lazy 컬렉션 사용 let largeNumbers = Array(1...1000000) let result = largeNumbers.lazy .filter { $0 % 2 == 0 } .map { $0 * 2 } .prefix(10) // 처음 10개 요소만 계산
사용자 정의 고차함수 작성하기
때로는 특정 문제에 맞는 고차함수를 직접 작성하는 것이 유용할 수 있습니다.
// 배열의 각 요소에 함수를 적용하고 결과를 새 배열로 반환하는 함수 extension Array { func myMap<T>(_ transform: (Element) -> T) -> [T] { var result: [T] = [] for item in self { result.append(transform(item)) } return result } } // 사용 예 let numbers = [1, 2, 3, 4, 5] let doubled = numbers.myMap { $0 * 2 } // 결과: [2, 4, 6, 8, 10]
'Apple🍎 > Swift' 카테고리의 다른 글
클로저 종결판 2 (0) 2025.02.26 Swift를 위한 람다 계산법 핵심 (0) 2025.02.25 클로저 종결판 (0) 2025.02.23 일급객체가 뭔데? (0) 2025.02.18 [Swift] Property 제대로 써보자. (0) 2023.12.12