JetBrains开发者日见闻(一)之Kotlin/Native 尝鲜篇

2,589

简述: 今天我们来讲点Kotlin中比较时髦的东西,有的人可能会说:“不像你之前的风格啊,之前的文章不是一直在死扣语法以及语法糖背后秘密。当你还在死扣泛型语法的时候,别人的文章早就说了Kotlin/Native和Kotlin1.3的新特性”。瞬间感觉自己out了,今天我们就说说这些时髦的东西,也许你能看到一些和别人不一样的东西哦。

前段时间你们的熊猫小哥哥(也就是我),由于对Kotlin过度热爱,一天偶然看到2018 JetBrains开发者日-Kotlin专场活动,脑袋一热,瞬间心动了,马上就买了门票和火车票去北京(第一次一个人去北京)参加活动了。因为看到有Kotlin中文社区两位大佬(这两位大佬是我一年多以前开始写Kotlin的时候就关注了他们)的演讲日程以及JetBrains资深布道师Hali的演讲,没有过多思考直接买票,不要怂就是干。最后顺便和Kotlin社区的大佬们面个基啥的,谢谢大佬们的热情款待。此次北京之行收获挺多的,有时候知道一些最新技术方向和动态会比你埋头闭门造车好的很多。

因为在我的公众号上(Kotlin开发者联盟),有一些小伙伴希望我能从北京的开发者会上带点东西回来,所以总结了一下结合自己实际的开发,给大家带来以下几篇文章。

  • 1、Kotlin/Native1.0 Beta(尝鲜篇)
  • 2、Kotlin中1.3版本新特性都有哪些?
  • 3、Kotlin中的Coroutine(协程)在Android上应用(协程学前班篇)
  • 4、Ktor异步框架初体验(Ktor学前班篇)

那么,今天就开始第一篇,看过一些大佬写关于Kotlin/ Native的文章,基本上都是翻译了Kotlin Blog的官网博客, 具体如何实践的还是比较少的。今天我不打算这么讲,既然今天的主题是时髦那就讲点有意思的东西。一起来看下今天提纲:

一、重新认识Kotlin语言

在开始之前,我觉得有必要一起重新来认识一下Kotlin这门语言,很多人一直都认为它不就是门JVM语言和Java、Scala一样都是跑在JVM虚拟机上。其实Kotlin并不仅仅是一门JVM语言,它的野心是真的大,JVM语言已经无法满足它的雄心壮志了。它是一门多平台的静态编译型语言,它可以用于JVM上(只不过在JVM层面比较出名而已,导致很多人都认为它是门JVM语言),实则它可以编译成JavaScipt运行在浏览器中也可以编译成IOS的可运行文件跑在LLVM上

二、Kotlin/Native的基本介绍

用官方的话来说Kotlin / Native是一种将Kotlin代码编译为本机二进制文件的技术,可以在没有虚拟机的情况下运行。它是基于LLVM的后端,用于Kotlin编译器和Kotlin标准库的本机实现

Kotlin/Native目前支持以下平台:

  • --- iOS (arm32, arm64, emulator x86_64)
  • --- MacOS (x86_64)
  • --- Android (arm32, arm64)
  • --- Windows (mingw x86_64)
  • --- Linux (x86_64, arm32, MIPS, MIPS little endian)
  • --- WebAssembly (wasm32)

为了更好说明Kotlin/Native能力,下面给出张官方的Kotlin/Native能力图:

对于Kotlin/Native之前一直没有去玩过,只是经常听到社区小伙伴们说编译起来巨慢,感觉好时髦啊。抱着好奇心,并且也符合我们这篇文章时髦的主题,决定一步步带大家玩一玩。

三、Kotlin/Native开发IOS HelloWorld

1、需要准备的开发工具

  • AppCode 2018.1(建议下载最新版本,这里不是最新版本不过也能玩哈,最新版本应该到了2018.3)
  • Kotlin/Native Plugin 181.5087.34(注意: 插件和AppCode IDE的版本匹配问题,建议把IDE安装好,然后IDE搜索下载会默认给最佳匹配的插件版本的)
  • Xcode 9.2(注意: 这里Xcode版本需要AppCode版本匹配,否则会有问题的,不过不匹配的话IDE会有提示的,建议如果AppCode 2018.1(Xcode 9.2), AppCode 2018.3(Xcode 10.0))

2、创建一个Kotlin/Native项目

