본문 바로가기

iOS

싱글톤 패턴, 옵저버 패턴, 코디네이터 패턴

728x90
반응형
SMALL

싱글톤 패턴

싱글톤 패턴은 객체를 단 한 번만 생성하고, 이후에는 생성된 객체를 계속해서 사용하는 디자인 패턴입니다. 이 패턴을 사용하면 전역 변수를 사용하지 않아도, 전역 변수와 같은 기능을 수행할 수 있습니다.

Swift에서도 싱글톤 패턴을 적용하여 객체를 생성하고, 사용할 수 있습니다. 이 디자인 패턴을 적용하면 객체를 여러 번 생성하는 것을 방지하여 메모리 사용량을 줄일 수 있습니다.

싱글톤 패턴의 장단점은 다음과 같습니다.

장점:

  • 객체 생성 횟수를 줄여 메모리 사용량을 최적화할 수 있습니다.
  • 전역 변수를 사용하지 않아도 객체를 공유할 수 있습니다.

단점:

  • 멀티스레드 환경에서 안전하지 않을 수 있습니다.
  • 디버깅이 어려울 수 있습니다.

다음은 Swift에서 싱글톤 패턴을 적용한 예제 코드입니다.

class Singleton {
    static let sharedInstance = Singleton()
    private init() {}
}

위 예제 코드에서 sharedInstance는 유일한 객체를 저장하는 프로퍼티입니다. private init()은 객체 생성을 외부에서 할 수 없도록 하는 것입니다. 이렇게 구현하면 Singleton 클래스의 객체는 Singleton.sharedInstance를 통해서만 접근할 수 있습니다.

 

옵저버 패턴

옵저버 패턴(Observer Pattern)은 관찰 중인 객체에서 발생하는 이벤트를 여러 다른 객체에 알리는 메커니즘을 정의하는 디자인 패턴입니다. 이 패턴을 사용하면 객체 간의 결합도를 줄이고 유연한 설계를 가능하게 합니다. 이 패턴을 사용하면, 한 객체의 상태가 변경되면 그 객체에 의존하는 다른 객체들에게 알림이 전달되어 자동으로 업데이트 됩니다. 이를 통해 객체 간의 결합도를 줄이고 유연한 설계를 가능하게 합니다.

옵저버 패턴의 장단점은 다음과 같습니다.

장점:

  • 느슨한 결합도: 옵저버 패턴을 사용하면 Subject(관찰 대상)과 Observer(관찰자)가 느슨하게 결합되어 있어서, 하나의 객체가 변경되더라도 다른 객체에게 영향을 미치지 않습니다.
  • 확장성: 새로운 Observer를 추가하는 것은 간단합니다. Subject와 Observer는 인터페이스를 통해 결합되어 있으므로, 새로운 Observer를 추가하더라도 기존 코드를 수정할 필요가 없습니다.
  • 재사용성: Subject와 Observer를 별도로 구현하므로, 이를 다른 프로젝트에서도 사용할 수 있습니다.

단점:

  • 메모리 누수: Subject와 Observer의 강한 참조로 인해 메모리 누수가 발생할 수 있습니다. 이를 해결하기 위해서는 Observer를 등록/해제하는 기능을 구현하거나, 약한 참조(weak reference)를 사용하는 등의 방법이 필요합니다.

Swift에서 옵저버 패턴을 구현하는 방법은 크게 두 가지가 있습니다. 하나는 Combine 프레임워크의 Publisher를 사용하는 방법이고, 다른 하나는 NotificationCenter를 사용하는 방법입니다.

 

Combine을 사용한 예제 코드

import Combine

class Subject {
    var value: Int = 0 {
        didSet {
            publisher.send(value)
        }
    }
    
    let publisher = PassthroughSubject<Int, Never>()
}

class Observer {
    var cancellables = Set<AnyCancellable>()
    
    init(subject: Subject) {
        subject.publisher
            .sink(receiveValue: { value in
                print("Received value: \(value)")
            })
            .store(in: &cancellables)
    }
}

let subject = Subject()
let observer = Observer(subject: subject)
subject.value = 1
subject.value = 2

NotificationCenter를 사용한 예제 코드

// 옵저버 등록
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(_:)), name: NSNotification.Name("MyNotification"), object: nil)

// 옵저버 해제
NotificationCenter.default.removeObserver(self)

// 옵저버 처리 함수
@objc func handleNotification(_ notification: Notification) {
    // Notification 처리 코드
}

 

코디네이터 패턴

