Back pressure

Combine은 구독자가 데이터의 흐름을 제어하도록 설계되어 있습니다. 그래서 구독자는 파이프라인에서 처리가 발생하는 시기와 대상도 제어합니다. 구독자가 얼마나 많은 정보를 원하거나 수용할 수 있는지에 대한 정보를 제공함으로써 파이프라인 내에서 처리를 주도한다는 것을 의미합니다. 이러한 요청은 구성된 파이프라인을 통해 전파되며 각 Operator는 차례대로 데이터 요청을 수락하고 연결된 Publisher에게 정보를 요청합니다.
이 것을 백 프레셔 라고 불리는 Combine의 기능이라고 합니다!

파이프라인이 취소되면 해당 파이프라인은 다시 시작되지 않습니다. 취소된 파이프라인 대신 새로운 파이프라인을 만들어야 합니다.

생명주기(Life Cycle)

lifeCycle

1. 구독자가 게시자에게 연결되면 구독자의 호출로 시작됩니다. .subscribe(_: Subscriber

2. 게시자는 차례대로 구독을 호출을 확인합니다. receive:subscription: Subscription

3. 구독이 승인된 후 구독자는 값을 요청합니다 request(_: Demand)

4. 게시자가 값이 있다면 값을 보내게 됩니다. 게시자는 요청 한 양보다 더 많은 값을 줄 수 없습니다. receive(_: Input)

5. 구독이 완료된 후 언제든지 구독자는 다음과 같이 구독을 취소할 수 있습니다. .cancel()

6. 게시자는 선택적으로 완료가 되었다는것을 보냅니다. 정상적인 종료일 수도 있고 오류 유형을 알려주는 완료일 수도 있습니다. 취소된 파이프라인은 완료를 보내지 않습니다. receive(completion:)

반응형

Publisher(게시자)

  • 요청 시 데이터를 제공해줌
  • 구독이 없는 경우 Publisher는 데이터를 제공하지 않음
  • 두 가지 유형을 제공함(Outpt Type, Failure Type)

publisher

Subscriber(구독자)

  • 구독자는 데이터를 요청하고 Publisher가 제공한 데이터(및 오류)를 처리해야 함
  • 입력에 대한 유형과 실패에 대한 유형 두 가지 연관된 유형
  • 구독자는 데이터를 요청을 시작하고 수신하는 데이터의 양을 제어함
  • Subscriber가 없으면 게시가 아예 되지 않기 때문에 Subscriber는 작업을 주도하는 것으로 생각할 수 있습니다.
    subscriber

Operator(연산자)

  • 연산자는 Publisher Protocol과 Subscriber Protocol을 모두 채택하는 클래스
  • 게시자 구독 및 모든 구독자에게 결과 보내기를 지원
  • 게시자가 데이터를 제공하고 구독자가 요청한 데이터를 처리, 반응 및 변환하기 위해 체인을 만들 수 있음
    operator

연산자는 값 또는 유형을 변환하는데 사용되며 스트림을 분할 또는 복제하거나 스트림을 병합할 수도 있습니다. 그리고 항상 (출력/실패) 유형의 조합으로 정렬되어야 합니다. 만약 유형이 올바르지 않으면 컴파일러에서 오류가 발생합니다!


아래는 간단한 Combine 파이프라인 입니다.

let _ = Just(5)                     // 1
    .map { value -> String in     // 2
        return "a string"
    }
    .sink { receivedValue in      // 3
        print("The end result was \(receivedValue)")
    }

1) Just 파이프라인은 정의되어있는 값으로 응답하는 게시자입니다. 이미 정의되어있는 값이기 때문에 실패 유형이 없죠? 그래서 Error 유형은 NEVER가 됩니다.
2) map을 통해 기존의 값을 기반으로 한 새로운 유형의 값을 반환할 수 있습니다. 예제에서는 기존 값을 무시하고 새로운 String 값을 반환합니다.
3) sink 구독자로 인해 파이프라인이 종료됩니다.

파이프라인은 출력 유형과 실패 유형 두 가지에 의해 연결된 일련의 작업입니다. 파이프라인을 생성할 때 최종 목표를 달성하기 위해 데이터, 유형(스트림의 성공 여부 등)을 변환하는 데 연산자를 사용하게 됩니다. 여기서 말하는 최종 목표는 대부분 사용자 인터페이스 요소를 변화시키는 작업이겠죠? 그렇기에 이러한 변환을 돕기 위한 게 바로 Combine의 연산자입니다.

Combine의 대표 연산자 tryMap

