2019년 5월 31일 금요일

[iOS] pip in application - 1

동영상 샘플앱을 만드는 중에.
AVPlayer를 어플 안의 pip(picture in picture)로 만들어 띄어달라는 요청을 받았다.

그것에 대한 자료가 없어 조금 정리해 보기로 한다.

우선 view controler의 구조는 다음과 같다.
nav -> [list] -> [detail] -> [player]

고객처에서 storyboard를 달가워하지 않기에 모든 view는 xib로 작성하고 기능별로 target을 만들어 관리해야한다.

위의 구조에서 player에서 pip 버튼을 누르면 detail 혹은 list화면에서도 보여야한다.
네비게이션이 왔다갔다할때 보이는 view controller 안에 player를 연속으로 넣어야한다.
따라서 navigation animator 쪽과 presentationViewController, prsentedViewController 등을 활용해야 하기에 공식문서를 한국어로 적어본다.




인스턴스 프로퍼티 presentedViewController
uiviewcontroller의 프로퍼티로서 self에 의해 표시된(presented) 뷰컨트롤러. 혹은 뷰컨트롤러 상속 계층 안에 있는 조상 중 하나.

선언
var presentedViewController: UIViewController? { get }

설명
present(_:animated:completion:) 메소드를 사용하여 뷰 컨트롤러를 모달(modal)로 표시(present)할 때, 메소드로 부터 호출된 뷰컨트롤러에 표시된(presented) 뷰컨트롤러가 이 프로퍼티가 세팅된다.
만약 현재 뷰컨트롤러가 다른 뷰컨트롤러를 모달로서 표시하고 있지 않는다면 이 값은 nil이 된다.



인스턴스 프로퍼티 presentingViewContr self (뷰컨트롤러)에 의해 present된 뷰 컨트롤러

선언
var presentingViewController: UIViewController? { get }

설명
present(_:animated:completion:) 메소드를 사용하여 뷰 컨트롤러를 모달(modal)로 표시(present)할 때, 메소드로 부터 호출된 뷰컨트롤러에 표시된(presented) 뷰컨트롤러가 이 프로퍼티가 세팅된다.
만약 뷰컨트롤러가 모달로서 표시된것이 아니라 상속 계층 중 하나로부터 된것이라면 이 프로퍼티는 그 표시된 상속계층안의 조상을 값으로 갖는다. 만약 현재 뷰컨트롤러나 상속 계층 안의 모든조상이 모달로서 표시되지 않았다면 이 값은 nil이 된다.



2019년 3월 25일 월요일

Controlling the User Interface with the Apple TV Remote - 번역

Controlling the User Interface with the Apple TV Remote
Apple TV 리모콘으로 유저 인터페이스 컨트롤하기


원본
https://developer.apple.com/library/archive/documentation/General/Conceptual/AppleTV_PG/WorkingwiththeAppleTVRemote.html


iOS 디바이스에서는 유저는 직접 터치스크린으로 컨트롤한다. AppleTV에서는 리모컨을 이용해서 간접적으로 조작한다. 유저는 스크린에 있는 특정 아이템을 눌러 화면 전환을 하고 버튼을 누름으로 선택한다. 화면에 있는 아이템은 유저가 화면을 움직이는대로 포커스 맞춰지게 된다. 포커스는 외부 스크린에 있는 것을 나타내거나 리모트 컨트롤러나 다른 입력기기를 통한 간접적인 유저입력을 나타낸다. 포커스 기반의 상호작용 모델은 하나의 뷰의 화면에서 Focuse된 것을 고려하고 유저는 포커스 업데이트를 발생시켜 포커스를 다른 뷰로 옮길 수 있다. 포커스된 뷰는 유저 액션의 타겟으로 사용된다. 예를들어 화면에 버튼이 포커스되었다면 버튼의 액션은 select 이벤트가 리모트로부터 보내졌을때 발생된다.

UIKit 프레임워크는 포커스기반 인터페이스만을 지원하고 대부분의 경우에 그렇게 되어야만 한다고 생각되는 대로 동작하도록 되어있다. 그렇기 때문에 포커스를 업데이트 할수 있어도 강제적으로 포커스를 세팅하거나 어떤 방향으로 움직이는 것은 할 수 없다. 예를들어 UIButton 오브젝트는 포커스가 가능하다. 하지만 UILabel은 불가능하다. 커스텀 UI 컴포넌트를 위한 앱을 위해서는 supporting focus within your app에서 설명한 것 처럼 포커스 동작 또한 커스텀할 필요가 있다.


포커스를 조정하는 포커스 엔진

UIKit안에 있는 포커스와 포커스를 움직이는 시스템을 포커스 엔진이라고 한다. 유저는 리모트컨트롤러나 게임 컨트롤러, 시뮬레이터 같은 것들을 통해서 포커스를 조정할 수 있다. 포커스 엔진은 여러가지 입력 디바이스로부터의 포커스 이동 이벤트가 발생하는 것을 대기한다. 이벤트가 들어오면 어디서 포커스를 업데이트해야하는지와 어디에 알려줘야하는지에 대해 자동적으로 판단한다. 이 시스템은 여러 앱들의 UX를 통일시켜 일관성을 지켜주고, 모든 어플에 대해 현재와 미래의 입력 방식을 자동적으로 서포트 하게 해주고, 개발자들이 기본적인 네비게이션을 정의하거나 처음부터 다시 개발하는 것을 하는 대신 그들의 어플의 유니크한 행동을 구현하는 것에 집중하도록 도와준다.

