Android/App개발2019. 6. 4. 14:02

https://github.com/google/dagger

 

google/dagger

A fast dependency injector for Android and Java. Contribute to google/dagger development by creating an account on GitHub.

github.com

의존성주입 라이브러리인 Dagger2를 알아보기 전에 Dagger1을 먼저 알아보자


2012년에 자바로 의존성을 주입하는 아이디어를 좋아한 스퀘어 개발자 몇명이 만들었다. 결국 어노테이션 기반의 코드생성과 guice와 비슷한 api를 갖지만 더 유연하고 빠르게 만들었다.
Dagger의 동작방식은 주입하고자 하는 모든 의존성을 위한 프로바이더를 포함하는 모듈들을 정의하여 객체그래프에 모듈을 로딩하고 결국 필요한 타겟에 내용을 주입하는 방식이다. 충분히 단순한구조(구현은 단순하지 않음)는 개발자가 코드를 분리할 수 있게 도움을 주고 클래스 상단에 객체를 생성하는 보기싫은 코드들을 라이브러리가 생성하는 인젝터들에 옮겨준다.
하지만 이런 잇점과 동시에 빠른 풀리퀘스트(하나 혹은 둘)를 해결하지 못하는 문제가 있다. 이는 기본아키텍쳑와 관련되어 있다.

- 런타임에 그래프 구성 : 성능에 악영향. 
- 리플랙션(예:Class.forName()) : 읽기 힘든 자동생성 코드와 프로가드의 악몽 발생
- 보기 싫은 자동생성코드 : 팩토리로 부터 수동 생성한 경우와 비교하여 아주 보기 싫음

결국 이런 이유로 수많은 이슈들이 트래킹되었고 이 이슈들은 Dagger2에서 해결하는것으로 이슈가 클로즈 되었다.

Dagger2

스퀘어의 원작자와 구글의 코어라이브러리 팀이 Dagger의 다음 이터레이션을 가져왔다.
약속대로 여러 이슈들이 해결되어 새버전이 배포되었다.

- 리프랙션 없음 : 프로가드 지옥도 해결
- 런타입 그래프구성 없음 : 성능 향상
- 추적가능 : 더 좋은 코드와 리플랙션 제거로 인해 더 읽이 쉽고 따라하기 쉬워짐.

Module

Dagger2에서 가장 변경이 없는것이 모듈이다. 주입하고자 하는 의존성의 프로바이더 메서드를 정의하면 된다. 

SharedPreference가 필요하다고 외쳐보자!

@Module 
public class ApplicationModule { 
    private Application mApp; 
    public ApplicationModule(Application app) { 
        mApp = app; 
    } 
    @Provides
    @Singleton
    SharedPreferences provideSharedPrefs() { 
        return PreferenceManager.getDefaultSharedPreferences(mApp); 
    } 
}

Component

Dagger를 아는 사람은 모듈에서 ingect = {} 가 사라진것을 알수 있다. Dagger2는 콤포넌트가 이를 대체한다.

@Singleton 
@Component(modules = {ApplicationModule.class}) 
public interface ApplicationComponent { 
    void inject(DemoApplication app); 
    void inject(MainActivity activity); 
}

주입하고자 하는 타겟들을 정의하면 되며 만일 누락되면 "cannot find method" 컴파일에러가 날것이다.

Application

다음은 콤포넌트를 담을 컨테이너이다.
응용 프로그램에 따라 더 복잡한 방법으로 구성 요소를 저장할 수도 있지만이 예제에서는 응용 프로그램 클래스에 저장된 단일 구성 요소로 충분하다.

public class DemoApplication extends Application { 
    private ApplicationComponent mComponent; 
    @Override 
    public void onCreate() { 
        super.onCreate(); 
        mComponent = DaggerApplicationComponent.builder() 
                    .applicationModule(new ApplicationModule(this))
                    .build(); 
    } 
    public ApplicationComponent getComponent() { 
        return mComponent; 
    } 
}

특별한것이 없다 해당 콤포넌트를 build하고 인스턴스화 해서 저장한다.
위 코드에서 이해가 안되는 부분이 있을수 있는데 DaggerApplicationComponent가 어디서 나왔냐 하는것일것이다. 이는 Dagger%COMPONENT_NAME%형식으로 명명되어 자동생성된 클래스이다. 컴파일러가 자동으로 만들어준다. 

