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

댓글을 달아 주세요

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

댓글을 달아 주세요

iOS2020. 1. 23. 11:19

생체인증(지문, 얼굴)등으로 인증을 구축하고 할 때 활용하여 사용이 가능하다.

 

지문이나 얼굴일식을 통한 인증관련 모듈이 하드웨어로 되어 있으며 LocalAuthentication.framework를 통해서는 True/False만을 리턴한다.

 

canEvaluatePolicy로 현재 단말이 사용가능한 정책을 확인하고 evaluateEvaluate로 인증수행한다.

 

해당 프레임웍으로 간편인증등에 다양하게 적용이 가능할것이다.

Posted by 삼스

댓글을 달아 주세요

  1. 이전 댓글 더보기
  2. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  3. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  4. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  5. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  6. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  7. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  8. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  9. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  10. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  11. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  12. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  13. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  14. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  15. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  16. e

    2020.06.17 18:25 [ ADDR : EDIT/ DEL : REPLY ]
  17. e

    2020.06.17 18:26 [ ADDR : EDIT/ DEL : REPLY ]
  18. e

    2020.06.17 18:26 [ ADDR : EDIT/ DEL : REPLY ]
  19. e

    2020.06.17 18:26 [ ADDR : EDIT/ DEL : REPLY ]
  20. e

    2020.06.17 18:26 [ ADDR : EDIT/ DEL : REPLY ]
  21. e

    2020.06.17 18:26 [ ADDR : EDIT/ DEL : REPLY ]

iOS2019. 5. 21. 12:36

Cookie?
쿠키는 브라우저세계에서 주로 세션관리, 사용자 프리퍼런스와 상태등을 주로 다루며 일반적이고 널리 사용된다.
서버에서 제공하는 작은 속성데이터이다. 웹브라우저에서 저장되며 서버에 요청시 공유된다.

쿠키구조
적은양의 데이터이지만 구조적이고 속성들은 쿠키의 동작을 정의한다. RFC6265, RFC2109

쿠키와 모바일앱
쿠키는 웹세계에서 사용되며 대부분 모바일개발자는 다룰필요가 없다. REST API 로 서버에 데이터를 요청한다. 대부분의 유스케이스와 앱에 맞다. 하지만 간혹 경계가 모호해지는 경우가 있다. 쿠키를 조작해야 하고 모바일앱은 이를 잘 수행한다.
SSO를 예를 들면 여러 시스템에서 세션을 공유해야 할것이다. 이때 쿠키가 이 목표를 달성할수 있게 해준다.

iOS에서 쿠키 다루기
NSHTTPCookie, NSHTTPCookieStorage라는 아주 잘 설계된 클래스 2개가 제공된다.

NSHTTPCookie클래스는 쿠키 데이터 클래스이며 필수 파라메터가 있다. name, value, domain or originalurl, path이다, 모두 제공되지 않으면 nil리턴된다.

NSMutableDictionary* cookieProperties = [NSMutableDictionary dictionary];

//set rest of the properties
[cookieProperties setObject:@"MyCookie" forKey:NSHTTPCookieName];
[cookieProperties setObject:@"cookie12345ytehdsfksdf" forKey:NSHTTPCookieValue];
[cookieProperties setObject:@"/" forKey:NSHTTPCookiePath];
[cookieProperties setObject:@".example.com" forKey:NSHTTPCookieDomain];
//create a NSDate for some future time
NSDate* expiryDate = [[NSDate date] dateByAddingTimeInterval:2629743];
[cookieProperties setObject:expiryDate forKey:NSHTTPCookieExpires];
[cookieProperties setObject:@"TRUE" forKey:NSHTTPCookieSecure];

NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];

HttpOnly같은 속성은 쿠키에서 중요하며 딕셔너리로 셋팅할수 없다. 위와 같은 방식의 쿠키생성은 유용하지 않은것 같다. 

권장되는 방식은 다음과 같다.

NSHTTPCookie에 더 훌룡한 API가 제공되는데 http response 문자열을 모두 덤프할 수 있고 HttpOnly를 포함해서 모든 필요한 속성이 셋팅된 완전한 쿠키를 리턴한다.

NSArray* httpCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[httpresponse allHeaderFields] forURL:url];