오직 포커스엔진만이 명시적으로 포커스를 업데이트 할 수 있다. 즉, 직접적으로 뷰에 포커스를 할당하거나 특정 방향으로 움직이게 하는 API는 없다는 뜻이다. 포커스 엔진은 유저가 이동 이벤트를 보내거나 시스템이 업데이트를 요청하거나 어플리케이션이 업데이트를 요청할 때 단지 포커스를 업데이트한다. Updating Focus Programmtically를 참조하라.

포커스엔진은 의도하지 않게 스크린주변을 맴돌지 않고 다른 어플들과 비슷한 움직을 갖도록 포커스를 컨트롤한다. 이 컨트롤은 유저를 혼란에 빠지지않게 도와주고 개발자들이 포커스를 관리하기 위한 고유의 솔루션을 구현하지 않아도 되도록 도와준다.


UIFocusEnvironment 프로토콜

포커스 엔진은 뷰 계층에서의 포커스 동작을 정의해놓는 UIFocusEnvironment 프로토콜을 사용하여 당신의 어플과 통신한다. UIKit 클래스에서 UIView, UIViewController, UIWindow 그리고 UIPresentationController가 이것을 구현하고 있다. 다른말로 직접적으로든 간접적으로든 스크린에서 뷰를 컨트롤하는 것들은 구현하고 있다는 말이다. 따라서 당신의 View나 ViewConroller에서 UIFocusEnvironment를 오버라이딩하는 것으로 당신은 당신의 어플에서 포커스 동작을 컨트롤 할 수 있다.

유저가 만들어내는 포커스 이동

포커스 엔진은 리모트 컨트롤러나 다른 입력으로 부터의 네비게이션 이벤트에 따라 포커스가 어디로 움직여야 되는지 자동적으로 결정된다. 유저는 왼쪽, 오른쪽, 위, 아래, 하드웨어가 지원한다면 사선까지, 이러한 2차원 방향중 어느 방향으로도 포커를 움직 일 수 있다. 예를들어 유저가 왼쪽으로 스와이프 한다면 포커스 엔진은 현재 포커스 되어 있는 뷰에서 바로 왼쪽에 있는 포커스가능한 뷰를 찾으려고 할 것이다. 만약 새로운 뷰를 찾는다면 포커스는 그 뷰로 움직이고 찾지 못한다면 현재 뷰에 포커스는 머물러 있을 것이다.

포커스 엔진은 또한 입력 디바이스에서 지원한다면 자동적으로 정교한 행동들을 처리한다. 예를들어 리모트 컨트롤러에서 빠른 스와이프를 한 뒤의 가속도 기반 움직임이나 포커스의 속도에 따른 애니메이션 관련 속도 조절, 네비게이션 효과음 재생, 그리고 스크린 밖으로 나가는 포커스움직임의 경우 스크롤뷰 오프셋 갱신 등이다. 포커스 관련 애니메이션에 관해 더 알고 싶다면 다음 링크를 확인하라. UIFocusAnimationCoordinator Class Reference.


포커스가 어디로 움직여야하는지의 결정

 사용자의 액션에 따라 포커스가 어디로 움직여야 하는지 결정할 때, 포커스엔진은 어플의 모든 포커스 가능한 볼 수 있는 영역들의 하이라이트들과유저 인터페이스의 내부 사진을 갖는다. 이것은 만약 포커스 가능한 뷰가 완전하게 다른 뷰에 가려져 있고 부분적으로 가려져 있는 모든 뷰들은 보여지는 영역들만 고려될 것이다. 이러한 테크닉을 사용하면서 포커스 엔진은 현재 포커스 되어 있는 뷰로 부터 시작해서 움직이는 경로 안에 있는 포커스 가능한 영역의 모든 것을 찾는다. 검색 영역의 크기는 현재 포커스 된 뷰와 직접적으로 연관되어 있다.

image: ../Art/large_view.pdf

만약 포커스엔진이 포커스할 새로운 뷰를 찾는다면, 포커스엔진은 움직임이 발생하기 전에 움직임을 인증(validate)할 기회를 준다. OS는 이전, 이후 포커스 된 뷰를 포함하고 있는 모든 포커스 환경에서 shouldUpdateFocusInContext 함수를 호출한다. 이전에 포커스된 뷰가 먼저 알림을 받고 포커스된 뷰가 알림을 받고 마지막으로 부모뷰들이 알림을 받는다. 만약 어떤 포커스 환경이 shouldUpdateFocusInContext 함수로부터 NO를 리턴 받는다면 이동은 캔슬된다.  만약 포커스 환경에 대해 더 알고 싶다면 다음 링크를 참조하라. UIFocusEnvironment Protocol Reference


첫 포커스와 선호하는 포커스 체인