Combine의 연산자를 보다 보면 try###라는 이름의 연산자들이 있습니다. 모두 기존의 함수와 비슷한 동작을 하지만 실패 유형을 가지고 있어 실패 유형에 대한 처리를 도와줍니다. map은 위의 예제에서처럼 String만을 반환하지만 tryMap의 경우 유형을 제공하는 게시자를 <String, Never>을 사용하는 구독자로 끝납니다. 간단하게 throw를 해야 한다면 try가 붙은 연산자를 사용하면 됩니다.

let _ = Just(5) 
    .map { value -> String in 
        switch value {
        case _ where value < 1:
            return "none"
        case _ where value == 1:
            return "one"
        case _ where value == 2:
            return "couple"
        case _ where value == 3:
            return "few"
        case _ where value > 8:
            return "many"
        default:
            return "some"
        }
    }
    .sink { receivedValue in 
        print("The end result was \(receivedValue)")
    }
  • Just<Int, Never> 유형의 조합을 만들었고 정의된 단일 값을 제공합니다.
  • .map() 함수를 통해 Int를 받아 String으로 반환합니다. 실패 유형인 Never는 변경되지 않으므로 통과됩니다.
  • sink를 통해 <String, Never> 조합을 수신합니다.
반응형

SwiftUI로 iOS개발을 하다보면 자연스럽게 Combine을 접하게 됩니다. 사실 Combine에 대해 정확한 개념이 없더라도 개발하는데에 있어서 크게 무리가 없었어요. 하지만 큰 프로젝트를 진행하면서 시간의 흐름에 따른 값 처리 같은 부분, 코드의 퀄리티 등을 높이기 위해 Combine을 깊게 학습할 필요가 있다고 생각이 들어 Combine에 대한 공부를 시작해 보려고 합니다.

이번 글에서는 Combine에 대한 간단한 정의? 로 적어볼까 합니다!

Combine?

Apple에서는 Combine을 다음과 같이 설명합니다.

  • 시간의 흐름에 따라 발생하는 이벤트를 처리하기 위한 Swift API
    Combine은 RxSwift와 유사한 Reactive Programming 라이브러리 입니다.

함수형 Reactive Programming

함수형 프로그래밍을 하기 위한 가장 대표적인 함수로 map, filter, reduce가 있습니다.
Combine은 이러한 함수들을 모두 지원하며 스트림을 분할하고 병합하는 기능이 포함되어 있습니다. 그래서 스트림을 통해 흐르는 데이터를 변환하는 작업까지 함수형 프로그래밍으로 만들 수 있게 됩니다.

Combine의 관찰자 패턴은 단일 객체를 감시하여 변경 및 업데이트 알림을 제공합니다. 시간이 지남에 따라 이러한 알림으로 개체 스트림을 구성합니다. 또한 단일이 아닌 변경되는 둘 이상의 요소를 관찰하는 논리로 만들 수 있습니다. 그 외에도 일부는 실패 할 수 있는 추가 비동기 작업을 수행하기도 합니다. 타이밍에 따라 스트림의 콘텐츠를 변경하거나 컨텐츠의 타이밍을 변경할 수 있고 이러한 이벤트 스트림의 흐름, 타이밍, 오류 발생을 처리하고 시스템이 이러한 모든 이벤트에 응답하는 방식을 조정하게 됩니다.

우리는 사실 이미 Combine을 사용하고 있다.

Combine은 Apple에서 지원하는 다른 프레임워크 중 일부에서 활용하고 있습니다. 예를 들면 Foundation의 NotificationCenter, URLSession, Timer 등...

언제 Combine을 사용해야 할까?

  • 다양한 입력에 반응하는 무언가를 설정하는 경우

너무 포괄적인 문장이지만 위에 대한 경우에 Combine을 사용하기가 가장 자연스럽다고 합니다. 사실 유저 인터페이스에 관련된 거의 대부분의 상황이 될거 같네요.

Combine은 이를 한단계 더 발전시켜 속성을 감시하고, 객체를 바인딩해주며, UI까지 컨트롤할 수 있게되어 더 높은 수준의 이벤트 전송과 수신, 거의 모든 Apple의 기존 API 시스템과 통합을 지원합니다.

Combine으로 할 수 있는 대표적인 작업들

  • 필드에 입력한 값이 유효한 경우에만 Submit 버튼이 활성화되도록 설정
  • 비동기작업을 수행하고 반환된 값을 사용하여 View를 업데이트할 방법과 대상을 선택할 수 있음
  • 사용자가 텍스트필드에 동적으로 입력하고 입력한 내용을 기반으로 사용자 인터페이스 보기를 업데이트함

가장 기본적인 예들이며 그 외에 가장 중요한 비동기 작업의 오류 처리가 있습니다.

반응형

SwiftUI로 개발을 하다보면 네비게이션 바를 커스텀 색상으로 바꾸어야 할 경우가 생깁니다.

예를 들면 특정 색을 가지고있다가 특정 뷰에서만 투명하게 처리한다던지...