서버응답인 http response에는 키/밸류 쌍인 "Set-Cookie"로 일반적으로 넘어온다. 위 API는 쿠키문자열을 response로부터 취해서 cookie를 만든다. 여기서 URL이 중요하면서도 유용한다. 앞서 얘기한데로 쿠키는 필수파라메터로 name, domain, value, path가 있다고 했다. 하지만 RFC문서에는 Domain은 옵션이다 이는 빈도매인이 있을 수 있다는 것이다. NSHttpCookie클래스에는 originURL을 셋팅할수 없다. 딕셔너리로 쿠키를 생성하는 방법은 완전히 쓸모가 없다. 하지만 이 방법은 쿠키문자열에 domain이 없으면 originURL을 채우고 유효한 쿠키를 제공한다. 이에 더해 서버에서 전달한 추가적인 정보를 알아서 셋팅해준다. 이 방법이 경험적으로 쿠키를 만드는 가장 좋은 방법이다.

쿠키 저장
NSHTTPCookieStorage가 쿠키를 저장해준다.

[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];

여기서 가장 중요한 특성은 iOS가 네트웍 요청에 도메인이 매칭된다면 자동으로 추가해준다는 것이다. 예를 들어 domain이 ".example.com"인 쿠키가 저장되어 있고 "www.test.example.com"으로 요청을 하게된다면 추가코드없이 자동으로 추가된다.

NSURLSession요청은 URL이 domain이 매칭되면 요청에 쿠키를 셋한다.  webview의 경우 아래와 같이 초기로드를 위해 저장소에서 액세스하여 쿠키를 추가 할 수 있다

NSURLRequest* request; //your request
[request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies]];
[request HTTPShouldHandleCookies];

Posted by 삼스

댓글을 달아 주세요

iOS2019. 5. 10. 16:47

이게 개념이 잡히지 않으면 개발환경 설정 시 마다 고생하기 마련이다.

이참에 정리를 좀 하자.

 

일단 기본적으로 애플은 앱실행권한을 관리를 하고 있다. 이 권한이 획득되지 못하면 앱이 실행이 될수 없다.

그러기 위해서는 애플로부터 인증서를 발급받아야 한다. 그리고 단말에서 실행을 하려면 프로비저닝이 단말에 설치가 되어야 한다.

프로비저닝은 단말과 애플인증서의 연결해주는 역할을 하며 권한을 획득하여 얻어지면 앱이 실행이 된다.

 

이 과정은

1. 애플인증서 생성

2. 프로비저닝 생성

3. 빌드 및 실행

이 되는데 하나하나씩 정리해 보자

 

1. 애플인증서 생성

개발자 PC에 키체인앱은 개인키와 공개키를 가지고 있다 이를 기반으로 먼저 CSR(Certificate Signing Request)을 먼저 만들어야 한다.

(1) 키체인 앱 실행

(2) 키체인접근 > 인증서 지원 > 인증기관에서 인증서 요청 선택

(3) 개발자의 이메일주소와 이름을 입력

(4) 요청항목의 "디스크에 저장됨" 선택

(5) 계속을 누르면 CertificateSigningRequest.certSigningRequest 파일이 저장된다.

 

저장된 CSR파일을 애플개발자 콘솔에 인증서 추가 화면에서 등록한다.

(1) https://developer.apple.com/ 접속 및 로그인

(2) Certificates, Identifiers and Profile > Certificates > Development 이동

(3) 추가버튼 누르고 iOS App Development선택하고 화면 안내에 따라 CSR파일 등록 

(4) 등록 완료되면 인증서 파일 다운로드

 

동일한 방법으로 Production의 AppStore and Ad Hoc선택하여 배포용도 등록

 

2. 프로비저닝 생성

프로비저닝은 앱아이디별로 생성이 가능하며 앱아이디를 먼저 등록해주어야 한다.

등록된 앱아이디로 Provision Profiles 화면에서 안내에 따라 개발과 배포용을 등록한 후 프로비저닝 파일을 다운로드 한다.

이 때 개발용의 경우 설치를 허용할 단말을 선택할 수 있는데 이 화면에서 선택한 단말들만 해당 프로비저닝으로 설치및 실행이 가능하다.

 

위 과정을 거치면 

1. 개발자의 공개키와 개인키

2. 애플이 인증한 인증서

3. 디바이스에 설치 가능한 프로비저닝

 

위 3가지로 빌드를 하면 .app파일이 생성되며 2가지로 구성된다.

1. 실제로 사용될 프로비저닝 프로파일 : 빌드시 사용된 프로비저닝이다.

