Generic

hyun·2025년 6월 4일
1

iOS

목록 보기
10/54

필수 과제 3번을 하는데

func a(_ input: [Int]) -> [Int] {
    return input.enumerated()
        .filter { $0.offset % 2 == 0 }
        .map { $0.element }
}

func b(_ input: [String]) -> [String] {
    return input.enumerated()
        .filter { $0.offset % 2 == 0 }
        .map { $0.element }
}

let intTest = [1, 2, 3, 4, 5]
let stringTest = ["가", "나", "다", "라", "마"]

print("a 함수 결과(Int) :", a(intTest))         // [1, 3, 5]
print("b 함수 결과(String) :", b(stringTest))   // ["가", "다", "마"]

a와 b는 각각 Int와 String형으로 짝수번째 요소 제거 후 반환하는 코드를 짰는데
c를 통해 하나의 함수로 대체할 수 있는 방법을 구현해야 하는 것에서 난관을 겪게 됨
같은 로직이지만 타입만 달라서 코드가 중복될 수밖에 없는데
이 때 제네릭 함수를 사용하면 해결할 수 있을 듯 함


 제네릭이란?

모양은 똑같지만, 안에 들어가는 재료는 바뀌는 틀

도시락 통에 김밥도 넣을 수 있고 과일도 넣을 수 있고.. 또 뭐가 있지
암튼 되게 다양한 것들을 넣을 수 있을텐데
이 때 도시락 통이 제네릭 함수가 되는 것임
한 마디로 데이터 타입이 바뀌지만 구조는 그대로 쓸 수 있는 것.

제네릭 없이 만들면?

func printIntArray(_ input: [Int]) {
    for i in input {
        print(i)
    }
}

func printStringArray(_ input: [String]) {
    for i in input {
        print(i)
    }
}

👉 요로코롬 숫자 배열, 문자 배열 따로따로 만들어야 하는데

제네릭을 쓰면?

func printArray<T>(_ input: [T]) {
    for i in input {
        print(i)
    }
}

이렇게 만들기만 하면 됨
이때 T는 함수가 호출될 때 알아서 타입을 바꿔주는 친구임

이제 숫자, 글자 등등을 다 출력할 수 있음

왜 써야할까

제네릭 없음제네릭 있음
같은 기능 함수 여러 개하나만 만들면 됨
코드 중복 많음코드 짧고 간단함
타입마다 따로 처리한 번에 처리 가능

→ 코드가 깔끔해지고 일일히 넣어주는 것 보다 범용성이 넓어져서 다양한 타입을 넣을 수 있음


 c 함수

그러니까 c를 제네릭 함수를 통해 만들면

func c<T>(_ input: [T]) -> [T] {
    return input.enumerated().filter { $0.offset % 2 == 0 }.map { $0.element }
}

print("c 함수 결과 (int) : ", c(intArray))
print("c 함수 결과 (string) : ", c(stringArray))

이런 식으로 구현할 수 있는 것.
코드를 자세하게 뜯어보면


func c<T>(_ input: [T]) -> [T]
부분
func c함수 이름 c
< T >제네릭 타입 T 사용 → 어떤 타입의 배열이든 처리 가능
(_ input: [T]) -> [T]T 타입의 배열을 받아서, 같은 T 타입의 배열로 돌려줌



return input.enumerated()
    .filter { $0.offset % 2 == 0 }
    .map { $0.element }

.enumerated()

input.enumerated()

배열의 각 요소에 인덱스 번호를 붙여주는 함수

결과는 [(offset: Int, element: T)] 형태의 시퀀스 됨

["가", "나", "다"] → [(0, "가"), (1, "나"), (2, "다")]


.filter { $0.offset % 2 == 0 }

.filter { $0.offset % 2 == 0 }

조건에 맞는 요소만 남기는 함수.
offset이 짝수 인덱스(0, 2, 4)만 남김

[(0, "가"), (1, "나"), (2, "다"), (3, "라")]
→ 필터링 후 → [(0, "가"), (2, "다")]


.map { $0.element }

.map { $0.element }

요소를 변형해서 새 배열을 만드는 함수.
인덱스를 떼고 원래 요소만 남기게 됨

[(0, "가"), (2, "다")]
→ ["가", "다"]


 d 함수