第一步: 选择左侧的Kotlin/Native, 并选择右侧的Sing View App with a Kotlin/Native Framework

第二步: 填写项目名和包名,选择语言Swift(这里先以Swift为例)

第三步: 最后finish即可创建完毕Kotlin/Native项目,创建完毕后项目结构如下

4、运行Kotlin/Native项目

如果你比较幸运跑起来的话,效果应该是在模拟器装一个APP并且起了一个空白页,终端上输出了"Hello from Kotlin!"的Log,类似这样:

注意: 但是你是真题测试,而且Run顶部默认只有一个IOS Device选项的话,然后你又点了Run 说明而且会报如下错误

这个问题是因为默认IOS Device选项是表示用真机调试哈,然后这边就需要一个IOS开发者账号。设置开发者账号的话,建议使用Xcode去打开该项目然后给该项目配置一个开发者账号。

设置完毕Xcode后,AppCode会自动检测到刷新的。

四、Kotlin/Native开发IOS 运行原理分析

看到上面IOS HelloWorld项目运行起来,大家有没有思考一个问题,Kotlin的代码的代码是怎么在IOS设备上跑起来呢?

实际上,在这背后使用了一些脚本和工具在默默支撑着整个项目的运行,如前所述,Kotlin / Native平台有自己的编译器,但每次想要构建项目时手动运行它明显不是高效的。 所以Kotlin团队了选择Gradle。Kotlin / Native使用Gradle构建工具在Xcode中自动完成Kotlin / Native的整个构建过程。在这里使用Gradle意味着开发人员可以利用其内部增量构建架构,只需构建和下载所需内容,从而节省开发人员的宝贵时间。

如果,你还对上述有点疑问不妨一起来研究下Kotlin/Native项目中的构建参数脚本:

  • 打开构建脚本是需要在Xcode中打开的,具体可以参考如下图:

通过以上项目可以分析到在Xcode中编译一个Kotlin/Native项目,实际上在执行一段shell脚本,并在shell脚本执行中gradlew命令来对Kotlin/Native编译,该脚本调用gradlew工具,该工具是Gradle Build System的一部分,并传递构建环境和调试选项。 然后调用一个konan gradle插件实现项目编译并输出xxx.kexe文件,最后并把它复制到iOS项目构建目录("$TARGET_BUILD_DIR/$EXECUTABLE_PATH")。

最后来看下Supporting Files中的build.gradle构建文件,里面就引入了konan插件(Kotlin/Native编译插件), 有空的话建议可以深入研究下konan插件,这里其实也是比较浅显分析了下整个编译过程,如果深入研究konan插件源码的话,更能透过现象看到Kotlin/Native本质,这点才是最重要的。

buildscript {
    ext.kotlin_version = '1.2.0'
    repositories {
        mavenCentral()
        maven {
            url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
        }
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.7"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib"
}

apply plugin: 'konan'

konan.targets = [
    'ios_arm64', 'ios_x64'
]
        
konanArtifacts {
    program('KotlinNativeOC')
}
        

五、Kotlin/Native项目结构分析

1、Kotlin/Native + Swift项目结构分析

我们知道main函数是很多应用程序的入口,ios也不例外,在AppDelegate.swift中有@UIApplicationMain的注解,这里就是APP启动的入口。

@UIApplicationMain //main函数注解入口,所以AppDelegate类相当于启动入口类
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?//默认加了UIWindow



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // KNFKotlinNativeFramework class is located in the framework that is generated during build.
    // If it is not resolved, try building for the device (not simulator) and reopening the project
    NSLog("%@", KNFKotlinNativeFramework().helloFromKotlin())//注意: 这里就是调用了Kotlin中的一个helloFromKotlin方法,并把返回值用Log打印出来,所以你会看到App启动的时候是有一段Log被打印出来
                  
    return true
    }
    ...
}

KotlinNativeFramework类

class KotlinNativeFramework {
    fun helloFromKotlin() = "Hello from Kotlin!" //返回一个Hello from Kotlin!字符串
}

但是呢,有追求的程序员绝对不能允许跑出来的是一个空白页面,空白页面那还怎么装逼呢? 哈哈。在ViewController.swift中的viewDidLoad函数中加入一个文本(UILabel)。

class ViewController: UIViewController {
    override func viewDidLoad() {
    super.viewDidLoad()
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 21))
        label.center = CGPoint(x: 160, y: 285)
        label.textAlignment = .center
        label.font = label.font.withSize(15)
        label.text = "Hello IOS, I'm from Kotlin/Native"
        view.addSubview(label)
    }
    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
    }
}

