본문 바로가기

스폰지밥으로 공부하는 swift/객체지향과 디자인패턴

1급 객체 넌 도데체 뭐하는 놈이냐

728x90

Swift에서 함수는 1급 객체로 간주된다..라는 말을 들어보셨나요?

저는 처음에 이 1급 객체라는 말을 보고 나서 도데체 이해가 안갔었는데요, 클로저를 공부하다가 다시 한번 마주하게 되어 다시 공부해보게 되었고, 간단하게 설명하는 글을 작성해보려합니다.

 

1급 객체라는 말은, 1급이라고 해서 뭐 특별한 혜텍을 받는것은 아니구요. 함수를 다른 객체와 동일하게 취급한다는 의미입니다. 함수를 변수에 할당하거나 다른 함수의 인자로 전달할 수 있으며, 함수의 반환 값으로 사용될 수 있는 것을 말해요.

1급 객체의 특징과 중요성

한마디로 요약하자면 함수를 일반 데이터처럼 다루는게 가능하다~ 라고 이해하시면 좋을 것 같네요! 이러한 특성 덕분에 고차 함수가 구현 가능하고 Swift에서 함수형 프로그래밍 패러다임이 가능하게 해줍니다.

  • 고차 함수: 다른 함수를 인자로 받거나 함수를 결과로 반환하는 함수입니다.
  • 함수형 프로그래밍: 데이터의 변형 없이 순수 함수(pure function)를 사용하여 프로그램의 동작을 설명하는 프로그래밍 패러다임입니다.

이러한 1급 객체의 특성은 코드의 재사용성, 모듈성, 유지보수성을 높이는 데 크게 도움이 되는데요. 특히 복잡한 프로그램에서 코드의 가독성을 높이고 오류 가능성을 줄이며 개발 과정을 단순화하는 데 도움이 된다고 하네요~

코드예시

 

집게사장이 집게리아의 메뉴를 좀 확장 싶은데요. 원래 대표메뉴였던 크랩버거 뿐만 아니라, 해조류 샐러드와 해파리 쉐이크도 신메뉴로 추가하기로 했습니다.

// 크랩버거, 해조류 샐러드, 해파리 쉐이크를 만드는 함수
func makeKrabbyPatty() -> String {
    return "크랩버거"
}

func makeSeaweedSalad() -> String {
    return "해조류 샐러드"
}

func makeJellyfishShake() -> String {
    return "해파리 쉐이크"
}

// 주문을 처리하는 함수들
func serveKrabbyPatty() {
    print("\(makeKrabbyPatty()) 나왔습니다!")
}

func serveSeaweedSalad() {
    print("\(makeSeaweedSalad()) 나왔습니다!")
}

func serveJellyfishShake() {
    print("\(makeJellyfishShake()) 나왔습니다!")
}

// 각각의 요리를 주문
serveKrabbyPatty()
serveSeaweedSalad()
serveJellyfishShake()

 

이렇게 구현이 되었습니다. 하지만 각 요리마다 별도의 serve 함수가 정의되고있는데 뭔가 줄이고 싶죠?

 

새로운 메뉴가 추가될 때마다 해당 메뉴를 서빙하는 새로운 함수를 만들어야 하므로 코드의 중복이 발생하고 유지보수가 어려워 질 것 같네요.

 

이런 상황에서 우리는 함수의 1급 객체의 특성을 이용하면 코드를 줄일 수 있습니다 ㅎㅎ

serveOrder 함수는 다양한 요리를 만드는 함수를 인자로 받아 처리하는데요, 새로운 메뉴가 추가되더라도 serveOrder 함수를 재사용할 수 있기 때문에 코드의 중복을 줄이고 유지보수성을 향상시킬 수 있을 것 같아요 :)

// 크랩버거, 해조류 샐러드, 해파리 쉐이크를 만드는 함수
func makeKrabbyPatty() -> String {
    return "크랩버거"
}

func makeSeaweedSalad() -> String {
    return "해조류 샐러드"
}

func makeJellyfishShake() -> String {
    return "해파리 쉐이크"
}

// 주문을 처리하는 고차 함수
func serveOrder(dish: () -> String) {
    print("\(dish()) 나왔습니다!")
}

// 다양한 요리를 주문하는 방법
serveOrder(dish: makeKrabbyPatty)
serveOrder(dish: makeSeaweedSalad)
serveOrder(dish: makeJellyfishShake)

이 예제에서, 다양한 요리를 만드는 함수들(makeKrabbyPatty, makeSeaweedSalad, makeJellyfishShake)은 모두 1급 객체로, serveOrder 함수의 인자로 전달됩니다. 새로운 메뉴가 추가되어도 serveOrder 함수를 재사용함으로써, 코드의 중복이 줄고 훨씬 유연해졌죠.