func d<T: Numeric>(_ input: [T]) -> [T] {
    return input.enumerated()
        .filter { $0.offset % 2 == 0 }
        .map { $0.element }
}

c 함수랑 비슷하지만 타입 제한 (T: Numeric) 이 추가됨

func d<T: Numeric>(_ input: [T]) -> [T]

부분의미
func d함수 이름은 d
<T: Numeric>T는 제네릭 타입인데 Numeric 프로토콜을 따라야 함
(_ input: [T])T 타입의 배열을 받아서
-> [T]같은 타입의 배열을 반환



T: Numeric
숫자만 받아들일 수 있음
Int, Float, Double 같은 것만 가능하고
String, Bool, Date 같은 건 안됨

c랑 d의 다른 점

  • c는 문자든 숫자든 다 받아줘서 범용적이지만,

  • d는 숫자만 받아야 하는 상황에서 오류 방지에 유리함
    그러니까 한 마디로!!! 이 함수는 숫자만 받아야 한다는 걸 컴파일 시점에서 강제화 햐서
    오류를 미리 방지하려는 것임
    물론 코드의 의도도 드러낼 수 있음 !



지금까지 짠 코드를 보면

main.swift

//
//  main.swift
//  ClosureBeyond
//
//  Created by 노가현 on 6/2/25.
//

import Foundation

let num = 3
let intArray = [1, 2, 3, 4, 5]
let stringArray = ["가", "나", "다", "라", "마"]

print(sum(num, 7))
calculator(closure: sum)

convertIntArrayToStringArray()
filterEvenNumbersAndConvertToString()
exampleMyMap()

print("a 함수 :", a(intArray))
print("b 함수 :", b(stringArray))
print("c(Int) 함수 :", c(intArray))
print("c(String) 함수 :", c(stringArray))
print("d 함수 :", d(intArray))



highOrderFunction.swift

//
//  HighOrderFunctions.swift
//  ClosureBeyond
//
//  Created by 노가현 on 6/2/25.
//

import Foundation

func convertIntArrayToStringArray() {
    let numbers = [1, 2, 3, 4, 5]
    let result = numbers.map(String.init)
    print("숫자 -> 문자열 :", result)
}

func filterEvenNumbersAndConvertToString() {
    let input = Array(1...10)
    let output = input
        .filter { $0.isMultiple(of: 2) }
        .map(String.init)
    print("짝수만 문자열로 :", output)
}

func myMap(_ array: [Int], _ transform: (Int) -> String) -> [String] {
    var result: [String] = []
    for item in array {
        result.append(transform(item))
    }
    return result
}

func exampleMyMap() {
    let result = myMap([1, 2, 3, 4, 5]) { String($0) }
    print(result)
}

func a(_ input: [Int]) -> [Int] {
    input.enumerated()
        .filter { $0.offset.isMultiple(of: 2) }
        .map(\.element)
}

func b(_ input: [String]) -> [String] {
    input.enumerated()
        .filter { $0.offset.isMultiple(of: 2) }
        .map(\.element)
}

func c<T>(_ input: [T]) -> [T] {
    input.enumerated()
        .filter { $0.offset.isMultiple(of: 2) }
        .map(\.element)
}

func d<T: Numeric>(_ input: [T]) -> [T] {
    input.enumerated()
        .filter { $0.offset.isMultiple(of: 2) }
        .map(\.element)
}



closures.swift

//
//  Closures.swift
//  ClosureBeyond
//
//  Created by 노가현 on 6/2/25.
//

import Foundation

let sum: (Int, Int) -> String = { a, b in
    "두 수의 합은 \(a + b)입니다"
}

func calculator(closure: (Int, Int) -> String) {
    let result = closure(4, 5)
    print(result)
}

요로코롬 짰는데

가독성이 떨어진 거 같아서 코드를 수정하게 됨

원래 코드에서는 짝수 인덱스를 확인할 때 offset % 2 == 0을 사용하고, 요소를 꺼낼 때 map { $0.element }을 사용했는데 가독성이 떨어진 거 같아서 리팩터링할 때는
offset % 2 == 0 대신 offset.isMultiple(of: 2)를 사용함
이 값이 2의 배수인가?를 더 잘 나타내주는 방식이라 생각했음

KeyPath 문법으로 map { $0.element }도 map(.element)로 줄였음

0개의 댓글