'분류 전체보기'에 해당되는 글 366건

  1. 2020.07.28 iOS App Extension #3 만들기
  2. 2020.07.28 iOS App Extension #2 동작 이해하기
  3. 2020.07.28 iOS App Extension #1 기초
  4. 2020.07.13 Goodbye OOP
  5. 2020.07.10 swift log util
  6. 2020.07.10 Firebase analytics 기본
  7. 2020.05.06 CEF# dialog handler 58
  8. 2020.04.27 CEF# JavaScript Binding(JSB) 337
  9. 2020.04.06 Playback Notifications with ExoPlayer 536
  10. 2020.02.19 CEF 186
iOS2020. 7. 28. 13:54

앱익스텐션을 개발할 준비가 되었다면 먼저 적절한 익스텐션포인트를 결정해야 한다. 원하는 익스텐션포인트에 해당하는 Xcode 익스텐션 템플릿으로 시작하여 코드와 UI를 커스텀하는 방식으로 진행하는것이 좋다. 테스트와 최적화가 완료된 후 포함하는 앱에 함께 배포할 준비가 된것이다.

 

적절한 익스텐션포인트를 선택하여 개발 시작하기

 

익스텐션포인트가 잘 정의되어 있기 때문에 이를 선택하는것이 우선이다. 이를 선택함으로써 사용가능한 API와 동작이 결정된다.

 

익스텐션포인트는 iOS, OS X에서 제공되고 Info.plist에 익스텐션포인트 아이디키로 정의되어 있으며 다음 링크에서 확인이 가능하다.

 

익스텐션포인트를 선택하면 새로운 타겟을 추가한다. 가장 쉬운 방법은 기본설정을 모두 가지고 있는 Xcode template을 사용하여 추가하는 방법이다.

 

Xcode프로젝트에 타겟을 추가하려면 File > New > Target를 선택. Application Extension for iOS 또는 macOS 를 선택. 그러면 다음 그림에서처럼 선택가능한 템플릿들이 나온다. 

 

 

 

템플릿을 선택하면 익스텐션코드를 수정하기 전에 빌드하고 실행해볼수 있다. 빌드가 완료되면 .appex로 끝나는 익스텐션번들을 얻을 수 있다.

 

대부분 디폴튼 익스텐션을 시스템프리퍼런스나 셋팅에서 활성화함으로써 다른앱을 통해서 접근하여 테스트가 가능하다. 예를 들어 OS X extension을 사파리에서 웹페이지를 오픈하고 툴바의 공유버튼을 선택할 수 있고 이 때 나타나는 당신의 익스텐션을 선택 가능하다.

 

디폴트익스텐션 템플릿 살펴보기

 

각 익스텐션템플릿은 Info.plist, 하나의 view controller클래스 그리고 기본 UI를 포함하고 있으며 이것들은 익스텐션포인트에서 정의된 것들이다. 기본 view controller 클래스(또는 기반클래스)는 반드시 구현해야 하는 필수 메서드들을 가질 수 있다.

 

앱익스텐션포인트 타겟의 Info.plist에 익스텐션에 대한 상세한 정보를 포함할 수 있다. NSExtension키가 기본으로 있으며 Dictionary로 키/값쌍으로 익스텐션포인트를 기술한다. 예를 들어 NSExtensionPointIdentifier 키는 익스텐션포인트의 리버스 DNS명이다. 다음은 NSExtension 딕셔너리에서 볼 수 있는 키/값들이다.

 

- NSExtensionAttributes 익스텐션포인트에 해당하는 속성들의 딕셔너리로 PhotoEditing익스텐션의 경우 PHSupportedMediaTypes가 예이다.

- NSExtensionPrincipalClass 템플릿에서 생성하는 기본뷰컨트롤러 클래스의 이름으로 SharingViewController같은 호스트앱이 익스텐션을 호출할때 이 클래스를 인스턴스화하게 된다.

- NSExtensionMainStoryboard(iOS 익스텐션만 해당)  기본 스토리보드파일, MainInterface로 보통 명명됨

 

Info.plist외에 일부 capability가 추가될 수 있다. iOS Document Provider익스텐션의 경우 com.apple.security.application-groups 인타이틀먼트를 포함한다.

 

모든 OS X 앱익스텐션 템플릿들은 App Sandbax와 com.apple.security.files.user-selected.read-only 인타이틀먼트를 기본으로 포함한다. 익스텐션이 네트웍이나 연락처의 사진에 접근한다면 추가적인 capability  를 포함해야 한다.

 

 

 호스트앱의 요청에 응답하기.

 

호스트앱에서 유저가 익스텐션을 선택하면 익스텐션이 오픈되고 호스트앱의 요청이 발생한다. 고수준에서는 익스텐션은 이 요청을 받게되며 유저의 작업을 수행하고 사용자의 액션에 따라 요청을 완료하거나 취소한다. 

공유익스텐션의 경우 호스트앱의 요청을 받아서 표시된 뷰에서 응답한다. 유저가 내용을 구성하고 포스트 전송 또는 취소를 선택하면 익스텐션은 완료 또는 취소를 처리한다.

 

호스트앱이 요청을 전달하면 거기에는 익스텐션의 컨텍스트를 기술한다. 많은 익스텐션에서 익스텐션 컨텍스트에서 중요한 부분은 익스텐션에서 원하는 작업에 대한 아이템을 기술하는 것이다. 예를 들어 OS X 공유 익스텐션의 컨텍스트는 유저가 포스팅하는데 필요한 선택된 테스트가 될것이다.

 

호스트앱의 요청이 발생하면 앱익스텐션은 기반 뷰컨트롤러의 extensionContext프로퍼티를 컨텍스트를 얻기 위해 사용할 수 있다. 자식뷰컨트롤러 역시 체이닝을 통해 이 프로퍼티에 접근 할 수 있다.

 

이어서 NSExtensionContext클래스로 컨텍스트의 아이템을 사용할 수 있다.  뷰컨트롤러의 loadView메서드에서 컨텍스트와 아이템에 접근할 수 있으며 이 정보를 뷰에 표시할 수 있다. 다음과 같이 이 작업을 수행할 수 있다.

 

  1. NSExtensionContext *myExtensionContext = self.extensionContext;

컨텍스트 객체의 inputItems에 익스텐션에서 필요한 아이템들이 있다. NSExtensionItem객체의 배열을 답고 있으며 다음과 같이 사용 가능하다.

 

  1. NSArray *inputItems = myExtensionContext.inputItems;

각 NSExtensionItem객체는 아이테믜 유형을 설명하는 여러개의 프로퍼티(title, content text, attachment, userInfo등)를 담고 있다. 

 

attachments 프로퍼티는 아이템과 관련된 미디어데이터 배열을 포함한다. 공유요처어의 경우 유저가 공유하고자 하는 웹페이지의 표현이 포함될수 있다.

 

input items 으로 작업 후 보통 유저에게 테스크를 완료 혹은 취소를 선택하도록 한다. 유저의 선택에 따라 completeRequestReturningItems:completionHandler: 메서드를 호출하는데 NSExtensionItem객체를 추가로 반환할 수 있고 cancelRequestWithError:메서드로 에러코드를 반환할 수 있다.

 

iOS에서 앱익스텐션이 잠재적으로 긴 작업을 완료하기 위해 긴 시간이 필요할 수 있다. 웹사이트에 컨텐츠를 업로드할 때 가 해당된다. 이럴경우 NSURLSession클래스로 백그라운드에서 전송을 시작할 수 있다. 백그라운드 전송이 ㅂ ㅕㄹ도의 프로세스를 사용하기 때문에 전송은 계속될 수 있고 낮은 우선순위의 테스크로 처리되고 익스텐션이 완료된 후 종료된다.

 

업로드/다운로드 이외의 VoIP, 오디오 백그라운드 재생같은 백그라운드 작업은 불가하다. 만일 UIBackgroundModes키를 익스텐션의 Info.plist에 기술한다면 AppStore에서 리젝될것이다.

 

효율성 및 성능 최적화

 

앱익스텐션은 유저에게 빠르고 가벼운 느낌을 주어야 한다. 1초이내의 빠르게 적재되도록 앱익스텐션을 설계해야 한다.

 

포그라운드앱의 메모리제한보다 앱익스텐션은 훨씬 더 적게 사용해야 한다. 두 플랫폼에서 모두 시스템은 유저가 호스트엡의 주목적에 맞게 돌아가기를 원하기 때문에 익스텐션을 적극적으로 종료할것이다. 일부 익스텐션은 더 적은 메모리를 사용한다. 예로 위젯은 동시에 유저가 몇개의 위젯을 열수도 있기 때문에 더 효율적이어야 한다.

 

