Android2021. 5. 27. 14:24


고전적인 비동기 처리라 하면 Thread와 mutex, semaphore를 활용한 방법이 있겠다.
이어 개발자들은 비동기 처리를 좀더 쉽게 하기 위해 나온게 Rx시리즈가 있는데 이는 데이터 흐름과 함수형프로그래밍의 관점에서 다루어서 더 인기가 있게 되었다.
이어서 일부 언어에서는 코루틴을 지원하기 시작했고 안드로이드의 새로운 개발언어인 kotlin도 지원하기에 이르렀다.
c#, javascript, go등의 언어에서 await, async를 사용해보았다면 이미 알고 있는 것이다.

스레드로 비동기적이면서 순차적인 작업을 하려고 하면 콜백을 사용하게 마련이고 이런 경우 단계가 늘어날수록 콜백의 늪에 빠지게 된다.

이를 좀더 이해하기 쉽게 코딩할수 있도록 지원한게 Rx시리즈이다.
순차적으로 파이프라인을 이어서 다음 작업을 진행할 수 있다.

그런데 Rx는 학습곡선이 좀 있다. 그리고 원하는 기능을 구현하기 위해서는 실제 작업을 수행하는 루틴과 별도로 부수적이 액세서리 코드들이 더 들어가게 된다.

코루틴은 이를 더 쉽고 간결하게 표한하게 해주며 코드의 가독성도 높여준다.

코루틴이 어떻게 이를 지원하는지에 대해서 먼저 간단하게 설명하겠다.

일반적인 루틴은 호출되면 프로그램의 컨텍스트가 해당 서브루틴으로 넘어가며 서브루틴에서 return될때 호출한 메인 루틴으로 컨텍스트가 복구된다. 이는 예상하는것보다 많은 비용이 수반된다.
코루틴은 메인루틴에서 코루틴이 호출될때 바로 빠져나오게 된다. 그리고 해당 코루틴이 끝나면 코루틴이 호출되었던 부분으로 다시 돌아올수 있다.

일반적인 루틴과 어떤 차이가 있는지에 대해 쉽게 설명하자면 서브루틴이 아주 오래걸리는 작업을 수행하는데 이 루틴이 메인UI스레드에서 동작중이라면 해당 작업이 완료되기전까지 화면이 멈추게 된다.
하지만 코루틴의 이 경우에도 화면이 멈추지 않고 다른 스레드는 돌아가게 된다. 

코루틴이 호출될때 별도의 스레드로 분리되어 호출되고 코드는 더 진행되지 않고 대기하는데 다른 스레드에 영향을 주지 않으면서 대기하다가 코루틴이 완료되면 다시 대기하였던 부분부터 수행된다고 생각하면 된다.

c#등의 언어로 아래와 같은 코드에 해당한다.

  await doSomething();

개발자들은 코드로 설명하는것이 항상 옳다.
thread, RxJava, coroutine으로 동일한 작업을 할 때의 차이를 코드로 살펴보자.

라면을 끓여 먹는다고 하자

라면을 사서 -> 냄비를 준비하고 -> 물을 부어 끊이고 -> 면과 스프를 넣어 끓인 후 -> 그릇에 담아서 -> 먹는다.

위 과정은 모두 순차적으로 진행해야함 최종적으로 내가 먹을 수 있다.
라면을 사지 않고 라면을 먹을수는 없다.

이를 비동기로 구현하려면 thread 호출로 해야 할것이고 이 경우 콜백형태를 사용할것이고

buyRamyeon(money) { ramyeon -> 
  getPot(ramyeon) { ramyeon, pot ->
    doBoilPot(ramyeon, pot) { cookedRamyeon ->
       moveToDish(cookedRamyeon) { ramyeonInDish ->
          eatIt(ramyeonInDish)
       }
    }
  }
}



이렇게 작업해도 문제는 없다. 다만 많은 비용을 수반하고 우리가 그 동안 많이 보아온 콜백지옥의 향연이 펼쳐진다.

이를 RxJava로 구현한다고 하면 아래와 비슷할것이다.