2. _CodeSignature 폴더 : CodeResources 파일을 담고 있고 모든 파일의 암호화 해시정보를 갖고 있다.

 

AdHoc버전의 앱은 디바이스에 설치되어 실행될때 

 

1. .app에 포함된 프로비저닝 프로파일이 애플인증서로 서명되었는지 확인

2. CodeResources참조하여 모든 파일의 해시를 확인하여 무결성 체크

3. 디바이스에 프로비저닝 프로파일이 있는지 확인

 

위 과정을 거쳐서 실행된다.

 

엔터프라이즈배포의 경우는 애플은 그 회사를 신뢰하고 별도의 확인과정을 거치지 않고 바로 실행되게 해준다.

 

앱스토어배포의 경우는 그 어떤 디바이스에서도 실행될수 없다. 오로지 애플에 제출용으로만 사용이 가능하며 애플이 리뷰후 다시 사인하여 배포한다.

 

팁!)

새로운 팀원이나 다른 PC에 개발환경을 설정할 때 매번 애플인증서를 만들수는 없다.

이 때는 이미 등록된 인증서와 해당 인증서를 등록한 개발자의 키파일을 전달 받아서 설치해주어야 한다. 그러지 않으면 인증서의 출처를 확인하지 못하여 정상적으로 빌드가 안되는것 같다. 이 부분에 대해서는 정확히 아는 사람이 설명을 좀 해주었으면 ....

 



Posted by 삼스

댓글을 달아 주세요

iOS2018. 8. 4. 13:17


애플의 인앱결재의 자동결재를 정리한다.


종류는 다음과 같다.


  • 소비형 : 한번 사면 바로 소모됨
  • 비소비형 : 한번사면 평생 유효
  • 자동갱신형 : 주기적으로 재구매(결재)가 자동으로 이루어짐
  • 비갱신형 : 기간 만료되면 다시 구매를 해야 함.

자동갱신

애플에서는 해당 구매의 만료일 10일전에 결재인프라를 먼저 확인한다. 이 때 카드정보등이 유효하지 않으면 메일등을 보내서 갱신을 유도한다.

하루전에는 실제로 결재를 시도한다. 이 때 결재가 실패할 수 있다. 

두가지가 있을 수 있다.


  • 결제정보의 일시적인 오류
  • 정기결제기능을 off

위 상황에서는 결재가 실패하게 되며 사용자가 결재정보를 유효하게 갱신하거나 정기결재기능을 다시 on하게 되면 애플에서는 구매를 수행하게 된다. 이 때 부득이 지연이 발생한다.


앱을 서비스 하는 입장에서는 이런 지연이 발생 한경우를 파악하여 적절히 사용자에게 안내를 해야 불온한 의도를 가진 사용자들의 공격을 막아낼 수 있을 것이다.


재결제내역 확인

앱과 서버가 모두 수행하는것이 좋으며 일반적으로 서버에서 하는것이 좀더 안전하다. 왜 더 안전한지에 대한 설명은 찾아볼 수 없으나 예상으로는 앱은 해킹이 가능하기 때문으로 추정해본다. 


앱 기동시에는 addTransactionObserver를 호출하면 된다. 이 때 재구매건이 있으면 paymentQueue에 결제건이 들어오게 된다. 이 결재건을 사용자가 직접 구매했을 때 와 동일하게 처리하면 된다.


서버에서는 만료일이 가까워진 결재건에 대해서 original 결재정보로 apple서버에 조회하여 마지막 영수증정보과 최종 영수증 정보가 다르면 리뉴얼 된것으로 파악하면 된다.


영수증정보의 갱신

영수증정보는 동일계정으로 로그인 된 다른 단말에서 재구매가 일어나도 앱 기동시마다 영수증정보를 최신으로 유지하기 때문에 동기화가 가능하다.


현재 구매상태인지 여부 확인

최초 결제의 영수증의 transactionId가 originalTransactionId가 되며 이 후 재결제가 일어나면 새로운 영수증이 발급되며 이 때 transactionId도 갱신이 된다. 하지만 originalTransactionId는 최초의 값이 유지된다.

따라서 영수증 목록에서 originalTransactionId로 필터링 후 expireDate가 아직 미래인 영수증이 있다면 아직 구매중인것으로 판단하면 된다.


구매취소 여부 확인

영수증에 Cancelation Date가 있다면 구독취소한것으로 판단하면 된다고 한다. Cancelation필드가 있으면 ExpireDate와 관계없이 취소된 구독이다.



