iOS/Swift

Swift Closure

태애니 2025. 5. 3. 11:43
728x90

Closure

 

명명된 함수 생성없이 실행되는 코드의 그룹을 말한다

 

 

이 클로저는 정의된 context 에서 상수, 변수에 대한 참조를 캡처하고 저장할 수 있다.

이러한 상수와 변수를 closing over 라고 한다.

Swift 는 캡처의 모든 메모리 관리를 처리한다.

 

 

클로저는 세가지의 형태 중 하나의 모습을 한다

 

  1. 전역 함수 : 이름을 가지고 어떠한 값도 캡처하지 않음
  2. 중첩 함수 : 둘러싼 함수로 부터 값을 캡처
  3. 주변 context 의 값을 캡처할 수 있는 경량 구문으로 작성되는 이름 없는 클로저

 

 

Function 과 Closure

 

 

 

 

 

import SwiftUI

struct ClosureFunctionDemoView: View {
    // MARK: - 1. Function (이름 있는 함수)
    func greetFunction(name: String) -> String {
        return "Hello from function, \(name)"
    }

    // MARK: - 2. Closure (변수에 저장된 익명 함수)
    let greetClosure: (String) -> String = { (name: String) -> String in
        return "Hello from closure, \(name)"
    }

    // MARK: - 3. Closure 축약형들
    let square1: (Int) -> Int = { (num: Int) -> Int in return num * num }
    let square2: (Int) -> Int = { (num: Int) in return num * num }
    let square3: (Int) -> Int = { (num: Int) in num * num }
    let square4: (Int) -> Int = { $0 * $0 }

    // MARK: - 4. map 고차함수와 익명 클로저
    let names = ["Lee", "Kim", "Park"]

    // MARK: - 5. 변수 캡처 예시
    func makeIncrementer(by amount: Int) -> () -> Int {
        var total = 0
        return {
            total += amount
            return total
        }
    }

    @State private var incrementResult: [Int] = []
    @State private var selectedExample = 0
    @State private var showExplanation = false
    
    // MARK: - 각 예제에 대한 설명
    let explanations = [
        "함수는 코드 블록에 이름을 붙인 것입니다. 매개변수와 반환 타입을 명시하고, 함수 이름으로 호출합니다.",
        
        "클로저는 이름 없는 함수로, 변수에 저장하거나 다른 함수에 인자로 전달할 수 있습니다. 기본 문법은 { (매개변수) -> 반환타입 in 실행코드 } 형태입니다.",
        
        "클로저는 여러 가지 방법으로 축약할 수 있습니다:\n1. 타입 추론으로 매개변수와 반환 타입 생략\n2. return 키워드 생략 (단일 표현식일 때)\n3. 매개변수 이름 대신 $0, $1 등의 위치 기반 이름 사용",
        
        "map, filter, reduce와 같은 고차함수는 클로저를 인자로 받아 컬렉션의 각 요소에 적용합니다. 이를 통해 간결하고 함수형 프로그래밍 스타일의 코드를 작성할 수 있습니다.",
        
        "클로저는 생성된 컨텍스트의 변수를 '캡처'할 수 있습니다. 클로저가 생성된 이후에도 이 변수에 접근하고 수정할 수 있는 것이 클로저의 강력한 특성입니다."
    ]

    // MARK: - View Body
    var body: some View {
        NavigationView {
            VStack {
                // 선택 세그먼트
                Picker("예제 선택", selection: $selectedExample) {
                    Text("함수").tag(0)
                    Text("클로저").tag(1)
                    Text("축약형").tag(2)
                    Text("고차함수").tag(3)
                    Text("변수캡처").tag(4)
                }
                .pickerStyle(SegmentedPickerStyle())
                .padding()
                
                ScrollView {
                    VStack(alignment: .leading, spacing: 20) {
                        // 예제별 내용 표시
                        Group {
                            if selectedExample == 0 {
                                exampleOne
                            } else if selectedExample == 1 {
                                exampleTwo
                            } else if selectedExample == 2 {
                                exampleThree
                            } else if selectedExample == 3 {
                                exampleFour
                            } else if selectedExample == 4 {
                                exampleFive
                            }
                        }
                        .padding()
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(8)
                        
                        // 설명 섹션
                        if showExplanation {
                            Text("📝 설명")
                                .font(.headline)
                            
                            Text(explanations[selectedExample])
                                .padding()
                                .background(Color.blue.opacity(0.1))
                                .cornerRadius(8)
                        }
                        
                        Spacer()
                    }
                    .padding()
                }
                
                Button(showExplanation ? "설명 숨기기" : "설명 보기") {
                    withAnimation {
                        showExplanation.toggle()
                    }
                }
                .padding()
                .buttonStyle(.bordered)
            }
            .navigationTitle("Swift 클로저 강의")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
    
    // MARK: - 예제별 View
    var exampleOne: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("1️⃣ 일반 함수 (Function)")
                .font(.headline)
            
            Text("함수 선언:")
                .fontWeight(.medium)
            
            Text("""
            func greetFunction(name: String) -> String {
                return "Hello from function, \\(name)"
            }
            """)
            .font(.system(.body, design: .monospaced))
            .padding(8)
            .background(Color.black.opacity(0.03))
            .cornerRadius(4)
            
            Text("함수 호출 결과:")
                .fontWeight(.medium)
            
            Text(greetFunction(name: "Taenee"))
                .foregroundColor(.blue)
        }
    }
    