Observable.just(money)
.observeOn(MAIN_Thread)
.subscribeOn(IO_Thread)
.flatMap { money -> buyRamyeon(money) }
.flatMap { ramyeon -> getPot(ramyeon) }
.flatMap { ramyeon, pot -> doBoilPot(ramyeon, pot) }
.flatMap { ramyeonInDish -> moveToDish(cookedRamyeon) }
.subscribe({ 
ramyeonInDish -> eatIt(ramyeonInDish)
}, {
// 실패케이스 처리
})



콜백헬에서는 확실히 벗어난듯 하다.
하지만 just, observeOn, subscribeOn등에 대해 알아야 하며 Rx는 상당히 급한 학습곡선에 해당한다.

이를 coroutine으로 구현한다면 아래와 비슷할것이다.

suspend iWantEatRamyeon(money) {
  try {
    val ramyeon = buyRamyeon(money)
    val pot = getPot()
    val cookedRamyeon = doBoilPot(ramyeon, pot)
    val ramyeonInDish = moveToDish(cookedRamyeon)
    eatIt(ramyeonInDish)
  } catch (e: Exception) {
    // 실패케이스 처리
  }
}


Coroutine 문법을 자세히 아라보려면 https://selfish-developer.com/entry/Kotlin-Coroutine 여그를 보자. 잘 정리되어 있넹 

Posted by 삼스
Android/App개발2021. 5. 25. 13:48

하단에서 올라오는 팝업 다이얼로그

 

위와 같이 하단에서 솟구치는 팝업 다이얼로그는 Dialog를 상속하여 Animation을 적절히 활용하면 구현이 가능하다.

https://github.com/samse/SlideUpDialog

 

 

samse/SlideUpDialog

Contribute to samse/SlideUpDialog development by creating an account on GitHub.

github.com

 

 

Posted by 삼스
Android/App개발2021. 3. 26. 17:13

MultiWindow를 지원할지에 대해 프로그램 설계시 미리 고려할 사항이 있겠다.
지원하고 한다면 screenOrientaion로 가로/세로로 고정하지 말고 모든 사이즈의 화면에 대응되도록 레이아웃을 고려하여 작성해야한다.
액티비티의 최소사이즈를 지정하면 그 지정한 사이즈 이하로는 조정이 되지 않는다.