실제 프로그래밍에서 사용예시

진행하고 있는 smeem 프로젝트의 코드인데요, rootView의 인자값으로 메서드가 들어간 것을 볼 수 있습니다 ㅎㅎ 

final class ForeignDiaryViewController: DiaryViewController {
    
    private let viewModel: ForeignDiaryViewModel
    private let viewFactory = DiaryViewFactory()
    
    private var cancelBag = Set<AnyCancellable>()
    
    // MARK: - Life Cycle
    
    init(viewModel: ForeignDiaryViewModel) {
        self.viewModel = viewModel
        super.init(rootView: viewFactory.createStepOneKoreanDiaryView())
        
        bind()
    }

 

이 외에도 다양한 예시가 있는데요~

1. API 연결 - 비동기 콜백 처리

func fetchData(from url: String, completion: @escaping (Data?, Error?) -> Void) {
    guard let url = URL(string: url) else { return }

    URLSession.shared.dataTask(with: url) { data, response, error in
        DispatchQueue.main.async {
            completion(data, error)
        }
    }.resume()
}

1급 객체의 특성 활용: 여기서 completion 클로저는 함수의 인자로 전달되고 네트워크 요청이 끝난 후 호출됩니다. 이 코드에서 클로저는 변수나 상수에 저장될 수 있고 다른 함수의 인자로 전달될 수 있으며.

2. UI 로직 - 버튼 액션 처리

let button = UIButton()
button.addAction(UIAction { _ in
    print("버튼이 탭되었습니다.")
}, for: .touchUpInside)

1급 객체의 특성 활용: 액션 처리 클로저는 UIAction 생성자에 직접 전달되어, 버튼 탭 이벤트에 대한 콜백 함수로서 동작합니다. 이는 클로저가 다른 객체의 메소드 인자로 전달되어 사용될 수 있음을 보여줍니다.

3. 애니메이션 - 애니메이션 완료 클로저

UIView.animate(withDuration: 1.0, animations: {
    // 애니메이션 변화를 여기에 정의
}) { finished in
    if finished {
        print("애니메이션이 완료되었습니다.")
    }
}

1급 객체의 특성 활용: 애니메이션 완료 후 실행될 클로저는 UIView.animate 함수의 인자로 전달됩니다. 클로저가 실행 시점을 조절할 수 있는 콜백 함수로 활용될 수 있음을 보여주고 있습니다.

4. 고차 함수 사용 - 컬렉션 변형

let numbers = [1, 2, 3, 4, 5]
let squaredNumbers = numbers.map { $0 * $0 }

1급 객체의 특성 활용: 여기서 클로저 { $0 * $0 }map 함수의 인자로 전달되어 배열의 각 요소에 적용됩니다. 이는 함수(혹은 클로저)가 다른 함수의 인자로 전달될 수 있음을 보여주고 고차 함수의 개념을 잘 나타냅니다.

5. 비동기 작업 - GCD를 사용한 비동기 코드 실행

DispatchQueue.global(qos: .background).async {
    // 백그라운드 작업
    let result = "백그라운드 작업 결과"

    DispatchQueue.main.async {
        // 메인 스레드에서 UI 업데이트
        print(result)
    }
}

1급 객체의 특성 활용: 여기서 비동기 실행을 위한 클로저는 DispatchQueue.async 메소드의 인자로 전달됩니다. 이는 클로저가 실행 블록으로서 다른 함수에 전달되어, 지정된 타이밍에 실행될 수 있음을 보여줍니다.

6. 콜백 및 이벤트 핸들링 - 사용자 정의 이벤트 처리

class EventManager {
    private var eventHandler: (() -> Void)?

    func bindEventHandler(_ handler: @escaping () -> Void) {
        eventHandler = handler
    }

    func triggerEvent() {
        eventHandler?()
    }
}

bindEventHandler 메소드를 통해 외부에서 클로저를 전달받아 eventHandler에 저장합니다.

이 클로저는 나중에 triggerEvent 메소드가 호출될 때 실행됩니다. 이 패턴은 클로저가 변수에 저장될 수 있고, 나중에 호출될 수 있는 콜백 함수로 활용됨을 보여줍니다. 이는 객체지향 설계에서의 이벤트 리스너나 옵저버 패턴과 유사한 방식입니다.

 

이번 포스팅에서는 다양하게 1급 객체의 특성을 활용한 코드 예시까지 보았습니다.

 

1급객체를 이해하는데에 도움이 되었으면 좋겠네요!