最后重新run一遍,效果如下:

2、Kotlin/Native + Objective C项目结构分析

在IOS同事帮助下,进一步了解IOS APP启动基本知识,这将有助于我们接下来改造我们项目结构,使得它更加简单,完全可以删除额外的Swift代码,包括APP启动代理那块都交由Kotlin来完成。

  • 第一步: 先创建一个Kotlin/Native + OC 的项目,这里就不重复创建过程,直接把OC目录结构给出:

  • 第二步: 可以看到OC与Swift项目结构差不多哈,可以看到其中有几个重要的文件,main.m、AppDelegate.m、ViewController.m main.m APP启动入口,相当于main函数,先从main函数入手,然后一步步弄清整个启动流程。
#import <UIKit/UIKit.h>
#import "AppDelegate.h"


int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));//这里也调用了AppDelegate类
    }

}
  • 第三步: 然后转到AppDelegate.m,可以看到在didFinishLaunchingWithOptions函数中调用了KNFKotlinNativeFramework中的helloFromKotlin函数。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // KNFKotlinNativeFramework class is located in the framework that is generated during build.
    // If it is not resolved, try building for the device (not simulator) and reopening the project
    NSLog(@"%@", [[[KNFKotlinNativeFramework alloc] init] helloFromKotlin]);//注意这里调用helloFromKotlin,并输出日志
                  
    return YES;
}

3、Kotlin/Native + Kotlin项目结构分析

到这里很多人就会问了,看你上面说了那么并没有看到你Kotlin在做什么事,全是Swift和OC在做APP启动。现在就是告诉你Kotlin如何去替代它们做APP启动的事了。

  • 先新创建一个项目,这次创建的不再是Sing View App with a Kotlin/Native Framework, 而是一个Application项目。

  • 生成后的目录文件全是Kotlin文件,具体如下:

  • 生成的main.kt替代main.m,并设置对应启动的AppDelegate
import kotlinx.cinterop.autoreleasepool
import kotlinx.cinterop.cstr
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toCValues
import platform.Foundation.NSStringFromClass
import platform.UIKit.UIApplicationMain

fun main(args: Array<String>) {
    memScoped {
        val argc = args.size + 1
        val argv = (arrayOf("konan") + args).map { it.cstr.getPointer(memScope) }.toCValues()

        autoreleasepool {
            UIApplicationMain(argc, argv, null, NSStringFromClass(AppDelegate))//注意: 在这里设置对应启动的AppDelegate
        }
    }
}
  • 生成AppDelegate替代原来的AppDelegate.m,并且在内部设置好启动的Window.
import kotlinx.cinterop.initBy
import platform.Foundation.NSLog
import platform.UIKit.*

class AppDelegate : UIResponder(), UIApplicationDelegateProtocol {
    override fun init() = initBy(AppDelegate())
    private var _window: UIWindow? = null
    override fun window() = _window
    override fun setWindow(window: UIWindow?) { _window = window }
    override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {//监听APP启动完成,打印Log
        NSLog("this is launch from kotlin appDelegate")
        return true
}
    companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta//注意:一定得有个companion object否则在main函数NSStringFromClass(AppDelegate)会报错
}
  • 再生成一个ViewController,这个ViewController很类似Android中的Activity。
import kotlinx.cinterop.*
import platform.Foundation.*
import platform.UIKit.*

@ExportObjCClass
class ViewController : UIViewController {

    constructor(aDecoder: NSCoder) : super(aDecoder)
    override fun initWithCoder(aDecoder: NSCoder) = initBy(ViewController(aDecoder))

    @ObjCOutlet
    lateinit var label: UILabel

    @ObjCOutlet
    lateinit var textField: UITextField

    @ObjCOutlet
    lateinit var button: UIButton

    @ObjCAction
    fun buttonPressed() {
        label.text = "Konan says: 'Hello, ${textField.text}!'"
    }
}

运行出来的效果如下:

六、Kotlin/Native开发一个地图Demo

1、IOS项目ViewController与组件绑定过程分析