앱익스텐션은 자신의 메인 루프를 가지고 있지 않기 때문에 메인루프에서 적절한 동작을 하려면 설정된 룰을 따라야 한다. 예로 익스텐션이 메인루프를 블럭한다면 이는 다른 익스텐션이나 앱에세 좋지않은 사용자경헙을 선사하게 될것이다.

 

GPU는 시스템에서 리소스를 공유한다는 것을 상기해야 한다. 공유리소스에 대해 앱익스텐션은 우선순위가 낮다. 예로 투데이 위젯이 그래픽이 강조된 게임에서 사용된다면 아주 나쁜 결과를 초래할것이다. 메모리가 부족해지면 시스템은 이런 익스텐션을 죽이려고 한다.  시스템리소스를 많이 사용하는 기능은 앱이 적합하며 앱익스텐션은 그렇지 않다.

 

간소화된 UI설계

 

대부분의 앱익스텐션은 당신의 익스텐션을 오픈할때 보게되는 커스텀UI를 제공해야 한다. UI는 단순하고 억제되고 단일작업을 촉진하는데 집중해야 한다. 성능과 UX를 개선하고자 한다면 익스텐션이 제공하는 기능을 지원하지 않는 추가적인 UI는 피해야 한다.

 

Xcode 앱익스텐션 템플릿은 시작하는데 필요한 UI를 제공한다.

 

아이콘은 일관성과 가독성을 위해 포함하는 앱과 동일한게 좋다. 

 

액션익스텐션은 포함하는 앱의 아이콘의 이미지버전을 사용한다. 반드시 제공되어야 한다.

 

공유익스텐션은 자동으로 포함하는 앱의아이콘을 갖는다. 공유익스텐션 타겟에 다른 아이콘을 제공해도 Xcode는 이를 무시한다. 

나머지 다른 익스텐션들도 앱의 아이콘을 제공해야 한다.

 

앱익스텐션 이름은 짧고 가독성좋은 이름이어야 하며 포함하는 앱의 이름을 포함해야 한다. 패턴은 <포함하는앱이름>-<앱익스텐션이름>이다. 이는 유저가 시스템에서 쉽게 다룰수 있게 해준다. 옵션으로 포함하는앱이름을 사용해도 되는 경우가 있는데 이 경우는 오직 하나의 익스텐션만 제공하는 앱의 경우이다.

 

앱익스텐션이름은 타겟의 CFBundleDisplayName값이다. 이 값을 지정하지 않으면 CFBundleName값이 사용되며 포함하는 앱이름이 된다.

 

로컬화된 앱익스텐션으로 다국어가 적용된 앱익스텐션이름을 제공할 수 있다.

 

일부 앱익스텐션은 짧은 설명글이 필요할 수 있는데 OS X 위젯의 경우 투데이 뷰에서 보기를 원하는 위젯을 선택하는데 도움을 준다. InfoPlist.strings 파일에서 widget.description키의 값을 편집하면 된다.

 

모든 디바이스에서 iOS 앱익스텐션 지원하기

 

universal을 반드시 지원해야 한다. 아이폰, 아이팟터치, 아이패드등에서 모두 가능해야 한다. 포함하는 앱이 어떤 디바이스를 타겟으로 하는것과는 무관하다. Xcode 앱익스텐션 템플릿은 유니버셜로 설정되어 있다.

 

Xcode 빌드설정에서 디바이스패밀리를 "Universal"로 지정해야 한다.

 

 

AutoLayout과 size클래스를 UI 디자인시 적용한다. 그러면 모든 디바이스 사이즈와 방향에 대응될것이다. 

 

 

디버그, 프로파일링 그리고 테스트

 

앱익스텐션디버그는 다른 프로세스 디버깅하는 방법과 한가지만 제외하고 아주 비슷하다. 앱익스텐션의 Run phase에서 실행파일로 호스트앱을 지정한다. 지정한 호스트앱의 UI로 앱익스텐션에 접근하면 Xcode 디버거가 익스텐션에 연결한다.

 

Xcode 앱익스텐션 템플릿의 scheme에서 Ask On Launch 옵션이 사용된다. 이 옵션을 사용하면 앱을 빌드하여 실행할 때 마다 어떤 호스트앱을 사용할지 지정할 수 있다. 이렇게 하지 않고 개별적으로 호스트를 지정하고 싶다면 scheme editor에서 info tab의 app extension scheme's Run phase를 이용한다.

 

다음은 앱익스텐션에 Xcode debugger를 연계하는 절차이다.

 

1. Produce > Scheme > MyExtensionName 또는 toobar의 MyExtensionName 의 scheme 팝업메뉴 선택

2. 지정한 호스트앱을 시작하기 위해 Build and Run 버튼 선택

3. 호스트앱의 UI에서 앱익스텐션 호출하면 Xcode 디버거가 앱익스텐션의 프로세스에 연결되고 브레이크포인트가 활성화되고 익스텐션이 시작된다. 그러면 다른 프로세스의 디버깅할떄와 동일하게 디버깅 가능하다.

 

디버깅전에 익스텐션 scheme이 선택되어 있는지 확인해야. 앱의 scheme을 선택하면 앱익스텐션 디버깅이 아니고 앱디버깅이다.

 

OS X에서 호스트앱 테스트 및 디버깅으로 접근하기 전에 앱익스텐션활성화 단계를 수행해야 한다. 시스템 프리퍼런스의 익스텐션 페인에서 대부분의 익스텐션타입을 활성화 하라. 공유나 액션메뉴의 More를 선택하여 익스텐션 페인을 오픈할수도 있다.

 

OS X 투데이위젯을 위해 위젯시뮬레이터로 테스트 및 디버깅할 수 있다.

 

iOS에서 커스텀 키보드를 위해 Settings > General > Keyboard > Keyboards에서 활성화해야 한다.

 

OS X 디버깅 세션 동안에 Xcode는 빌드된 앱익스텐션을 등록한다. 이는 개발버전을 OS X에 설치하려면 FInder로 빌드위치에서 애플리케이션폴더같은 위치로 복사해야 한다. 

 

Xcode 디버깅콘솔로그에서 앱익스텐션 바이너리는 CFBundleDisplayName프로퍼티값 대신에 CFBundleIdentifier프로퍼티값이 사용된다.

 

앱익스텐션이 효율적이고 응답성이 있어야 하기 때문에 앱익스텐션 동작중에 디버그네비게이터의 디버그게이지를 살피는것이 좋다. 디버그 게이지는 CPU, 메모리, 다른 시스템리소스들의 사용량을 볼수 있게 해준다. 성능이슈의 증거를 보게 된다면 앱익스텐션을 프로파일하기 위해 instrument를 사용할 수 있고 향상된 부분을 확인할수도 있다. 

 

앱배포

 

포함하는 앱없이 스토어에 앱익스텐션을 배포할수 없으며 다른앱으로 이전할수도 없다.

 

iOS 앱익스텐션을 배포하려면 포함하는 앱을 스토어에 배포해야 한다.

OS X 앱익스텐션을 배포하려면 스토어에 포함하는 앱을 배포하는것이 추천되나 필수는 아니다.

 

Mac App Store가 아닌 다른 채널로 OSX 앱익스텐션을 배포하면 Getekeeper가 유저가 오픈할 때 걸러내게 되며 포함하는 앱을 승인한다. 또한 개발자 ID외의 인증서로 서명된 경우 사용자는 Gatekeeper를 명시적으로 재정의하여 익스텐션을 사용할수 있도록 포함앱을 열어야 한다.

 

리뷰를 통과하려면 포함하는앱은 유저에게 기능을 제공해야 한다. 앱익스텐션만 제공할수는 없다.

 

Posted by 삼스
iOS2020. 7. 28. 13:54

앱익스텐션은 앱이 아니며 제한되고 잘 범위지어진 정책으로 정의된 각각 익스텐션포인트에 맡게 구현된다.

 

앱익스텐션 생명주기

 

앱이 아니기 때문에 생명주기나 환경이 앱과 다르다. 대개는 뷰컨트롤러에서 표시된 앱의 UI로부터 유저에게 선택되어졌을때 시작된다. 앱익스텐션을 선택하여 실행해주는 앱을 호스트앱이라고 칭한다. 호스트앱은 앱에 제공되는 컨텍스트를 제공하며 유저의 액션의 응답으로써 리퀘스트가 전달될 때 익스텐션의 생명주기가 시작된다. 그리고 호스트앱으로부터 전달받은 요청을 모두 처리하게 되면 익스텐션도 종료된다.

 