어플리케이션이 실행될 때 기본적으로 포커스엔진이 스타팅 포커스를 결정한다. 이 뷰는 일반적으로 화면의 왼쪽 위 구석에 가장 가까운 포커스 가능한 뷰가 된다.
image: ../Art/search_hierarchy_2x.png

그러나 어플리케이션은 UIFocusEnvironment프로토콜의 선호하는 포커스 뷰를 이용하여 포커스가 기본적으로 어디로 가야하는지에 대한 힌트를 제공할 수 있다. 첫 포커스를 세팅할 때 포커스엔진은 우선 preferredFocusedView 오브젝트를 리턴하는 루트 뷰 컨트롤러의 선호하는 포커스 뷰를 갖고 있는 윈도우에 대해 묻는다. 왜나하면 반환값은 UIFocusEnvironment를 구현하는 UIView오브젝트이고 포커스엔진은 UIView 오브젝트는 이것의 선호하는 포커스 뷰에 대해 묻는다. 이 반환된 선호하는 포커스 뷰들의 연결 리스트가 "선호하는 포커스 체인(Preferred focus chain)"이다.

다음은 어떻게 포커스는 결정되는지에 대한 예제이다.

  1. 포커스 엔진이 preferredFocusedView에 root view controller의 prefferedFocusView객체를 반환해주는 root window에 대해 묻는다.
  2. Root view controller, TAB View Controller는 선택된 뷰 컨트롤러의 preferredFocusedView객체를 반환한다.
  3. 선택된 뷰컨트롤러는 특정한 UIButton 인스턴스를 반환하기 위해 preferredFocusedView 함수를 오버라이드 한다.
  4. UIButton 인스턴스는 기본적으로 self를 리턴하고 포커스 가능하므로 포커스엔진이 다음 포커스 뷰로 선택 한다.

포커스가 특정 뷰에 업데이트 될 때 마다 새롭게 포커스가 되는 뷰는 선호하는 포커스 체인에서 뷰의 가장 깊은 선호하는 포커스가능한 뷰로 설정 된다. 뷰 컨트롤러가 현재 포커스된 뷰의 가장 위에 모달하게 표시 될 때 포커스는 선호하는 포커스 체인을 사용하여 새로운 뷰컨트롤러로 갱신된다.


포커스 업데이트

 포커스 업데이트는 유저가 포커스를 움직일때 발생하고 어플은 프로그램적으로 요청하거나 시스템이 자동적으로 발동시킨다.

포커스 업데이트의 해부

 포커스가 업데이트 되거나 서브뷰 혹은 다른 뷰의 계층에 있는 새로운 뷰로 옮겨갈 때 다음과 같은 이벤트들이 일어난다.

  • 새롭게 포커스되는 뷰나 선호하는 포커스 뷰를 반영하여 FocusedView 속성이 갱신된다.
  • 포커스엔진이 이전에 포커스된 뷰나 다음 포커스될 뷰를 포함하여 didUpdateFocusInContext:withAnimationCoordinator: 함수를 호출함으로 모든 포커스 환경에 알린다. 업데이트에 대한 응답에 있어 포커스 관련된 에니메이션을 스케쥴링 하기 위해 제공된 애니메이션 코디네이터를 사용하라. UIFocusAnimationCoordinator를 참조
  • 모든 포커스 환경에 관련된 것들이 알림을 받은 후 모든 조정된 애니메이션들이 동시에 발생 된다.
  • 만약 다음 포커스 된 뷰가 스크롤 뷰 안에 있고 스크린 밖에 있따면 스크롤뷰는 다음 포커스된 뷰가 스크린 안에 오게 하기 위해 스크롤한다. 

포커스 관련 애니메이션의 조정
포커스의 업데이트가 일어날때, 현재 뷰는 포커스된 상태로 애니메이션 되고 이전 뷰는 포커스되지 않은 상태로 애니메이션되고 다음으로 포커스될 뷰는 포커스된 상태로 애니메이션 된다. 그러나 어플에 정의되어 있는 일반적인 애니메이션들과 달리 UIKit은 확실한 시스템 레벨의 행동을 달성하기 위해 포커스관련된 애니메이션들의 타이밍과 커브를 적용하고 있다. 예를들어 포커스가 빠르게 움직일 때 애니메이션의 타이밍은 유저의 움직임을 유지하기 위해 속도를 높인다. UIKit은 시스템이 정의한 포커스를 지원하는 뷰 클래스에 포커스 애니메이션을 제공한다. 시스템이 정의한 행동들에 맞춰 커스텀 애니메이션을 만들기 위해서는 UIKit의 빌트인 클래스인 UIFocusAnimationCoordinator와 addCoordniatedAnimation:Completion 함수를 사용해야한다.

Coordinator에 추가된 애니메이션은 제공된 coordinator의 포커스 환경에 따라 포커싱 애니메이션 혹은 언포커싱 애니메이션과 동시에 작동한다. 포커싱뷰의 독점 조상들을 포함하여 언포커싱과 포커싱 뷰들의 공통 부모들은 포커싱 애니메이션과 동시에 애니메이션이 동작한다.  언포커싱뷰의 독점 조상은 언포커싱 애니메이션과 동시에 애니메이션이 발생한다.


