싱글톤 패턴
싱글톤 패턴은 객체를 단 한 번만 생성하고, 이후에는 생성된 객체를 계속해서 사용하는 디자인 패턴입니다. 이 패턴을 사용하면 전역 변수를 사용하지 않아도, 전역 변수와 같은 기능을 수행할 수 있습니다.
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 함수는 하위 코디네이터가 완료되면 배열에서 제거하는 데 사용됩니다.
'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 |