[240123] Today I Learned

YoungHyun Kim·2024년 1월 23일
1

TIL ✍️

목록 보기
39/68
post-thumbnail

소프트웨어 아키텍처

시스템 전체의 구조와 조직을 결정하는 프로세스

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() // delegate 객체를 생성하고,

myObject.delegate = delegateObject // myObject안의 변수에 해당 객체를 저장해서
myObject.someMethods() // delegate 객체 내의 메서드에 접근할 수 있다!!

특징

  • 다중상속을 지원하지 않는 Swift에서, 이러한 Protocol을 사용하면 다중 상속과 유사한 구현이 가능함!
  • Delegate는 자체적으로 이벤트를 발생시키지 않는다
  • A객체에서 B객체의 delegate를 호출하면, delegate는 B객체의 메서드를 제공하고 A객체에서 발생한 이벤트나 데이터를 처리하게 된다

Observer 패턴 : 객체의 변경(이벤트)를 해당 객체에 의존하는 타 객체에 전달하여 변경사항을 적용함

  • 내장된 NotificationCenter를 사용하면 Observer 패턴을 쉽게 구현할 수 있다.
  • 변경점을 모든 Observer들에게 전달하면, 적절한 NotificationHandler를 선언해서 해당 알림이 일어났을 때 실행할 작업을 만들 수 있다.

특징

  • 객체 간의 결합도를 낮춰 유연성을 높인다
  • 변경사항을 적용할 객체들을 동적으로 관리할 수 있음
  • 변경사항을 적용할 객체 간의 직접적인 호출을 피할 수 있음
  • 객체의 변경사항에 대한 응답을 캡슐화하여 코드의 유지보수를 쉽게 함

주의점

  • 통제가 불가능: 한번 보내진 메세지를 제어하거나 중단할 방법이 없음
  • 디버깅이 어렵다: 메시지의 소스와 대상을 느슨하게 연결했기 때문에, 누가 이벤트를 보내고 수신했는지 파악이 어려움
  • 순서 보장이 불가능: 보내진 메세지가 어떤 순서로 처리될 지 보장할 수 없음
  • 메모리 누수의 위험성이 있음: 객체가 관찰자 목록에서 잘 제거될 수 있도록 하는 것이 중요함
  • 밀접한 결합: 이벤트 소스와 이벤트 핸들러 간의 밀접한 결합이 생기는 것을 주의해야 함

Singleton 패턴 : 하나의 객체 인스턴스만 생성, 전역 제공하는 디자인 패턴

  • 앱 전역에 공유해야하는 상태나 기능을 효율적으로 관리하기 좋음
class MySingleton {
	static let shared = MySingleton() 
    
    private init() {} // 객체 외부에서 인스턴스를 생성할 수 없도록 접근제한자 private를 사용함
    func doSomething() {
    	// code
    }
}

특징

  • 전역에서 단 하나의 인스턴스만 생성되었기 때문에, 객체 간의 의존성을 줄일 수 있음
  • 앱 전역에 공유해야하는 상태나 기능(Database, Userdata 등)을 효율적으로 관리하기 좋음

주의점

  • 싱글톤은 앱의 전역 상태를 관리하므로, 프로그램의 예측이 어려울 수 있다
    • 싱글톤이 유지해야 하는 상태를 최소한해서 관리하는 것이 좋음
  • 싱글톤이 앱의 전역 상태를 관리하므로, 테스트하기가 어려움
    • 테스트케이스 간의 상태가 공유될 가능성이 높아 단일 테스트 케이스에 대한 테스트 진행이 어렵다
    • 테스트 방법을 고려해서 싱글톤을 설계해야 함
  • 클래스가 직접 싱글톤 인스턴스에 의존하면, 높은 수준의 결합을 형성하는 것임
    • 이것을 완화하기 위한 의존성 주입 등의 기법이 있음

비동기 프로그래밍

동기 : 순차적으로 진행되는 것
비동기 : 순차적으로 기다리지 않고, 여러 작업이 동시에 진행되는 것을 의미

쓰레드 Thread : 컴퓨터의 가장 작은 실행 단위

  • 각각의 쓰레드는 독립적으로 실행될 수 있으며, 여러 쓰레드가 동시에 작업을 수행할 수 있음
  • 이러한 다중 쓰레드 작업은 시스템 자원을 효율적으로 활용하고, 병렬적으로 작업을 처리할 수 있도록 도와줌

동기 Synchronous : 순차적 실행

  • 큐에 들어선 한 작업이 완료될 때 까지 다른 작업은 대기 -> 순차적으로 작업을 수행
    • 작업의 수행 순서가 중요한 경우에 사용!

비동기 Asynchronous : 여러 작업의 동시 실행

  • 큐에 들어선 작업이 시작되어도, 결과를 기다릴 필요 없이 다음 작업이 수행될 수 있음
    • 여러 작업이 동시에 수행되기에, 시스템 자원을 효과적으로 활용할 수 있음
  • 대표적으로 네트워크 요청, 파일 입출력, 사용자 입력 대기 등과 관련이 깊음

DispatchQueue : GCD를 사용하여 비동기적으로 작업을 관리하는데 사용되는 클래스

  • DispatchQueue를 사용하면 다양한 작업을 백그라운드 스레드에서 실행하고, 특정 스레드에서 작업을 예약, 실행할 수 있음.

    • 이러한 특성으로 앱의 성능을 향상시키고, 사용자 인터페이스의 반응성을 유지할 수 있음

    DispatchQueue의 종류

    • Serial Queue : 작업을 순차적으로 실행, 한 번에 하나의 작업만 실행
    • Concurrent Queue : 여러 작업을 동시에 실행, 여러 작업을 동시에 시작하며, 시스템 자원과 상황에 따라 동시에 실행됨

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
    ... // fetchDataUsingCallback 메서드는 주어진 URL에서 데이터를 다운로드하고,
    ... // 다운로드가 완료되면, Completion 클로저를 호출
    ... // Completion 클로저의 결과값을 전달함
    }
}
  • 순수한 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
    ... // await 키워드를 사용해서 URLSession.shared.data(from: url)메서드를 호출
    ... // 데이터 다운로드가 완료될 때 까지 일시 중단
    ... // 데이터 다운로드가 완료되면, 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을 활용한 비동기 프로그래밍

  1. Publisher에게 Subscriber가 붙음
  2. Publisher가 subscription을 보내고, Subscriber는 subscription를 통해Publisher에게 N개의 Value를 받겠다고 요청합니다.
  3. PublisherSubscriber에게 자유롭게(N개의) 값을 보낼 수 있습니다.
  4. Publisher 시간 흐름에 따라 값 시퀀스의 변화를, 흐름을 subscriber에게 발행하는 역할
  5. Publisher가 제한된 상황에서는 completion이나 error를 내려보내게 됩니다.
  • 내장된 Combineimport하여 사용할 수 있음
  • Cancellable은 구독을 해제하여 메모리 누수 방지할 수 있도록 하는 프로토콜

RxSwift를 활용한 비동기 프로그래밍

  • 대략적인 흐름은 Combine과 많이 다르지 않음
  • 외부 라이브러리 RxSwift를 설치하여야 사용할 수 있음
  • Observable은 비동기저긍로 데이터를 다운로드하고, 데이터가 발생할 때마다 onNext이벤트를 발생시킨다
  • 여러 비동기 작업을 처리해야하는 경우 callback에 비해 비교적 간단한 flatMap연산자를 활용하여 처리할 수 있음
profile
iOS 개발자가 되고 싶어요

0개의 댓글

관련 채용 정보