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를 수행할 수 있도록 도와줍니다. 하지만 스레드에 안전하지 않기 때문에 서로 다른 스레드에서 동일한 변수를 변경, 사용하게 된다면 문제가 되니 주의해야 합니다!

반응형

1. 가장 먼저 Android Studio 를 설치합니다.

 

https://developer.android.com/studio?hl=ko

 

Download Android Studio and SDK tools  |  Android Developers

<!-- hide description -->

developer.android.com

 

2. Flutter Plugin을 설치합니다

2-1. 설치 도중에 Dart도 필요하다고 뜨니 설치를 눌러줍시다.

- 이미 알고 계시겠지만 Dart는 Flutter 앱 개발 시 사용되는 언어입니다.

3. Restart를 하고 나면 새로 생긴 Create New Flutter Project 를 선택합니다.

4. Flutter 홈페이지에서 sdk를 설치합니다.

https://docs.flutter.dev/get-started/install/macos

 

macOS install

How to install on macOS.

docs.flutter.dev

 

4.1 원하는 위치에 flutter 폴더를 이동 시킨 후 터미널에서 환경변수를 추가합니다.

4.1.1 홈 폴더 zshrc를 vi로 열어줍니다.

vi ~/.zshrc

4.1.2 아래 항목을 입력하고 저장합니다.

export PATH=$PATH:~/{폴더위치}/flutter/bin

4.1.3 터미널에서 source 명령어로 zshrc 파일을 재실행 합니다.

source ~/.zshrc

4.1.4 환경변수 세팅이 잘 되었는지 확인합니다.

flutter --version

flutter doctor

5. flutter doctor를 입력하니 Android sdk 가 없다고 하니 설치합니다.

5.1 Andorid Studio를 실행한 후 More Actions에서 SDK Manager를 선택합니다.

5.2 SDK Tools 탭으로 이동한 후 Andorid SDK Command-line Tools를 추가합니다

5.3 이제 Android SDK를 설치하였으니 다시 flutter doctor를 불러보니 flutter doctor --android-licenses를 실행하라고 하네요. 바로 실행해줍니다.

flutter doctor --android-licenses

5.4 5번 정도 y를 입력하면 모든 라이센스 동의가 완료됩니다.

그럼 다시 flutter doctor를 실행해봅시다.

 

 

Android Studio와 Flutter SDK 설치가 완료되었습니다. 이제 Flutter 프로젝트를 생성할 수 있게 되었습니다!

반응형

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