Figure 3-3 Custom focus animationimage: ../Art/animation_chart_2x.png


뷰들은 공통적으로 포커스를 갖느냐 잃느냐에 따라 다른 애니메이션들을 갖는다. 당신은 어떤 뷰가 어떤 종류의 애니메이션을 취할지 didUpdateFocusInContext:withAnimationCoordinator함수를 오버라이딩하고 현재 뷰의 포커스상태의 컨텍스트를 확인하므로 특정할수 있다.
 다음 예제는 didUpdateFocusInContext:withAnimationCoordiantor를 오버라이딩하는 예제이다.


- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
[super didUpdateFocusInContext:context withAnimationCoordinator:coordinator];

if (self == context.nextFocusedView) {
[coordinator addCoordinatedAnimations:^{
// 포커싱 애니메이션
} completion:^{
// completion
}];
} else if (self == context.previouslyFocusedView) {
[coordinator addCoordinatedAnimations:^{
// 언포커싱 애니메이션
} completion:^{
// completion
}];
}
}
언포커싱과 포커싱뷰의 공통 부모들은 특정 애니메이션을 할 수 있다. 예를들어 UICollectionView는 이전 포커스된 셀과 다음 포커스된 셀의 애니메이션을 할 수 있다. 이 경우 추천하는 접근 방식은 UICollectionViewCell을 상속받아 거기서 애니메이션 코드를 구현하는 것이다.

포커싱이슈의 디버깅

UIKit은 어플이 진행되는 동안의 포커스 이슈에 대한 디버그를 돕는다.

왜 뷰가 포커스 되지 않는 것인가?

뷰가 포커스 할 수 없으리라 기대할 수 있는 몇가지 이유들이 있다.
  • 뷰의 canBecomeFocused함수가 NO를 리턴한다
  • 뷰의 isHidden 속성이 YES이다
  • 뷰의 알파 값이 0이다
  • 뷰의 user interaction이 disabled로 설정되어 있다
  • 뷰가 다른 뷰에 의해 가려져 있다.
UIKit은 위에 있는 케이스들을 한번에 테스트 할 수 있도록 UIView 클래스에 있는 숨겨진 함수인 _whyIsThisViewNotFocusable을 제공한다. 이 함수는 특정한 뷰 레퍼런스가 있는 디버거로만 발동되고 문제를 발생시키고 있는 이슈들을 사람이 읽을 수 있게 리스트로 프린트아웃 된다.


  • (lldb) po [(UIView *)0x148db5234 _whyIsThisViewNotFocusable]
  • ISSUE: This view has userInteractionEnabled set to NO. Views must allow user interaction to be focusable.
  • ISSUE: This view returns NO from -canBecomeFocused.
  • (lldb) po [(UIView *)0x14b644d70 _whyIsThisViewNotFocusable]
  • ISSUE: One or more ancestors are not eligible for focus, preventing this view from being focusable. Details:
  • <ExampleAncestorView 0x148db5810>:
  • ISSUE: This view has userInteractionEnabled set to NO. Views must allow user interaction to be focusable.

왜 포커스는 당신이 예상하지 않는 곳으로 움직이는가

때때로 포커스는 당신이 예상한대로 움직이지 않거나 전혀 움직이지 않는다. 포커스엔진은 많은 것들을 제공하고 있고 때때로 당신은 어떻게 이런 결정을 하는지에 대해 더 많은 정보를 얻고 싶을 것이다. UIKit은 UIFocusUpdateContext인스턴스를 보면 어떻게 포커스 엔진이 찾는지 볼수있게 표시된 것을 보내고 있다. 이 이미지를 보려면 shouldUpdateFocusInContext나 didUpdateFocusInContext:withAnimationCoordinator에 브레이크 포인트를 설정해야 한다. 어플이 실행되고 브레이크 포인트에 도달했을때 디버거에서 context 파라메터를 선택하고 quick look을 열어라.




Figure 3-4Select the context parameter in the debuggerimage: ../Art/ContextParameter_2x.png

만약 브레이크 포인트가 포커스가 움직일때 도달 했다면 Quick Look은 포커스엔진이 다음 포커스될 뷰를 찾는 검색 경로에 대한 이미지를 표시할 것이다.




Figure 3-5Quick Look presentation of an Imageimage: ../Art/QuickLookPresentationOfImage_2x.png

