소프트웨어 아키텍처
시스템 전체의 구조와 조직을 결정하는 프로세스
MVC(Model-View-Controller)
Model : 데이터, 데이터를 조작하는 로직을 포함
- 주로 데이터 구조체
struct
나, class
로 표현됨
- DB나 네트워크와 같은 외부 데이터 소스와의 상호작용 담당
View : 사용자 인터페이스
- 사용자가 실제적으로 볼 수 있는 것들로 이루어짐
- 사용자의 입력을 받아 controller에 전달, model의 데이터를 받아 화면에 표현
Controller : 모델과 뷰 간 상호작용 관리
- 사용자의 입력(상호작용)을 받아 처리하고, 모델에 이를 전달(계산하는 부분이라고 생각하면 편할지도)
Model-View-Controller 구조의 장점
- 간단한 구조 : 개발자에게 직관적이고 이해하기 쉬운 구조를 제공
- 공식적으로 mvc를 따르는 Cocoa Framework와의 호환성이 좋다!
단점
- Massive view controller: 뷰 컨트롤러가 매우 비대해지는 경향이 있음(많은 책임와 역할이 집중되기 때문) -> 코드의 복잡성 증가, 이것은 유지보수성을 악화시킴...
- 테스트 용이성 : 사용자 인터페이스과 비즈니스 로직이 뒤섞인 구조 때문에 테스트 분리에 어려움이 있음
하지만 커스텀하게 문제를 해결할 수 있음!! 아예 해결할 수 없는 단점이 아니다~~
MVVM (Model-View-ViewModel)
Model : 데이터와 비즈니스 로직 담당
- 데이터만 가지고있던 MVC때의 Model과 다르게, 비즈니스 로직도 품고 있다.
ViewModel : 모델과 뷰 간 중간 매개체
- 뷰에 표시할 데이터를 가지고 있으며, 사용자 입력받아 모델에 전달해줌 (비즈니스 로직에 대해 집중)
View : 스토리보드와 컨트롤러를 총칭하는 개념
- 원초적으로 화면을 그리고 사용자 인터렉션을 받는 부분
Model-View-ViewModel 구조의 장점
- 뷰와 로직의 분리로 인해서 가독성이 향상되며 유지보수성이 높다
- ViewModel은 순수한 비즈니스 로직을 담고 있어, 테스트 용이성이 높다
단점
- 러닝커브: 초기에 이해가 어려움. 데이터 바인딩과 뷰모델 작성등의 개념 이해에 시간이 걸릴 수 있음
- 보일러플레이트(비슷한 코드가 반복적으로 사용되는 현상) : 비교적 작은 프로젝트에 적용되는 MVVM은 코드의 복잡성이 늘어나고, 보일러플레이트가 일어날 수 있음
디자인 패턴
소프트웨어 개발 과정에서 자주 발생하는 문제들에 대한 해결책을 재사용 가능한 형태로 정리한 것
- 개발자들 간의 공통 언어를 제공하여 효율적인 의사소통을 돕는다.
아키텍처와 디자인 패턴 간의 차이
| 아키텍처 | 디자인패턴 |
---|
규모와 적용 범위 | 시스템 전체의 구조와 레이아웃을 다룸 시스템의 주요 구성 요소와 이들 간의 관계, 데이터 흐름, 성능 최적화, 보안 정책 등을 정의 | 클래스나 객체의 작은 규모의 디자인 문제 해결, 개별 컴포넌트나 모듈 내의 상세 구조, 상호작용을 다룸 |
목적 | 시스템의 기본 구조를 정의 -> 전체적인 모습 관리, 시스템 성능, 확장성, 유지보수성을 보장 | 클래스나 객체 간의 상호작용을 구조화하여 재사용 가능한 솔루션 제공 |
적용 시점 | 시스템 설계 시 고려됨, 초기 개발 단계에서 정의됨 | 클래스나 객체의 작은 규모의 구조를 설계할 때 고려됨 주로 구현 단계에서 적용 |
Delegate(대리자) 패턴 : 한 객체가 다른 객체의 대리자가 되는...
- 다른 객체의 이벤트나 데이터를 처리하는 방식으로 구현
- 객체 간의 결합도를 낮추고, 유연하고 확장 가능한 코드 작성이 가능
let myObject = MyClass()
let delegateObject = MyDelegateClass()
myObject.delegate = delegateObject
myObject.someMethods()
특징
- 다중상속을 지원하지 않는 Swift에서, 이러한 Protocol을 사용하면 다중 상속과 유사한 구현이 가능함!
- Delegate는 자체적으로 이벤트를 발생시키지 않는다
- A객체에서 B객체의 delegate를 호출하면, delegate는 B객체의 메서드를 제공하고 A객체에서 발생한 이벤트나 데이터를 처리하게 된다
Observer 패턴 : 객체의 변경(이벤트)를 해당 객체에 의존하는 타 객체에 전달하여 변경사항을 적용함
- 내장된 NotificationCenter를 사용하면 Observer 패턴을 쉽게 구현할 수 있다.
- 변경점을 모든 Observer들에게 전달하면, 적절한
NotificationHandler
를 선언해서 해당 알림이 일어났을 때 실행할 작업을 만들 수 있다.
특징
- 객체 간의 결합도를 낮춰 유연성을 높인다
- 변경사항을 적용할 객체들을 동적으로 관리할 수 있음
- 변경사항을 적용할 객체 간의 직접적인 호출을 피할 수 있음
- 객체의 변경사항에 대한 응답을 캡슐화하여 코드의 유지보수를 쉽게 함
주의점
- 통제가 불가능: 한번 보내진 메세지를 제어하거나 중단할 방법이 없음
- 디버깅이 어렵다: 메시지의 소스와 대상을 느슨하게 연결했기 때문에, 누가 이벤트를 보내고 수신했는지 파악이 어려움
- 순서 보장이 불가능: 보내진 메세지가 어떤 순서로 처리될 지 보장할 수 없음
- 메모리 누수의 위험성이 있음: 객체가 관찰자 목록에서 잘 제거될 수 있도록 하는 것이 중요함
- 밀접한 결합: 이벤트 소스와 이벤트 핸들러 간의 밀접한 결합이 생기는 것을 주의해야 함
Singleton 패턴 : 하나의 객체 인스턴스만 생성, 전역 제공하는 디자인 패턴
- 앱 전역에 공유해야하는 상태나 기능을 효율적으로 관리하기 좋음
class MySingleton {
static let shared = MySingleton()
private init() {}
func doSomething() {
}
}
특징
- 전역에서 단 하나의 인스턴스만 생성되었기 때문에, 객체 간의 의존성을 줄일 수 있음
- 앱 전역에 공유해야하는 상태나 기능(Database, Userdata 등)을 효율적으로 관리하기 좋음
주의점
- 싱글톤은 앱의 전역 상태를 관리하므로, 프로그램의 예측이 어려울 수 있다
- 싱글톤이 유지해야 하는 상태를 최소한해서 관리하는 것이 좋음
- 싱글톤이 앱의 전역 상태를 관리하므로, 테스트하기가 어려움
- 테스트케이스 간의 상태가 공유될 가능성이 높아 단일 테스트 케이스에 대한 테스트 진행이 어렵다
- 테스트 방법을 고려해서 싱글톤을 설계해야 함
- 클래스가 직접 싱글톤 인스턴스에 의존하면, 높은 수준의 결합을 형성하는 것임
- 이것을 완화하기 위한 의존성 주입 등의 기법이 있음
비동기 프로그래밍
동기 : 순차적으로 진행되는 것
비동기 : 순차적으로 기다리지 않고, 여러 작업이 동시에 진행되는 것을 의미
쓰레드 Thread : 컴퓨터의 가장 작은 실행 단위
- 각각의 쓰레드는 독립적으로 실행될 수 있으며, 여러 쓰레드가 동시에 작업을 수행할 수 있음
- 이러한 다중 쓰레드 작업은 시스템 자원을 효율적으로 활용하고, 병렬적으로 작업을 처리할 수 있도록 도와줌
동기 Synchronous : 순차적 실행
- 큐에 들어선 한 작업이 완료될 때 까지 다른 작업은 대기 -> 순차적으로 작업을 수행
비동기 Asynchronous : 여러 작업의 동시 실행
- 큐에 들어선 작업이 시작되어도, 결과를 기다릴 필요 없이 다음 작업이 수행될 수 있음
- 여러 작업이 동시에 수행되기에, 시스템 자원을 효과적으로 활용할 수 있음
- 대표적으로 네트워크 요청, 파일 입출력, 사용자 입력 대기 등과 관련이 깊음
DispatchQueue : GCD를 사용하여 비동기적으로 작업을 관리하는데 사용되는 클래스
Swift로 비동기 프로그래밍 구현
1. Callback 활용
fetchDataUsingCallback
메서드를 활용하여 비동기 프로그래밍을 구현할 수 있음.
func fetchDataUsingCallback(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
...
...
...
}
}
- 순수한 Swift로만 구현할 수 있음.
- 여러 개의 연쇄 비동기 작업을 처리해야 하는 경우에는,
Callback Hell, pyramid of doom
이라고 불리는, Callback의 깊이가 너무 깊어지는 상황이 발생할 수도 있음.
2. Concurrency 활용
func fetchDataUsingConcurrency(from url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
...
...
...
}
- Swift 5.5 이상 버전에서 순수 Swift로만 구현할 수 있음
- Callback과 달리, 비동기 코드를 동기적으로 작성하는 것 처럼 보이지만, 백그라운드 스레드에서 비동기적으로 실행시킬 수 있음
async : 비동기 작업을 나타내는 함수나 블록을 표시
async
키워드가 붙은 함수 내부에서 await
을 사용하여 비동기 작업이 완료될 때까지 대기하고 결과를 가져올 수 있음!
await : 비동기 작업의 완료를 기다리고 해당 작업의 결과를 반환
await
키워드는 async
함수 내에서 사용
await
키워드는 값을 반환하는 함수나 메서드 앞에서 사용, 비동기 작업이 완료될 때까지 실행을 일시중지하고 그 결과를 반환
Reactive Programming : 데이터 스트림과 변화에 반응 -> 데이터 처리 수행
데이터 스트림의 생성, 변형, 구독 등을 다루는 프로그래밍 패러다임
Reactive Programming의 개념을 구현한 프레임워크에 Combine, RxSwift 등이 있다
Combine : Reactive Programming을 위한 공식 라이브러리, Publisher, Subscriber, Operator 등의 개념을 사용
RxSwift : Reactive Programming을 위한 Third-party 라이브러리, Observable, Observer, Operator 등의 개념을 사용
Reactive Programming 구성요소에 대한 이해
Observable(RxSwift)
/ Publisher(Combine)
- 데이터의 스트림을 나타내는 핵심 개념, 데이터의 발행자
Observer(RxSwift)
/ Subscriber(Combine)
Observable
/ Publisher
가 발행한 데이터를 구독, 처리하는 역할
Operators(RxSwift, Combine)
- 데이터 스트림을 변환하거나 조작하기 위한 함수들을 제공 (
map, filter, merge
등의 연산자를 사용해서 데이터를 다룰 수 있음)
Schedulers(RxSwift, Combine)
- 비동기 작업을 수행하는 스케쥴러를 제공해서 코드의 실행 시기와 순서를 관리
Combine을 활용한 비동기 프로그래밍

Publisher
에게 Subscriber
가 붙음
Publisher
가 subscription을 보내고, Subscriber
는 subscription를 통해Publisher
에게 N개의 Value를 받겠다고 요청합니다.
Publisher
는 Subscriber
에게 자유롭게(N개의) 값을 보낼 수 있습니다.
Publisher
시간 흐름에 따라 값 시퀀스의 변화를, 흐름을 subscriber에게 발행하는 역할
Publisher
가 제한된 상황에서는 completion이나 error를 내려보내게 됩니다.
- 내장된
Combine
를 import
하여 사용할 수 있음
Cancellable
은 구독을 해제하여 메모리 누수 방지할 수 있도록 하는 프로토콜
RxSwift를 활용한 비동기 프로그래밍
- 대략적인 흐름은
Combine
과 많이 다르지 않음
- 외부 라이브러리
RxSwift
를 설치하여야 사용할 수 있음
Observable
은 비동기저긍로 데이터를 다운로드하고, 데이터가 발생할 때마다 onNext
이벤트를 발생시킨다
- 여러 비동기 작업을 처리해야하는 경우
callback
에 비해 비교적 간단한 flatMap
연산자를 활용하여 처리할 수 있음