    var exampleTwo: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("2️⃣ 클로저 (Closure)")
                .font(.headline)
            
            Text("클로저 선언:")
                .fontWeight(.medium)
            
            Text("""
            let greetClosure: (String) -> String = { (name: String) -> String in
                return "Hello from closure, \\(name)"
            }
            """)
            .font(.system(.body, design: .monospaced))
            .padding(8)
            .background(Color.black.opacity(0.03))
            .cornerRadius(4)
            
            Text("클로저 호출 결과:")
                .fontWeight(.medium)
            
            Text(greetClosure("Taenee"))
                .foregroundColor(.blue)
        }
    }
    
    var exampleThree: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("3️⃣ 클로저 축약형")
                .font(.headline)
            
            Text("4가지 방식의 클로저:")
                .fontWeight(.medium)
            
            Text("""
            // 1. 표준 형태
            let square1: (Int) -> Int = { (num: Int) -> Int in return num * num }
            
            // 2. 반환 타입 추론
            let square2: (Int) -> Int = { (num: Int) in return num * num }
            
            // 3. return 키워드 생략
            let square3: (Int) -> Int = { (num: Int) in num * num }
            
            // 4. 단축 인자 이름 ($0)
            let square4: (Int) -> Int = { $0 * $0 }
            """)
            .font(.system(.body, design: .monospaced))
            .padding(8)
            .background(Color.black.opacity(0.03))
            .cornerRadius(4)
            
            Text("모든 클로저 호출 결과 (3의 제곱):")
                .fontWeight(.medium)
            
            Text("square1(3) = \(square1(3))")
                .foregroundColor(.blue)
            Text("square2(3) = \(square2(3))")
                .foregroundColor(.blue)
            Text("square3(3) = \(square3(3))")
                .foregroundColor(.blue)
            Text("square4(3) = \(square4(3))")
                .foregroundColor(.blue)
        }
    }
    
    var exampleFour: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("4️⃣ 고차함수와 클로저")
                .font(.headline)
            
            Text("배열과 map 사용:")
                .fontWeight(.medium)
            
            Text("""
            let names = ["Lee", "Kim", "Park"]
            
            // map으로 모든 요소 대문자화
            names.map { $0.uppercased() }.joined(separator: ", ")
            """)
            .font(.system(.body, design: .monospaced))
            .padding(8)
            .background(Color.black.opacity(0.03))
            .cornerRadius(4)
            
            Text("map 결과:")
                .fontWeight(.medium)
            
            Text(names.map { $0.uppercased() }.joined(separator: ", "))
                .foregroundColor(.blue)
            
            // 다른 고차함수 예제
            Divider()
            
            Text("다른 고차함수 예제:")
                .fontWeight(.medium)
            
            Text("• filter: \(names.filter { $0.count > 3 }.joined(separator: ", "))")
            Text("• contains: \(names.contains { $0.starts(with: "P") } ? "P로 시작하는 이름 있음" : "P로 시작하는 이름 없음")")
            Text("• sorted: \(names.sorted().joined(separator: ", "))")
        }
    }
    
    var exampleFive: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("5️⃣ 클로저의 변수 캡처")
                .font(.headline)
            
            Text("변수 캡처 코드:")
                .fontWeight(.medium)
            
            Text("""
            func makeIncrementer(by amount: Int) -> () -> Int {
                var total = 0
                return {
                    total += amount  // 외부 변수 total을 캡처
                    return total
                }
            }
            """)
            .font(.system(.body, design: .monospaced))
            .padding(8)
            .background(Color.black.opacity(0.03))
            .cornerRadius(4)
            
            Text("실행 결과:")
                .fontWeight(.medium)
            
            Button("2씩 증가시키기") {
                let incrementByTwo = makeIncrementer(by: 2)
                incrementResult = [
                    incrementByTwo(),
                    incrementByTwo(),
                    incrementByTwo()
                ]
            }
            .buttonStyle(.bordered)
            
            if !incrementResult.isEmpty {
                Text("결과: \(incrementResult.map { String($0) }.joined(separator: ", "))")
                    .foregroundColor(.blue)
                    .padding(.top, 4)
                
                Text("캡처된 total 변수가 호출마다 유지되어 2, 4, 6으로 증가합니다.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
        }
    }
}

#Preview {
    ClosureFunctionDemoView()
}

 

 

import Foundation

// 예제 배열들
let ages = [15, 22, 19, 30, 17]
let scores = [40, 85, 75, 90]

// MARK: - 필터 예제: 나이 20 이상만 필터링

// 전체 타입 명시 + return 사용
let adults1 = ages.filter({ (age: Int) -> Bool in return age >= 20 })
print(adults1) // [22, 30]

