본문 링크 (Original Link)

안드로이드 앱들에서의 스위프트

2017.08.20

# • #

by John Holdsworth, translated by pilgwon

iOS와 Android 생태계 사이에 코드를 공유할 수 없는 것은 이미 알려진 문제이고 Java가 빠른 시일내에 iOS에 나타날 가능성은 거의 없기때문에(Kotlin은 가능할지도 모르죠!), Swift를 Android 앱들에 사용할 수 있는지에 대한 가능성에 초점을 맞춰야 합니다.

Swift가 오픈 소스화 된 지 얼마안돼, Zhuowei Zhang는 swift-dev 메일링 목록으로 메일을 보냈고, Android 바이너리들을 만들어낼 수 있는 Swift의 포크를 소개했습니다. Brian Gesiak가 풀 리퀘스트를 작업했고 2016년 2월에 머지되었습니다. Zhuowei는 또한 이 컨셉 어플리케이션을 발표했는데, 가장 중요한 부분은 그것이 Swift Package Manager를 빌드에 사용해서 당신이 기존의 프로젝트들처럼 다른 오픈 소스 파일들을 git으로 가져올 수 있다는 점입니다. Geordie J가 간단한 트릭을 통해 Swift가 포함된 앱을 Google Play에 올리는 방법을 발표한 것은 얼마 되지 않았지만 이에 대한 DispatchFoundation은 빠르게 진행되었습니다.

Docker 컨테이너에서 안드로이드를 위한 Swift 도구모음을 구축하는데에 사용할 수 있는 Gonazalo Lerralde의 swifty 로봇 환경을 통해 Swift 4는 다시 한 번 힘을 얻었습니다. 이 점과 최근의 두가지 PR들1 2은 Swift로 작성된 iOS, Android 간에 모델, 비즈니스 로직 그리고 네트워크 코드를 공유할 수 있는 가능성을 열어주는 환경이 존재한다는 것을 의미합니다.

Swift Android Toolchain 베타

이러한 컨트리뷰션들을 하나로 모은 Toolchain은 Android을 위해 준비되었고, 이 링크에서 다운로드 받으실 수 있습니다.

아카이브를 추출한 후에 swift-install/setup.sh 스크립트를 실행시키면 이 도구모음을 사용하여 macOS 나 Ubuntu Linux 16.04에서 개발할 수 있습니다. 이것은 기존의 도구모음을 릴리즈 트리로 연결시키며 빌드 과정에 연결하기 위해 기본적인 그래들을 설치합니다. Android Studio 프로젝트에 Swift를 추가하려면 net.zhuoweizhang:swiftandroid:1.0.0build.gradle의 클래스 경로 디펜던시를 추가하고 적용하세요.

이 아이디어들은 모두 이 간단한 예제앱에서 나왔습니다. Swift를 위한 몇몇 일반적으로 지원되는 JNI코드는 Package.swift에 포함 된 java_swift 프로젝트에서 가져온 것들입니다. 도구모음의 그래들 플러그인의 한 기능은 코드 생성기인 genswift.java를 포함하고 있고, 당신의 바인딩 파일의 패키지 이름이 “swiftbindings”라는 경로를 포함하고 있으면 필요한 경우에 모든 JNI 코드 생성기가 자동으로 작동합니다.