Injection

마지막으로 위에서 설정한 인젝션 설정을 SharedPreference에 접근하고자 하는 액티비티내에서 사용하게 된다.

public class MainActivity extends Activity { 
    @Inject SharedPreferences mSharedPrefs; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        ((DemoApplication) getApplication())
                    .getComponent()
                    .inject(this); 
        mSharedPrefs.edit()
                    .putString("status", "success!")
                    .apply(); 
    } 
}

예상한데로 @Inject어노테이션은 변경되지 않았고 인젝션내에서만 일부 변경이 있다.  

Named Injection


동일 타입의 여러 객체가 필요한 경우 예를 들어 SharedPreference를 용도가 다르거나 다른파일을 지정하는 등의 경우 어떻게 할것인가?
이 경우 이름을 부여할 수 있다. @Named 어노테이션을 추가하는것만으로 가능하다.

@Provides 
@Named("default") 
SharedPreferences provideDefaultSharedPrefs() { … } 

@Provides 
@Named("secret") 
SharedPreferences provideSecretSharedPrefs() { … }

주입 대상에서도 동일하게..

@Inject @Named("default") SharedPreferences mDefaultSharedPrefs; @Inject @Named("secret") SharedPreferences mSecretSharedPrefs;


Lazy Injection

성능을 고려하면 지연된 주입이 반드시 필요하다 모든 의존성을 앱실행 초기에 하면 앱이 늦게 시작될테니까. 이를 위해 지연된 주입을 지 원한다.

@Inject Lazy mLazySharedPrefs; 
void onSaveBtnClicked() { 
    mLazySharedPrefs.get()
                .edit().putString("status", "lazy...")
                .apply(); 
}

위와 같이 정의 두면 호출전까지 주입이 되지 않는다. 이 후에는 주입된 상태를 유지한다.

Provider Injection

팩토리 패턴이 가능하다. 객체를 주입하지 않고 여러 인스턴스를 만들어야 하는 경우... 이 경우 Provider가 가능하다.

@Inject 
Provider mEntryProvider; 
Entry entry1 = mEntryProvider.get(); 
Entry entry2 = mEntryProvider.get();

프로바이더가 Entry객체의 두가지 인스턴스를 만든다. 인스턴스가 모듈에서 어떠게 생성될지는 알아서...

짜증나게 하는 지점!

inject() 메서드는 강력한 타입체크를 수행한다. 이는 디버깅에 유리하지만 베이스클래스로부터 주입하는 경우 복잡하게 한다.
직관적으로 베이스클래스를 대상으로 하는 inject()메서드를 만들것이다 하지만 이는 서브클래스에 대해서는 동작하지 않는다.

해결책은 멀까? 

방법#1 은 베이스클래스에 abstract 메서드를 정의하는것이다. 

@Override 
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); 
    injectComponent(((DemoApplication) getApplication())
                .getComponent()); 

protected void injectComponent(ApplicationComponent component);

그리고 서브클래스에서 구현한다.

@Override
protected void injectComponent(ApplicationComponent component) { 
    component.inject(this); 
}

방법#2 는 리플랙션을 사용하는것이다. 이는 당신이 다루고자 하는 타입에 해 당하는 inject()메서드를 찾는것으로 귀결된다.

먼저 당신이 정의한 콤포넌트의 모든 메서드들을 타입별로 캐시한다.

// Keep the returned cache in some helper. 
Map<Class, Method> buildCache() { 
    Map<Class, Method> cache = new HashMap<>(); 
    for (Method m : ApplicationComponent.class.getDeclaredMethods()) { 
        Class[] types = m.getParameterTypes(); 
        if (types.length == 1) { 
            cache.put(types[0], m); 
        } 
    } 
    return cache; 
}

그리고 주입시에 getClass()로 캐시로부터 꺼내고 invoke를 호출한다.

// Use for injecting targets of any type. void inject(ApplicationComponent component, Object target) { 
    Method m = cache.get(target.getClass()); 
    if (m != null) { 
        m.invoke(component, target); 
    } 
}