예를 들어 OS X 호스트앱에서 텍스트일부를 선택했다고 상상해보자, 공유버튼이 활성화 되고 텍스트를 공유할 수 있는 공유목록에서 익스텐션하나를 선택한다. 호스트앱은 유저의 선택에 응답으로 선택한 테스트를 포함하는 요청을 앱익스텐션에 전달하게 된다. 이 상황에 대한 일반화한 다음 그림을 참고하라.

 

 

 

그림의 step2에서 시스템은 호스트앱의 요청을 포함하는 앱익스텐션의 인스턴스를 만들고 서로간의 통신채널을 셋업한다. 익스텐션은 호스트앱의 컨텍스트내에서 표시되어지고 작업을 수행하기 위한 호스트앱 요청을 수신한다(여기서는 선택한 텍스트이다).

 

그림의 step3에서 유저는 수행하거나 취소할 수 있고 닫을수도 있다. 이 액션의 응답으로 익스텐션은 유저의 작업을 바로 수행함으로 써 호스트앱의 요청을 완료하거나 필요하다면 백그라운드 프로세스로 수행한다. 호스트앱은 익스텐션뷰를 떼어내게 되고 유저는 이전 컨텍스트로 돌아가게 된다. 익스텐션의 작업이 끝나면 바로 또는 후에 결과가 호스트앱에 반환될것이다.

 

step4처럼 앱익스텐션의 작업을 수행한 후 시스템은 익스텐션을 종료한다.

 

 

앱익스텐션 통신

 

앱익스텐션은 기본적으로 호스트앱과 통신한다. 그리고 트랜잭션프로세싱처럼 동작한다. 호스트앱에서 요청을 보내고 앱익스텐션에서 응답을 받는다.다음 그림에서 호스트앱에서 시작된 실행중인 익스텐션과 이를 포함하는 앱간의 관계를 보여준다.

 

 

 

앱익스텐션과 이를 포함하는 앱간에 직접적인 통신은 없다 포함하는 앱은 포함된 익스텐션이 수행중에 실행되지 않는다. 포함하는 앱은 호스트앱과 통신하지 않는다.

 

일반적인 요청/응답의 트랜잭션처럼 시스테메서 호스트앱대신에 앱익스텐션을 오픈하며 호스트에서 제공하는 익스텐션컨텍스의 데이터를 전달한다. 익스텐션은 UI를 표시하고 작업을 수행한다. 그리고 익스텐션의 목적에 부합하는 데이터를 호스트에 반환한다.

 

그림에서 점선은 익스텐션과 포함하는 앱간에 유효한 상호작용이 제한된다는것을 의미한다. 투데이위젯은 시스템에 포함하는 앱의 오픈을 요청할 수 있는데 NSExtensionContext 클래스의 openURL:completionHandler: 메서드로 가능하다.

 

아래 그림에서 Read/Write 화살표를 보면 앱익스텐션과 포함하는 앱은 private으로 정의된 shared container에 공유된 데이터에 접근할 수 있다.

 

 

 

앱익스텐션에서 할수 없는 것들

 

시스템에 그 책임이 집중되기 때문에 앱익스텐션은 일부 작업은 불가하다. 아래 나열한것들은 수행 불가하다.

 

- sharedApplication 객체에 접근 불가하여 이 객체의 모든 메서드사용불가

- NS_EXTENSION_UNAVAILABLE 메크로, 잘알고 있는 메크로 또는 유효하지 않는 프레임워크등의 사용 불가, 예) HealthKit, EventKit UI framework등

- 카메라, 마이크 사용불가

- 오랫동안 동작하는 백그라운드테스크 사용불가. (NSURLSession을 이용한 upload/download를 일으킬수 있고 포함하는 앱에 결과를 보고할 수 있다)

- AirDrop으로 데이터 수신할수 없음(UIActivityViewController로 데이터를 보내는것은 가능)

Posted by 삼스
iOS2020. 7. 28. 13:53

App Extension으로 영향력을 증대하기

 

앱익스텐션은 앱을 뛰어 넘어 컨텐츠와 기능을 더 확장할 수 있게 한다. 

임의작업을 활설화하기 위해 앱익스텐션을 작성할 수 있다. 예를 들어 브라우저에서 당신의 소셜서비스에 유저가 포스팅하도록 공유 익스텐션을 제공할 수 있다. 또는 유저가 좋아하는 팀을 따라잡기 위해 알림센터에서 현재 점수를 표시하는 투데이 위젯을 제공할 수 있다. 시스템 키보드를 대체하는 커스텀 키보드를 제공할 수도 있다.

 

iOS와 macOS에서 몇가지 타입의 앱익스텐션을 정의한다. 

앱익스텐션이 활성화된 하나의 시스템영역은 하나의 익스텐션포인트로 부른다. 

익스텐션포인트는 사용정책과 해당 영역에서 앱익스텐션을 생성할 때 사용하는 API들을 제공한다.

 

다음 표는 iOS와 macOS에서 제공하는 앱익스텐션의 목록을 제공하며 각 익스텐션의 익스텐션포인트를 예로 들고 있다.

 

Action(iOS, macOS; UI, nonUI)

- host앱에서 컨텐츠를 다루거나 표시한다.

Audio Unit(iOS, macOS; UI, nonUI)

- host앱에 전달할 오디오스트림을 생성하거나 host앱으로부터 오디오스트림을 받아서 수정 한 후 host앱으로 다시 전달한다.

Broadcast UI(iOS, tvOS)

Broadcast Upload(iOS, tvOS)

Call Directory(iOS)

- 전화번호를 구별하고 블럭한다. CallKit Framework로 구현 가능하다.

Content Blocker(iOS, macOS)

- 컨텐츠블로킹앱이 규칙을 업데이트 했는지 표시(UI없음)

Custom Keyboard(iOS)

- 시스템키보드를 대체함.

Document Provider(iOS; ui, nonUI)

- 파일저장소에 접근하고 조작한다.

Finder Sync(macOS)

- Finder에 파일 동기화 상태를 바로 반영(Ui없음)

Game App(watchOS)

- Apple Watch에서 게임앱을 작성할 수 있게 해줌.

iMessage(iOS)

- 메세지앱과 상호작용

Intents(iOS)

- Siri와 통합하여 작업을 처리할 수 있다. SiriKit Programming Guide참조

Notification Content(iOS)

Notification Service(iOS)

Photo Editing(iOS, macOS)

- 사진앱으로 사진이나 비디오를 편집

Share(iOS, macOS)

- wetsite나 컨텐츠들을 공유하는 포스팅

Smart Card Token(macOS)

Spotlight Index(iOS)

- 앱 동작 중이 아닌동안에 앱의 컨텐츠를 인덱스함

Sticker Pack(iOS)

- 사용자가 메세지앱에서 사용할 수 있는 스티커셋을 제공

Today(iOS, macOS)

- 알림센터의 투데이에 빠르게 업데이트하거나 빠르게 태스크를 수행

TV Services(tvOS)

VPN(iOS, macOS)

- VPN client

WatchKit App(watchOS)

- 애플워치를 위한 알림UI나 앱을 제공

Xcode Source Editor(macOS)

 

시스템에서 각 앱익스텐션에 대한 제한된 영역을 정의하기 때문에 작성하려고 하는 앱에 가장 적합한 영역을 선택하는것이 중요하다. 경험공유를 활성하한 앱을 작성하고자 한다면 Share Extension Xcode template로 시작하여 공유익스텐션포인트를 사용해야 한다.

 

앱익스텐션과 앱은 구별된다.

 

앱에 익스텐션을 추가하고 배포한다고 해도 앱익스텐션은 바이너리가 분리되고 앱과 독립적으로 동작한다.

 

앱익스텐션은 새로운 타겟으로 생성하게 된다. 다른 타겟들 처럼 익스텐션타겟도 각각 셋팅과 빌드에 포함되는 파일들을 지정하게 된다. 하나의 앱에 여러개의 익스텐션타겟을 추가할 수 있다.

 

익스텐션개발을 시작하는 가장 좋은 방법은 xcode가 제공하는 템플릿으로 시작하는 것이다. 각 템플릿들은 익스텐션포인트에 맞는 구현파일과 셋팅을 포함한다.

 

유저에 앱익스텐션을 배포하려면 앱스토어에 앱과함께 제출하면 된다. 유저가 앱설치 시 익스텐션도 함께 설치된다.

 

앱익스텐션을 설치한 후 유저는 반드시 활성화하는 액션을 수행해야 한다. 유저는 유형에 따라 익스텐션을 활성화할 수 있다. 예를 들어 투데이위젯 익스텐션인 경우 유저는 익스텐션을 활성화 하기 위해 알림센터의 투데이뷰를 편집할 수 있다. 혹은 어떤 경우는 iOS는 설정에서 macOS는 시스템프리퍼런스에서 활성화 할 수 있다.

 