결재내역 복원하기





Posted by 삼스

댓글을 달아 주세요

iOS2018. 2. 13. 15:51


iOS7이전에는 자바스크립트와 무언가 작용을 하려면 UIWebView의 stringByEvaluatingJavaScriptFormString:를 사용해야 했다. 이는 웹뷰의 자바스크립트 런타임에서만 가능했다. 이는 싱글스레드인 환경의 제약이 있다.


JavaScriptCore는 Objective-C에서 자바스크립트의 풀런타임환경에 접근이 가능하게 한다. 문법확인, 스크립트실행과 변수 접근, 콜백수신  그리고 Objective-C 객체의 공유등으로 광범위한 상호작용이 가능하다.


JSVirtualMachine클래스로 불리는 가상머신에서 실행된다. 이는 여러개의 VM을 통해 멀티스레드 자바스크립팅이 가능하다는 것이다. JSVirtualMachine 각각은 여러개의 JSContext를 가질 수 있다. JSContext는 자바스크립트 런타입환경에 말하고 몇가지 기능을 제공한다. 이 중 두가지는 글로벌객체에 접근하는것과 스크립트를 실행할 수 있다는 것이다.


글로벌 영역에 정의된 변수에 대한 접근은 키로 바로 접근이 가능하다. 


    JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
    context[@"a"] = @5;
    JSValue *aValue = context[@"a"];
    double a = [aValue toDouble];
    NSLog(@"%.0f", a);

함수호출은 다음과 같다


[context evaluateScript:@”var square = function(x) {return x*x;}”]; 

JSValue *squareFunction = context[@”square”]; 

NSLog(@”%@”, squareFunction); 

JSValue *aSquared = [squareFunction callWithArguments:@[context[@”a”]]]; 

NSLog(@”a^2: %@”, aSquared); 

JSValue *nineSquared = [squareFunction callWithArguments:@[@9]]; 

NSLog(@”9^2: %@”, nineSquared);


evaluateScript로 스크립트 코드를 로드하고 callWithArguments로 스크립트내의 함수를 호출한다. 호출된 결과는 JSValue로 반환된다.

스크립트코드는 블럭코드형태로도 가능하다.



    context[@"factorial"] = ^(int x) {
        int factorial = 1;
        for (; x > 1; x--) {
            factorial *= x;
        }
        return factorial;
    };
    [context evaluateScript:@"var fiveFactorial = factorial(5);"];
    JSValue *fiveFactorial = context[@"fiveFactorial"];
    NSLog(@"5! = %@", fiveFactorial);
스크립트와 objective c간의 타입관계는 다음과 같다.

//   Objective-C type  |   JavaScript type
// --------------------+---------------------
//         nil         |     undefined
//        NSNull       |        null
//       NSString      |       string
//       NSNumber      |   number, boolean
//     NSDictionary    |   Object object
//       NSArray       |    Array object
//        NSDate       |     Date object
//       NSBlock       |   Function object
//          id         |   Wrapper object
//        Class        | Constructor object
스크립트NSNumber와 NSBlocks 타입은 mutable이 아니다 즉 한쪽에서 변경했다고 다른쪽에서 반영이 되지 않는다. id와 Class는 mutable이다. 
임의 오브젝트나 클래스가 자바스크립트에 브릿지될 수 있다. 
NSNull, NSString, NSNumber, NSDictionary, NSArray 또는 NSDate는 관련 클래스 계층을 JavaScript 실행 컨텍스트로 가져 와서 동등한 클래스 및 프로토 타입을 생성하는 JavaScriptCore를 생성하려고 한다.

JSExport protocol로 자바스크립트에 커스텀클래스의 일부를 노출할 수 있다. 래퍼객체는 다른곳에 생성될것이다. 그리하여 한개의 객체가 양쪽의 실행컨텍스트에 공유될 수 있다.

다음이 그 예이다.

    @protocol ThingJSExports <JSExport>
    @property (nonatomic, copy) NSString *name;
    @end

    @interface Thing : NSObject <ThingJSExports>
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic) NSInteger number;
    @end

    @implementation Thing
    - (NSString *)description {
        return [NSString stringWithFormat:@"%@: %d", self.name, self.number];
    }
    @end

JSExport를 상속하여 ThingJSExports를 선언함으로써 자바스크립트에 노출할 수 있다. 이제 objective-c객체의 정보를 변경하면 자바스크립트에 그 값이 반영이 될것이다. 그 반대로 마찬가지.
다음은 id와 class를 브릿지한 예이다.