기본클래스에서 모든 설정을 숨기거나 이것을 수행하는 코드를 다루는 누군가에게는 유용할것이다.

짜증2 는 콤포는트 구현이 재빌드를 요구하고 클래스가 사라질때는 컴파일에러가 발생한다는 것이다. 심간한 불편함은 아니지만 프로젝트 구성 초기에는 좀 귀찮을 수 있다.

원문 : https://blog.gouline.net/dagger-2-even-sharper-less-square-b52101863542

 

Dagger 2: Even Sharper, Less Square

It was only a matter of time before version 2.0 of the well-known dependency injection library Dagger hit production and that seemed like…

blog.gouline.net

한글로 된 잘 정리된 문서

https://medium.com/@jason_kim/tasting-dagger-2-on-android-%EB%B2%88%EC%97%AD-632e727a7998

Posted by 삼스

댓글을 달아 주세요

  1. 코드제다이

    정리가 정말 잘되어 있네요 감사합니다 많이 배우고 갑니다 ㅎㅎ

    2020.01.10 10:30 [ 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 삼스

댓글을 달아 주세요

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

댓글을 달아 주세요

  1. 윤훤호

    개발에 열성적이시네요~
    즐건밤되세요~

    2017.09.03 00:37 [ ADDR : EDIT/ DEL : REPLY ]

블록체인2017. 7. 11. 15:06


코인 사용하기


토큰을 배포하면 토큰 목록에 추가되고 총 잔액이 계정에 표시된다. 토큰을 보내려면 보내기 탭으로 이동하여 토큰이 포함 된 계정을 선택하라. 계정에있는 토큰은 이더 아래에 나열된다. 그들을 선택하고 보내려는 토큰의 양을 입력하라.

다른 사람의 토큰을 추가하려면 계약 탭으로 이동하여 토큰보기를 클릭하라. 예를 들어, 감시 목록에 Unicorn (🦄) 토큰을 추가하려면 주소 0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7을 추가하기 만하면 나머지 정보가 자동으로로드된다. 확인을 클릭하면 토큰이 추가된다.

유니콘 토큰은 Ethereum Foundation이 관리하는 주소 0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359에 기부 한 사람들을 위해 독점적으로 만들어지는 기념품이다. 이에 대해 더 알고 싶다면 여기를 읽어보아라.


이더리움으로 토큰을 발행하는 방법을 배웠다. 원하는 토큰을 만들 수 있다. 하지만 토큰으로 무엇을 할 수 있을까? 회사의 주식이 가능합니다. 또한 중앙위원회를 사용하여 인플레이션을 제어하기 위해 새로운 동전을 ㅂ라행 할 시기에 투표할 수 있다. 또한 crowdsale을 통해 어떤문제를 위해 돈을 모으기 위해 사용가능 하다. 다음엔 무엇을 만들래?

Posted by 삼스

댓글을 달아 주세요

  1. 비밀댓글입니다

    2017.09.01 03:22 [ ADDR : EDIT/ DEL : REPLY ]

블록체인2017. 7. 11. 14:54

전체 코드


pragma solidity ^0.4.2;

contract owned {

    address public owner;


    function owned() {

        owner = msg.sender;

    }


    modifier onlyOwner {

        if (msg.sender != owner) throw;

        _;

    }


    function transferOwnership(address newOwner) onlyOwner {

        owner = newOwner;

    }

}


contract tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData); }