Quick Look 이미지가 보여주는 종류의 정보이다.

  • 빨강: 이전에 포커스된 뷰
  • 빨강 점선의 아웃라인: 검색경로
  • 보라색: 검색 경로안의 포커스 가능한 UIView영역
  • 파랑: 검색 경로 안의 UIFocusGuide영역

    2018년 10월 30일 화요일

    RxSwift의 기본 - 스케쥴러

    스케쥴러(Scheduler)는 스레드(Thread)의 리엑티브엑스적 개념이다.
    하지만, 스레드와 동일한 개념은 아니다.

    일반적인 스레드의 설명을 먼저하자면

    스레드는 크게 두가지로 나뉜다.
    - 직렬 처리 스레드
    - 병렬 처리 스레드

    웹 Api를 리퀘스트A와 리스폰스B가 있다고 해보자
    리퀘스트A와 리스폰스B는 직렬 처리이다.
    만약 리퀘스트 A가 여러번 발생할 수 있다면 이것은 병렬처리로 해야한다.

    즉, 하나의 처리는 대부분 직렬처리와 병렬처리가 병행된다.
    일반적으로 하나의 큰 직렬처리 안에 여러개의 병렬처리가 쪼개져 있다.


    GCD에서의 queue
    - main: app의 main thread에서 동작 병렬 Queue
    - global: 전체 시스템에서 공유하는 병렬 Queue
    - custom: 개발자가 만들 수 있음.

    iOS에서 UI의 변경 작업은 반드시 main 쓰레드에서 동작한다. (안드로이드도 동일)
    또한 한번에 여러가지 UI이벤트가 있을 수 있으므로 동일하게 병렬 처리 큐이다.
    또한 UX의 이유로 main thread는 정지하면 안된다.
    UI animation 처리가 아닌 연산 처리로 main thread를 점유하면 안된다.


    GCD의 큐에는 총 4가지의 QoS(Quality of Service: 우선 순위)가 존재한다.
    - userinterative: 즉각적인 반응이 필요한 queue. main 쓰레드가 이 QoS를 사용한다.
    - userinitiated: 유저의 UI 중 중요도가 떨어지는 element를 async로 작업할때 쓴다.
    - utility: 유저의 프로세스에 필요한 연산작업에 사용한다. 프로그래스바, I/O, Networking 등
    - background: 유저에게 직접적으로 필요하지 않은 작업들. logging 등

    병렬처리에 있어서 주의 해야할 것들
    공유자원
    상호배제
    데드락
    starvation
    우선순위 역전

    멀티 스레딩에 있어서 가장 걱정해야 하는 것은 자원의 공유이다.
    하나의 자원이 공유되는데 이 자원이 비동기(async) 병렬 작업으로 여러곳에서 순서없이 동시에 접근하여 데이터가 변경되는데 이 변경된 데이터를 다른곳에서 참조한다고 할때 데이터의 정합성을 보증할 수 없다. 이 것을 위해 세마포어(Semaphore) 와 뮤텍스(Mutex)라는 개념이 있다.

    Semaphore(세마포어) - 원래뜻은 기차등에서 사용하는 까치발 신호기
    공유된 자원데이터를 여러 `프로세스`가 접근하는 것을 막음
    자원데이터를 카운팅하여 몇가지의 쓰레드가 접근 할 수 있는지 확인할 수 있음
    지정한 수 이상 쓰레드가 접근하면 대기하도록 함.

    Mutex(Mutual Exclusion: 상호 배제)
    공유된 자원 데이터를 여러 `쓰레드`가 접근하는 것을 막음
    즉, 멀티쓰레드중 한가지의 쓰레드만 사용가능한 리소스
    1과 0만을 갖고 있는, 즉 한계치가 1인 Binary Semaphore(이진 세마포어)




    RxSwift에 있어서 스케쥴러 클래스


    ConcurrentDispatchQueueScheduler
    큐의 종류와 QoS를 지정할 수 있는 스케쥴러
    background에서 작업할때 사용한다.


    ConcurrentMainScheduler  
    메인스레드를 사용하는 스케쥴러
    만약이 스케쥴러가 메인스레드에서 호출되면 별도의 스케쥴링 없이 바로 실행된다.
    이 스케쥴러는 SubscribeOn에 최적화되어 있으므로 ObserveOn을 할 경우에는 MainScheduler를 사용할 것.


    CurrentThreadScheduler  
    현재 스레드에서 실행되는 스케줄러.
    기본 오퍼레이터는 이 스케줄러를 사용한다.

    VirtualTimeScheduler  
    가상의 시간을 만들어내고 그 위에 스케쥴러를 돌린다.

    HistoricalScheduler  
    절대시간(Date)이나 상대시간(TimeInterval)을 이용하여 VirtualTimeScheduler를 사용하는 클래스


    MainScheduler  
    DispatchQueue.main에서 작동하는 스케쥴러.
    이 스케쥴러가 DispatchQueue.main에서 사용되면 별도의 스케쥴링 없이 바로 실행된다.
    UI작업에서 주로 사용된다.
    SerialDispatchQueueScheduler가 특화된 클래스.
    ObserveOn에 특화된 스케줄러
    SubscribeOn을 할 경우에는 ConcurrentMainScheduler을 사용할 것.


    OperationQueueScheduler  
    특정 OperationQueue에서 작업하는 스케줄러
    maxConcurrentOperationCount을 사용하여 대규모 작업을 백그라운드에서 실행할 경우 사용


    SerialDispatchQueueScheduler  
    특정 큐를 지정할 수 있는 스케줄러.
    단, 병렬 큐를 지정해도 직렬로 처리하므로 주의.
    사용하기전에 serialQueueConfiguration를 통해 몇가지 설정이 가능함




    * thread-safe
    쓰레드 자체를 안전하게 사용하는지에 관한 이야기.
    iOS에서는 GCD(Grand Central Dispatch)에 의해서 자동적으로 thread-safe가 된다.

    thread-safe의 조건
    1. Re-entrancy
    어떤 함수가 한 스레드에 의해 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하더라도 그 결과가 각각에게 올바로 주어져야 한다.

    2. Thread-local storage
    공유 자원의 사용을 최대한 줄여 각각의 스레드에서만 접근 가능한 저장소들을 사용함으로써 동시 접근을 막는다.
    이 방식은 동기화 방법과 관련되어 있고, 또한 공유상태를 피할 수 없을 때 사용하는 방식이다.
    3. Mutual exclusion
    공유 자원을 꼭 사용해야 할 경우 해당 자원의 접근을 세마포어 등의 락으로 통제한다.

    4. Atomic operations
    공유 자원에 접근할 때 원자 연산을 이용하거나 '원자적'으로 정의된 접근 방법을 사용함으로써 상호 배제를 구현할 수 있다.

    2018년 10월 29일 월요일

    RxSwift의 기본 - 실수편

    일주일동안 메모리 누수를 잡았다.
    하루 8시간씩 변수 하나하나 바꿔가며 테스트한다는게 여간 에너지를 소모하는 일이다.

    결국. 조심해야 할것은 역시나 ARC(Auto Reference Counting)
    drive나 subscribe의 경우는 CompositeDisposable()에 들어간다.

    Variable, PublishSubject 또한 CompositeDisposable에 넣어줄 수 있다.

    그런데. .do(onNext: { _ in } ... ) <- 요 메소드는 뭔가 어렵다...
    만약 .do() 메소드 안에 self를 써야한다면 반드시 [weak] self를 사용해야한다.

    2018년 10월 25일 목요일

    RxSwift의 기본 - Disposable

    Disposable.

    RxSwift에서 Disposable을 보면서 이것 참 C 스럽다는 생각이 많이 든다.
    C언어를 해본사람들은 알겠지만 C에서 가장 귀찮은게 뭐냐고 물어본다면 포인터와 메모리 관리다.
    이 배열의 첫 아이템이 배열의 주소니까 그걸 따라가서 복사한다음에 어쩌구 저쩌구...
    그리고 그걸 자연스럽게 해제해주지 않으면 프로그램이 shut down!

    사실 RxSwift에서도 동일한 작업이 이뤄진다.
    이 시퀀스(Observable)이 저쪽에서 구독(subscribe)되고 또 그게 다른 변수로 복사된다음 합쳐지고 다시 이건 해제(Disposable)되는데 다른건 아직 안되서 결국은 메모리 누수가 발생한다.

    제대로 해제해주지 않아 발생하는 에러...  RxSwift가 발전하면 언젠가는 이게 편하게 쓸수 있는 날이 올거라 믿는다.

    아무튼, 이러한 이유로 어떻게 메모리 해제를 해야하는가에 대해 정리하고자 한다.
    원래는 DisposeBag에 전부 넣어서 관리를 하려 했으나... 메인스케쥴러가 아닌 곳에서 시퀀스가 생성되고 돌아가다보니 뷰 컨트롤러가  Dismiss되어도 메모리에 찌꺼기처럼 계속 남는다.

    ARC에서도 상호참조가 문제였는데 여기는 그 문제가 더욱 깊어진다.
    좋다고 덮어놓고 쓰다보면 밤샘지옥이 기다리고 있을 것이다.

    일단 RsSwift에서 사용하는 Disposable의 종류를 알파벳순으로 정리해보면 다음과 같다.

    AnonymousDisposable
     - action base의 Disposable
     - Dispose 된 이후 실행시킬 Action을 지정할 수 있다.

    BinaryDisposable
     - 동시에 Dispose될 2개의 Disposable을 지정한다.

    BooleanDisposable
     - dispose되었는지에 대한 스테이터스를 확일 할 수 있는 Disposable

    CompositeDisposable
     - 여러개의 disposable을 하나로 묶을 수 있는 Disposable

    NopDisposable
     - Nop: No operation
     - Dispose를 해도 Dispose하지 않고 아무것도 하지 않는다.

    RefCountDisposable
     - ARC(Auto Reference Counting)를 해주는 Disposable

    ScheduledDisposable
     - 지정한 스케쥴러에 의해 Dispose처리를 함

    SerialDisposable
     - 새로운 Disposable을 넣으면 기존에 있던 Disposable이 Dispose됨. -> 병렬처리

    SingleAssignmentDisposable
     - 1개만을 집어 넣을 수 있는 Disposable
     - 2번째를 넣으면 Exception을 발생시킨다

    DisposeBag
     - 자기 자신이 메모리에서 해제될때 같이 Dispose되는 Disposable


    ** Disposable은 프로토콜 타입이다. 따라서 Disposable만을 생성하는 것은 불가능하다.

    2018년 10월 23일 화요일

    RxSwift의 기본 - RxCocoa - 작성중

    UIKit에 맞춰 편리하게 만들어 놓은 라이브러리.
    Driver 라던가 control event 등이 추가되어 있음.

    RxSwift의 기본 - RxSwift란 무엇인가 - 작성중

    어느 책이나 기본 가이드를 보더라도 가장 처음에 있는 내용이다.

    이 글을 보는 사람들은 대부분 RxSwift가 무엇인지 알겠지만 그래도 다시 한번 글로서 정리해서 메타 지식을 명확히 하기 위해 이번 장을 마련했다.



    설치 방법은 github에서 확인하는 것이 가장 정확하고 빠르다.
    https://github.com/ReactiveX/RxSwift


    ReactiveX의 공식 페이지 

    RxSwift의 기본 - RxSwift의 3대 기둥(Observable, Operator, Scheduler) - 작성중

    RxSwift는 아래와 같은 3개의 기둥이 있다.

    - Observable

    - Operator

    - Scheduler


    Observable

    - Observable은 시퀀스의 타입이다.
    - 스트림과 비슷한 개념이지만 시퀀스라고 이름이 붙어 있다.
    - 뜻 그대로 옵저버 패턴에 있어서 옵저버가 가능한 시퀀스를 만들어내는 타입이다.

    이 시퀀스에는 크게 3가지의 이벤트가 있다.
     - onNext: 시퀀스에 아이템을 흘려보낸다.
     - onError: 시퀀스에 에러가 발생했으므로 에러를 넘기고 종료한다.
     - onComplete: 시퀀스를 종료한다.

    하나의 시퀀스가 종료되면 다시 생성하기 전까지 아이템이 흘러가지 않는다.


    Observable(시퀀스)를 선언하고 subscribe(구독)을 하므로 rxSwift를 사용하게 된다. 그리고 마지막에 dispose(처리)하므로 시퀀스가 메모리에서 삭제된다.

    간단한 예를들어보면
    1
    2
    3
    4
    Observable<Int>.just(1)
        .subscribe { event in
            print(event)
        }.dispose()
    cs

    위와 같은 코드가 있다고 할때 .just(1)이 observable을 생성하는 operator가 된다. 그리고 1을 만드는 시퀀스를 구독하여 print하고 그대로 dispose()하고 있는 간단 심플한 코드이다.



    Operator

    rxSwift를 쓰는 이유는 하나의 이벤트를 기준으로 여러가지 기능을 하기 위해서다. 따라서 여러가지 이벤트를 하나의 이벤트로 합치거나, 시간을 조절하여 이벤트를 받거나 발생시킬 필요가 있다. 그 때 쓰는 것이 Operator이다.
    그리고 이 operator가 함수형 프로그래밍의 꽃이 된다.
    예를들면, map(), merge(), zip(), concat() 등이 대표적인 Operator이다.
    map, filter, reduce는 일반적인 배열에서의 사용법과 같다.


    Scheduler
    시퀀스의 이벤트를 어떤 큐에서 어떤 타이밍에 발생시킬가에 대한 문제.

    RxSwift의 기본 - Hot/Cold Observable

    Hot/ Cold Observable이란 무엇인가?


    우선 RxSwift의 깃허브를 번역해 보겠다.


    Hot/Cold 의개념은 별도의 타입이 아니다. 시퀀스의 속성중 하나로 생각하는 것이 쉽고 바른 생각이다.

    ReactiveX.io에 나온 정의를 적어보도록 하겠다.

    Observable은 시퀀스를 언제 발생하기 시작하죠? 이것은 Observable의 속성에 따라 다릅니다. Hot Observable은 만들어지자 마자 시퀀스에 신호를 보내기 시작합니다. 따라서 나중에 구독을 시작하는 Observer라든지 어떠한 Observer라도 중간부터 Observing을 시작할 수 있습니다. 반면에 “Cold” Observable은 Observer가 구독할때까지 기다렸다가 시퀀스에 신호를 보냅니다. 따라서 Cold Observable은 처음부터 전체의 시퀀스를 구독하는 것을 보증할 수 있습니다. 

    간단하게 정리를 해보자면

    - Hot Observable

    생성과 동시에 시퀀스에 신호를 보내는 Observable
    Observer가 중간부터 신호를 받을 수 있음.

    - Cold Observable

    구독과 동시에 시퀀스에 신호를 보내는 Observable
    생성하더라도 시퀀스에 신호를 보내지 않음
    Observer는 처음부터 모든 신호를 받을 수 있다고 보장함




    [One More Step]

    한발짝 더 다가가보자면
    Hot/Cold에 관련한 operater들을 설명해보도록 하겠다.

    publish

    Observable을 ConnectableObservable로 변환해 준다. 따라서 Observable의 속성이 Hot이 된다.


    refCount

    hot속성을 갖고 있는 Observable의 레퍼런스(구독자)를 카운팅 한다.
    구독자가 제로(0)가 되면 Observable이 dispose된다.

    replay

    Observable을 ConnectableObservable로 변환해 준다. 따라서 Observable의 속성이 Hot이 된다.
    구독을 시작하면 발생했던 아이템을 처음부터 전부 반환해준다.

    share

    publish + refCount

    RxSwift의 기본 - 서문

    리엑티브 익스텐션(Reactive eXtension, 이하 리엑티브) 프로그래밍을 한지 어언 1년이 다되어감에도 불구하고 여전히 개념이 확실히 잡혀있지 않다.

    기본적인 observable과 driver, 그리고 map, distinct같은 연산자만 쓰다보니 실력이 늘지를 않는다.

    역시 이게 문제였는지 에러가 발생한다.

    한 유저가 1초에 30건씩 request를 보내고 있는 것이다.

    1초에 30건씩 한 유저니 동시접속 100유저면 3천건 1분에 18만건 10분에 180만건 한시간에 1억건이 넘는 리퀘스트를 처리해야 하니 서버가 뻗어버린다.

    다행히도 인프라팀에서 이걸 일찍 발견하여 그 리퀘스트를 날리는 부분을 막는 핫픽스를 업데이트 하고 트래픽도 늘렸기에 별문제는 없었다.

    그렇게 한숨돌리고 원인을 찾아보니.

    rxswift에서 중요한 개념인 hot/cold의 개념이 역시나 발목을 잡았다.

    물론 저걸 몰라도 풀수 있는 문제였다.

    핵심적인 문제는 rxSwift에서 dispose를 할때 어플 전반적으로 DisposeBag을 활용했는데 이게 viewmodel이 상호참조가 되어버려 인스턴스가 종료되지 않아 결국 DisposeBag도 dispose되지 않은채 영영 살아있고 Observable.Interval()을 사용하고 있었기에 설정 한 시간만큼 기하급수로 리퀘스트가 늘어나고 있었다.


    따라서 이 기회에 hot과 cold의 개념을 포함한 rxSwift의 기본기를 좀더 확실하게 다지고자 포스트를 써 본다.

    2018년 5월 28일 월요일

    공부회에 대해.

    최근 회사에서 iOS 공부회를 시작했다.

    그래서 거기서 자주쓰는 네타를 몇가지 적어보려 한다.


    오래 기억하는 방법
    기억은 이해를, 이해는 지식을 필요로한다.
    기억이란 자극을 머리에 아로새겨 두었다가, 자극이 없는 상태에서 상기할 수 있는 정신 기능

    기억을 하는 3가지 단계
    번째는 새로운 지식을 외워서 뇌에 입력하는 단계
    두 번째는 외운 것이 뇌에 저장되는 단계
    세 번째는 다시 생각하는 회상 단계이다.

    기억은 감정을 동반할 때에 오래 간다.


    지식에 대하여
    : 知 = 화실 + : 아는 것이 많으면 입에서 화살이 나오듯 말을 빨리 하는 .
    : 識 = 말씀 + 찰흙 : 찰흙으로 구슬을 만들어 적이 처들어오면 알리게 하였음. 그런 종류의 찰흙. 그것을 말로 하는 것이 .

    지는 안에서 나가는 것이고
    식은 밖에서 알게되는


    지는 내가 알고 있던 것이고

    식은 남에게 배우는 .



    일상 알고리즘


    러시아 페인트공 알고리즘

    러시아에 한 페인트공이 있었다.
    첫째날 300드를 칠하더니 
    둘째날은 150야드
    셋째날은 30야드밖에 못칠하는 것이었다.

    관리자가 어떻게 첫째날은 10배나 칠을 했나라고 질문을 했다.

    : 저도 어쩔수 없었습니다. 매일 페인트 통에서 멀어지니까요.

    조엘 스폴스키의 조엘 온 소프트웨어
    -> 잘 돌지만 일정 이상 규모가 되면 급격히 성능이 저하되고 문제를 일으키는 코드 
    -> 기술부채라고 표현함


    마세라티 문제
    어떤 마세라티 모델과 색상을 구매할지 고민하는 것
    나중에 마세라티 살 능력이 됐을때 할 고민을 미리 하는것
    돈많은 금융과 증권계에서는 보트네이밍이라고 함.

    당장 필요하지 않은 기술을, 담보되지 않은 미래의 큰 성공에 대비하여 준비하는 것
    천만명을 처리할 서비스는 사용자가 10만은 됐을때 고민
    엔지니어만 있는 스타트업에서 자주 발생하는 현상



    야크 털깍기
    어떤 목적을 달성하기 위해 전혀 상관없는 연속된 작업을 해야하는 상황
    봄이와서 세차를 하는데 호스가 터졌음
    차로 홈 데포가서 호스를 사려보니 다리를 지나가야 함 -> 통행카드 필요
    통행카드를 옆집 밥아저씨한테 빌리려는데 아들이 캠핑가려고 밥한테 베개를 빌린후에 안 갖다줌
    그런데 베개에 있는 야크털이 많이 빠져서 지금 베개를 갖다 줄 수 없음
    그래서 세차를 하기 위해 동물원에 가서 야크 털을 깍음

    배우는 것도 많지만 문제는 일정.


    실제 세계에서는 이 세가지의 균형을 맞춰야 함.

    blog test

    blog test.
    여러가지 블로그를 해보려 했지만
    tistory는 로그인이 너무 어렵고... 제한된 곳도 많고.
    github는 초기 설정이 어렵고..
    우선 구글에서 제공해 주는 것으로 시작해 보려 한다!