각 앱익스텐션 타입별로 서로 다른 작업유형을 활성화함에도 불구하고 대부분의 익스텐션에 공통적인 UX 부분이 있다. 가장 훌룡한 UX는 빠르고 이어지고 하나의 작업에 집중하는 것이다.

 

유저는 시스템에서 제공되는 일련의 UI에 반을하여 당신의 앱익스텐션을 오픈하게 된다. 예를 들어 공유익스텐션은 시스템에서제공되는 공유버튼에 의해 표시된 목록에서 당신의 앱익스텐션을 선택함으로써 비로소 활성화된다.

 

대부분의 앱익스텐션이 자체 커스텀 UI요소를 갖고 있지만 앱익스텐션에 진입하기 전까지 유저는 커스텀UI를 보지 않는다. 유저가 당신의 앱익스텐션에 진입할 때 당신의 커스텀 UI는 사용자에세 새로운 맥락으로 이동되었다는것을 알게 한다. 현재 사용중인 앱과 당신의 익스텐션을 구분할 수 있기 때문에 오동작하거나 제대로 수행하지 못하는 액스텐션을 삭제할 수 있게 해준다.

 

 

 

Posted by 삼스
기타2020. 7. 13. 15:42

https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53

 

Charles scalfani의 글이 흥미로워서 정리해보았다.

 

수십년간 개발자로 살아온 나에게도 OOP는 종교였다. 

상속, 캡슐화, 다형성은 이 페러다임의 3개의 기둥이다.

 

이 기둥 3개로 우리는 실세계의 모든 상황을 모델링할 수 있다고 배웠다.

 

필자는 더이상 잘못된 방향으로 살고 싶지 않았다며 본인의 감회를 풀어낸다.

 

먼저 상속..

 

 

첫번째 문제 상속

 

OOP의 가장 큰 장점으로 설명되며 아주 전통적인 예로 Shape를 가지고 설명들을 한다.

 

 

 

 

많이 보았을 거다. 그리고 우리는 재사용성이라는 외마디를 외치게 된다. 신념에 차서.

 

재사용을 위한 모든 준비는 되어 있고 언제든 이를 다시 사용할 시기만 학수고대 할것이다.

 

Banana Monkey Jungle Problem

 

그리고 마침내 새로운 프로젝트가 시작되고 나의 소중한 코드들을 가져와서 사용하려고 할것이다.

 

이런! 부모클래스도 필요하네?

이런! 부모의 부모클래스, 그 부모의 부모클래스도 필요하네??

아 하지만 문제 없어 난 할수 있어~

이런 다 했는데 왜 컴파일이 안되지? 오 이런 이 클래스가 저 클래스를 참조하자나.. 저것도 가져와야 해.. 

머 가져오지 머.. 문제 없어.

 

오~ 이런 난 이런 클래스들은 필요가 없다구. 먼가 잘못되었어..

 

Erlang의 창시자인 Joe Amstrong의 말이 있지.

 

"OOP의 문제는 당신은 단지 바나나를 원하지만 그 바나나는 바나나와 정글전체를 쥐고 있는 고릴라라는 것이다"

 

이를 두고 banana monkey jungle 문제라고 한다.

 

이 문제를 해결하기 위해 필자는 hierarchy를 너무 깊이 설계하지 않는다. 

하지만 재사용성을 상속의 핵심이라고 한다면 메커니즘에 이런 제한을 주는것은 재사용의 이점에 대한 제한이 될것이다.

 

그러면 진짜 속시원한 해결책은 무엇일까?

 

포함(contain)과 위임(delegate)라고 생각한다.

 

Diamond Problem(다중상속문제)

 

 

 

 

대부분의 객체지향언어는 논리적으로 작성된것 같지만 이를 지원하지 않는다. 객체지향언어가 이를 지원하기 위해 어려운 이유에 대해서는 다음 의사코드를 한번 보자

 

Class PoweredDevice {

}

 

Class Scanner inherits from PowerdDevice {

  function start() {

  }

}

 

Class Printer inherits from PoweredDevice {

  function start() {

  }

}

 

Class Copier inherits from Scanner, Printer {

}

 

위 의사코드를 보면 Scanner와 Printer 클래스가 모두 start함수를 구현하고 있다.

Copier의 start를 호출하면 Scanner나 Printer의 어떤 start가 호출될 수 있을까? 알수 없다.

 

이에 대한 해결책은 간단하며 위와 같이 설계하지 않고 따로 따로 구현하면 된다.

하지만....... 어떻게 모델링해야 하지?? 재사용은 포기하고 싶지 않고?

 

이 때 포함 위임을 통해서 해결리 가능하다.

 

Class PoweredDevice {

}

 

Class Scanner inherits from PoweredDevice {

  function start() {

  }

}

 

Class Printer inherits from PoweredDevice {

  function start() {

  }

}

 

Class Copier {

  Scanner scanner

  Printer printer

  function start() {

    printer.start()

  }

}

 

Copier클래스가 이제 Printer와 Scanner의 인스턴스를 포함하고 있다.  start함수의 구현은 Printer 클래스에 위임하고 있다. 이는 쉽게 Scanner에 위임이 가능하다.

 

 

깨지기 쉬운 기반클래스 문제

 

이제 나는 상속을 얕게 만들고 다중상속되지 않도록 하여 Diamond문제는 발생하지 않게 하였다.

 

모든건 잘 될거라고 생각하지만...

 

어떤날은 잘 동작하고 어떤날은 동작을 멈춘다.. 코드는 변경된바가 없는 체로...

 

버그가 있겠지.. 하지만 변경한적이 없는걸...

 

이라고 개발자들은 자기위안을 삼지만... 이 코드의 세계는 그렇게 호락호락하지 않다.. 거기에 버그는 있게 마련이다. 반드시~

 

내가 작성한 코드에는 문제가 없었지만 내가 상속한 클래스에서 변경이 발견되었다면... 가능하다.

 

어떻게 내가 상속했던 기반클래스이 변경이 왜 나의 코드에 예외를 발생하는것일까?

 

다음의 자바코드를 살펴보자

 

import java.util.ArrayList;

 

public class Array {

  private ArrayList<Object> a = new ArrayList<Object>();

  public void add(Object element) {

    a.add(element);

  }

  public void addAll(Object element[]) {

    for (int i=0; i<element.length; i++) {

      a.add(element[i]); // this line is going to be changed..

    }

  }

}

주석된 라인의 코드에 주목하자. 여기서 바로 향후 예외가 발생할 수 있다.

 

여기 add와 addAll함수가 있다. add는 엘리먼트하나를 addAll은 다수의 엘리먼트를 추가하는데 add를 호출하여 추가하고 있다.

 

이제 상속한 클래스를 하나 정의해보겠다.

 

public class ArrayCount extends Array {

  private int count = 0;

  @Overrie

  public void add(Object element) {

    super.add(element);

    ++ count;

  }

  @Override

  public void addAll(Object elements[]) {

    super.addAll(elements);

    count += elements.length;

  }

}

 

ArrayCount클래스는 일반적인 Array 클래스의 특별한 버전이다. 다른점은 ArrayCount는 count로 엘리먼트의 개수를 저장하고 있다는 것이다.

 

두 클래스를 자세히 살펴보자. 자세히 ...

 

Array add는 로컬 ArrayList에 엘리먼트를 추가

Array addAll은 로컬 ArrayList에 엘리먼트들을 추가

 

ArrayCount add는 부모의 add를 호출하고 count를 추가한다.

ArrayCount addAll은 부모의 addAll을 호출하고 엘리먼트의 개수만큼 count를 추가한다.

 

예상한데로 정확히 동작한다.

 

자 이제 예외를 유발하는 변경을 주석된 라인부분을 다음과 같이 수정되었다고 하자.

 