contract token {

    /* Public variables of the token */

    string public standard = 'Token 0.1';

    string public name;

    string public symbol;

    uint8 public decimals;

    uint256 public totalSupply;


    /* This creates an array with all balances */

    mapping (address => uint256) public balanceOf;

    mapping (address => mapping (address => uint256)) public allowance;


    /* This generates a public event on the blockchain that will notify clients */

    event Transfer(address indexed from, address indexed to, uint256 value);


    /* Initializes contract with initial supply tokens to the creator of the contract */

    function token(

        uint256 initialSupply,

        string tokenName,

        uint8 decimalUnits,

        string tokenSymbol

        ) {

        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens

        totalSupply = initialSupply;                        // Update total supply

        name = tokenName;                                   // Set the name for display purposes

        symbol = tokenSymbol;                               // Set the symbol for display purposes

        decimals = decimalUnits;                            // Amount of decimals for display purposes

    }


    /* Send coins */

    function transfer(address _to, uint256 _value) {

        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough

        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows

        balanceOf[msg.sender] -= _value;                     // Subtract from the sender

        balanceOf[_to] += _value;                            // Add the same to the recipient

        Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place

    }


    /* Allow another contract to spend some tokens in your behalf */

    function approve(address _spender, uint256 _value)

        returns (bool success) {

        allowance[msg.sender][_spender] = _value;

        return true;

    }


    /* Approve and then communicate the approved contract in a single tx */

    function approveAndCall(address _spender, uint256 _value, bytes _extraData)

        returns (bool success) {    

        tokenRecipient spender = tokenRecipient(_spender);

        if (approve(_spender, _value)) {

            spender.receiveApproval(msg.sender, _value, this, _extraData);

            return true;

        }

    }


    /* A contract attempts to get the coins */

    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {

        if (balanceOf[_from] < _value) throw;                 // Check if the sender has enough

        if (balanceOf[_to] + _value < balanceOf[_to]) throw;  // Check for overflows

        if (_value > allowance[_from][msg.sender]) throw;   // Check allowance

        balanceOf[_from] -= _value;                          // Subtract from the sender

        balanceOf[_to] += _value;                            // Add the same to the recipient

        allowance[_from][msg.sender] -= _value;

        Transfer(_from, _to, _value);

        return true;

    }


    /* This unnamed function is called whenever someone tries to send ether to it */

    function () {

        throw;     // Prevents accidental sending of ether

    }

}


contract MyAdvancedToken is owned, token {


    uint256 public sellPrice;

    uint256 public buyPrice;


    mapping (address => bool) public frozenAccount;


    /* This generates a public event on the blockchain that will notify clients */

    event FrozenFunds(address target, bool frozen);


    /* Initializes contract with initial supply tokens to the creator of the contract */

    function MyAdvancedToken(

        uint256 initialSupply,

        string tokenName,

        uint8 decimalUnits,

        string tokenSymbol

    ) token (initialSupply, tokenName, decimalUnits, tokenSymbol) {}


    /* Send coins */

    function transfer(address _to, uint256 _value) {

        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough

        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows

        if (frozenAccount[msg.sender]) throw;                // Check if frozen

        balanceOf[msg.sender] -= _value;                     // Subtract from the sender

        balanceOf[_to] += _value;                            // Add the same to the recipient

        Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place

    }



    /* A contract attempts to get the coins */

    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {

        if (frozenAccount[_from]) throw;                        // Check if frozen            

        if (balanceOf[_from] < _value) throw;                 // Check if the sender has enough

        if (balanceOf[_to] + _value < balanceOf[_to]) throw;  // Check for overflows

        if (_value > allowance[_from][msg.sender]) throw;   // Check allowance

        balanceOf[_from] -= _value;                          // Subtract from the sender

        balanceOf[_to] += _value;                            // Add the same to the recipient

        allowance[_from][msg.sender] -= _value;

        Transfer(_from, _to, _value);

        return true;

    }


    function mintToken(address target, uint256 mintedAmount) onlyOwner {

        balanceOf[target] += mintedAmount;

        totalSupply += mintedAmount;

        Transfer(0, this, mintedAmount);

        Transfer(this, target, mintedAmount);

    }


    function freezeAccount(address target, bool freeze) onlyOwner {

        frozenAccount[target] = freeze;

        FrozenFunds(target, freeze);

    }


    function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {

        sellPrice = newSellPrice;

        buyPrice = newBuyPrice;

    }


    function buy() payable {

        uint amount = msg.value / buyPrice;                // calculates the amount

        if (balanceOf[this] < amount) throw;               // checks if it has enough to sell

        balanceOf[msg.sender] += amount;                   // adds the amount to buyer's balance

        balanceOf[this] -= amount;                         // subtracts amount from seller's balance

        Transfer(this, msg.sender, amount);                // execute an event reflecting the change

    }


    function sell(uint256 amount) {

        if (balanceOf[msg.sender] < amount ) throw;        // checks if the sender has enough to sell

        balanceOf[this] += amount;                         // adds the amount to owner's balance

        balanceOf[msg.sender] -= amount;                   // subtracts the amount from seller's balance

        if (!msg.sender.send(amount * sellPrice)) {        // sends ether to the seller. It's important

            throw;                                         // to do this last to avoid recursion attacks

        } else {

            Transfer(msg.sender, this, amount);            // executes an event reflecting on the change

        }               

    }

}