예를 들면, 앱 바인딩 소스들은 이 디렉토리에 있습니다. src/main/java/com/johnholdsworth/swiftbindings/*.java

생성된 JNI가 필요한 Swift 코드들은 이 곳에 있습니다. src/main/swift/Sources

Java에서 Swift로 소통하는데에 도움되는 Java 프록시 클래스들은 이 곳에 생성됩니다. src/main/java/org/swiftjava/com_johnholdsworth/*Proxy.java

결과는 다음과 같은 어플리케이션 & 빌드 과정입니다.

image1

이 도구모음은 베타 단계에 있는걸로 설명할 수 있고, Swift 4가 발표될 때 배포될 것으로 보여집니다. 만약 이 도구모음에 문제가 있다면, 이 목적으로 설정된 레포 문제를 신고하거나 이메일로 작성자에게 문의하세요. 주요한 출시에 대한 공지사항은 twitter@Injection4Xcode에서 보실 수 있습니다.

코드 생성기 - genswift.java

네이티브 앱들이 Java와 소통할 수 있는 유일한 방법인 Java Native Interface, JNI는 완전히 검사되지 않은 보일러 플레이트 코드를 요구하는 것으로 유명합니다. 이것을 피하기 위해서, 코드 생성 스크립트가 작성되어 개발자의 손에서 제거학고 강력한 유형의 안전성을 가져옵니다. 이 스크립트는 인터페이스들과 유형들을 지정하는 Java 소스들(“바인딩들”) 세트를 입력으로 사용합니다. 이 생성된 코드를 이용하여, 프리미티브, 객체, 컬렉션을 Java에서 Swift로 전달하고 메세지를 보낼 수 있습니다. 반대로, Swift 객체와 구조, 컬렉션은 바인딩에서 선언된 프로토콜 중 하나를 따르고 프로토콜의 이름이 “Listener”로 끝나면 Java로 다시 전달될 수 있습니다.

다음의 타입 매핑은 코드 생성기에서 사용 가능하고 인자나 반환값으로 사용됩니다.

image2

관습적으로, 모든 비-프리미티브 인자들은 선택 사항이고 비-프리미티브를 반환하는 함수의 반환 타입은 암묵적으로 풀리지 않은 선택 사항입니다. 반환 가능한 함수는 Exception 객체를 Swift에서 Java로 그 반대로도 반환할 수 있습니다.

두 가지 예제 어플리케이션의 양면을 연결하는 일반적인 바인딩 파일은 다음과 같이 보입니다.

package com.johnholdsworth.swiftbindings;

import com.johnholdsworth.swiftbindings.SwiftHelloTypes.TextListener;
import com.johnholdsworth.swiftbindings.SwiftHelloTypes.ListenerMap;
import com.johnholdsworth.swiftbindings.SwiftHelloTypes.ListenerMapList;

public interface SwiftHelloBinding {

    // JavaActivity 에서 Swift로 메세지 보내기
    public interface Listener {

        public void setCacheDir( String cacheDir );

        public void processNumber( double number );

        public void processText( String text );

        public void processedMap( ListenerMap map );

        public void processedMapList( ListenerMapList map );

        public void processStringMap( StringMap map );

        public void processStringMapList( StringMapList map );

        public double throwException() throws Exception;

        public SwiftHelloTest.TestListener testResponder( int loopback );
    }

    // Swift에서 Activity로 메세지 돌려보내기
    public interface Responder {

        public void processedNumber( double number );

        public void processedText( String text );

        public void processedTextListener( TextListener text );

        public void processedTextListenerArray( TextListener text[] );

        public void processedTextListener2dArray( TextListener text[][] );

        public void processMap( ListenerMap map );

        public void processMapList( ListenerMapList map );

        public void processedStringMap( StringMap map );

        public void processedStringMapList( StringMapList map );

        public double throwException() throws Exception;

        public String[] debug( String msg );

        public SwiftHelloTest.TestListener testResponder( int loopback );
    }
}

타입들을 자체 파일로 분리할 수 있습니다:

// Java와 Swift 사이에 공유된 타입과 인터페이스

package com.johnholdsworth.swiftbindings;

import java.util.HashMap;

public interface SwiftHelloTypes {

    // Java에 객체를 발표하는 예제.
    // 관련된 프로토콜을 클래스에 추가하고
    // 객체는 응답자 메세지로 전달 될 수 있습니다.
    public interface TextListener {
        public String getText();
    }

    // 이 부분들은 Java jars 에서 Type Erasure 때문에 필요합니다.
    public static class ListenerMap extends HashMap<String,TextListener> {
        public static Class<?> valueClass() {
            return TextListener.class;
        }
    }

    public static class ListenerMapList extends HashMap<String,TextListener[]> {
        public static Class<?> valueClass() {
            return (new TextListener [] {}).getClass();
        }
    }

    public static class StringMap extends HashMap<String,String> {
        public static Class<?> valueClass() {
            return String.class;
        }
        public StringMap() {
            super();
        }
        @SuppressWarnings("unchecked")
        public StringMap(Map map) {
            super(map);
        }
    }

    public static class StringMapList extends HashMap<String,String[]> {
        public static Class<?> valueClass() {
            return (new String [] {}).getClass();
        }
        public StringMapList() {
            super();
        }
        @SuppressWarnings("unchecked")
        public StringMapList(Map map) {
            super(map);
        }
    }
}

마지막으로, 이 어플리케이션의 양면을 묶기위해서는 Swift로 만들어진 두 언어를 묶기 위한 하나의 JNI 진입점이 있습니다. 이것은 어플리케이션이 시작될 때 Java의 네이티브 메소드로 호출됩니다.

// 어플리케이션의 자바 부분으로 다시 연결합니다.
var responder: SwiftHelloBinding_ResponderForward!

// 앱의 Java와 Swift 섹션을 묶기위한 일회성 호출
@_silgen_name("Java_net_zhuoweizhang_swifthello_SwiftHello_bind")
public func bind_samples( __env: UnsafeMutablePointer<JNIEnv?>, __this: jobject?, __self: jobject? )-> jobject? {

    // 이 Swift 인스턴스는 Java로 JNI를 통해 전달됩니다.
    responder = SwiftHelloBinding_ResponderForward( javaObject: __self )

    // 이 Swift 인스턴스는 Java에서 네이티브 호출을 받습니다.
    var locals = [jobject]()
    return SwiftListenerImpl().localJavaObject( &locals )
}

이제는 일반적으로 바인딩 인터페이스에 대해 Java 또는 Kotlin 코드를 작성하고 SwiftHelloTypes_TextListener와 같은 이름을 갖는 생성된 유형에 대해 Swift 코드를 작성합니다. 당신은 Android쪽의 JavaKotlin에서 어떻게 보이는지 볼 수 있습니다. 그에 맞는 Swift 구현 방식은 여기서 보실 수 있습니다.

2017년 8월 14일, 월요일

image3