public void addAll(Object elements[]) {

  for (int i=0; i<elements.lenggh; ++i) {

    add(elements[i];

  }

}

 

기반클래스의 소유자가 의도한 대로 자동 테스트는 여전히 패스된다.

 

하지만 소유자는 상속한 사실을 잘 잊게 마련이고 미련하기도 하다.

 

ArrayCount addAll은 부모의 addAll을 호출하고 내부에서 오버라이드된 상속된 클래스의 add를 호출하게 된다. 이는 상속된 클래스에서 addAll에서 추가되면서 카운트가 한번더 증가될수 있다. 

 

중복해서 카운트가 증가될 수 있다는 것이다. 실제 엘리먼트 개수와 다르게..

 

만일 이런 문제가 발생한다면 개발자는 기반클래스가 어떻게 구현되어 있는지 확인이 필요하게 되며 기반클래스 작성자는  변경이 발생하게 되면 이 클래스를 사용하는 개발자들에게 모두 알려야 한다. 

 

이런 잠재적인 문제는 상속이라는 기둥에 대해 불신하게 한다.

 

이에 대한 해결책은 다시한번 포함과 위임이다.

 

포함과 위임으로 우리는 화이트박스프로그래밍에서 블랙박스프로그래밍으로 갈수 있다. 화이트박스프로그래밍에서는 기반클래스의 구현을 알아야 한다.

 

블랙박스프로그래밍은 기반클래스에 코드를 주입할 수 없기 때문에 완전하게 무시할 수 있다. 단지 인터페이스에 집중할 수 있다.

 

이 트렌트는 혼란스럽다.

 

상속은 재사용에 있어서 큰승리로 여겨졌다.

객체지향언어는 포함과 위임보다 상속이 쉽게 설계되었다.

 

이젠 상속에 대해 근원적인 의시밍 생기기 시작했을것이다. 중요한것은 계층을 통한 분류를 맹신하지 않아야 한다는 것이다.

 

 

계층화의 문제

 

새로운 회사를 시작할 때 마다 회사문서를 어디에 위치시킬지 고민하게 된다. 예를 들어 직원명부등..

 

문서폴더를 만들고 회사폴더를 만들건지. 또는 회사폴더를 만들고 문서폴더를 만들지...

 

어떤게 맞고 최선인건가....?

 

분류기반 계층화에서 기반클래스는 좀더 일번적인것이고 상속(자식)클래스는 좀더 특화된 것들이다. 그리고 좀 더 특화된 것들이 상속체인의 끝부분에 위치하게 된다.

 

하지만 그 위치를 변경하면 이 모델에서 먼가 확실하게 잘못되게 된다.

 

이게 머가 잘못된 것일까...?

어떤 계층구조가 맞는 것인가..?

 

포함

 

실세계를 살펴본다면 포함계층화(배타적소유계층화)를 어디에서든지 보게 될것이다. 찾아볼수 없는것은 분류기반계층화이다. 객체기반 페러다임은 실세계 모델링을 기반으로 하며 하나는 객체로 간주된다. 이는 잘못된 모델이다. 실세계는 없다.

실제로 실세계는 포함계층화에 가깝다. 당신의 양말이 아주 훌룡한 예이다. 양말은 서랍에 포함되고 옷장에 포함되고 침실에 포함되고 집에 포함되어 있다.

 

PC 저장장치도 그 예 인데. 파일들을 포함한다.

 

그래서 이제 어떻게 분류할건가?

 

자 이제 회사문서를 생각해보자면 어디에 위치하던지 문제가 안된다. document폴더나 stuff폴더에 위치시킬수 있다.

그리고 분류는 태크로 가능해진다. 다음과 같은 태그로 태그할 수 있다.

 

Document

Company

Handbook

 

태그는 순서나 계층이 없다. 이는 diamond 문제도 해결한다.

태그는 문서에 대해 여러 타입을 지정할 수 있어서 인터페이스와 유사하다.

 

균열이 너무 많으면 상속기둥이 무너진것처럼 보인다.

 

 

두번째문제 캡슐화

 

캡슐화는 객체지향이 갖고 있는 두번째 큰 장점이다.

객체의 상태는 외부에서 보호된다. 

누군가에 의해 접근되는 글로벌변수에 대한 걱정을 더이상 할 필요가 없다.

 

캡슐화는 변수들에 대해 안전하다. 아주 끝내준다. 원원하라 캡슐화여..

 

참조의 문제

 

캡슐화는 참조의 문제가 있다. 

객체는 함수에 전달될 때 값이 아니라 참조로 전달된다.

 

이는 함수는 객체를 전달하지 않으며 대신에 참조나 객체의 포인터를 전달하게 된다는 것이다.

 

만일 객체가 객체생성자에 참조로 전달된다면 이 생성자에서는 캡슐화로써 내부에 private 변수로 참조를 저장할 수 있다.

 

이제 전달된 객체는 안전하지 않다.

 

왜냐구? 일련의 다른 코드에서 객체의 포인터를 갖게 되기 때문이다. 그 코드는 생성자로 호출된다. 그렇다면 생성사로 전달 할 수 없는 객체에 대한 참조를 가져야 하나?

 

해결책은 생성자는 전달되는 객체를 복재해야 한다. 그리고 얕은 복제가 아니고 깊은 복제이어야 한다. 즉 객체가 포함하는 객체들이 있다면 모두 복제되어야 한다.

 

여기서 망하는 지점이 있다. 모든 객체가 복제가 가능한것이 아니다. 일부 운영체제 리소스들은 불가할 수 있다.

 

싱글메인스트림의 객체기반 언어들을 이 문제를 가지고 있다.

 

안녕.. 캡슐화

 

 

세번째 문제 다형성

 

다형성은 객체지향 삼위일체의 빨간머리 의붓자식이다. 일종의 Larry Fine이다.

그가 어디 있던지 그는 하나의 캐릭터이다.

 

다형성이 위대하지 않다는것이 아니다  객체지향언어에서 취할필요는 없다는 것이다.

 

인터페이스가 이를 지원할것이다. 그리고 인터페이스는 얼마든지 다양한 행위에 대해서 섞어서 제한없이 구현이 가능하다.

 

따라서 우리는 객체지향다형성에 대해서는 안녕을 고하고 인터페이스기반의다형성을 반겨야 할 때 이다.

 

 

 

약속의 깨기

 

자 객체지향은 그동안 오랬동안 약속을 해왔다. 이런 약속은 교실에 앉아있는 네이티브프로그래머, 블로그의 읽을거리들 그리고 온라인코스를 학습자들에게 여전히 유효하다.

 

객체지향의 거짓말을 깨닫는데 몇년이 걸렸다. 눈은 없어서 경험이 없고 신뢰를 했다.

그리고 나는 완전히 타버렸다.

 

안녕.. 객체지향!

 

 

 

그럼 어쩌라고?

 

함수형 프로그래밍~ 이다. 수년동안 아주 잘 사용되어 왔다. 

 

한번 타버리면 창피함이 두배가 된다 

 

 

 

세줄정리

 

1. 상속은 재사용의 어머니 이지만 나에게 필요없는 넘들까지 줄줄이 딸려온다.

2. 캡슐화는 객체가 참조로 전달될 수 있다는 점에서 쉽게 깨진다.

3. 다형성은 복잡하기만 하며 인터페이스로 단순하게 얼마든지 다양하게 구현이 가능하다.

 

이를 보완하기 위한 방법들은 포함과 위임이 있으며 최근에는 함수형프로그래밍으로 대체가 가능하므로 객체지향과는 안녕을 고하자..

 

 

Posted by 삼스
iOS2020. 7. 10. 17:23

class Log {
static func debug(_ msg: String, file: String, line: Int) {
print("\(Date()) \(file.components(separatedBy: "/").last ?? file) line \(line) \(msg)")
}
}

사용예...
Log.debug("log message", file:#file, line:#line)



Posted by 삼스
Android2020. 7. 10. 17:03

Firebase analytics 기본

모바일앱의 사용자로그를 수집하기 위해 만들어졌으며 Bigquery와 함께 좀더 자세한 로그분석이 가능하나 Bigquery는 비용이 발생한다.
먼가 계속 내용이 추가되고 변경되기 때문에 문서는 영문을 보기를 권한다.

현재는 iOS, android만 지원함. 웹은 지원하지 않음.

 수집하는 정보
 사용자속성과, 이벤트 두가지로 구분하여 로그정보를 수집한다.

 "홍길동이 회원가입을 했다"라는 일이 일어났을 경우 "홍길동"은 사용자속성이고 "회원가입"은 이벤트이다.

 둘다 정보에 키/값쌍의 파라메터를 추가할 수 있다. 

이벤트
 자동으로 수집되는 이벤트(앱실행, 제거(iOS는 수집불가), 업데이트, 스크린뷰 등)
 최대 500개의 이벤트를 사용할 수 있다.
 저장 개수는 무제한이나 일반적인 상황이 아닌경우 구글에서 제한을 걸 수 있다. 명확하게 명시되어 있지는 않으나 일반적인 상황에서는 발생하지 않음.
 대소문자 구분, firebase_, google_, ga_, ...등으로 시작할 수 없음.
 하나의 이벤트에 25개의 파라메터 추가가 가능하며 파라메터의 키/값은 각 40/100글자의 제한이 있음.
 iOS의 경우 앱이 오류로 종료된 이 후 앱을 재시작없이 삭제되면 이벤트가 누락될 수 있음
사용자속성
 사용자고유의 속성을 추가 가능.
 정보가 유지되고 이벤트에 붙여 읽을수 있다.
 25개의 사용자 속성 사용 가능. 생각보다 많은 개수가 아니므로 잘 설계하여 사용해야 한다.
 키는 24자, 값은 36자 제한이 있으며 firebase_, google_, ga_, ...등은 사용불가.

디버그
 디버그뷰를 통해서 이벤트가 전달되는것을 실시간으로 확인이 가능함.

 안드로이드와 iOS각 디버그모드를 활성화한 후 진행해야 한다.

 

이상과 현실

 애널리틱스를 처음 하려고 하는 이유는 마케팅적인 이유와 서비스운영적인 면에서 오류를 추적하거나 사용패턴을 분석하려는 등의 목적으로 접근하곤 한다.

 적용자체는 굉장히 쉬우나 실제로 유용한 정보를 얻기는 ... 비용적으로 힘들다. 

 의미 있는 정보를 얻고자 한다면 BigQuery를 사용해야 하며 사용하려면 애널리틱스360 계정이 필요하며 대기업용, 중소기업용이 있는데 15만불/년 의 비용을 지불해야 한다고 한다. 이게 대기업용인지 중소기업용인지 모르겠으나 거의 2억정도의 비용을 들인다면 작은 기업들은 접근이 아예 힘들듯..

 따라서 해당 라이센스를 가지고 마케팅정보를 분석하여 서비스 해주는 업체들이 따로 있다고 한다. 그러면 비용이 좀 줄기는 하곘으나 공짜를 기대한 개발자는 좌절하게 될것이다.

 무료버전으로 분석할 수 있는 정보는 아주 미약하지만 특정 이벤트가 어느날에 몇 회 정도 발생했는지 정도의 정보만을 목적으로 한다면 무료로 가능하다.

 

 

 

 

Posted by 삼스
Windows2020. 5. 6. 14:56

CEF#에서 alert, confirm, prompt등의 dialog를 커스텀하는 방법에 대해 정리한다.

 

IJsDialogHandler인터페이스를 구현한 객체를 ChromiumWebBrowser의 JsDialogHandler에 대입한다.

 

예)

browser.JsDialogHandler = new MyJsDialogHandler();

 

JsDialogHandler의 구현체에서는 OnJSDialog를 오버라이드 한다.

 

public bool OnJSDialog(IWebBrowser chromiumWebBrowser,

  IBrowser browser, string originUrl, CefJsDialogType dialogType,

  string messageText, string defaultPromptText,

  IJsDialogCallback callback, ref bool suppressMessage) 

{

  switch(dialogType)

  {

    case CefJsDialogType.Alert: // alert

      MessageBox.Show(messageText, "Notice", MessageBoxButton.OK);

      callback.Continue(true);

      return true;

    case CefJsDialogType.Confirm: // confirm

      var result = MessageBox.Show(messageText, "Notice", MessageBoxButton.YesNo, MessageBoxImage.Warning);

      callback.Continue(result == MessageBoxResult.Yes);

      return true;

  }

  return false;

}

Posted by 삼스
Windows2020. 4. 27. 16:20

CEF javascript 연동

.NET에서는 다음과 같이 자바스크립트 메서드를 호출한다.

browser.ExecuteScriptAsync("document.body.style.background = 'red';");

browser.ExecuteJavaScriptAsync("(function(){ document.getElementsByName('q')[0].value = 'CefSharp Was Here!'; document.getElementsByName('btnK')[0].click(); })();");

여러개의 프래임으로 구성된 경우 임의 프레임의 스크립트를 다음과 같이 호출 한다.

browser.GetBrowser().GetFrame("SubFrame").ExecuteJavaScriptAsync("document.body.style.background = 'red';");


그럼 언제 자바스크립트를 호출할 수 있는가?

자바스크립트는 V8Context에서만 호출할 수 있다.
IRenderProcessMessageHandler의 OnContextCreated와 OnContextReleased 가 자바스크립트가 호출될 수 있는 환경의 범위를 제공한다. 각 프레임별로 호출되며 frame.IsMain으로 메인프레임여부를 판가름할 수 있다.

OnFrameLoadStart에서 DOM에 접근할 수 있으며 로딩이 완료되기 던에 DOM의 스크립트를 실행할 수 있다.

browser.RenderProcessMessageHandler = new RenderProcessMessageHandler();

public class RenderProcessMessageHandler : IRenderProcessMessageHandler {

  // Wait for the underlying JavaScript Context to be created. This is only called for the main frame.
  // If the page has no JavaScript, no context will be created.
  void IRenderProcessMessageHandler.OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
  {
    const string script = "document.addEventListener('DOMContentLoaded', function(){ alert('DomLoaded'); });";

    frame.ExecuteJavaScriptAsync(script);
  }
}

//Wait for the page to finish loading (all resources will have been loaded, rendering is likely still happening)
browser.LoadingStateChanged += (sender, args) =>
{
  //Wait for the Page to finish loading
  if (args.IsLoading == false)
  {
    browser.ExecuteJavaScriptAsync("alert('All Resources Have Loaded');");
  }
}

//Wait for the MainFrame to finish loading
browser.FrameLoadEnd += (sender, args) =>
{
  //Wait for the MainFrame to finish loading
  if(args.Frame.IsMain)
  {
    args.Frame.ExecuteJavaScriptAsync("alert('MainFrame finished loading');");
  }
};

* 스크립트는 프레임레벨에서 수행되고 모든 페이지는 한개 이상의 프레임으로 구성된다.
* IWebBrowser.ExecuteScriptAsync 확장메서드는 하위호환성을 위해 남겨 있으며 main frame에서 js를 수행하는 숏컷처럼 사용가능하다.
* frame이 자바스크립트를 포함하지 않으면 V8Context가 생성되지 않는다.
* frame이 로드된 후 context가 없는 경우 IFrame.ExecuteJavaScriptAsync를 호출하여 V8Context을 생성할 수 있다.
* OnFrameLoadStart가 호출될때 DOM 로딩이 완료되지 않는다.
* IRenderProcessMessageHandler.OnContextCreated/OnContextReleased는 메인프레임에서만 호출된다.

결과를 리턴하는 자바스크립트 메서드의 호출은 ?

//An extension method that evaluates JavaScript against the main frame.
Task response = await browser.EvaluateScriptAsync(script);
//Evaluate javascript directly against a frame
Task response = await frame.EvaluateScriptAsync(script);

자바스크립트는 비동기로 수행되기 때문에 에러메세지, 결과, 성공플래그등을 포함하는 Task를 반환한다. 
자바스크립트가 수행될때 기본적으로 알아야 할 사항은 다음과 같다.

* 언제 호출하는게 가능한지에 대해 알아야 하며 위에서 설명하였다.
* 프레임레벨에서 스크립트가 수행되고 모든 페이지는 하나이상의 프레임으로 구성된다.
* 스크립트는 렌더프로세스내에서 수행되고 성능상의 이유로 IPC를 통해 전달되고 데이터만 반환한다.
* 프리미티브 타입인 int, double, date, bool과 string이 제공된다.
* 결과로 객체가 제공되고 IDictionary<string, object>형태로 접근을 더 용이하게하기 위해 dynamic 키워드가 제공된다.
* 프리미티브타입이나 앞서 얘기한 객체가 IList

형태로 배열이 결과로 제공될 수 있다.
* HTMLCollection같은 배열과 유사한 객체의 경우 Array.from으로 배열로 반환하여 사용될수 없다.
* 반환되는 객체 그래프의 복잡성이 제한이 있다. 자바스크립트 JSON.toStringify()로 자바스크립트 객체를 JSON문자열로 변환하고 다시 .NET객체로 디코디할수 있다(참조: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)

// Get Document Height
var task = frame.EvaluateScriptAsync("(function() { var body = document.body, html = document.documentElement; return  Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); })();");

//Continue execution on the UI Thread
task.ContinueWith(t =>
{
    if (!t.IsFaulted)
    {
        var response = t.Result;
        EvaluateJavaScriptResult = response.Success ? (response.Result ?? "null") : response.Message;
    }
}, TaskScheduler.FromCurrentSynchronizationContext());

//As stated above, it's best to return only the data you require, these examples demo taking a `HTMLCollection` and returning and returning a simplified representation.

//Get all the span elements and create an array that contains their innerText
Array.from(document.getElementsByTagName('span')).map(x => ( x.innerText));
//Get all the a tags and create an array that contains a list of objects 
//Second param is the mapping function
Array.from(document.getElementsByTagName('a'), x => ({ innerText : x.innerText, href : x.href }));


.NET 클래스를 자바스크립트에 바인딩하는 방법!!

JSB(JavaScript Binding)sms JavaScript와 .NET간의 통신을 가능하게 한다. 동기, 비동기방식 모두 지원한다. Sync버전은 더이상 개발되지 않고 있다.

Async JavaScript Binding

1. 바인딩할 클래스 정의

public class BoundObject {
public int Add(int a, int b) {
return a+b;
}
}

2. JavaScriptObjectRepository로 클래스인스턴스 등록

첫번째 방법
//For async object registration (equivalent to the old RegisterAsyncJsObject)
browser.JavascriptObjectRepository.Register("boundAsync", new BoundObject(), true, options);

두번째 방법
browser.JavascriptObjectRepository.ResolveObject += (sender, e) => {
var repo = e.ObjectRepository;
if (e.ObjectName == "boundAsync")
{
BindingOptions bindingOptions = null; //Binding options is an optional param, defaults to null
bindingOptions = BindingOptions.DefaultBinder //Use the default binder to serialize values into complex objects, CamelCaseJavascriptNames = true is the default
bindingOptions = new BindingOptions { CamelCaseJavascriptNames = false, Binder = new MyCustomBinder() }); //No camelcase of names and specify a default binder
repo.Register("boundAsync", new BoundObject(), isAsync: true, options: bindingOptions);
}
};