// 타입 생략
let adults2 = ages.filter({ (age) in return age >= 20 })
print(adults2) // [22, 30]

// return 생략
let adults3 = ages.filter({ (age) in age >= 20 })
print(adults3) // [22, 30]

// 축약된 매개변수
let adults4 = ages.filter({ $0 >= 20 })
print(adults4) // [22, 30]

// 트레일링 클로저 문법
let adults5 = ages.filter { $0 >= 20 }
print(adults5) // [22, 30]


// MARK: - 정렬 예제: 내림차순 정렬

// 전체 타입 명시 + return 사용
let sorted1 = scores.sorted(by: { (a: Int, b: Int) -> Bool in return a > b })
print(sorted1) // [90, 85, 75, 40]

// 타입 생략
let sorted2 = scores.sorted(by: { (a, b) in return a > b })
print(sorted2) // [90, 85, 75, 40]

// return 생략
let sorted3 = scores.sorted(by: { (a, b) in a > b })
print(sorted3) // [90, 85, 75, 40]

// 축약된 매개변수 사용
let sorted4 = scores.sorted(by: { $0 > $1 })
print(sorted4) // [90, 85, 75, 40]

// 트레일링 클로저 문법
let sorted5 = scores.sorted { $0 > $1 }
print(sorted5) // [90, 85, 75, 40]

 

 

import SwiftUI

struct AdvancedClosureExampleView: View {
    // MARK: - 상태 관리
    @State private var squareResult = 0
    @State private var uppercasedNames = ""
    @State private var incremented: [Int] = []
    @State private var fetchedData = ""
    @State private var weakSelfMessage = ""
    
    // MARK: - 일반 함수
    func greetFunction(name: String) -> String {
        return "Hello from function, \(name)"
    }

    // MARK: - 클로저
    let greetClosure: (String) -> String = { name in
        "Hello from closure, \(name)"
    }

    // MARK: - 축약형 클로저 (타입 명시)
    let square: (Int) -> Int = { number in
        return number * number
    }

    // MARK: - map 사용 예시
    let names = ["Lee", "Kim", "Park"]
    
    func performUppercase() {
        uppercasedNames = names.map { $0.uppercased() }.joined(separator: ", ")
    }

    // MARK: - 변수 캡처 예시
    func makeIncrementer(by amount: Int) -> () -> Int {
        var total = 0
        return {
            total += amount
            return total
        }
    }
    
    func performIncrement() {
        let incrementByThree = makeIncrementer(by: 3)
        incremented = []
        incremented.append(incrementByThree())
        incremented.append(incrementByThree())
        incremented.append(incrementByThree())
    }

    // MARK: - @escaping 클로저 예시
    func fetchData(completion: @escaping (String) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            completion("✅ Data loaded asynchronously")
        }
    }
    
    func performFetch() {
        fetchData { result in
            self.fetchedData = result
        }
    }

    // MARK: - 비동기 함수 예시 (struct에서는 weak self 불필요)
    func loadSomethingAsynchronously(completion: @escaping () -> Void) {
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            DispatchQueue.main.async {
                self.weakSelfMessage = "✅ Loaded asynchronously"
                completion()
            }
        }
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                Group {
                    Text("클로저 기본 예제").font(.headline)
                
                    Text(greetFunction(name: "Taenee"))
                    Text(greetClosure("Taenee"))
                }
                
                Divider()
                
                Group {
                    Text("단순 클로저 실행").font(.headline)
                    
                    Button("🔢 Square 5 using closure") {
                        squareResult = square(5)
                    }
                    .buttonStyle(.bordered)
                    
                    Text("5 squared = \(squareResult)")
                }
                
                Divider()
                
                Group {
                    Text("고차함수 map 예제").font(.headline)
                    
                    Button("🔤 Uppercase names") {
                        performUppercase()
                    }
                    .buttonStyle(.bordered)
                    
                    if !uppercasedNames.isEmpty {
                        Text(uppercasedNames)
                    }
                }
                
                Divider()
                
                Group {
                    Text("변수 캡처 예제").font(.headline)
                    
                    Button("➕ Increment by 3") {
                        performIncrement()
                    }
                    .buttonStyle(.bordered)
                    
                    if !incremented.isEmpty {
                        Text("Incremented: \(incremented.map { String($0) }.joined(separator: ", "))")
                    }
                }
                
                Divider()
                
                Group {
                    Text("비동기 처리 예제").font(.headline)
                    
                    Button("🌐 Fetch async data (@escaping)") {
                        performFetch()
                    }
                    .buttonStyle(.bordered)
                    
                    if !fetchedData.isEmpty {
                        Text(fetchedData)
                    }
                }
                
                Divider()
                
                Group {
                    Text("비동기 로딩 예제").font(.headline)
                    
                    Button("🧠 Simulate async loading") {
                        loadSomethingAsynchronously { }
                    }
                    .buttonStyle(.bordered)
                    
                    if !weakSelfMessage.isEmpty {
                        Text(weakSelfMessage)
                    }
                }
            }
            .padding()
        }
    }
}

#Preview {
    AdvancedClosureExampleView()
}
728x90