screenOrientation를 지정한 상태에서 실행중이 앱을 멀티윈도우로 진입시키면 액티비티가 재시작된다.
이를 막기 위해서는 다음 속성을 추가하면 되는데(https://medium.com/androiddevelopers/5-tips-for-preparing-for-multi-window-in-android-n-7bed803dda64)

android:configChanges="screenSize|smallestScreenSize
|screenLayout|orientation"

이 경우에도 일부 단말에서는 재시작되는것을 확인하였다.

만일 앱을 세로나 가로로 고정해야 한다면 난감한 상황이 될것인데 이 런 경우는 그냥 멀티윈도우 기능을 끄는것이 나을것이다.

android:resizeableActivity="false"

애초에 멀티윈도우를 지원하고자 한다면 고정하지 않고 화면을 설계하는것이 나을것 같다.

자세한 내용은 아래 링크 참조

https://developer.android.com/guide/topics/ui/multi-window

Posted by 삼스
Windows2021. 2. 2. 09:04

서비스 프로젝트를 작성 후 배포하기 위해 서비스를 설치하고 설치 후 바로 서비스가 구동되도록 하는 방법에 대해 설명한다.

 

서비스프로젝트에 설치관련 클래스 추가

  1. 작성한 서비스.cs파일 더블클릭
  2. 도구상자에 ProjectInstaller 선택
  3. ServiceProcessInstaller, ServiceInstaller 생성됨

 

설치 프로젝트 추가

  1. 파일 -> 프로젝트 추가 > Setup Project
  2. 프로젝트 아이콘에 마우스우측 버튼 클릭
  3. Add -> 프로젝트 출력
  4. 추가할 프로젝트를 콤보박스에서 선택하고 기본출력을 선택
  5. View -> 사용자지정작업에서 Install, Commit, Rollback, Uninstall에 각각 마우스우측버튼 클릭 후 '사용자지정작업'으로 작성한 서비스프로젝트 추가
  6. 빌드 수행하면 msi와 setup.exe가 생성됨

 

설치 서비스 실행되도록

  1. ProjectInstaller.Designer.cs코드보기
  2. initializeComponent()에서 서비스설치 관련 파라메터 수정
  • ServiceName, DisplayName, Description
  • StartType : Automatic으로 설정
  • Committed 핸들러 오버라이드 등록

            this.serviceProcessInstaller1.Password = null;

            this.serviceProcessInstaller1.Username = null;

            this.serviceProcessInstaller1.Account = ServiceAccount.LocalSystem;

            this.serviceInstaller1.ServiceName = "My Service";

            this.serviceInstaller1.DisplayName = "My service";

            this.serviceInstaller1.Description = "This is My service";

            this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;

            this.Committed += new System.Configuration.Install.InstallEventHandler(MyServiceWindowsServiceInstaller_Committed);

 

  3. ProjectInstaller.cs 코드보기

  4. Committed 핸들러 오버라이드 수행

  • 서비스 컨트롤러 생성하여 서비스를 시작함.

        void MyServiceWindowsServiceInstaller_Committed(object sender, InstallEventArgs e)

        {

            // Auto Start the Service Once Installation is Finished.

            var controller = new ServiceController("MyService");

            controller.Start();

        }

 

 

Posted by 삼스
Interesting2021. 1. 25. 18:00

우리가 https로 서비스를 제공하는 사이트에 접속할때의 데이터 흐름을 정리해 보겠다.

 

https로 서비스를 제공하기 위해서는 먼저 인증서를 발급받아야 한다. 

이는 인증기관에 등록을 요청하여 발급이 가능하며 매년 재발급시마다 비용을 지불해야 한다.

발급받은 인증서는 웹서버에 설치되어 브라우저에서 접속시마다 일련의 인증과정을 거칠때 사용된다.

 

이 과정 전체를 나타낸 시쿼스다이어그램이다.

 

1. 인증기관(CA)에 인증서 발급요청을 한다.

2. 인증기관은 검토를 거친 후 인증서에 CA공개키와 사이트정보를 포함한 후 CA개인키로 암호화한다.

3. 암호화된 인증서가 발급이된다.

4. C공개키는 브라우저에 제공된다.

 

여기까지가 인증서 발급의 단계이다. 이어서 브라우저에서 접속시의 시나리오는 다음과 같다.

 

5. 브라우저에서 접속을 시도한다.

6. 웹서버는 사이트 인증서를 제공한다.

7. 브라우저는 사이트인증서를 CA공개키로 복호화한다. 복호화하면 사이트공개키를 얻을 수 있다.

8. 브라우저는 대칭키를 생성한다.

9. 브라우저는 사이트공개키로 대칭키를 암호화한다.

10. 웹서버는 암호화된 대칭키를 사이트개인키로 복호화한다.

11. 웹서버는 복호화된 대칭키로 요청받은 웹사이트 리소스들을 암호화하여 응답한다.

12. 브라우저는 암호화된 리소스들을 대칭키로 복호화하여 렌더링한다.

 

6과 7과정에서 웹서버에서 제공한 인증서를 브라우저에서 CA공개키로 복호화 시 복호화가 되고 복호화된 정보가 현재 접속한 사이트 정보와 동일하면 이는 허가된 접근으로 판정이 나게되며 

이 후 통신구간에는 대칭키를 서로 교환하여 통신함으로써 통신시 오버로드를 줄이는 방식으로 프로세스가 진행된다.

 

 

 

 

 

 

 

Posted by 삼스
카테고리 없음2020. 12. 17. 16:56

웹소켓프로토콜로 웹소켓서버와 웹소켓API로 통신할 수 있다.

 

webSocket = new WebSocket(url, protocols);

url : wss:// 스키마사용을 권장하며 보안을 고려하지 않는다면 ws://스키마를 사용해도 된다.
protocols(옵션) : 하나이상의 프로토콜을 지정할 수 있다. 웹소켓의 서브프로토콜을 지정하며 하나이상의 프로토콜을 지정하여 여러가지 유형의 작업을 할 수 있다.

대상이 접근을 허용하지 않으면 보안에러(SecurityError)가 발생한다. 

연결시 에러가 발생하면 onerror가 먼저 발생하고 이어서 onclose가 발생한다.

 

var exampleSocket = new WebSocket("wss://www.example.com/socketserver", "protocol1");
var exampleSocket2 = new WebSocket("wss://www.example.com/socketserver", ["protocol1", "protocol2"]);

exampleSocket.readyState는 CONNECTING상태가 되고 연결이 준비가 되면 OPEN상태로 된다.
연결이 되면 exampleSocket.protocol로 서버에서 선택된 프로토콜을 확인할 수 있다.

전송은 다음과 같이 한다.

exampleSocket.send("서버가 기다리고 있는 메세지")

전송은 문자열과 Blob 또는 ArrayBuffer가 가능하다.

연결은 비동기이고 성공할지 실패할지 알수 없기 때문에 객체 생성후 바로 전송하면 전송을 보장할 수 없다.
따라서 onopen이벤트 핸들러에서 작업해야 한다.

exampleSocket.onopen = function (event) {
    exampleSocket.send("서버가 기다리고 있는 메세지");
}

JSONJSON객체를 전송하고자 한다면 문자열로 치환하여 전송하여야 한다.

 

var msg = {
    type: "message",
    text: "test message"
};

exampleSocket.send(JSON.stringify(msg));

서버로부터의 메세지는 onmessage핸들러에서 가능하다.

exampleSocket.onmessage = function (event) {
  console.log(event.data);
}

웹소켓으로 수신되는 문자셋은 UTF-8포맷이다.

 

웹소켓연결을 종료하려면 close를 호출한다.

exampleSocket.close();

종료하기 전에 bufferedAmount속성을 확인해서 아직 전송하지 않은 데이터가 있는지 확인할 수 있다. 0인경우 아직 남은것으로 판단하면 된다.

 

비보안연결과 보안연결을 혼합하여 사용할 수 없다. 브라우저들이 이런 경우를 허용하지 않는다.

 

 

Posted by 삼스
Android2020. 9. 8. 11:09

Android 10(Q, 29 level)으로 TargetApi로 설정한 앱이 Android10이상의 단말에 설치된 경우 외부저장소에 대해 Scoped storage모드로 동작한다.

 

Scoped Stroage

 

Android10 타겟에 Android10 단말에서 동작한다.


외부저장소의 공용파일공간이 모두 사라지고 개별앱공간이 샌드박스로 격리되어 제공되며 다른앱이 접근 불가하다

 

MediaStore는 내가 추가한 파일을 읽거나 쓰는데 권한없이 사용 가능하나. 다른앱의 파일을 읽기 위해서는 권한이 필요하다.

 

파일경로(file:///) 만으로 읽고 쓰기가 불가(FileNotFound혹은 권한이 없다는 에러)하다. FileProvider 혹은 시스템 파일 선택기를 통해서 사용자가 직접 파일을 선택한 이후 에야 접근이 가능하다.


Environment.getExternalStorageDirectory()는 deprecated되었다.

 

타앱에 파일을 전달할때 파일경로로 전달불가하며 FileProvider로 ContentUri를 만들고 공유 받을 앱에 임시로 URI접근권한을 허용하는 방법을 사용한다(이전 포스팅 참조)

 

공용저장공간은 /sdcard자체가 공용공간이었으나 이제는 MediaStore와 StorageAccessFramework를 이용하여 공용저장공간에 읽고 쓸수 있다. 이제 이 영역만이 외부저장소라고 할수 있다.

 

사진, 동영상, 음악파일은 MediaStore를 통해 접근이 이미 가능했고 추가로 Download에 저장된 파일에 접근하기 위한 콜렉션을 제공한다. Download도 내가 생성한 파일을 제외하고는 사용자가 시스템파일선택기를 통해 사용자가 명시적으로 선택한 경우에만 접근이 가능하다.

 

파일접근 정리

파일위치 권한 접근 앱삭제시 제거
개별앱 공간 필요없음 getExternalFileDir() Y
미디어콜랙션(사진,비디오,오디오) 다른앱파일접근시만 읽기권한필요 MediaStore or SAF N
다운로드 필요없음 SAF N

느끼고 있겠지만 많은 앱개발자들은 이전 방식을 더 선호한다. 보안에 민감한 클라이언트들은 안그러겠지만...

어쨌든 이전방식을 아직은 사용가능하다.

 

AndroidManifest.xml의 application tag에 requestLegacyExternalStorage플래그를 true로 하면 임시로 이전 저장소정책을 사용할수 있다. 하지만 Target을 Android11로 하면 이 플래그도 지원하지 않을 것이라고 한다.

 

<!-- 임시로 Q(10)에서 opt-out함 -->
<uses-sdk android:targetSdkVersion="29" />
<application android:requestLegacyExternalStorage="true" ... >

문제는 참조하는 다른 라이브러리들이 대응이 안된 경우이다. 별수 없이 해당 라이브러리에서 대응이 되어야 한다.

코드내에서 정책을 확인하기 위해서 isExternalStorageLegacy()를 제공한다.

// storage mode 체크
if (Envirioment.isExternalStorageLegacy()) {
...
}

경우에 따라서는 isExternalStorageLegacy()로 분기하여 파일접근하는 로직을 구분하여 적용해야 할수도 있을것 같다.

Posted by 삼스
Android2020. 9. 8. 10:59

ContentProvider의 특별한 서브클래스로 file:/// Uri가 아니라 content:// Uri로 파일에 접근하고 공유할 수 있게 해준다.

 

file:/// Uri는 보안에 취약하지만 content:// Uri는 안드로이드의 보안인프라의 기반으로 보안문제를 해결해준다.

 

FileProvider를 저으이하고 유효한 파일을 지정하고 Content URI로 파일탐색하고 임시적으로 권한을 획득하고 타앱에 Content URI를 제공하는 것에 대해 정리하겠다.

 

FileProvider의 정의

 

코드를 작성할 필요가 없고 XML에 FileProvider를 기술한다.
android:authorities속성이 중요하며 당신만의 도메인으로 정의한다. android:export는 false로 하여 public하지 않게 설정한다. android:grantUriPermissions속성은 true로 파일에 대해 임시권한을 부여할수 있게 허용한다.

 

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.mydomain.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
        ...
    </application>
</manifest>

유효한 파일 기술하기

 

FileProvider는 사전에 기술한 폴더의 파일들에 대해서만 content uri를 생성한다. 이는 별도의 xml파일에 기술한다. 아래코드는 private 파일영역의 images/폴더를 정의한 예이다.

 

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>

<paths>엘리먼트는 하나이상의 엘리먼트를 포함해야 한다.

다음은 다양한 폴더경로에 대한 설정들이다.

 

<!-- cache는 앱의 내부저장소의 캐시폴더를 의미하며 getCacheDir()의 결과와 동일하다.-->
<cache-path name="name" path="path" />  
<!--external은 외부저장소의 루트폴더를 의미하며 Context.getExternalFilesDir(String)의 결과와 동일하다. -->
<external-path name="name" path="path" /> 
<!--외주저장소의 캐시폴더를 의미한다. Context.getExternalCacheDir() -->
<external-cache-path name="name" path="path" />
<!--외부저장소의 media폴더를 의미한다. Context.getExternalMediaDirs(). 이 폴더는 API 21+에서만 유효하다.-->
<external-media-path name="name" path="path" />

엘리먼트의 name속성은 URI path 세그먼트로 실제 경로를 숨겨주는 역할을 한다. 실제 경로는 path속성에 기술된다. path는 실제 서브폴더명이다.

 

<paths>엘리먼트에 여러개의 files-path를 기술하여 여러 폴더의 내용을 공유할 수 있다.

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    <files-path name="my_docs" path="docs/"/>
</paths>

paths를 정의한 파일은 별도의 파일로 res/xml/폴더에 file_paths.xml로 저장하고 provider 엘리먼트의 mata-data에 기술해준다.

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.mydomain.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

파일의 Content URI생성하기

 

타앱에 파일을 공유하려면 해당 파일에 대해 content URI를 생성해야 한다. File객체를 생성 후 getUriForFile()의 인자로 호출한다. 이렇게 만들어진 URI를 인텐트에 담아서 전달하고 전달받은 앱은 ContentResolver.openFileDescriptor로 ParcelFIleDescriptor를 얻어서 파일에 접근한다.

File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

//위 코드의 결과로 생성된 URI는 다음과 같다.
content://com.mydomain.fileprovider/my_images/default_image.jpg

 

타앱에 ContentURI 제공

타앱에 특정파일의 content URI를 제공하는 여러가지 방법이 있는데 일반적으로 startActivityForResult()으로 당신의 앱에 인텐트를 전달하는 방법을 사용한다. 당신의 앱에서는 바로 응답하거나 사용자의 선택할 수 있는 UI를 제공하여 응답하는 방법이 있다. 두 방법다 선택한 파일의 content URI를 setResult()로 인텐트에 담아서 반환한다.
또한 ClipData 객체에 content URI를 담아서 반환할수 있으며 Intent.setClipData()를 호출하여 담으면 된다. 이 때는 여러개의 각각의 content URI를 갖는 ClipData객체를 담을수 있다.

Posted by 삼스
카테고리 없음2020. 7. 29. 15:46

이름없이 실행이 가능한 함수를 람다식이라고 한다.
언어별로 람다식이 어떻게 다른지 정리해 보려고 한다.

 

자바

형식은 다음과 같다.

 

(매개변수, ...) -> { 실행코드 ... }

// 일단 인터페이스 정의가 필요하다.
interface Adder {
    public int add(int a, int b);
}

// 람다를 인자로 사용하는 함수 정의
public void add(int a, int b, Adder adder) {

    int value = adder.add(a, b);

}

// 람다를 인자로 사용하는 함수 호출
add(10, 20, (a, b) -> {

    return a + b;

}

// 람다식을 함수포인터처럼 정의
Adder adder = (a, b) -> (a+b);

// 정의한 람다함수식 호출
adder(10, 20);

 

코틀린

// 익명함수

val adder = fun(a, b) { return a + b; }

println(adder(10, 20));



val adder: (Int, Int) -> Int = { a, b ->  a + b }
println(adder(10, 20));



val hello:  (String) -> String { "Hello $it" }
val res = hello("Samse")



// 하나의 메서드만 제공하는 인터페이스의 경우 다음예와 같이 코드를 간결하게 작성할수 있다. 

fun setOnClickListener(listener: (View)->Unit)
button.setOnClickListener({ view -> doSomething() })

button.setOnClickListener() { doSimething() }

button.setOnClickListener { doSomething() }

 

ObjC

블록이 람다에 해당한다.
^코드로 블럭을 구분한다.

블럭코드 정의하여 사용하기

int (^adder)(int, int) = ^(int a, int b) { return a + b; };
adder(10, 20);

 

블럭코드 타입을 정의하여 인자로 사용하기

typedef int (^adder) (int a, int b) 

- (void)add:(int)a value:(int)b adder:(adder)adder {
    int value = adder(a, b);
    NSLog(@"value : %d", value);
}

[self add:10 value:20 adder:^(a, b) -> {
    return a + b;
}];



Swift


클로저가 블록에 대응되며 좀더 고급스럽다.
일급객체로서 인자로 전달할수 있고 변수/상수가 될수 있으며 함수의 반환값으로도 사용가능하다. 즉 함수를 반환하여 함수형프로그래밍이 가능하다.

{ (매개 변수) -> 반환타입 in 
  구현 코드 ...
}

매개변수 없을 때 in 삭제 가능

let action = UIAlertAction(title: String?, style: UIAlertActionStyle, handler ((UIAlertAction) -> Void)?)

let action = UIAlertAction(title: "OK", style: .default) {
(action) in
    ...
}

 

매개변수로 넘기는 경우의 예

func sorted(by areInIncreasingOrder: (E, E) -> Bool) -> [E]

func reverse(a: Int, b: Int) -> Bool {
    return a > b
}

let values: [Int] = [ 1, 2, 3, 4, 5 ];
let reversed: [Int] = values.sorted(by: reverse)

let reversed: [Int] = values.sorted(by: { (a: Int, b: Int)-> Bool in
    return a> b
})

클로저가 매개변수가 한개이거나 맨뒤에 있는 경우 좀더 코드를 생략이 가능하다.

 

let reversed3: [Int] = values.sorted() { (a: Int, b: Int)-> Bool in return a > b }

let reversed4: [Int] = values.sorted { (a: Int, b: Int)-> Bool in return a > b } // 소괄호 삭제

let reversed5: [Int] = values.sorted { (a, b) in return a > b } // 타입삭제

let reversed6: [Int] = values.sorted { return $0 > $1 } // 인덱스 사용

 

Posted by 삼스
Windows2020. 7. 28. 13:56

CEF

 

Chromium Embedded Framework (CEF).

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

 

 

BSD-license이고 구글 크로미엄프로젝트 기반으로 2008년에 마샬그린브랫에의해 시작된 오픈소스 프로젝트이다.

크로미엄프로젝트와 다르게 구글 크롭앱개발에 주로 집중하였고 써드파티앰의 내장된 브라우저 사용 케이스에 집중하였다.

CEF는 크로미텀의 하부와 코드의 복잡함으로부터 고수준의 안정화된 API, 특정목적의 배포, 그리고 바이너리 배포를 통해 벗어날수 있게 해준다.

CEF기본 구현은 사용자로부터 조금의 또는 전혀 통합하는 작업을 필요로 하지 않는다.

현재 1억개 이상의 인스턴스가 사용되고 있다. 그 일부가 위키페이지에 정리되어 있다.

 

사용케이스 몇가지는 다음과 같다.

* 기존 네이티브 애플리케이션에 HTML5호환 웹브라우저를 내장

* 가벼운 네이트브 쉘애플리케이션으로 기본적으로 웹기술에 기반하여 개발된 UI를 호스팅

* 자체적인 드로잉프레이뭐크를 사용하는 오프스크린 웹컨텐츠를 렌더링

* 웹속성이나 애플리케이션의 자동화된 테스트를 수행

 

CEF는 수많은 언어와 운영체제를 지원하며 새로운 또는 기존애플리케이션에 쉽게 통합이 가능하다. 성능과 쉬운 사용을 목표로 설계되었다.

기반프레임워크는 C, C++로 네이티브라이브러리로 제공되고 애플리케이션과 크로미엄과 상세한 구현으로부터 분리해준다.

이는 브라우저와 커스텀플러그인, 프로토콜, 자바스크립트 객체 그리고 자바스크립트 확장 등을 포함하는 앱간에 밀접한 통합을 제공한다.

애플리케이션은 리로스로딩을 제어하거나, 네비게이션, 메뉴, 프린트 등을 하면서 구글 크롬웹브라우저와 동일한 성능과 HTML5기술을 사용할 수 있다.

 

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

 

시작하기

 

시작은 튜터리얼 위키페이지에서 CEF 사용에 대한 개요를 읽고 아키텍쳐적인 내용이나 사용이슈에 대한 더 깊은 논의를 하기 위하여 GeneralUsage 위키를 읽어라.

API문서를 살펴보고 지원과 관련된 논의에 대해서는 포럼을 참고하라

 

 

 

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 파일을 참조하십시오. 각 플랫폼에서 필요한 애플리케이션 레이아웃에 대한 자세한 설명은 아래를 참조하십시오.

 

60초 아키텍쳐

 

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

 

* 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 삼스