다음은 id와 class를 브릿지한 예이다.    Thing *thing = [[Thing alloc] init];
    thing.name = @"Alfred";
    thing.number = 3;
    context[@"thing"] = thing;
    JSValue *thingValue = context[@"thing"];

context[@"thing"] = [Thing class];

NSString *thingScript =

@"var t = new thing();"

"t.name = 'yslee';"

"t.number = 10;"

[context eveluateScript:thingScript];








Posted by 삼스

댓글을 달아 주세요

iOS2018. 2. 13. 15:01


iOS에도 이런게 나왔을 줄이야..


안드로이드 하이브리드 개발자들은 알고 있겠지만 addJavascriptInterface를 통해서 자바스크립트에 java object를 바인딩이 가능하다.

이를 통해 하이브리드를 통해서 동기화 호출도 가능하다.

하지만 iOS는 불가했었다.

cordova도 NSURLProtocol을 통해서 하이브리드를 지원하는데 역시 동기화 호출을 불가하다.

하지만 불가능하지는 않다. 다른 방법으로 가능한 방법이 있으며 이를 통해서 실제 동기화 호출을 구현하기도 하였다.


그런데 iOS7부터 지원하는 JavaScriptCore프레임웍으로 가능한것 같다.


http://www.dbotha.com/2014/11/19/using-javascript-core-for-game-scripting-on-ios/ 를 참조하였다.


게임스크립팅 엔진을 javascriptcore로 변경하는것에 대한 글이다.


일반적인 게임튜터리얼의 스크립트는 아래와 같다.


/* tutorial1.js */
showText("Hello World!", /*x:*/ 160, /*y:*/ 100);
wait(5); // sleep 5 seconds
hideText()

showText("Tap the screen to continue", 160, 100);
waitForTap(); // pause execution until the user taps the UI
hideText();

showText("Match three coloured blocks to win!", 160, 100);
waitForLevelEnd(); // pause execution until a level end event occurs
hideText();

loadLevel("level2.map")

예제에서 보듯이 어떤 레벨에 도달할때까지 스크립트실행을 중지하는것이 좋다. 위에서 wait()함수에 대해 의문을 품게 될것이다. 싱글스레드의 자바스크립트 엔진에서는 의도한대로 작동을 기대하기 어렵다. 완전히 스레드가 멈추게 될것이므로..

JSVirtualMachine은 어떻게 실행을 중단할까?


스크립팅엔진자체에 대해 먼저 더 알아보자.


자바스크립트에 접근가능하도록 작성된 스위프트 프로토콜 메서드들을 보자


/* ScriptEngine.swift */
@objc protocol ScriptingEngineExports : JSExport {
    func wait(duration: Double)
    func waitForTap()
    func waitForLevelEnd()
    func loadLevel(mapName: String)
    func showText(text: String, _ x: Double, _ y: Double)
    func hideText()
}


금방 구현가능하고 앞서 기술한 튜터리얼 스크립트에 매우 가깝게 매칭된다.


이번엔 스크립팅 엔진을 살펴보자.


/* ScriptEngine.swift */
@objc class ScriptEngine : NSObject, ScriptEngineExports {
    var context: JSContext!
    let engineQueue = dispatch_queue_create(
            "script_engine", DISPATCH_QUEUE_SERIAL)
    
    override init() {
        super.init()
        
        dispatch_async(engineQueue) {
            self.context = JSContext()
            self.context.setObject(self, forKeyedSubscript: "$")
            self.context.evaluateScript(
                "function wait(duration) {$.wait(duration);}" + 
                "function waitForTap() {$.waitForTap();}" +
                "function waitForLevelEnd(){$.waitForLevelEnd();}" +
                "function print(text) {$.print(text);}" + 
                "function println(text) {$.print(text);}" + 
                "function loadLevel(name) {$.loadLevel(name);}")
        }
    }
    
    /* to be continued... */ 
}

engineQueue은 JSContext를 사용하기 위한 dispatch_queue이다. 이는 메인스레드와 분리하여 스크립트의 콜백을 스위프트가 받을 수 있도록 한다.

여기서 중요한것은 JSContext가 메인스레드에서 분리되어서 인스턴스화되었다는 것이다. 이는 메인스레드가 블럭되는것을 방지한다.