객체가 JavaScript로 바인딩 된 경우 .Net에 알림을 받으려면 ObjectBoundInJavascript 이벤트 또는 ObjectsBoundInJavascript 이벤트를 구독 할 수 있다 (두 이벤트 모두 명백히 유사 함).

browser.JavascriptObjectRepository.ObjectBoundInJavascript += (sender, e) => {
var name = e.ObjectName;

Debug.WriteLine($"Object {e.ObjectName} was bound successfully.");
};    

3. 스크립트에서 CefSharp.BindObjectAsync 호출

<script type="text/javascript">

(async function() {

  await CefSharp.BindObjectAsync("boundAsync");

  // 

  boundAsync.add(16, 5).then(function (actualResult) {

    const expectedResult = 21;

    assert.equal(expectedResult, actualResult, "Add 16 + 5 resulte in " + expectedResult);

  });

})();

</script>

 

CefSharp.BindObjectAsync가 호출되면 JavascriptObjectRepository가 주어진 이름으로 인스턴스가 등록되어 있는지 확인한다. 만일 등록이 안되어 있다면 ResolveObject이벤트가 발생한다. 파라메터 없이 CefSharp.BindObjectAsync가 호출되면 등록된 경우 바운드가 되고 등록이 안되었으면 ObjectName을 All로 설정하여 ResolveObject가 모두 호출된다.