看到上面的运行Demo,大家有没有在思考一个问题IOS项目中的ViewController是怎么和UI组件绑定在一起的呢?我个人认为这个很重要,换句话说这就是IOS开发最基本的套路,如果这个都不弄明白的话,下面Demo开发就是云里雾里了,掌握了这个基本套路的话,作为一个Android开发者,你基本上就可以在IOS项目开发中任意折腾了。

  • 第一步: 在kotlin目录下新建一个KNMapViewController类,并且它去继承UIViewController以及实现MKMapViewDelegateProtocol接口,并重写viewDidLoad()函数。并且在viewDidLoad函数实现map地图基本配置。
//导入Kotlin以与Objective-C和一些Cocoa Touch框架互操作。
import kotlinx.cinterop.*
import platform.CoreLocation.CLLocationCoordinate2DMake
import platform.Foundation.*
import platform.MapKit.MKCoordinateRegionMake
import platform.MapKit.MKCoordinateSpanMake
import platform.MapKit.MKMapView
import platform.MapKit.MKMapViewDelegateProtocol
import platform.UIKit.*

@ExportObjCClass//注意: @ExportObjCClass注解有助于Kotlin创建一个在运行时可查找的类。
class KNMapViewController: UIViewController, MKMapViewDelegateProtocol {
    @ObjCOutlet //注意: @ObjCOutlet注解很重要,主要是将mMapView属性设置为outlet。这允许您将Main.storyboard中的MKMapview链接到此属性。
    lateinit var mMapView: MKMapView
    constructor(aDecoder: NSCoder) : super(aDecoder)
    override fun initWithCoder(aDecoder: NSCoder) = initBy(KNMapViewController(aDecoder))
    override fun viewDidLoad() {
        super.viewDidLoad()
        val center = CLLocationCoordinate2DMake(32.07, 118.78)
        val span = MKCoordinateSpanMake(0.7, 0.7)
        val region = MKCoordinateRegionMake(center, span)

        with(mMapView) {
            delegate = this@KNMapViewController
            setRegion(region, true)
        }
    }
}
  • 第二步: 用Xcode打开项目中的Main.storyboard,删除原来自动生成一些视图组件(如果你处于AppCode中开发项目,实际上直接在AppCode中双击Main.storyboard就会自动使用Xcode打开当前整个项目,并打开这个项目)

  • 第三步: 给当前空的视图绑定对应ViewController,这里是KNMapViewController

  • 第四步: 在当前空的视图中添加一个map view组件并且设置组件的约束条件。

  • 第五步: 右击组件MKMapView可以看到黑色对话框,里面Referencing Outlets还空的,说明当前ViewController没有和MKMapView组件绑定

  • 第六步: 配置outlet,这里说下AppCode很坑爹地方,需要手动去source code中手动配置outlet,选中main.storyboard右击open as 然后选择打开source code

  • 第七步: 在view和viewController结尾标签之间配置connection
    配置的code如下:
<connections>
   <outlet property="mMapView" destination="dest id" id="generate id"/>
</connections>
<!--property属性值就是KNMapViewController中的mMapView变量名;destination属性值是一个map view标签中id(可以在subviews标签内的mapView标签中找到id), id属性则是自动生成的,可以按照格式自己之指定一个,只要不出现重复的id即可-->

配置结果如下:

  • 第八步: 检验是否绑定成功, 回到main.stroyboard视图,右击组件查看黑色框是否出现如下绑定关系,出现了则说明配置成功。

2、接着上述配置步骤,就可以回到AppCode中运行项目了

以上的运行结果就说明了,我们Demo已经运行成功了。并且我已经把此次Kotlin/Native项目都放到了GitHub上,如果感兴趣的小伙伴可以clone下来玩一玩这个时髦的鬼东西。如果觉得对你有帮助,还请大佬们给个star。

Kotlin/Native Demo GitHub

七、Kotlin/Native开发体验分析

Kotlin/Native目前还是处于1.0的beta,所以还是有很多的地方是不让人满意的。下面我总结这次Kotlin/Native开发体验的优缺点:

优点:

通过上述的几个例子,可以明显表明Kotlin/Native语言层面跨平台能力还是很强的,和OC,Swfit项目操作性也很强,该有的基本上都已经实现了,所以对于它后续发展还是非常值得关注的。据我了解到,Kotlin团队目前首要重心就是在Kotlin/Native这一块,希望他们能给我们带来更多关于Kotlin/Native的惊喜。

缺点