ScriptEngine 객체 인스턴스는 $변수를 통해서 접근이 가능하다.  evaluateScript가 호출되어 스크립트에 억세스 할 수 있는 ScriptEngineExports의 기능을 노출한다. 스위프트의 함수를 호출함으로 인해 ScriptEngine의 메서드가 호출된다.


실행중인 스크립트를 로드하는것은 간단하다. 



/* ScriptEngine.swift */
var currentScriptName: String?

func runScript(scriptName: String) {
    var ext = scriptName.pathExtension
    var fileName = scriptName.substringToIndex(
        advance(scriptName.startIndex, 
                scriptName.utf16Count - ext.utf16Count - 1))
    
    if fileExtension.utf16Count == 0 {
        fileName = scriptName
        ext = "js"
    }
    
    let url = NSBundle.mainBundle()
        .URLForResource(fileName, withExtension: ext)
    let scriptCode = String(contentsOfURL: url!, 
                            encoding: NSUTF8StringEncoding, 
                            error: nil)!

    self.currentScriptName = fileName + "." + fileExtension

    dispatch_async(engineQueue) {
        self.context.evaluateScript(scriptCode)
        return
    }
}

다음과 같이 실행이 가능하다.


let scriptEngine = ScriptEngine()
scriptEngine.runScript("tutorial1")

이제 어떻게 다양한 Wait()함수를 실행할 수 있는지 살펴보자


/* ScriptEngine.swift */
func wait(duration: Double) {
    NSThread.sleepForTimeInterval(duration)
}

이 wait함수는 engineQueue에 의해 관리되는 분리된 스레드에서 호출될것이 보장된다. 그래서 sleepForTimeInterval은 메인스레드가 아니라 이 스레드에서 동작할것이다. 자바스크립트는 실행이 잠시 중단될것이다.


다음은 좀 복잡한 대기로직들이다.


/* ScriptEngine.swift */
let tapCondition = NSCondition()
let levelEndCondition = NSCondition()
var seenTap = false
var seenLevelEnd = false

func waitForCondition(condition: NSCondition, 
        inout predicate: Bool) {
    condition.lock()
    while (!predicate) { // loop to protect against spurious wakes
        condition.wait()
    }
    predicate = false
    condition.unlock()
}

func notifyCondition(condition: NSCondition) {
    condition.lock()
    condition.signal()
    condition.unlock()
}

func waitForTap() {
    waitForCondition(tapCondition, &seenTap)
}

func notifyTap() {
    notifyCondition(tapCondition)
}

func waitForLevelEnd() {
    waitForCondition(levelEndCondition, &seenLevelEnd)
}

func notifyLevelEnd() {
    notifyCondition(levelEndCondition)
}

waitForTap()과 waitForLevelEnd()는 자바스크립에서 대응되는 함수가 호출되었을때 인스턴스 메서드로 노출된다. waitForCondition(condition:predicate:)는 해당 컨디션이 시그널될때까지 스레드를 블럭한다. notifyTap(), notifyLevelEnd()는 해당 컨티션발생시 시그널되고 engineQueue스레드를 깨워서 자바스크립트가 다시 살아나게 한다. 이 메서드들은 게임엔진에서 해당 이벤트가 발생하였을때 호출해야 한다.


이제 남은 노출된 함수는 구현하기에 따라 다르며 여기서는 다음과 같이 구현하였다.


/* ScriptEngine.swift */
func postNotification(type: GameNotification, 
        var userInfo: [NSObject : AnyObject]?) {
    
    if userInfo == nil {
        userInfo = [NSObject : AnyObject]()
    }
    
    userInfo![kGameNotificationUserInfoCurrentScriptName] 
        =  self.currentScriptName
    
    dispatch_async(dispatch_get_main_queue(), {
        NSNotificationCenter.defaultCenter()
            .postNotificationName(
                type.name(), object: self, userInfo: userInfo)
    })
}

func loadLevel(levelName: String) {
    postNotification(GameNotification.LoadLevel, 
        userInfo: [kGameNotificationUserInfoLevelName: levelName])
}

func showText(text: String, _ x: Double, _ y: Double) {
    postNotification(GameNotification.ShowText, 
        userInfo: [kGameNotificationUserInfoText: text, 
                   kGameNotificationUserInfoTextX: x, 
                   kGameNotificationUserInfoTextY: y])
}

func hideText() {
    postNotification(GameNotification.HideText, nil)
}


Posted by 삼스

댓글을 달아 주세요