2016-03-31 12:07:00 cpongo3 阅读数 2
  • swift基础视频教程

    Swift,苹果于2014年苹果开发者大会发布的新开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序。 Swift是一款易学易用的编程语言,而且它还是第一套具有与脚本语言同样的表现力和趣味性的系统编程语言。Swift的设计以安全为出发点,以避免各种常见的编程错误类别。

    7088 人正在学习 去看看 传智

Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序。Swift已经开源,目前最新版本为2.2。我们知道Objective-C是具有动态性的,能够通过runtime API调用和替换任意方法,那Swift也具有这些动态性吗?\

分析用例

\

我们拿一个纯Swift类和一个继承自NSObject的类来做分析,这两个类里包含尽量多的Swift的类型比如Character、String、AnyObject、Tuple。

代码如下:

(点击放大图像)

\

587f29ad34082967b268ae7205bce925.png

\\

方法、属性

\

动态性比较重要的一点就是能够拿到某个类所有的方法、属性,我们使用如下代码来打印方法和属性列表。

(点击放大图像)

\

9e469dfea770d9fb02ce88f8f0f95fc9.png

\\

调用showClsRuntime的代码如下:

let aSwiftClass:TestASwiftClass = TestASwiftClass();\showClsRuntime(object_getClass(aSwiftClass));\print(\"\\");\showClsRuntime(object_getClass(self));
\

看看我们得到什么结果?\

(点击放大图像)

\

b06b22ec14fb15487203aa9d8bb2ba0f.png

\

* 对于纯Swift的TestASwiftClass来说任何方法、属性都未获取到

* 对于TestSwiftVC来说除testReturnTuple

testReturnVoidWithaCharacter两个方法外,其他的都获取成功了。\

这是为什么?\

  • 纯Swift类的函数调用已经不再是Objective-c的运行时发消息,而是类似C++的vtable,在编译时就确定了调用哪个函数,所以没法通过runtime获取方法、属性。 \
  • TestSwiftVC继承自UIViewController,基类为NSObject,而Swift为了兼容Objective-C,凡是继承自NSObject的类都会保留其动态性,所以我们能通过runtime拿到他的方法。

但为什么testReturnTuple

testReturnVoidWithaCharacter却又获取不到呢?\