UINavigationController를 extension한 후 viewDidload()를 override하여 사용할 수 있어요.

View에서 편하게 사용하기 위해 View안에 NotificationCenter로 구현하여 사용합니다!

 

extension View {
	func setNavigationBarColor(color: Color) {
    	DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 
              NotificationCenter.defualt.post(
              name: NSNotification.Name("UPDATENAVIGATIONBAR"),
              object: nil,
              userInfo: ["color": color]
            )
        }
    }
    
    func resetNavigationBar() { 
    	DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 
        	NotificationCenter.defualt.post(
              name: NSNotification.Name("UPDATENAVIGATIONBAR"),
              object: nil
            )
        }
    }
    
    func setNavigationTitleColor(color: Color) { 
    	DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 
              NotificationCenter.defualt.post(
              name: NSNotification.Name("UPDATENAVIGATIONBAR"),
              object: nil,
              userInfo: [
              	"color": color,
                "forTitle": Bool
              ]
            )
        }
    }

}

extension UINavigationController { 

	open override func viewDidLoad() {
    	NotificationCenter.default.addObserver(
        self,
        selector: #selector(updateNavigationBar(notification:)),
        name: NSNotification.Name("UPDATENAVIGATIONBAR", object: nil
        )
    }
    
    @objc
    func updateNavigationBar(notification: Notification) { 
    
    	if let info = notification.userInfo {
        	let color = info["color"] as! Color
            
            if let _ = info["forTitle"] { 
            	navigationBar.standardAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor(color)]
                navigationBar.standardAppearance.titleTextAttributes = [.foregroundColor: UIColor(color)]
                
                navigationBar.scrollEdgeAppearance?.largeTitleTextAttributes = [.foregroundColor: UIColor(color)]
                navigationBar.scrollEdgeAppearance?.titleTextAttributes = [.foregroundColor: UIColor(color)]
                
                navigationBar.compactAppearance?.largeTitleTextAttributes = [.foregroundColor: UIColor(color)]
                navigationBar.compactAppearance?.titleTextAttributes = [.foregroundColor: UIColor(color)]
                
                return
            }
            
            if color == .clear { 
              let transparentAppearance = UINavigationBarAppearance()
              transparentAppearance.configureWithTransparentBackground()

              navigationBar.standardAppearance = transparentAppearance
              navigationBar.scrollEdgeAppearance = transparentAppearance
              navigationBar.compactAppearance = transparentAppearance
              return
            }
            
            let apperance = UINavigationBarAppearance()
            appeerance.backgroundColor = UIColor(color)
            
            navigationBar.standardAppearance = appeerance
            navigationBar.scrollEdgeAppearance = appeerance
            navigationBar.compactAppearance = appeerance
            
        } else {
        	let apperance = UINavigationBarAppearance()
            
            let transparentAppearance = UINavigationBarAppearance()
            
            navigationBar.standardAppearance = appeerance
            navigationBar.scrollEdgeAppearance = transparentAppearance
            navigationBar.compactAppearance = appeerance 
        }
    }

}

 

 

사용할때는 아래처럼 사용합니다.

 

Button(action: {
	setNavigationBarColor(color: .red)
}) { 
	Text("빨간색으로 네비게이션바 색상 빨간색으로 변경하기")
}

Button(action: { 
	resetNavigationBar()
}) { 
	Text("reset")
}

Button(action: {
	setNavigationBarTitleColor(color: .blue)
}) {
	Text("네비게이션바 타이틀 색상 파란색으로 변경하기")
}​

 

반응형

안녕하세요🐱 이번에는 GCD에 포함되어 있는 DispatchQue.main에 대해 다루어 보려고 합니다.

 

DispatchQueue.main는 메인 스레드에서 처리되는 Serial queue입니다. 모든 UI 작업이 여기서 수행해야 합니다.

 

sync, async

// 1
DispatchQueue.main.sync { 
	// code
}

// 2
DispatchQueue.main.async { 
	// code
}

먼저 sync는 동기, async는 비동기 실행이겠죠?

 

1) DispatchQueue.main.sync

작업이 다 끝날 때 까지 현재의 queue는 block되어 기다립니다.

 

DispatchQueue.main.sync는 메인 Thread에서 순차적으로 해당 코드를 실행해라! 라는 의미가 되지만 그냥 DispatchQueue.main.sync를 호출하면 Deadlock(교착 상태)에 걸려 앱이 강제종료가 됩니다.

DispatchQueue.main은 메인 스레드에서 전역적으로 사용 가능한 serial queue입니다. 앱의 실행에 있어서 끊임없이 앱의 이벤트를 처리하고 있습니다. 하지만 DispatchQueue.main.sync를 호출함으로써 끊임없는 앱의 이벤트 처리가 대기상태로 멈추게 되면서 모든 앱의 이벤트 처리가 멈추게되며 우리가 호출한 DispatchQueue.main.sync 마저도 멈추게 됩니다. 그렇기 때문에 꼭 필요한 상황에서만 사용해야 합니다. 