缺点还是有很多的:

  • 1、Kotlin/Native编译速度有点慢的惊人,不过运行速度还是不错的。
  • 2、AppCode中用Kotlin开发IOS项目,没有很强的代码提示,很多API导包都比较麻烦,对于初学者更是一脸懵逼。
  • 3、AppCode中的Kotlin/Native插件居然没提供右键创建Kotlin类、接口之类,包括官方也给出这是个问题不过提供一个fix方案。

最后可以测试一下:

  • 4、AppCode中使用Kotlin开发IOS项目,配置outlet还得切换到在Xcode手工操作,希望后续改进。
  • 5、AppCode耗内存还大了,比AndroidStudio还厉害,多开了两个项目,基本就是每操作IDE就在转圈圈,希望后续改进。

八、聊点关于Kotlin/Native和Flutter框架那点事

1、简述:

其实关于这个主题,我是不太想去说的,因为我不想引起编程语言界的口战。但是总有人喜欢去把Kotlin/Native和Flutter放在一起去做对比,因为他们好像都有一个共同点就是跨平台都能开发Android、IOS应用。并且在此次Jetbrains开发者日上就有参会嘉宾在会上问官方布道师Hali,Kotlin/Native和Flutter有什么不一样,优势在哪?

2、提出个人观点:

针对这个问题我说下个人的观点:

首先,我个人是非常看好Flutter这个移动端跨平台框架,它能够完全抹平Android,IOS API层面的差异性。换句话说就是一个小白,也许他不懂Java,OC或Swift,他只要熟悉dart并且熟悉Flutter框架API就能开发出Android和IOS两个原生般体验的应用。确实很爽啊,无论站在公司节约人力和维护成本还是开发者技术成本角度考虑都是不错的选择。可是从另一角度可以想象下一旦形成这样的局面,很多新项目都是用dart开发,swift和oc语言对于新的开发者而言你会去用吗? 苹果爸爸貌似就会不开心了,这其中故事大家可以自己去想像了(当然也许未来不是我说的那样发展,纯属个人猜想的哈)。

然后,我说下Kotlin/Native吧,其实Kotlin/Native和Flutter不是一个层面东西,不太很好做对比,Kotlin/Native更多的是语言编译器层面,而Flutter是框架层面。两者根本就不是一个世界的。Flutter是自己实现一套渲染机制和UI引擎,并且有着丰富Development API。而Kotlin/Native更多关注是编译器如何将kt源码编译成IOS LLVM可运行的二进制码,但是Kotlin/Native并没有像Flutter一样在API层面抹平平台差异性,而是 在语言层面做了平台差异性抹平。也就是说你要开发IOS应用不仅会Kotlin/Native还得会IOS 应用开发Development Api. 这貌似Kotlin/Native稍逊Flutter一筹,但是想想也有道理,API层面抹平不是在语言层面职责,还是需要框架层面来做到这一点,说不定哪天Kotlin团队也造出一个类似Flutter的轮子呢。而且语言层面跨平台带来的还有一点好处就是共享代码,Android、IOS、前端都可以用Kotlin来实现,可以把它们拥有相同逻辑用Kotlin编写的代码放入一个common包中统一管理,并且Kotlin对多平台共享这块做了很好的支持,然后来自于不同平台可以共享这块通用的逻辑实现,不用各自平台去写一套了。

3、给开发者抉择建议:

通过上述观点,我们总结到对于开发者而言,学习一门新的技术实际上一般会存在两层隐形成本。一层是对新的开发语言的掌握,另一层则是对这门技术的Development Api的熟悉和掌握。那么Kotlin/Native就是为了磨平第一层开发语言的障碍。语言层面能做的也只能这样了,不像Flutter它是一个框架,它可以直接从API层面抹平。如果Flutter最终能推广起来,在热更新和热修复这块支持比现在的原生还好的话,并且你是一个初学者,那么它绝对是一个不错的选择。

如果你是Kotlin开发者,你完全可以使用Kotlin/Native然后花一些时间熟悉下IOS的API也能把IOS应用玩起来。当然Kotlin/Native开发IOS应用只是其中一小部分,你完全用它去做更多有意义的事。

如果你是移动开发者,建议两门技术都可以去尝试一下毕竟技多不压身,当然语言只是一门工具,最终靠还是计算机扎实的基础、分析需求的能力以及架构项目的功能能力。有了这些能力再加上一个高效的编程语言那么你就更加所向披靡了。

Kotlin系列文章,欢迎查看:

原创系列:

翻译系列:

实战系列:

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~