example) https://github.com/cefsharp/CefSharp.MinimalExample/tree/demo/javascriptbinding

 

 

Posted by 삼스
Android/App개발2020. 4. 6. 14:45

https://medium.com/google-exoplayer/playback-notifications-with-exoplayer-a2f1a18cf93b

 

PlayerNotificationManager를 통해 미디어재생 시 알림센터에 정보를 표시하고 제어할 수 있다.

1. PlayerNotificationManager인스턴스 생성
2. PlayerNotificationManager.MediaDescriptionAdapter 로 재생중인 미디어 아이템 정보를 제공
3. 플레이어에 연결, 리소스가 소거되었을 때는 연결해제

PlayerNotificationManager 인스턴스 생성
 activity나 fragment의 onCreate에서 생성한다. 

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ...
  playerNotificationManager = new PlayerNotificationManager(
      this,
      new DescriptionAdapter(), 
      CHANNEL_ID,
      NOTIFICATION_ID);
}

채널아이디, 알림아이디, 그리고 재생중인 미디어 정보를 제공하기 위해 MediaDescriptionAdapter를 확장한 DescriptionAdapter를 파라메터로 넘긴다.

private class DescriptionAdapter implements
    PlayerNotificationManager.MediaDescriptionAdapter {
    
  @Override
  public String getCurrentContentTitle(Player player) {
    int window = player.getCurrentWindowIndex();
    return getTitle(window);
  }

  @Nullable
  @Override
  public String getCurrentContentText(Player player) {
    int window = player.getCurrentWindowIndex();
    return getDescription(window);
  }

  @Nullable
  @Override
  public Bitmap getCurrentLargeIcon(Player player,
      PlayerNotificationManager.BitmapCallback callback) {
    int window = player.getCurrentWindowIndex();
    Bitmap largeIcon = getLargeIcon(window);
    if (largeIcon == null && getLargeIconUri(window) != null) {
      // load bitmap async
      loadBitmap(getLargeIconUri(window), callback); 
      return getPlaceholderBitmap();
    }
    return largeIcon;
  }

  @Nullable
  @Override
  public PendingIntent createCurrentContentIntent(Player player) {
    int window = player.getCurrentWindowIndex();
    return createPendingIntent(window);
  }
}

플레이어에 연결은 다음과 같이 한다.

player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
playerNotificationManager.setPlayer(player);

플레이어가 해제되기 전에 먼저 연결을 끊어야 한다.

playerNotificationManager.setPlayer(null);
player.release();
player = null;


커스텀하기

 앱의 테마에 맞도록 알림을 커스텀하는 다양한 방법이 존재한다. 재생컨트롤의 동작여부를 설정하고 알림속성을 설정할 수 있도록 매니져가 기능을 제공한다. 이 속성들은 기본값을 가지고 있고 쉽게 바꿀수 있다.

재생컨트롤 액션들

 기본 컨트롤액션을 제공한다. 재생/일시정지, FF/REW, Next/Previous, Stop등이 있으며 생략도 가능하다

// omit skip previous and next actions
playerNotificationManager.setUseNavigationActions(false);
// omit fast forward action by setting the increment to zero
playerNotificationManager.setFastForwardIncrementMs(0);
// omit rewind action by setting the increment to zero
playerNotificationManager.setRewindIncrementMs(0);
// omit the stop action
playerNotificationManager.setStopAction(null);

커스텀액션도 CustionActionReceiver를 확장하여 구현하여 PlayerNotificationManager 생성자의 5번째 파라메터로 넣어서 구현 가능하다.

알림 속성

 알림매니저는 UI와 알림의 동작의 setter를 제공한다. 이는 NotificationCompat.Builder의 속성에 상응한다.

manager.setOngoing(false);
manager.setColor(Color.BLACK);
manager.setColorized(true);
manager.setUseChronometer(false);
manager.setSmallIcon(R.drawable.exo_notification_small_icon);
manager.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE);
manager.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);

MediaSession

 최종적으로 Google Assistant등을 지원하기 위해 MediaSession API를 사용하고 있다면 media style 알림의 장점을 모두 취하기 위해 session에 token을 셋팅할수 있다.

 playerNotificationManager.setMediaSessionToken(token);




 

Playback Notifications with ExoPlayer

Displaying a notification with playback controls is a best practice for media apps on Android. For audio playback in the background it’s…

medium.com

 

Posted by 삼스
카테고리 없음2020. 2. 19. 13:26

CEF

CEF

Chromium Embedded Framework (CEF). 
chromium 기반의 브라우저를 내포하는 앱을 만들 수 있는 프레임워크이다.

소개

BSD-license이고 구글 크로미엄프로젝트 기반으로 2008년에 마샬그린브랫에의해 시작된 오픈소스 프로젝트이다.
크로미엄프로젝트와 다르게 구글 크롭앱개발에 주로 집중하였고 써드파티앰의 내장된 브라우저 사용 케이스에 집중하였다.
CEF는 크로미텀의 하부와 코드의 복잡함으로부터 고수준의 안정화된 API, 특정목적의 배포, 그리고 바이너리 배포를 통해 벗어날수 있게 해준다.
CEF기본 구현은 사용자로부터 조금의 또는 전혀 통합하는 작업을 필요로 하지 않는다.
현재 1억개 이상의 인스턴스가 사용되고 있다. 그 일부가 위키페이지에 정리되어 있다.

사용케이스 몇가지는 다음과 같다.
* 기존 네이티브 애플리케이션에 HTML5호환 웹브라우저를 내장
* 가벼운 네이트브 쉘애플리케이션으로 기본적으로 웹기술에 기반하여 개발된 UI를 호스팅
* 자체적인 드로잉프레이뭐크를 사용하는 오프스크린 웹컨텐츠를 렌더링
* 웹속성이나 애플리케이션의 자동화된 테스트를 수행

CEF는 수많은 언어와 운영체제를 지원하며 새로운 또는 기존애플리케이션에 쉽게 통합이 가능하다. 성능과 쉬운 사용을 목표로 설계되었다.
기반프레임워크는 C, C++로 네이티브라이브러리로 제공되고 애플리케이션과 크로미엄과 상세한 구현으로부터 분리해준다.
이는 브라우저와 커스텀플러그인, 프로토콜, 자바스크립트 객체 그리고 자바스크립트 확장 등을 포함하는 앱간에 밀접한 통합을 제공한다.
애플리케이션은 리로스로딩을 제어하거나, 네비게이션, 메뉴, 프린트 등을 하면서 구글 크롬웹브라우저와 동일한 성능과 HTML5기술을 사용할 수 있다.

