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 삼스
iOS2018. 1. 16. 11:19


CoreLocation framework에서 현재 위치의 위/경도 좌표를 얻을 수 있고 위/경도 좌표로 주소정보를 얻을 수 있다.



    _locationManager = [[CLLocationManager alloc] init];

// 사용중에만 유효하도록 

    if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {

        [_locationManager requestWhenInUseAuthorization];

    }

// 위치정보 와치 시작

    [_locationManager startUpdatingLocation];

    NSLog(@"%f/%f", _locationManager.location.coordinate.latitude, _locationManager.location.coordinate.longitude);

    

// 위/경도 정보로 주소 얻기

    CLLocation *location = [[CLLocation alloc] initWithLatitude:_locationManager.location.coordinate.latitude longitude:_locationManager.location.coordinate.longitude];

    NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"ko-kr"];

    CLGeocoder *geocoder = [[CLGeocoder alloc] init];

    [geocoder reverseGeocodeLocation:location preferredLocale:locale completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        NSLog(@"%@", [[[placemarks objectAtIndex:0] addressDictionary] objectForKey:@"FormattedAddressLines"]);

    }];



Posted by 삼스
블록체인2017. 9. 1. 11:14


https://medium.com/@codeAMT/how-to-launch-swarm-for-dapp-testing-8003e55380e2


이더리움은 최근에 web3.0 기반 기술의 에코시스템에 포함되기 위해 쭈욱 개발되어져 왔다. 브라우저내장의 Mist지갑, dapp messaging package인 Whisper, 탈중앙화된 데이터스토리지인 Swarm등이 있다.


다음은 web3.0에서 dapp개발을 위한 거룩한 삼위일체를 보여준다.



Swarm이 무엇이고 어떻게 동작하는가?


Peer to peer, low latency, 네트웍 데이터 공유를 가능하게 하는 서버같은 노드이고  해시주소로 최종사용자가 리소스에 접근가능하게 해준다.

실서버는 불필요.

파일공유나 데이터자장소를 투명하고 비용효율이 좋은 서비스를 만들 수 있는 잠재력을 가지고 있다. 분산은 피어간의 연결을 통해서 중단없는 서비스가 가능하다.

최종사용자는 새로운 리소스를 배포하는데 비용을 지불한다.

개발자는 이 경로에 게시하는 데이터에 대한 민감도를 고려해야 한다. 데이터는 해시주소에 저장되면 삭제할 수 없다.

비트코인처럼 테스트넷에서 테스트가 가능하며 테스트넷에 swarm을 적재하고 web 3.0dapp으로 게이트웨이를 사용하는 법을 안내할것이다.


TestNet에 Swarm처음 적재하기


1 : go-ethereum 설치


go언어를 설치해야 한다. 다음 링크를 참조하라.


* Go workspace 폴더 생성

* $GOPATH환경변수에 등록

* .bash_profile에 추가

* 작업폴더 생성 (src/github.com/ethereum)

* 작업폴더로 이동 하여 go-ethereum clone

* cd go-ethereum, grab 프로젝트 의존성




2. geth 와 swarm 패키지 컴파일


geth는 노드를 적재하기 위해 swarm은 파일공유를 하기 위해 컴파일 한다.


* run "go install -v ./cmd/geth"

* run "go install -v ./cmd/swarm"



백그라운드에서 go isntall명령이 geth와 swarm 바이너리를 만든다. 



3. 데이터 폴더와 Swarm TestNet계정 만들기


* 폴더를 만들고 testnet 모드로 노드를 시작

* 계정 생성

* 비밀번호 입력



계정이 생성되면 새로 생성된 계정주소를 반환한다.


4. geth node 적재


여기서 geth에 fast 옵션을 추가로 시작하였으며 이는 fast 동기화 다운로드를 수행한다는 뜻이다.



잠시후에 테스트넷 폴더가 데이터 폴더에 자동으로 생성된것을 알 수 있다. 


5. Swarm testnet 시작

새로운 터미널탭에서 swarm을 시작한다. 3가지 옵션이 필요하다

* bzzaccount : geth 계정 주소
* 데이터폴더 경로
* ethapi(테스트넷 폴더내의 geth.ipc로 불리는 파일의 경로)

실행후 계정 비밀번호를 입력한다. 



p2p 네트웍이기 때문에 피러들과 연결되려면 조금 기다려야 한다. 연결이 완료되면 아래와 같은 화면을 볼수 있다.






이제 데이터 폴더에 블랙체인을 모두 다운받아야 한다. 그러면 브라우져를 실행할 준비가 완료된다.


6. 로컬호스트에 억세스하기 위해 브라우저 실행


브라우저를 열고 8500포트에 접속하면 다음화면을 볼수 있다.




이제 서버가 동작하고 있다.


"bzz:/"프록시로 해시나 도메인주소를 입력하면 웹컨텐츠를 볼수 있다.




web3.0브라우저를 사용하면 bzz 스키마로 바로 접근이 가능하다.


7. Web 3.0 서핑하기


swarm 개발자들의 샘플 dapp을 방문해 보아라. http://swarm-gateways.net/bzz:/photoalbum.eth/


로컬에서 swarm홈페이지의 dapp같은 것을 볼 수 있다.



이 앱으로 사용자가 새로 게시하거나 재배치하고 삭제할 수 있다. 오픈소스 앨범이고 개인적 용도로 공유할 수도 있다.


dapp을 수정하게 되면 해시값도 변경된다. swarm은 모든 애플리케이션이 유일한 해시값을 갖기 때문이다.


앱위에 마우스를 갖다 대면 팝업으로 뜨는 메뉴가 있다. dapp의 삭제, 게시, put, get제어가 가능하다.

따라서 이미지를 아래로 이동시키면 아래처럼 새로운  dapp 주소가 생성된다.




애플리케이션 공유하기


일반 웹에서 dapp을 보려면 일반게이트웨이를 통해서 해당 고유 해시를 보아야 한다.


1. 해시 복사

2. 새브라우저 열기

3. 게이트웨이에 bzz 프로토콜 추가

4. 붙여넣기



내 노드에서 시험하기


홈스테드 버전의 Mist지갑에 Swarm이 포함되어 있으며 지갑앱을 통해서 테스트가 가능하다.


Mist지갑 > 계정 > Upload to Swarm을 선택 후 이미지 파일을 선택한다.



로컬호스트 주소에 bzz://로 생성된 해시코드를 주소로 집너 넣는다.

그러면 아래와 같이 내가 업로드한 이미지 파일을 볼 수 있다.







Posted by 삼스