코디네이터 패턴은 iOS 애플리케이션 아키텍처 패턴 중 하나로, 화면 전환을 처리하고 애플리케이션의 흐름을 제어하는 역할을 합니다. 이 패턴을 사용하면 뷰컨트롤러들이 서로 직접적으로 연결되지 않고, 각각의 역할에 맞게 독립적으로 분리하여 작성할 수 있습니다. 이러한 패턴의 장점으로는 애플리케이션의 유지보수성이 향상되고, 코드의 재사용성이 높아지며, 확장성이 좋아진다는 것이 있습니다. 반면에, 구현하기 어렵고 처음에는 복잡해 보일 수 있습니다.

 

Swift에서 코디네이터 패턴을 구현하기 위해서는 먼저 화면 간 전환을 처리할 코디네이터 클래스를 작성하고, 각각의 화면을 처리할 뷰컨트롤러들을 만들어야 합니다. 이때, 코디네이터 클래스는 뷰컨트롤러의 생성과 전환을 담당하며, 뷰컨트롤러들은 각각의 화면에 대한 로직을 구현합니다.

 

장점

  • 유지보수성과 확장성: 뷰컨트롤러 간의 결합도를 낮춰 유지보수성과 확장성을 높일 수 있습니다. 이는 애플리케이션의 규모가 커질수록 더욱 중요해지는 부분입니다. 또한, 각각의 뷰컨트롤러가 독립적으로 작동하기 때문에 코드의 재사용성도 높아집니다.

단점

  • 구현의 여려움: 코디네이터 패턴을 구현하기 위해서는 구현이 복잡해질 수 있으며, 코드의 길이가 늘어날 수 있다는 단점이 있습니다. 하지만, 이러한 단점은 적절한 디자인 패턴과 적극적인 리팩토링으로 극복할 수 있습니다.
import UIKit

protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    var navigationController: UINavigationController { get set }
    
    func start()
}

extension Coordinator {
    func childDidFinish(_ child: Coordinator?) {
        for (index, coordinator) in childCoordinators.enumerated() {
            if coordinator === child {
                childCoordinators.remove(at: index)
                break
            }
        }
    }
    
    func addChildCoordinator(_ coordinator: Coordinator) {
        childCoordinators.append(coordinator)
    }
}

class MainCoordinator: Coordinator {
    var childCoordinators = [Coordinator]()
    var navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let vc = ViewController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: false)
    }
    
    func goToDetail() {
        let child = DetailCoordinator(navigationController: navigationController)
        addChildCoordinator(child)
        child.start()
    }
}

class DetailCoordinator: Coordinator {
    var childCoordinators = [Coordinator]()
    var navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let vc = DetailViewController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: true)
    }
    
    func goToNextDetail() {
        let child = NextDetailCoordinator(navigationController: navigationController)
        addChildCoordinator(child)
        child.start()
    }
}

class NextDetailCoordinator: Coordinator {
    var childCoordinators = [Coordinator]()
    var navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let vc = NextDetailViewController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: true)
    }
    
    func didFinish() {
        navigationController.popViewController(animated: true)
        parentCoordinator?.childDidFinish(self)
    }
}

class ViewController: UIViewController, Storyboarded {
    weak var coordinator: MainCoordinator?
    
    @IBAction func buttonTapped(_ sender: Any) {
        coordinator?.goToDetail()
    }
}

class DetailViewController: UIViewController, Storyboarded {
    weak var coordinator: DetailCoordinator?
    
    @IBAction func buttonTapped(_ sender: Any) {
        coordinator?.goToNextDetail()
    }
}

class NextDetailViewController: UIViewController, Storyboarded {
    weak var coordinator: NextDetailCoordinator?
    
    @IBAction func buttonTapped(_ sender: Any) {
        coordinator?.didFinish()
    }
}

protocol Storyboarded {
    static func instantiate() -> Self
}

extension Storyboarded where Self: UIViewController {
    static func instantiate() -> Self {
        let id = String(describing: self)
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        return storyboard.instantiateViewController(withIdentifier: id) as! Self
    }
}

이 예제에는 각각 고유한 탐색 흐름을 담당하는 'MainCoordinator', 'DetailCoordinator' 및 'NextDetailCoordinator'의 세 가지 코디네이터 클래스가 포함되어 있습니다. 각 코디네이터 클래스는 ChildCoordinators 및 navigationController 속성뿐만 아니라 start() 함수를 구현해야 하는 Coordinator 프로토콜을 준수합니다.

addChildCoordinator 함수는 자식 코디네이터를 부모 코디네이터의 childCoordinators 배열에 추가하는 데 사용되며 childDidFinish 함수는 하위 코디네이터가 완료되면 배열에서 제거하는 데 사용됩니다.

728x90
반응형
LIST

'iOS' 카테고리의 다른 글

2023 WWDC Student Challenge 도전.  (0) 2023.04.14
시리얼(Serial) 큐, 컨커런트(Concurrent) 큐  (0) 2023.04.13
REST API  (0) 2023.04.13
Content hugging과 Compression resistance  (0) 2023.04.12
intrinsicContentSize  (0) 2023.04.12