수많은 개인과 단체가 시간과 자원을 CEF개발에 기연하고 있지만 더 많은 커뮤니티로부터의 참여는 항상 환영한다.


CEF Tutorial

CEF3로 간단한 앱을 작성하는 방법을 설명한다. CEF3 사용에 대해서는 GeneralUsage위키페이지를 참고하라.

시작하기

CEF개발을 시작하기 쉽게 해주는 샘플프로젝트를 제공한다. cef-project 웹사이트에 방문하고 단계별로 설명을 따라해보기 바란다.


커스텀URL 로딩하기

샘플프로젝트는 구글 홈페이지를 로딩한다. 다른 url로 변경하려면 커맨드라인에서 다음과같이 수행한다.

# Load the local file “c:\example\example.html
cefsimple.exe --url=file://c:/example/example.html

샘플프로젝트에서 cefsimple/simple_app.cc에서 다음라인을 수정하고 다시 컴파일하면 바로 진입한다.

// Load the local file “c:\example\example.html

if (url.empty())
  url = "file://c:/example/example.html";



애플리케이션 요소들

모든 CEF애플리케이션은 다음의 기본 콤포넌트들로 구성된다.

1. CEF dynamic library(libcef.dll 윈도우용, libcef.so 리눅스용, Chromium Embedded Framework.framework 맥용)
2. 지원 파일들(*.pak, *.bin, ...)
3. 리소스 (html/js/css, string, ...)
4. 클라이언트 실행파일

CEF dynamic library, 지원파일들, 리소스는 모든 CEF 애플리케이션에 존재한다. 바이너리 배포판의 Debug / Release 또는 Resources 디렉토리에 포함된다. 이러한 파일 중 필요한 파일과 제외 할 수있는 파일에 대한 자세한 내용은 이진 배포에 포함 된 README.txt 파일을 참조하십시오. 각 플랫폼에서 필요한 애플리케이션 레이아웃에 대한 자세한 설명은 아래를 참조하십시오.

아키텍쳐

다음은 이 튜터리얼의 기본적인 중요한 아이템들을 요약한다.

* CEF는 멀티프로세스를 사용한다. 메인 애플리케이션 프로세스는 '브라우저' 프로세스로 불린다. 서브프로세스들은 렌더러, 플러그인, GPU등으로 생성해되게 될것이다.
* 윈도우와 리눅스에서는 메인과 서브프로세스가 같이 실행될 수 있다. OS-X는 실행과 서브프로세스의 번들이 분리되어야 한다.
* 대부분의 프로세스는 다중스레드를 갖는다. 
* 일부 콜백과 함수들은 개개 프로세스 또는 개개의 스레드를 사용할 수 있다. 콜백이나 함수를 처음 사용하는 경우 헤더파일의 코멘트를 반드시 읽어볼것을 권장한다.

소스코드

cefsimple애플리케이션은 CEF를 초기화하고 하나의 브라우저팝업윈도우를 생성한다. 애플리케이션은 모든 브라우저윈도우가 닫히면 종료된다. 프로그램 흐름은 다음과 같다.

1. OS는 브라우저프로세스를 시작한다. 진입포인트는 main또는 wWinMain
2. 진입포인트 함수는
  프로세스레벨의 콜백을 처리하는 SimpleApp의 인스턴스를 생성한다.
  CEF를 초기화 하고 CEF message loop를 시작한다.
3. CEF가 초기화 되면 SimpleApp::OnContextInitialized가 호출된다. 이 메서드는
  SimpleHandler의 싱글턴 인스턴스를 생성한다.
  CefBrowserHost::CreateBrowser를 사용하여 브라우저윈도우를 생성한다.
4. 모든 브라우저는 SimpleHandler인스턴스를 공유하며 이는 브라우저의 행위를 커스텀하고 관련된 콜백을 처리한다(life span, 로딩상태, 타이틀 표시등...)
5. 브라우저하나가 닫히면 SimpleHandler::OnBeforeClose가 호출된다. 모든 브라우저가 닫히면 CEF message loop가 끝나며 애플리케이션이 종료된다.


진입포인트 함수

이 함수는 CEF와 운영체제 관련된 객체들을 초기화해야 한다. 예를 들어 리눅스에서는 X11 에러헨들러를 인스톨해야 하고, OS-X에서는 필요한 코코아객체들의 할당이 필요하다.

* 윈도우 : cefsimple/cefsimple_win.cc
* 리눅스 : cefsimple/cefsimple_linux.cc
* OS-X 
  브라우저 프로세스 : cefsimple/cefsimple_mac.mm
  서브 프로세스 : cefsimple/process_helper.mac.cc


SimpleApp

프로세스레벨의 콜백을 처리해야 한다. interface/method로 노출되며 멀티프로세스에 의해 공유되고 각 프로세스만 에서 호출된다. 
예를 들어 CefBrowserProcessHandler인터페이스는 브라우저 프로세스에서만 호출된다. 
CefRenderProcessHandler인터페이스(샘플에서는 없음)는 렌더프로세스에서만 호출된다.
GetBrowserProcessHandler는 반드시 this를 리턴해야 한다. SimpleApp은 CefApp과 CefBrowserProcessHandler를 구현해야 하기 때문이다. GeneralUsage 위키페이지나 API 헤더파일을 보고 CefApp과 관련된 인터페이스들에 대해 좀더 많은 정보를 살펴보길 바란다.

* 공유된 구현 : cefsimple/simple_app.h, cefsimple/simple_app.cc

SimpleHandler

브라우저레벨의 콜백을 처리해야 한다. 브라우저 프로세스로부터 실행된다. 샘플에서우리는 CefClient인스턴스를 모든 브라우저에서 사용했다. 하지만 당신의 애플리케이션은 각각 전용의 인스턴스를 사용해도 된다. GeneralUsage 위키페이지나 API 헤더파일을 보고 CefApp과 관련된 인터페이스들에 대해 좀더 많은 정보를 살펴보길 바란다.

* 공유된 구현 : cefsimple/simple_handler.h, cefsimple/simple_handler.cc
* 윈도우 : cefsimple/simple_handler_win.cc
* 리눅스 : cefsimple/simple_handler_linux.cc
* OS-X : cefsimple/simple_handler_mac.mm

빌드 순서

빌드 순서는 플랫폼에 의존한다. binary 배포와 함께 포함된 CMake파일을 찾아서 살펴보면 모든과정에 대한 이해가 가능할것이다. 모든 플랫폼에 공통된 빌드 순서는 일반적으로 다음과 같이 요약될 수 있다.

1. libcef_dll_wrapper static library를 컴파일
2. application 소스 컴파일, libcef dynamic library와 libceff_dll_wrapper static library 링크
3. library와 리소스들을 출력디렉토리에 복사


윈도우 빌드순서

1. libcef_dll_wrapper static library를 컴파일
2. cefsimple.ext 컴파일 및 링크
- 필요한 소스코드 : cefsimple_win.cc, simple_app.cc, simple_handler.cc, simple_handler_win.cc.
- 필요한 라이브러리 : comctl32.lib, shlwapi.lib, rcprt4.lib, libcef_dll_wrapper.lib, libcef.lib, cef_sandbox.lib
cef_sandbox.lib(sandbox지원시)은 VS 2015 update3으로 빌드된 정적라이브러리이다. 다른 버전의 VS는 컴파일이 안될수도 있다. sandbox를 지원하지 않으면 문제가 없으며 cefsimple_win.cc의 코멘트를 확인하여 비활성화 하면 된다.
- 리소스 파일 : cefsimple.rc
- 매니페스트 파일 : cefsimple.exe.manifest, compatibility.manifest
3. Resource 폴더의 모든 파일 대상폴더로 복사
4. Debug/Release 폴더의 모든 파일 대상폴더로 복사

결과 폴더의 구조는 2526 브랜치정도가 보일거다.

Application/
    cefsimple.exe  <= cefsimple application executable
    libcef.dll <= main CEF library
    icudtl.dat <= unicode support data
    libEGL.dll, libGLESv2.dll, ... <= accelerated compositing support libraries
    cef.pak, devtools_resources.pak, ... <= non-localized resources and strings
    natives_blob.bin, snapshot_blob.bin <= V8 initial snapshot
    locales/
        en-US.pak, ... <= locale-specific resources and strings


Posted by 삼스