从Objective-c的runtime 特性可以知道,所有运行时方法都依赖TypeEncoding,也就是method_getTypeEncoding返回的结果,他指定了方法的参数类型以及在函数调用时参数入栈所要的内存空间,没有这个标识就无法动态的压入参数(比如testReturnVoidWithaId: Optional(\"v24@0:8@16\") Optional(\"v\"),表示此方法参数共需24个字节,返回值为void,第一个参数为id,第二个为selector,第三个为id),而Character和Tuple是Swift特有的,无法映射到OC的类型,更无法用OC的typeEncoding表示,也就没法通过runtime获取了。\

Method Swizzling

\

动态性最常用的就是方法替换(Method Swizzling),将类的某个方法替换成自定义的方法,从而达到hook的作用。\

  • 对于纯Swift类(如TestASwiftClass)来说,无法通过objc runtime替换方法,因为由上面的测试可知拿不到这些方法、属性 \
  • 对于继承自NSObject类(如TestSwiftVC)来说,无法通过runtime获取到的方法肯定没法替换了。那能通过runtime获取到的方法就都能被替换吗?我们测一把。

    Method Swizzling的代码如下

(点击放大图像)

\

b5b51c481f05e0ae7d161f127bc61263.png

\

我们替换两个可以被runtime获取到的方法:viewDidAppeartestReturnVoidWithaId\

(点击放大图像)

\

a860fa74d84e4215c8ad5ff6471df24e.png

\

打印的日志为

F:testReturnVoidWithaId L:50\F:sz_viewDidAppear L:46
\

说明viewDidAppear已经被替换,但是testReturnVoidWithaId却没有被替换,这是为何?\

我们在方法里打个断点看看,如图:

(点击放大图像)

\

626f5e45d68fa289e18d732ef0cf6572.png

\

(点击放大图像)

\

0a1b212befb87a47fedf1ae48b4daf3e.png

\

可以看到区别,调用sz_viewDidAppear栈的前一帧为@objc TestSwiftVC.sz_viewDidAppear(Bool) -\u0026gt; ()有个@objc标识,而调用testReturnVoidWithaId则没有此标识。

@objc用来做什么的?与动态性有关吗?\

@objc

\

找到官方文档读读。

可以知道@objc是用来将Swift的API导出给Objective-C和Objective-C runtime使用的,如果你的类继承自Objective-c的类(如NSObject)将会自动被编译器插入@objc标识。

我们在把TestASwiftClass(纯Swift类)的方法、属性前都加个@objc 试试,如图:

(点击放大图像)

\

c315b59dde9e84f548283c0fa5992995.png

\

查看日志可以发现加了@objc的方法、属性均可以被runtime获取到了。

(点击放大图像)

\

7aa3d9b3dad528fa547b70e9ecc5e92d.png

\\

dynamic

\

文档里还有一句说明:

加了@objc标识的方法、属性无法保证都会被运行时调用,

因为Swift会做静态优化。要想完全被动态调用,必须使用dynamic修饰。

使用dynamic修饰将会隐式的加上@objc标识

这也就解释了为什么testReturnVoidWithaId无法被替换,因为写在Swift里的代码直接被编译优化成静态调用了。

而viewDidAppear是继承Objective-C类获得的方法,本身就被修饰为dynamic,所以能被动态替换。

我们把TestSwiftVC方法前加上dynamic再测一把,如图:

(点击放大图像)

\

614cf425c24bb37c077a483917b3223e.png

\

从堆栈也可以看出,方法的调用前增加了@objc标识,testReturnVoidWithaId方法被替换成功了。\

同样的做法,我们把TestASwiftClass的方法和属性也都加上dynamic修饰,做Method Swizzling,同样获得成功,如图

(点击放大图像)

\

a54ad26e8e01c87eabf583244cbdf18b.png

\\

Objective-C获取Swift runtime信息

\

在Objective-c代码里使用objc_getClass(\"TestSwiftVC\");会发现返回值为空,这是为什么?Swift代码中的TestSwiftVC类,在OC中还是这个名字吗?

我们初始化一个对象,并断点和打印看看,如下图:

(点击放大图像)

\

d9557f93db1520111e638c8276c29255.png

\

可以看到Swift中的TestSwiftVC类在OC中的类名已经变成TestSwift.TestSwiftVC,即规则为SWIFT_MODULE_NAME.类名称,在普通源码项目里SWIFT_MODULE_NAME即为ProductName,在打好的Cocoa Touch Framework里为则为导出的包名。\

所以要想从Objective-c中获取Swift类的runtime信息得这样写:

id cls = objc_getClass(\"TestSwift.TestASwiftClass\");\showClsRuntime(cls);\id cls2 = objc_getClass(\"TestSwift.TestSwiftVC\");\showClsRuntime(cls2);
\

Objective-C替换Swift函数

\

给TestSwiftVC和TestASwiftClass的testReturnVoidWithaId函数加上dynamic修饰,然后我们在Objective-C代码里替换为testReturnVoidWithaIdImp函数:

(点击放大图像)

\

6a2e92c1ed0db862adf542460153e64b.png

\

运行之后我们得到结果

F:void testReturnVoidWithaIdImp(__strong id, SEL, __strong id) L:20 self=\u0026lt;TestSwift.TestSwiftVC: 0x7fb4e1d148f0\u0026gt;\F:void testReturnVoidWithaIdImp(__strong id, SEL, __strong id) L:20 self=TestSwift.TestASwiftClass
\

说明两者的方法在加上dynamic修饰后,均能在Objective-c里被替换。(TestSwiftVC的testReturnVoidWithaId不加dynamic也会打印日志,为什么?留给读者思考)\

总结

\
  • 纯Swift类没有动态性,但在方法、属性前添加dynamic修饰可以获得动态性。 \
  • 继承自NSObject的Swift类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加dynamic修饰才可以获得动态性。 \
  • 若方法的参数、属性类型为Swift特有、无法映射到Objective-C的类型(如Character、Tuple),则此方法、属性无法添加dynamic修饰(会编译错误) \
  • Swift类在Objective-C中会有模块前缀

本文作者

\

尹峥伟(花名 君展),来自手机淘宝技术团队的资深无线开发工程师,主要负责手机淘宝基础架构研发,github开源库Wax的维护者,微信号yzwlvzxh,微博@君展。\\\


感谢徐川对本文的审校。

\

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

2017-02-09 16:12:43 zhaochen_009 阅读数 2939
  • swift基础视频教程

    Swift,苹果于2014年苹果开发者大会发布的新开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序。 Swift是一款易学易用的编程语言,而且它还是第一套具有与脚本语言同样的表现力和趣味性的系统编程语言。Swift的设计以安全为出发点,以避免各种常见的编程错误类别。

    7088 人正在学习 去看看 传智

写着玩儿的小程序,继续学习swift.运行效果+代码+知识点总结

运行效果:

           

代码:

Canvas类:画布,画图板状态管理、交互、处理手势
class Canvas:UIView{
    //负责线条的生成、操作与管理
    private let pathCreator:PathCreator
    //是否处于擦除状态
    private var isInErasering:Bool
    //橡皮擦视图
    private let eraserView:UIView
    
    override init(frame: CGRect) {
        isInErasering = false
        pathCreator = PathCreator()
        
        eraserView = UIView.init()
        eraserView.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
        eraserView.backgroundColor = UIColor.white
        eraserView.alpha = 0

        super.init(frame: frame)
        
        self.backgroundColor = UIColor.black
        
        self.addSubview(eraserView)
        
        let revokeBut = UIButton(type: UIButtonType.system)
        revokeBut.frame = CGRect(x: 20, y: 20, width: 80, height: 30)
        revokeBut.setTitle("撤销", for: UIControlState.normal)
        revokeBut.addTarget(self, action: #selector(revokeButClick), for: UIControlEvents.touchUpInside)
        self.addSubview(revokeBut)
        
        let cleanBut = UIButton(type: UIButtonType.system)
        cleanBut.frame = CGRect(x: 110, y: 20, width: 80, height: 30)
        cleanBut.setTitle("清空", for: UIControlState.normal)
        cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside)
        self.addSubview(cleanBut)
    
        let eraserBut = UIButton(type: UIButtonType.system)
        eraserBut.frame = CGRect(x: 200, y: 20, width:80, height: 30)
        eraserBut.setTitle("橡皮", for: UIControlState.normal)
        eraserBut.setTitle("画笔", for: UIControlState.selected)
        eraserBut.addTarget(self, action: #selector(eraserButClick(but:)), for: UIControlEvents.touchUpInside)
        self.addSubview(eraserBut)
        
        let ges = UIPanGestureRecognizer(target: self, action:#selector(handleGes(ges:)))
        ges.maximumNumberOfTouches = 1
        self.addGestureRecognizer(ges)
    }
    
    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override public func layoutSubviews() {
        
    }
    
    @objc private func handleGes(ges:UIPanGestureRecognizer) -> Void {
        let point = ges.location(in: self)
        switch ges.state {
        case UIGestureRecognizerState.began:
            if isInErasering {
                //擦除状态,显示出橡皮擦
                eraserView.alpha = 1
                eraserView.center = point
            }
            //生成新的一笔
            pathCreator.addNewPath(to: point,isEraser: isInErasering)
            self.setNeedsDisplay()
        case UIGestureRecognizerState.changed:
            if isInErasering {
                //移动橡皮擦
                eraserView.center = ges.location(in: self)
            }
            //更新当前笔画路径
            pathCreator.addLineForCurrentPath(to: point,isEraser:isInErasering)
            self.setNeedsDisplay()
        case UIGestureRecognizerState.ended:
            if isInErasering {
                //擦除状态,隐藏橡皮擦
                eraserView.alpha = 0
                eraserView.center = ges.location(in: self)
            }
            //更新当前笔画路径
            pathCreator.addLineForCurrentPath(to: point,isEraser: isInErasering)
            self.setNeedsDisplay()
        case UIGestureRecognizerState.cancelled:
            print("cancel")
        case UIGestureRecognizerState.failed:
            print("fail")
        default:
            return
        }
    }
    
    override public func draw(_ rect: CGRect) {
        //画线
        pathCreator.drawPaths()
    }
    
    @objc private func revokeButClick()->Void{
        //撤销操作
        pathCreator.revoke()
        self.setNeedsDisplay()
    }
    
    @objc private func cleanButClick()->Void{
        //清空操作
        pathCreator.clean()
        self.setNeedsDisplay()
    }
    
    @objc private func eraserButClick(but:UIButton)->Void{
        //切换画图与擦除状态
        if but.isSelected {
            but.isSelected = false
            isInErasering = false
        }else{
            but.isSelected = true
            isInErasering = true
        }
    }
}

PathCreator:具体线条绘制、管理

//每条子线段信息
struct BezierInfo{
    let path:UIBezierPath//具体线段
    let color:UIColor//线段对应颜色
    init(path:UIBezierPath,color:UIColor){
        self.path = path
        self.color = color
    }
}

class PathCreator{
    //所有笔画
    private var paths:[NSMutableArray]?
    //笔画内当前子线段
    private var currentBezierPathInfo:BezierInfo?
    //当前笔画的所有子线段
    private var currentPath:NSMutableArray?
    //当前笔画已经采集处理了几个触摸点
    private var pointCountInOnePath = 0
    
    static let colors = [UIColor.red,UIColor.orange,UIColor.yellow,UIColor.green,UIColor.blue,UIColor.gray,UIColor.purple]
    init() {
        paths = []
    }
    //添加新笔画
    func addNewPath(to:CGPoint,isEraser:Bool)->Void{
        //创建起始线段
        let path = UIBezierPath()
        path.lineWidth = 5
        path.move(to: to)
        path.lineJoinStyle = CGLineJoin.round
        path.lineCapStyle = CGLineCap.round
        if !isEraser {
            //绑定线段与颜色信息
            currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[0])
        }else{
            //处于擦除模式,颜色与画板背景色相同
            currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black)
        }
        //新建一个笔画
        currentPath = NSMutableArray.init()
        //将起始线段加入当前笔画
        currentPath!.add(currentBezierPathInfo)
        pointCountInOnePath = 0
        //将当前笔画加入笔画数组
        paths!.append(currentPath!)
    }
    //添加新的点,更新当前笔画路径
    func addLineForCurrentPath(to:CGPoint,isEraser:Bool) -> Void {
        pointCountInOnePath += 1//同一笔画内,每7个点换一次颜色
        if pointCountInOnePath % 7 == 0{//换颜色
            if let currentBezierPathInfo = currentBezierPathInfo{
                //将当前点加入当前子线段,更新当前子线段路径
                currentBezierPathInfo.path.addLine(to: to)
            }
            //生成新的子线段
            let path = UIBezierPath()
            path.lineWidth = 5
            path.move(to: to)
            path.lineJoinStyle = CGLineJoin.round
            path.lineCapStyle = CGLineCap.round
            if !isEraser{
                //给当前子线段设置下一个颜色
                currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[currentPath!.count % 7])
            }else{
                //处于擦除模式,颜色与画板背景色相同
                currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black)
            }
            //将当前子线段加入当前笔画
            currentPath!.add(currentBezierPathInfo)
        }else{
            if let currentBezierPathInfo = currentBezierPathInfo{
                //将当前点加入当前子线段,更新当前子线段路径
                currentBezierPathInfo.path.addLine(to: to)
            }
        }
    }
    
    func drawPaths()->Void{
        //画线
        let pathCount = paths!.count
        for i in 0..<pathCount{
            //取出所有笔画
            let onePath = paths![i]
            let onePathCount = onePath.count
            for j in 0..<onePathCount{
                //绘制每条笔画内每个子线段
                let pathInfo = onePath.object(at: j) as! BezierInfo
                pathInfo.color.set()
                pathInfo.path.stroke()
            }
        }
    }
    
    func revoke()->Void{
        //移走上一笔画
        if paths!.count > 0 {
            paths!.removeLast()
        }
    }
    
    func clean()->Void{
        //移走所有笔画
        paths!.removeAll()
    }
}

知识点总结:

1.结构体是值传递

一个基础概念,但开始使用时还是给忘了。数组[]在swift中是结构体(struct)实现,值传递。最开始把currentPath声明为了[],添加到paths[]中后,后续再去往currentPath中添加元素,paths中的对应的currentpath对象内容并未随之发生改变,后将currentPath改为了NSMutableArray(引用传递).

2.selector、@objc、private

(纯)swift与oc采用了不同的运行机制,swift不再采用与oc一样的运行时(runtime)与消息分发机制,selector作为oc运行机制的产物,swift中也对其进行了保留与支持。

@objc修饰符的作用是将swift定义的类、方法等暴露给oc。

于是,下列selector中指定的方法,都要使用@objc进行修饰

cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside)
let ges = UIPanGestureRecognizer(target: self, action:#selector(handleGes(ges:)))
如果一个swift类继承自NSObject,swift会默认给该类的非private属性或方法加上@objc修饰。
因为Canvas类(->UIView->UIResponder->NSObject)继承自NSObject,所以其属性或方法(非private)都会被自动加上@objc修饰
但是因为我代码中的这几个selector指向的方法都声明为了private,所以还是需要手动去做@objc修饰(如果是非private的,可以不写@objc)
@objc private func handleGes(ges:UIPanGestureRecognizer) -> Void

3.required的构造函数

required用于修饰构造方法,用于要求子类必需实现对应的构造方法
如果子类中没有实现任何构造方法,则不必去显式的实现父类要求的required构造方法;而当子类中有定义实现构造方法时,则必需显式的去实现父类要求的required构造方法,同时还要保留required修饰.
当实现一个类Canvas继承自UIView时,我们可以看到编译器强制要求我们实现构造方法
public init?(coder aDecoder: NSCoder)
通过xcode找到该方法是在NSCoding协议中被定义的
public protocol NSCoding {
    public func encode(with aCoder: NSCoder)
    public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}
可以看到,此处并没有进行requird修饰,为什么还要求强制实现该构造方法呢?
因为在协议中规定的构造方法,不用显式进行requird修饰,实现协议的对应类默认必需要去实现协议中规定的构造方法,且加上requird修饰

4.as

let x:UInt16 = 100
let y:UInt8 = 10
//x + y会报错,不自动类型转换,更安全
let n = UInt8(x) + y
上面例子中,当我们进行值类型之间的类型转换(UInt16->UInt8)时,其实借助的是UInt8的构造方法
/// Create an instance initialized to `value`.
    public init(integerLiteral value: UInt8)
而当引用类型之间需要进行强制转换时,则需要借助as操作符
因为转换可能失败(两个不相关的类之间进行转换),所以需要使用as?,转换结果为一个可选型,不成功时,可选型值为nil
当然,如果可以肯定转换是成功的,则可以使用as!进行转换,结果为目标类型的对象。
另外,看下面这个例子
var people:People?
let man:Man = Man()
people = man
print(people)//可选型变量
let beMan = people as! Man
print (beMan)//强制转化后beMan不是可选型
var people:People?
let man:Man = Man()
people = man
print(people)//可选型变量
let beMan = people as! Man?
print (beMan)//强制转化后beMan为可选型
转换后的结果类型完全由as!后面的目标类型决定,即便原对象在转换之前是可选型对象,但如果转换的目标类型不是可选型,则转换后得到的也就不是一个可选型了

2018-06-09 09:22:09 xiangzhihong8 阅读数 4948
  • swift基础视频教程

    Swift,苹果于2014年苹果开发者大会发布的新开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序。 Swift是一款易学易用的编程语言,而且它还是第一套具有与脚本语言同样的表现力和趣味性的系统编程语言。Swift的设计以安全为出发点,以避免各种常见的编程错误类别。

    7088 人正在学习 去看看 传智

AppCode简介

AppCode是JetBrains公司出品的用于开发Mac OS X以及iOS应用程序的集成开发环境,能够支持Xcode项目的运行和调试功能,并为Swift和Objective-C提供广泛的技术支持。目前,AppCode只能运行在Mac OS X操作系统环境下,作为XCode的高级替代品,AppCode继承了IDEA的诸多优良特性,是Mac OS X和iOS应用程序开发的又一利器。
Kotlin Native是一种能够将Kotlin源代码编译成不需要任何虚拟机支持的二进制技术,编译后的二进制数据可以直接运行在目标平台上。Kotlin Native在0.4版本引入了对Objective-C API的调用支持,并引入了对WebAssembly的实验性支持。要想使用Kotlin来开发iOS应用,需要先下载最新版的AppCode(即AppCode 2018.1.1及以上版本),然后安装【Kotlin Native for AppCode】插件。
安装的步骤为,依次选择【AppCode】→【Preferences】→【Plugins】打开JetBrains的插件页面,然后搜索【Kotlin Native for AppCode】并安装,如图16-6所示。
这里写图片描述

创建Kotlin Native项目

Kotlin Native使用Gradle作为默认构建工具,想要在Xcode中编译Kotlin Native项目,需要在项目中添加相关的运行脚本,该脚本调用Gradle来构建Kotlin Native工程。当然,也可以借助一些第三方的IDE,如AppCode,使用AppCode之前需要先安装【Kotlin Native for AppCode】插件。
启动AppCode,依次选择【Kotlin/Native】→【Single View APP with a Kotlin/Native Framwork】即可创建iOS应用程序,使用Kotlin/Native方式创建的iOS项目支持使用Kotlin语言来编写iOS应用程序,如图16-7所示。
这里写图片描述
等待项目构建完成,可以看到,使用Kotlin/Native方式创建的iOS项目的目录结构如图16-8所示。
这里写图片描述
相比使用Xcode创建的原生iOS项目而言,使用Kotlin/Native方式创建的iOS项目显然多一些配置文件(如Frameworks和KotlinNativeFramework)。文件的具体含义如下:

  • Projects:主要用于存放Mac软件开发的可执行文件等,iOS应用开发很少用到这个文件;
  • Frameworks:主要用于存放项目依赖的一些系统库和第三方库;
  • Project:此目录用于存放与项目相关的源码,也是iOS应用开发的核心组成部分;
  • KotlinNativeFramework:用于存放和KotlinNative相关的库。

此时,只需要点击【Run】按钮即可在iPhone模拟器或者真机中启动应用程序。不过,此时的程序还没有实现任何的功能,为了在项目中使用Kotlin来编写iOS应用程序,可以在Kotlin Native项目的Project文件中创建一个Kotlin的文件目录(如src/main/kotlin),然后添加一个ViewController类,如图16-9所示。
这里写图片描述
需要注意的是,使用AppCode创建的Kotlin Native项目默认是没有引入iOS的UIKit.framework系统库的,所以在使用iOS的相关库之前需要先导入相应的库。
ViewController类的源码如下:

import kotlinx.cinterop.ExportObjCClass
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.ObjCOutlet
import platform.Foundation.NSCoder
import platform.UIKit.*
import kotlinx.cinterop.initBy

@ExportObjCClass
class ViewController: UIViewController {

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

    @ObjCOutlet
    lateinit var label: UILabel

    @ObjCOutlet
    lateinit var textField: UITextField

    @ObjCOutlet
    lateinit var button:UIButton

    @ObjCAction
    fun click(){
        label.text="Hello,${textFiled.text}"
    }
}

由于开发者自己定义的文件目录未被标记为源根目录,是不会被编译系统识别的,所以需要将“src/main/kotlin”目录标记为项目源码目录才能够被系统识别。

Kotlin Native测试

众所周知,AppCode本身就是基于IntelliJ IDEA的Kotlin插件,所以很多支持Kotlin的功能对于Kotlin/Native也是同样支持的(如代码检查、重构等操作)。
这里写图片描述
同时,AppCode插件也支持使用kotlin.test框架来编写测试代码。在项目名上右键,依次选中【Project Settings…】→【项目名】,然后点击左下角的添加按钮,如图16-11所示。
这里写图片描述
等待项目构建完成,然后选中【edit configurations…】打开项目配置面板并添加“Kotlin/Native test”选项来添加测试用例,如图16-12所示。
这里写图片描述
当然,除了上面介绍的功能外,Kotlin Native还支持调用Objective-C标准 API,而且也为此提供了非常便捷的阅读API文档的方法,可以说Kotlin Native是Kotlin多平台的真正魅力之所在。

没有更多推荐了,返回首页