배포


아래로 스크롤하면 배포에 드는 예상 비용이 표시된다. 원한다면 작은 수수료를 설정하도록 슬라이더를 변경할 수 있지만 가격이 평균 시장 가격보다 너무 낮으면 거래가 성사되는데 더 오래 걸릴 수 있다. 배포를 클릭하고 암호를 입력한다. 몇 초 후에 대시 보드로 리디렉션되고 최신 트랜잭션에 "계약 생성"이라고 표시된다. 누군가가 거래를 선택하기까지 잠깐 기다리면 얼마나 많은 다른 노드가 거래를보고 확인했는지 나타내는 푸른 색 직사각형이 보일 것이다. 확인 사항이 많을수록 코드가 배포되었음을 확신 할 수 있다. 



Admin 페이지 링크를 클릭하면 새로 만든 통화로 원하는 모든 것을 할 수있는 세계에서 가장 간단한 중앙 은행 대시 보드를 이용할 수 있다.

계약서 읽기 아래 왼쪽에는 무료로 계약 정보를 읽을 수있는 모든 옵션과 기능이 있다. 토큰에 소유자가 있으면 여기에 주소가 표시된다. 해당 주소를 복사하여 잔액에 붙여 넣으면 모든 계정의 잔액이 표시된다 (잔액은 토큰이있는 계정 페이지에도 자동으로 표시됨).

오른쪽 계약서에 쓰기에서 어떤 방식으로든 블록 체인을 변경하거나 변경할 수있는 모든 기능을 볼 수 있다. 이것들은 가스를 소비 할 것이다. 새로운 동전을 만들 수있는 계약을 만든 경우 "민트 토큰"이라는 기능이 있어야한다. 그것을 선택한다.



새 통화가 생성 될 주소를 선택한 다음 금액을 입력한다 (소숫점이 2로 설정된 경우 금액 뒤에 2 개의 숫자를 추가하여 정확한 수량을 생성한다). 선택에서 소유자로 설정된 계정을 실행하고 이더 금액을 0으로두고 실행을 누른다.

몇 가지 확인 후 수취인 잔액은 새로운 금액을 반영하도록 업데이트된다. 그러나 수령인 지갑에 자동으로 표시되지 않을 수 있다. 사용자 정의 토큰을 인식하려면 지갑을 수동으로 감시 목록에 추가해야 한다. 토큰 주소를 복사한다 (관리자 페이지에서 사본 주소를 누르면된다). 아직 계약 탭으로 이동하지 않았다면 시계 토큰을 누른 다음 주소를 추가한다. 표시되는 이름, 기호 및 십진수는 최종 사용자가 사용자 정의 할 수 있다. 특히 비슷한 (또는 동일한) 이름을 가진 다른 토큰이있는 경우에 유용합니다. 기본 아이콘은 변경 가능하지 않으므로 토큰을 보내고받을 때 주의해야 한다. 토큰을 모방하지 않으면 실제 거래를 처리 할 수 있습니다.



Posted by 삼스

댓글을 달아 주세요

  1. 이규

    저 주소에서 이름, 심볼, 십진법만 적으면 토큰 만들 수 있는건가요??
    8억 개로 된 토큰을 만들고 싶으면 어떻게 해야할까요??

    2017.07.17 09:19 [ ADDR : EDIT/ DEL : REPLY ]
    • 저도 문서번역과 함께 테스트해보려 했는데 이더가 없어서 못하고 있어요.
      가난해서 이더가 없네요. ㅠㅠ
      이더가 생기면 저 자세히 테스트해보려 합니다.
      진행이되면 여기 다시 게시할려구요.

      2017.08.14 14:06 신고 [ ADDR : EDIT/ DEL ]