DispatchQueue.global().async {
	// UI가 변경되기 전에 실행
	DispatchQueue.main.sync { 
    	// UI 업데이트
    }
    // UI 변경 후 실행
}

가장 대표적인 상황은 위 코드처럼 백그라운드 스레드에서 비동기로 처리되는 도중에 UI업데이트를 기다렸다가 실행되어야 하는 상황입니다. 그 외에는 DispatchQueue.main.sync의 사용을 지양해야 합니다.

 

 

2) DispatchQueue.main.async

작업을 수행하자마자 현재의 queue에 컨트롤을 넘겨주고 작업이 끝나기 전까지 기다릴 필요가 없습니다.

 

 데이터를 로드하거나 특정 무거운 작업을 실행하게 될 때 어플리케이션의 UI는 느려지거나 멈추게 됩니다.  DispatchQueue.main.async는 클로저 안의 실행문을 작업을 대기열로 보내 비동기로 작업을 실행하여 동시에 2가지 이상의 task를 수행할 수 있도록 도와줍니다. 하지만 스레드에 안전하지 않기 때문에 서로 다른 스레드에서 동일한 변수를 변경, 사용하게 된다면 문제가 되니 주의해야 합니다!

반응형

HTTP 통신을 하다보면 자연스럽게 비동기 처리를 접하게 되는데요. (대표적인 HTTP 통신 라이브러리 Alamofire)

비동기 처리를 함으로써 특정 수행 작업을 하는 중간에 계속해서 다른 작업을 할 수 있게 됩니다.

앱에서 특히나 비동기 처리가 중요한 이유는 앱 사용 도중에 속도가 느려지거나 부자연스러운 UX가 보일 경우 이용자가 크게 거부감이 느껴지기 때문입니다. 

 

GCD(Grand Central Dispatch)

- GCD는 비동기 작업을 관리하기 위한 강력한 도구입니다. 평소 자연스럽게 사용했던 DispatchQue.main.async 등등...

- 사용하기 쉬운 장점이 있습니다.

 

동기성에 대해 설명하기 전에 먼저 집고 넘어가야 할 부분이 Process와 Thread입니다.

- Process는 운영체제로부터 자원을 할당받는 작업의 단위입니다.

- Thread는 할당 받은 자원을 이용하는 실행의 단위이며 프로세스 내에 여러개가 생길 수 있습니다.

 

어플리케이션 하나가 Process가 되고 그 안에 분기 처리가 Thread가 되는 셈 입니다. 그럼 Process가 어떠한 일을 할 때 컴퓨터의 자원을 최대한으로 활용하기 위해 병렬적으로 일을 하게 만들어야 합니다. 이 이유 때문에 비동기 작업은 놀고 있는 자원을 활용하는 Multi-Thread Programming의 영역이라고 합니다. I/O를 기다리게 하지 않고 다른 일을 하거나, 놀고 있는 여분의 CPU 코어들을 최대한 사용하게 하는 것이 핵심입니다.

 

싱글 코어에서는 time-slicing이라는 방법을 통해 동시성을 해결합니다. 한번에 하나의 Thread만 사용하기 때문에 Context Switching을 하며 동작중인 것을 멈추고 다른 동작을 실행한 후 다시 왔다 갔다 하며 작업을 수행합니다.

 

멀티 코어 에서는 동시에 여러개의 Thread를 수행하며 이 방식을 parallelism이라고 합니다.

 

GCD로 동시 실행을 활용할 수 있는데 병렬 처리를 위해서는 동시성이 필요하지만 동시성은 병렬 처리를 보장 하지 않는다고 합니다.

반응형

Xcode 13.2 버전 업데이트 후 발생하는 오류

 

애플 공식 답변

- We're currently investigating this issue — thank you to those who have filed bug reports so far.

To workaround this issue, please re-download Xcode 13.2 directly from Releases section of the Apple Developer website. This information is also captured in the Xcode 13.2 Release Notes.

If you still encounter this issue after downloading that specific version of Xcode, please open a bug report using Feedback Assistant, and post the FB number here.

 

해결방법은 Xcode 삭제 후 Apple Developer 웹 사이트에서 직접 재설치 한다.

 

https://developer.apple.com/download/all/?q=Xcode%2013.2

 

로그인 - Apple

 

idmsa.apple.com

 

반응형

'ios > Xcode' 카테고리의 다른 글

Terminal에서 바로 Xcode 프로젝트 파일 실행하기  (0) 2022.05.11

+ Recent posts