ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 고차함수 for Swift
    Apple🍎/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

    댓글

Designed by Tistory.