vscode开发佳明表盘-monkeyc语法篇(入门二)

这可能是最详细的monkeyc开发手册!篇幅较长全文15000字,
建议根据目录选择性观看,可以收藏开发时查找(每个功能点都有示例)
大标题后括号中有标注希望对你有所帮助!!!

程序的类型

Connect IQ 系统中有五种应用类型:

  • 表盘- 这是 Garmin 可穿戴设备的主屏幕。它们可以是简单的钟表,也可以是包含数十种健康和健身统计数据的复杂数据屏幕。
  • 数据字段- 数据字段是 Garmin 活动体验的插件。它们允许计算新指标或允许将新数据带入锻炼。
  • 小部件- 小部件是可以从主屏幕启动的微型应用程序。它们旨在提供一目了然的信息访问。
  • 设备应用程序- 设备应用程序是最强大的应用程序类型,并提供对系统的完全访问权限。
  • 音频内容提供商- 音频内容提供商是音乐可穿戴设备上媒体播放器的插件,它们在媒体和第三方内容服务之间架起了桥梁。

API 和应用权限

应用类型定义了应用的用户上下文。例如,表盘由于在低功耗模式下运行,因此受到许多限制。为了强制执行这些限制,Connect IQ 虚拟机将根据您的应用类型限制可用的 API。

模块名称数据字段表盘小部件应用程序音频内容提供商API 级别
Toybox.Activity1.0.0
Toybox.ActivityMonitor*1.0.0
Toybox.ActivityRecording*1.0.0
Toybox.Ant*1.0.0
Toybox.Application1.0.0
Toybox.Attention1.0.0
Toybox.Background*2.3.0
Toybox.BluetoothLowEnergy*3.1.0
Toybox.Communications*✓**1.0.0
Toybox.Complications*4.1.0
Toybox.Cryptography*3.0.0
Toybox.FitContributor*1.3.0
Toybox.Graphics1.0.0
Toybox.Lang1.0.0
Toybox.Math1.0.0
Toybox.Media3.0.0
Toybox.PersistedContent*2.2.0
Toybox.PersistedLocations*1.0.0
Toybox.Positioning*1.0.0
Toybox.Sensor*1.0.0
Toybox.SensorHistory*2.1.0
Toybox.SensorLogging*2.3.0
Toybox.StringUtil1.3.0
Toybox.System1.3.0
Toybox.Test2.1.0
Toybox.Time1.0.0
Toybox.Timer1.0.0
Toybox.UserProfile*1.0.0
Toybox.WatchUi1.0.0
Toybox.Weather3.2.0

*需要应用程序权限
** API 级别 5.0.0 中引入数据字段中的通信支持

您的应用类型所请求的 Toybox 模块超出此列表范围将导致“*未找到符号”*错误。

开发注意事项

1、可访问模块。

  • 出于对电池寿命的担忧,表盘对系统中 API 的访问最少。它们可以访问图形、位图、字体、当前活动跟踪器状态、当前电池状态和用户的活动资料。它们无法访问指南针、GPS 或其他传感器

2、自定义字体。

  • 如果您使用自定义字体来显示数字,请使用过滤器选项仅加载关键字形。这将节省内存,您可以将其用于其他图形

3、表盘睡眠。

  • 表盘大部分时间处于“睡眠模式”。在此模式下,执行仅限于每分钟更新一次,并且不能使用计时器或动画。当用户抬起手表查看时,表盘将退出睡眠模式。发生这种情况时,将WatchFace.onExitSleep()调用该方法,更新将增加到每秒一次,并且允许使用计时器和动画,直到WatchFace.onEnterSleep()调用该方法为止。

4、表盘委托

自 API 级别 2.3.0 起

为表盘提供WatchUi.WatchFaceDelegate系统输入。该委托应作为返回数组的第二个元素返回,AppBase.getInitialView()类似于其他应用程序类型的输入委托。此委托目前仅用于报告支持每秒更新的表盘的电量预算违规。如果在一分钟内超出执行预算,WatchFaceDelegate.onPowerBudgetExceeded()则会调用回调,提供有关表盘执行时间以及超出限制的信息。

5、数据字段和简单数据字段

数据字段的基类是WatchUi.DataField。此类扩展了WatchUi.View,并且在许多方面与其他 View 对象的行为类似。View.onUpdate()每次数据字段需要更新时,都会进行方法调用。

在 Garmin 活动中,用户控制数据页面布局;具体来说,是显示一个、两个、三个还是更多字段。Connect IQ 数据字段必须处理所有这些布局中的显示,开发人员可以使用模拟器在设备支持的所有布局中测试他们的字段。

许多开发人员只想显示单个值,而不想处理绘制数据字段的所有复杂性。在这些情况下,他们可以使用对象WatchUi.SimpleDataField。简单的数据字段可以处理多种尺寸的字段绘制,并且只需要开发人员实现一种DataField.compute()方法。该compute()方法传递一个Activity.Info对象,其中包含所有当前锻炼信息。

尽可能使用SimpleDataField,以确保您数据字段具有其他 Garmin 数据字段的原生外观和感觉。Connect IQ 将尝试确保您的数据以最佳字体和布局显示。

以下是“Beers Earned”数据字段的示例,它显示您在锻炼期间“赢得”了多少杯啤酒:

Monkey C编程语言

monkeyc 是 Garmin 的 Connect IQ 平台上使用的一种编程语言。它是 Garmin 特别为其可穿戴设备开发的,这些设备包括智能手表和其他健康跟踪器。monkeyc 是一种高层次、面向对象的语言,设计上与 Java 和 JavaScript 类似,这使得熟悉这些语言的开发者能够较快地上手。

monkeyc 的特性(概念)

  • 语法: 与 Java 类似,monkeyc 使用面向对象的编程结构,包括类、继承和接口。
  • 跨平台: 适用于所有支持 Connect IQ 的 Garmin 设备。
  • 轻量化: 由于设备资源有限,monkeyc 被设计为一种轻量级的语言,专门用于开发高效的小型应用程序。
  • 专用 API: 提供了丰富的 API,帮助开发者访问设备的传感器数据、UI 元素、文件系统等。

总的来说:monkeyc是开发佳明应用的语言,以.mc结尾。

示例(概念)

using Toybox.Application as App;
using Toybox.System;

class MyProjectApp extends App.AppBase {

    // onStart() is called on application start up
    function onStart(state) {
    }

    // onStop() is called when your application is exiting
    function onStop(state) {
    }

    // Return the initial view of your application here
    function getInitialView() {
        return [ new MyProjectView() ];
    }
}

顶部是 using 语句,它类似于 C++ 的using语句,或importJava™、Ruby 或 Python™ 中的 。using语句在词汇上将模块带入我们的名称空间。 在子句之后using,我们可以通过其缩写名称(在本例中为 )来引用模块SystemToybox是 Monkey C 系统模块的根模块;所有酷炫的操作都保存在那里。

要将值打印到调试控制台,请使用:
System.println( "Hello Monkey C!" );

System指的是Toybox.System我们用该using语句导入的模块。与 Java 命名空间不同,Monkey C 中的模块是静态对象,可以包含函数、类和变量。Javaprintln()程序员应该很熟悉该函数 - 它打印一个字符串并自动添加一个新行。该System模块有许多有用的功能:

  • print并将println输出发送到控制台
  • getTimer返回当前毫秒计时器。该值是一个 32 位整数,表示系统已运行的毫秒数。该值可用于计时,但允许翻转。
  • getSystemStats提供运行时系统的统计数据
  • exit将终止你的申请
  • error将退出应用程序并记录错误消息

与java比较(概念)

与 Java 一样,Monkey C 编译为虚拟机解释的字节码。此外,与 Java 一样,所有对象都分配在堆上,虚拟机会清理内存(Java 通过垃圾收集,Monkey C 通过引用计数)。与 Java 不同,Monkey C 没有原始类型 - 整数、浮点数和字符都是对象。这意味着原始类型可以像其他对象一样拥有方法。

Java 是一种静态类型语言,而 Monkey C 是鸭子类型。在 Java 中,开发人员必须声明函数所有参数的类型,并声明返回值的类型。Java 编译器会在编译时检查这些内容,以确保类型安全。鸭子类型的概念是“如果它走路像鸭子,叫声像鸭子,那么它一定是鸭子” [1]。例如:

function add( a, b ) {
    return a + b;
}
// Monkey C 编译器不验证类型安全性,如果函数错误处理方法,则会导致运行时错误。
function thisFunctionUsesAdd() {
    var a = add( 1, 3 ); // Return  4
    var b = add( "Hello ", "World" ); // Returns "Hello World"
}

对象、模块和内存(注意事项)

对象使用关键字创建class。类允许将数据和操作绑定到对象上。在 Monkey C 中,可以在类中定义变量函数其他类

构造函数

当使用该关键字实例化一个对象时new,会分配内存并initialize调用该方法:

class Circle{
    protected var mRadius;
    public function initialize( aRadius ) {
      mRadius = aRadius;
    }
}

function createCircle() {
    var c = new Circle( 1.5 );
}

在方法中你可以用self或者me代替java中的this关键字引用当前实例。

class A
{
    public var x;
    public var y;
    public function initialize() {
        me.x = "Hello"; // Set current instance x variable
        self.y = "Hello"; // Set current instance y variable
    }
}
继承

Monkey C 使用extends关键字来支持类继承:

import Toybox.System;

class A
{
    function print() {
        System.print( "Hello!" );
    }
}

class B extends A
{

}

function usageSample() {
    var inst = new B();
    inst.print();           // Prints "Hello!"
}

你可以增强方法:

import Toybox.System;

class A
{
    function print() {
        System.print( "Hello!" );
    }
}

class B extends A
{
    function print() {
        // Call the super class implementation
        A.print();

        // Amend the output
        System.println( "Hola!" );
    }
}

function usageSample() {
    var inst = new B();
    inst.print();           // Prints "Hello! Hola!"
}
字段访问级别

有三个访问级别—— publicprotectedprivate

public是默认值,但也可以明确指定。当public访问修饰符用于枚举、变量或函数时,这些成员对所有其他类都是可见的。

修饰符private指定该成员只能在其自己的类中访问。

修饰符protected指定该成员只能由其自身类或其子类之一访问。 关键字hidden与 关键字同义protected

import Toybox.System;

class Foo
{
    public var publicVar;
    protected var _protectedVar
    private var _privateVar;

    public function initialize() {
        publicVar = "a";
        _protectedVar = "b";
        _privateVar = "c";
    }
}

class Bar extends Foo {
    public function initialize() {
        // Initialize the parent
        Foo.initialize();
        publicVar = "b";
        _protectedVar = "c";
        // Error - can't access private member
        _privateVar = "d";
    }
}

function usageSample() {
    var v = new Foo();
    System.println( v.publicVar );
    // Error - cannot access protected member
    System.println( v._protectedVar );
    // Error - cannot access private member
    System.println( v._privateVar );
}
多态

大多数面向对象语言都支持多态函数的概念,即一个函数可以根据输入参数的数量和类型有多个定义。部分由于其鸭子类型的特性,Monkey C 不支持这种运行时多态性。

因为函数参数是鸭子类型,所以可以使用instanceof运算符实现某种程度的多态性:

import Toybox.Lang;

function aPolymorphicFunction(a) {
    switch(a) {
        case instanceof String:
            return doTheStringVersion(a);
        case instanceof Number:
        case instanceof Long:
            return doTheNumericVersion(a);
        default:
            throw new UnexpectedTypeException();
    }

}
强引用和弱引用

Monkey C 是引用计数的,这意味着当引用该内存的对象数量减少到零时,运行时系统将释放内存。引用计数允许内存非常快速地变为可用,这在低内存环境中非常重要。引用计数的弱点是循环引用。当引用链中形成一个循环时,就会发生循环引用。例如,假设对象 C 引用对象 A,对象 A 引用对象 B,对象B 引用对象 A。

要创建弱引用,请使用weak()方法。Weak 是 Monkey C 中的一种方法Lang.Object,可供所有 Monkey C 对象使用。

// I would make a "Hans and Franz" reference but I
// think certain advertising has made them uncool.
var weakRef = obj.weak()

如果你调用的是weak其中一种不可变类型(NumberFloatCharLongDoubleString,那么它将返回对象本身。否则它将返回一个WeakReference实例。

//! A weak reference is a loosely bound reference to
//! another object. If all strong references have been
//! freed, the get() method will return null.
//! This allows the developer to avoid circular references.
//! @since 1.2.0
class WeakReference
{
    //! Return if the reference is still alive.
    //! @return true if object is still alive, false otherwise.
    //!    When you are dead I will be STILL ALIVE
    //!    I feel fantastic and I am STILL ALIVE
    function stillAlive();

    //! Get the object referenced.
    //! @return Object referenced, or null if object is no
    //!         longer alive.
    function get();
}

您可以使用该stillAlive方法检查引用是否已被清除。用于get创建对该对象的强引用。仅在需要的范围内保留强引用!

// This is a triumph...
if( weakRef.stillAlive() ) {
    var strongRef = weakRef.get();
    strongRef.doTheThing();
}
导入和使用语句

您可以使用关键字将模块带入您的作用域级别import。使用时,import它会将模块后缀和模块中的所有类带入类型命名空间。这样就可以访问模块中的类而无需模块后缀,从而更易于键入。函数调用仍然需要访问模块后缀。

import Toybox.Lang;
import Toybox.System;

// Import lets you say goodbye to
// module prefixes
var globalX as Number or String = 0;

function hasANumber() {
    globalX = 2;  // Allowed
    globalX = "2"; // Allowed
    // Still require prefixes in code
    System.println("globalX = " + globalX);
}

您还可以使用关键字将模块纳入您的作用域级别usingusing允许通过符号将模块导入到另一个类或模块中:

using Toybox.System;

function foo() {
    System.print( "Hello" );
}

as子句提供了一种在范围内为模块分配不同名称的方法。这对于缩短模块名称或当您只是不同意我们的命名方案时很有用:

using Toybox.System as Sys;

function foo() {
    Sys.print( "Hello" );
}

importusing之间的区别很微妙。import将模块名称和类名称带入命名空间,而using仅将模块名称带入命名空间。 如果您使用monkeytypes,则应import专门使用,因为它将为您节省大量冗余模块引用。 最后,as子句仅支持 forusing语句。

一些变量作用域的注意
// =========================示例一:==========================
import Toybox.System;

// A globally visible function
function d() {
    System.print( "this is D!" );
}

module Parent
{
    // A module function.
    function b() {
        System.print( "This is B!" );
        d(); // Call a globally visible function
    }

    // A child class of a Parent module
    class Child
    {
        // An instance method of Child
        function a() {
            System.print( "This is A!" );
            b(); // Call a function in our parent module
            c(); // Call a static function within the class.
            d(); // Call a globally visible function.
        }

        // A static function of Child.
        // Note that static methods can't call instance method but still have
        // access to parent modules.
        static function c() {
            System.print( "This is C!" );
            b(); // Call a method in the parent module.
            d(); // Call a globally visible function
        }
    }
}

// =========================示例二:==========================
function helloFunction() {
    System.println("Hello Hello");
}

class A {
     function helloFunction() {
        System.println("Every time I say goodbye you say hello");
     }

    function b() {
        // Call global helloFunction
        $.helloFunction();
        // Call instance helloFunction
        helloFunction();
    }
}

如果你引用的是全局变量,使用 bling 可以提高运行时性能[2]

语法(编程相关)

函数

函数是程序的核心[1] 。函数定义离散的可调用代码单元。

Monkey C 函数可以接受参数,但由于 Monkey C 是一种动态类型语言,因此参数类型无需声明;只需声明其名称即可。此外,无需声明函数的返回值,甚至无需声明函数是否返回值,因为所有函数都会返回值

变量、表达式和运算符

Monkey C 支持的基本类型有:

  • Integers- 32 位有符号整数
  • Floats- 32 位浮点数
  • Longs – 64 位有符号整数
  • Doubles– 64 位浮点数
  • Booleans-truefalse
  • Chars - Unicode 字符
  • Strings- 字符的字符串
  • Objects– 实例化对象(使用 class 关键字定义)
  • Arrays- 使用语法分配new [X],其中“X”是计算数组大小的表达式
  • Dictionaries- 关联数组,使用语法分配{}
关键字

Here is a list of keywords in the Monkey C programming language. You cannot use any of the following as variables or symbols in your programs. The keywords native and alias are reserved, even though it is not currently used. true, false, null, NaN, new, and, and or might seem like keywords, but they are actually literals and operators; you cannot use them as identifiers in your programs.


下面是Monkey C编程语言的关键字列表。您不能在程序中使用以下任何变量或符号。关键字“native”和“alias”是保留的,即使当前没有使用它。’ true ', ’ false ', ’ null ', ’ NaN ', ’ new ', ’ and ‘和’ or '可能看起来像关键字,但它们实际上是文字和操作符;不能在程序中使用它们作为标识符。

asconstenumhasmoduleselfusing
breakcontinueextendshiddenprivatestaticvar
casedefaultfinallyifprotectedswitchwhile
catchdoforinstanceofpublicthrow
classelsefunctionmereturntry
声明变量

所有局部变量必须使用关键字var提前声明。在 Monkey C 语言中,所有值(包括数值)都是对象。

var n = null;               // Null reference
var x = 5;                  // 32-bit signed integers
var y = 6.0;                // 32-bit floating point
var l = 5l;                 // 64-bit signed integers
var d = 4.0d;               // 64-bit floating point
var bool = true;            // Boolean (true or false)
var c = 'x';                // Unicode character
var str = "Hello";          // String
var arr = new [20 + 30];    // Array of size 50
var dict = { x=>y };        // Dictionary: key is 5, value is 6.0
var z = arr[2] + x;         // Null pointer waiting to happen
运算符
优先级操作员描述
1new创建
!逻辑非
~按位非
( )函数调用
2*乘法
/分配
%模块
&按位与
<<左移
>>右移
3+添加
-减法
``
^按位异或
4<少于
<=小于或等于
>大于
>=大于或等于
==等于
!=不等于
5&&逻辑与
and
6`
or
7?:有条件的
符号

符号是轻量级常量标识符。当 Monkey C 编译器找到新符号时,它会为其分配一个新的唯一值。这允许将符号用作键或常量,而无需显式声明 const 或枚举:

var a = :symbol_1;
var b = :symbol_1;
var c = :symbol_2;
Sys.println( a == b );  // Prints true
Sys.println( a == c );  // Prints false

// 当想要创建键而不必声明枚举时,符号会很有用:
var person = { :firstName=>"Bob", :lastName=>"Jones" };
常量

常量是使用关键字声明的命名的不可变值const。它们可用于存储可能在整个代码中重复使用的不变值。常量必须在模块类级别声明;不能在函数内声明。

常量支持与变量相同的类型。需要注意的是,对于数组等数据结构,const其功能类似于 Java 的final关键字。例如,const数组可防止数组被新实例替换,但数组的元素可能会被修改。

const PI = 3.14;
const EAT_BANANAS = true;
const BANANA_YELLOW = "#FFE135";
枚举

枚举是从符号到整数的显式或自动递增常量映射。除非明确设置(参见第二个示例),否则每个后续符号都会自动分配其前一个符号的值加一,从 开始0。因此,在下面的示例中,符号 Monday 被自动分配值0,Tuesday 被分配值1,依此类推。这些符号可以像常量变量一样使用(本质上就是这样)。枚举必须在模块或类级别声明;不能在函数内声明。

// 示例一:
enum {
    Monday,   // Monday = 0
    Tuesday,  // Tuesday = 1
    Wednesday // Wednesday = 2
    // ...and so on
}

// 示例二:
enum {
    x = 1337, // x = 1337
    y,        // y = 1338
    z,        // z = 1339
    a = 0,    // a = 0
    b,        // b = 1
    c         // c = 2
}
调用方法

在您自己的类或模块中调用方法,只需使用函数调用语法:

function foo( a ) {
    //Assume foo does something really impressive
}

function bar() {
    foo( "hello" );
}

如果调用对象的实例,请在调用前面加上对象和“ .”。

访问类成员publicprotected变量时,应使用以下格式之一进行访问:

var x = mMemberVariable;
var y = self.mMemberVariable;

使用以下语法访问被重写的父成员函数:

class A
{
    function overridableMethod() {
        System.println("I am A!");
    }
}

class B extends A
{
    function overridableMethod() {
        System.println("B wins!");
        A.overridableMethod();
    }
}
if语句
myInstance.methodToCall( parameter );

if ( a == true ) {
    // Do something
} else if ( b == true ) {
    // Do something else
} else {
    // If all else fails
}

// Monkey C also supports the ternary operator
var result = a ? 1 : 2;
Switch 语句
switch ( obj ) {
    case true:
    // Do something
    break;
    case 1:
    // Do something
    break;
    case "B": {
        // Do something
        break;
    }
    // Executed based on the type
    // instead of the value
    case instanceof MyClass:
    // Do something
    break;
    default:
    // If all else fails
    break;
}

// Monkey C also supports fall-through into the next case statement
switch ( obj ) {
    case false:
    // Do something
    // Fall through and execute the code in the next case block
    case 2: {
        // Do something
        break;
    }
    case instanceof MyOtherClass:
    // Do something
    break;
    case "B":
    // Do something
    // Fall through and execute the code in the default block
    default:
    // If all else fails
    break;
}
Switch 块变量作用域
switch ( obj ) {
    case true:
    var aaa = 1; // Scoped at the switch block level
    ...
    case 1:
    var zzz = aaa; // Results in a compiler error because aaa was not initialized in this case block
    ...
    break;
    case "B": {
       var aaa = true; // Scoped at the code block level within the curly braces, no scoping conflict with variable aaa at the swtich block level
       ...
       break;
    }
    case instanceof MyClass:
    var aaa = "Hello!" // Results in a compiler error because aaa has already been defined in the switch block
    ...
    default:
    aaa = 0; // aaa was defined in the first case and initialized at the beginning of the default case, no errors!
    var good = aaa;
    ...
    break;
}
循环

Monkey C 支持for循环、while循环和do/while循环。while循环do/while具有熟悉的语法:

// do/while loop
do {
    // Code to do in a loop
}
while( expression );

// while loop
while( expression ) {
    // Code to do in a loop
}

// Monkey C does allow for variable declaration in for loops
for( var i = 0; i < array.size(); i++ ) {
    // Code to do in a loop
}

// 可以使用`break`and`continue`语句来管理循环内的控制。这些也应该具有熟悉的行为:
// This for loop should only print 5, 6, and 7.
for (var i = 0; i < 10; i += 1) {
    if (i < 5) {
        continue;
    }
    System.println(i);
    if (7 == i) {
        break;
    }
}

在 Monkey C 中,所有函数都有返回值。可以使用关键字明确设置返回值return

Instanceof 和 Has

作为一种鸭子类型语言,Monkey C 为程序员提供了极大的灵活性,但其缺点是编译器无法像 C、C++ 或 Java 那样执行类型检查。Monkey C 提供了两种工具来进行运行时类型检查 —instanceofhas

instanceof运算符可以检查对象实例是否继承自给定类。第二个参数是要检查的类名:

var value = 5;
// Check to see if value is a number
if ( value instanceof Toybox.Lang.Number )
{
    System.println( "Value is a number" );
}

has运算符允许您检查给定对象是否具有符号,该符号可能是公共方法、实例变量,甚至是类定义或模块。第二个参数是要检查的符号。

例如,假设我们在 中有磁力计库Toybox.Sensor.Magnetometer,但并非所有产品都有磁力计。以下是根据这些标准更改实现的示例:

var impl;
// Check to see if the Magnetometer module exists in Toybox
if ( Toybox has :Magnetometer )
{
    impl = new ImplementationWithMagnetometer();
}
else
{
    impl = new ImplementationWithoutMagnetometer();
}
回调

Monkey C 中的函数不是第一类,这意味着您不能将它们作为对象直接传递给其他函数。但是,使用method()从 继承的函数Toybox.Lang.Object,类实例可以创建一个Method对象,从而提供一种将其作为回调方法调用的方法。

class Foo
{
    function operation(a, b) {
        // The code here is really amazing. Like mind blowing amazing. You wish this method was in your program.
    }
}
function usageSample() {
    // Create a new instance of Foo
    var v = new Foo();
    // Get the callback for the operation method from the instance of Foo.
    var m = v.method(:operation);
    // Invoke v's operation method.
    m.invoke(1,2);
}

对象Method将调用其来源对象实例上的方法。它保持对源对象的强引用。

与类不同,模块不从 Object 继承,因此无法访问该method()函数。但是,Method可以创建一个新的实例,从而允许以类似的方式将模块级函数作为回调调用:

import Toybox.Lang;

module Foo
{
    function operation() {
        // Do something
    }
}
function moduleSample() {
    var v = new Method(Foo, :operation);
    v.invoke();
}
数组

Monkey C 中的数组与变量一样,都是无类型的,因此无需声明数据类型。创建新数组有两种形式。要创建固定的空数组size,请使用以下命令:

// Create a typeless array
var untypedArray = new [size];
// Create a typed array
var typedArray = new Array<Number>[size];

// New array. Will be typed as a Tuple
// [Number, Number, Number, Number, Number]
var untypedArray = [1, 2, 3, 4, 5];
// New typed array
var typedArray = [1, 2, 3, 4, 5] as Array<Number>;

// 元素是表达式,因此可以使用以下语法创建多维数组:
var array = [ [1,2], [3,4] ];

// Monkey C 没有直接创建空二维数组的方法,可以使用以下语法进行初始化:
// Shout out to all the Java programmers in the house
var array = new [first_dimension_size];

// Initialize the sub-arrays
for( var i = 0; i < first_dimension_size; i += 1 ) {
    array[i] = new [second_dimension_size];
}
字典

字典或关联数组是 Monkey C 中的内置数据结构:

var dict = { "a" => 1, "b" => 2 };  // Creates a dictionary
System.println( dict["a"] );        // Prints "1"
System.println( dict["b"] );        // Prints "2"
System.println( dict["c"] );        // Prints "null"

// 要初始化空字典,请使用以下语法:
var x = {};                         // Empty dictionary

// Dictionary创建新对象时可以添加类型后缀:
var x = {} as Dictionary<Symbol, String>;
// Valid
x[:option] = "value";
// Invalid
x["option"] = "value";

默认情况下,对象会根据其引用值进行哈希处理。类应重写hashCode()方法来Toybox.Lang.Object更改其类型的哈希函数:

class Person
{
    // 返回一个数字作为散列码。请记住,哈希码必须是
    // 对于两个相等的对象相同。
    // @返回哈希码值
    function hashCode() {
        // 使用唯一的个人id作为哈希码
        return mPersonId;
    }
}

随着内容的增加或减少,字典会自动调整大小和重新散列。这使它们非常灵活,但这是有代价的。如果意外或过度调整大小和重新散列,插入和删除内容可能会导致性能问题。此外,由于哈希表需要额外的空间进行分配,因此它们不如对象或数组那样节省空间。

Monkey Types (注意事项,编程了解)

Monkey Types 是 Monkey C 语言的渐进式类型系统。该类型系统旨在识别 Monkey C 历史上的鸭子类型特性,但添加了必要的组件以在编译时对您的应用程序进行类型检查。

as 子句

Monkey Types 引入了一个新关键字as。您可以使用as它将类型绑定到成员变量、模块变量、函数参数或函数返回值。局部变量总是在赋值时推断类型。

一旦类型绑定到值,编译器将只允许分配该类型的值。

using Toybox.Lang;
using Toybox.System;

var globalX as Lang.Number = 0;

function hasANumber() {
    globalX = 2;  // Allowed
    globalX = "2"; // Not allowed
    System.println("globalX = " + globalX);
}

在这个例子中,我们声明全局变量globalX只接受 的值Toybox.Lang.Number。一旦声明,编译器就只允许将该类型的值赋给globalX

由于 Monkey C 是一种鸭子类型语言,因此只允许将单一类型绑定到变量会过于严格。如果变量接受多种类型,则as允许子句附加子句。or

using Toybox.Lang;
using Toybox.System;

var globalX as Lang.Number or Lang.String = 0;

function hasANumber() {
    globalX = 2;  // Allowed
    globalX = "2"; // Allowed
    System.println("globalX = " + globalX);
}
import 语句

在传统的 Monkey C 中,该using语句将module后缀带入正在处理的文件的命名空间。要访问任何函数、变量或类定义,必须引用模块后缀。

对于添加类型信息,所有模块前缀都很烦人。Monkey Types 引入了语句import。使用时,import它会将模块后缀和模块中的所有类带入类型命名空间。这允许在没有模块后缀的情况下访问模块中的类,从而更容易输入。函数仍然需要模块后缀才能访问。

import Toybox.Lang;
import Toybox.System;

// Import lets you say goodbye to
// module prefixes
var globalX as Number or String = 0;

function hasANumber() {
    globalX = 2;  // Allowed
    globalX = "2"; // Allowed
    // Still require prefixes in code
    System.println("globalX = " + globalX);
}

注意import不支持使用as重命名源文件[1]中的模块。

命名类型与匿名类型

从上面的例子中可以看出,类型系统可以允许复杂的类型定义。有时类型模式重复,你只想通过名称来引用它。

语句typedef允许您在应用程序命名空间中创建命名类型。例如,以下将Numeric在全局命名空间中创建命名的 poly 类型。然后,该函数通过让子句引用类型声明来add绑定Numeric到参数a及其b返回值。as``Numeric


A typedef statement allows you to create a named type in the application namespace. For example, the following would create a poly type named Numeric in the global namespace. The function add then binds Numeric to parameters a and b and its return value by having the as clauses refer to the Numeric type declaration.

import Toybox.Lang;

typedef Numeric as Number or Float or Long or Double;

function add(a as Numeric, b as Numeric) as Numeric {
    return a + b;
}
返回值(void)

void 类型仅用于返回值,表示函数不允许返回值。它还表示函数不应通过调用此函数来期望返回值。

import Toybox.Lang;

function doNothing() as Void {
    // Compiler error - this is failing to
    // do nothing.
    return true;
}

function doSomething() as String {
    // Compiler error - cannot assign value
    // from a function that returns nothing
    var x = doNothing();
    // Compiler error - doSomething should
    // return a String
}
具体返回

具体类型是对程序命名空间中声明的类的单一引用。这是最传统和最熟悉的类型用法。如果值绑定到具体类型,它将仅接受该类或任何派生类的值。

import Toybox.Lang;
import WoolMarket;

class Wool {
    public var bagsFull;

    public function initialize(bags as Number) {
        bagsFull = bags;
    }
}

class Sheep {
    public var wool as Wool;

    public function initialize() {
        wool = new Wool(1);
    }
}

class BlackSheep extends Sheep {
    public function initialize() {
        Sheep.initialize();
        wool = new Wool(3);
    }
}

function processSheep(baa as Sheep) {
    if(baa.wool != null) {
        WoolMarket.sellWool(baa.wool);
    }
}

function example() {
    // Allowed
    processSheep(new Sheep());
    processSheep(new BlackSheep());
    // Not allowed
    processSheep(new Wool());
}

请注意,具体类型不会隐式接受null值。如果您希望值也接受,null则必须创建多类型(Null有关详细信息,请参阅)。

poly类型

多元类型允许将多个类型串联成一个类型。这允许类型系统模拟 Monkey C 的鸭子类型特性。要创建多元类型,只需or在定义类型时使用该子句即可。

  1. 其类型绑定到 poly 类型中的一种类型的值
  2. 绑定到多类型的值,其类型在目标类型的定义范围内
import Toybox.Lang;

// 这里进行串联
typedef Addable as Number or Float or Long or Double or String;
typedef Numeric as Number or Float or Long or Double;

function add(a as Addable, b as Addable) as Addable {
    return a + b;
}

function subtract(a as Numeric, b as Numeric) as Numeric {
    return a - b;
}

function doWork() {
    // Allowed
    var x as Addable = add("1", "2");
    // Not allowed; Addable has String which is
    // not within Numeric
    var y as Numeric = subtract(x, 2);
}
接口

接口类型要求类包含一组成员声明。成员可以是成员变量或函数。

import Toybox.Lang;

typedef LittleBoys as interface {
    var frogs as Array<Frogs>;
    var snails as Array<Snails>;
    var puppyDogTails as Array<PuppyDogTails>;
};

// Implements LittleBoys interface
class MaleChild {
    var frogs as Array<Frogs>;
    var snails as Array<Snails>;
    var puppyDogTails as Array<PuppyDogTails>;
}

// 请注意,该类不需要额外的修饰来实现接口。这允许在函数参数中定义匿名接口。
// Processing
function example(you as interface {
    var frogs as Array<Frogs>;
})
容器& 数组

Monkey C 语言有两种原生容器类型,ArrayDictionary

虽然 Monkey 类型系统不支持泛型,但它允许开发人员输入 的值类型Array或 的键和值类型Dictionary

import Toybox.Lang;

typedef ContainerA as Array<Number>;
typedef ContainerB as Dictionary<String, Number>;

仅当键和值类型都等效时,容器类型才会与其他容器类型匹配。Array<String>仅匹配Array<String>,而不匹配Array<String or Number>

Monkey C 目前无法推断容器类型,因此您需要声明容器。如果您想创建新的类型数组或字典,可以使用以下语法:

class ContainerClass {
 // Array of 10 items that takes only numbers
 var typedArray as Array<Number> = new Array<Number>[10];
 // Initialized array
 var initializedArray as Array<Number> = [1, 2, 3, 4, 5] as Array<Number>;
 // Initialized dictionary
 var initializedDictionary as Dictionary<String, String> = {"this"=>"that"} as Dictionary<String, String>;
}
元组

Monkey C 中的一种常见模式是使用数组作为结构化分组。Monkey 类型允许通过将索引项绑定到类型来对数组进行建模。

将 Tuple 类型视为 Dictionary 类型,不同之处在于键由顺序暗示

在以下示例中,返回的数组自动被类型化为 Tuple 类型[ StartView, StartDelegate]。这是根据允许的返回值 [Views, InputDelegates] 进行类型化并发现匹配:

function getInitialView() as [Views] or [Views, InputDelegates] {
    return [ new StartView(), new StartDelegate() ] ;
}

Tuple 类型 A 与 Tuple 类型 B 的匹配规则如下:

  • 元组 A 和 B 的长度必须相同
  • 对于每个索引,A 中的每个类型都必须是 B 的实例

使用语法创建的数组[ value, value...]现在将被类型化为 Tuple 而不是Array<Any>。如果容器类型更符合您正在实现的模式,您可以使用容器类型,但 Tuple 与容器类型具有天然兼容性。如果类型 A、B 和 C 在容器类型的多类型定义中,则类型的 Tuple[A, B, C]应为 的实例。Array<A or B or C>

function sumArray(x as Array<Numeric>) as Number {
    var result = 0;
    for (var i = 0; i < x.size(); i++) {
        result += x[i];
    }
    return result;
}

function sumThisTuple() as Number{
    // 这应该通过类型检查,因为
    // 元组[Number, Number…]]应该是一个instanceOf Array<Numeric>
    return sumArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}

元组类型也是可变的。当底层数组发生变化时,只要类型系统能够跟上,它们就会被修改。

function foo(x as [Number, Number, Number]) as [Number, Number, Number] {
    x[1] = "Hello"; // Allowed, type is now [Number, String, Number]
    return x; // Error, type mismatch
}
Dictionary 字典

Monkey C 中的一种常见模式是使用选项字典作为参数。这允许使用可扩展的 API。Monkey Types 允许通过将关键文字绑定到类型来对选项字典进行建模。

import Toybox.Lang;

function doWork(options as {
    :option1 as String,
    :option2 as {
        "name" as String,
        "value" as Number
    }
})

如果字典以内联方式声明,编译器将跟踪绑定到值的类型,然后进行类型检查以查看所有值类型是否匹配。它不需要提供所有键,并且如果添加其他键也不会出错。

doWork({:option1=>"x", :option3=>true})
枚举

现在,通过在声明后附加名称,枚举可以成为命名类型。枚举值将绑定到其枚举类型及其值类型。

import Toybox.Lang;

enum Dog {
    SPOT = "Spot",
    LUKE = "Luke",
    POCO = "Poco",
    COMMODORE = "Commodore",
    BINGO = "B_I_N_G_O"
}

function getDogName(dog as Dog) as String {
    // Return the dog name
    return dog.toString();
}
callback 回调

Monkey C 的基础对象包含method创建Method回调对象的方法[2]。回调类型允许您Method根据预期的参数和返回值来对对象进行分类。

import Toybox.Lang;

function doWork(
    x as Method(a as Number) as String
) as String {
    return x.invoke(2);
}
null值

Monkey Types 将 Null 视为其自身的独特类型。更重要的是,Monkey Types 需要明确声明是否null为允许值。

function doWork() as Number or Null

可以?与单一类型声明一起使用,使其成为接受空值的多项类型。

function doWork() as Number?
类型匹配和歧义

由于 Monkey C 的鸭子类型特性,Monkey 类型继承了歧义性。理想情况下,类型系统应该有非常明确的规则来判断类型是否匹配,但 Monkey 类型有 True、False 和 Maybe [3]

// 假设我们有以下内容:
var a as A;
var b as B;

a = b; // 这是允许的吗?

可以参考下表:

A↓ B→AnyConcretePolyInterfaceContainerDictionaryEnumCallbackNull
AnyTrueTrueTrueTrueTrueTrueTrueTrueTrue
ConcreteMaybe如果 B 是 A 或者扩展了 A,则为 True如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 FalseFalse如果 A 是字典或数组如果 A 是字典则为可能,否则为假如果枚举值类型与 A 匹配则为 True,否则为 FalseFalseFalse
PolyMaybe如果 B 是 A 中的类型,则为 True,否则为 False如果所有引用都存在于 A 和 B 中,则为 True。如果 B 中存在某些类型,且 A 中不存在,则为 Maybe。如果 B 和 A 之间没有匹配的类型,则为 False如果 B 是 A 中的类型,则为 True,否则为 False如果 B 是 A 中的类型,则为 True,否则为 False如果 B 是 A 中的类型,则为 True,否则为 False如果 B 是 A 中的类型,则为 True,否则为 False如果 B 是 A 中的类型,则为 True,否则为 False如果 B 是 A 中的类型,则为 True,否则为 False
InterfaceMaybe如果 B 是包含接口 A 的所有成员的对象,则为 True,否则为 False也许如果 poly 包含匹配的类型如果 B 的接口包含 A 中的所有成员,则为 True。如果类数组或字典包含接口的所有成员则为 True,否则为 False如果类 Dictionary 包含接口的所有成员,则为 True,否则为 FalseFalseFalseFalse
ContainerMaybeFalse如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 FalseFalse如果容器类型和键/值类型完全匹配则为 True,否则为 FalseFalseFalseFalseFalse
DictionaryMaybeFalse如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 FalseFalse如果所有键都与键类型匹配,并且所有值都与值类型匹配(如果适用),则为 True,否则为 False如果所有键都与键类型匹配,并且所有值都与值类型匹配(如果适用),则为 True,否则为 FalseFalseFalseFalse
EnumMaybe如果枚举值 B 的值类型与具体类型 A 匹配,则为 True如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 FalseFalseFalseFalse如果枚举类型匹配则为 True,否则为 falseFalseFalse
CallbackMaybeFalse如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 FalseFalseFalseFalseFalse如果函数签名匹配则为 True,否则为 FalseFalse
NullMaybeFalse如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 FalseFalseFalseFalseFalseFalseTrue

根据您对歧义的接受程度,类型检查器可以在三个不同的级别运行。

  1. 静默- 类型匹配失败被标记为错误,但歧义被忽略
  2. 警告- 类型匹配失败被标记为错误,歧义被标记为警告
  3. 错误- 类型匹配失败和歧义被标记为错误

编译消除歧义的代码库可以发现明显的类型错误,而编译出现歧义错误时的代码则需要在整个代码中添加类型支架。Monkey Types 旨在让您在选择时朝着积极的类型构建,而如果您不这样做,它仍能增加价值。

函数内的类型化

到目前为止,我们一直在明确添加类型脚手架。虽然这使得代码非常易读和明确,但它会为实现添加大量样板。

与类实例变量不同,Monkey Types 系统将通过跟踪分配来推断局部变量的类型。

import Toybox.Lang;
import OldMacDonaldsFarm;

function handleDog(dog as Dog, here as Array, there as Array, everywhere as Array) {
    here.add(dog.woofWoof());
    there.add(dog.woofWoof());
    everywhere.add(dog.woofWoof());
}

function handleCat(cat as Cat, here as Array, there as Array, everywhere as Array) {
    here.add(cat.meowMeow());
    there.add(cat.meowMeow());
    everywhere.add(cat.meowMeow());
}

function eieio() {
    var here = [], there = [], everywhere = [];
    //动物将以狗为基础输入
	//赋值。无需申报
	//它的类型
    var animal = new OldMacDonaldsFarm.Dog();
    //允许,动物当前被分配一个狗的值
    handleDog(animal, here, there, everywhere);
    //动物现在将被输入为猫基于
	//赋值
    animal = new OldMacDonaldsFarm.Cat();
    //允许,动物当前被分配一个Cat值
    handleCat(animal, here, there, everywhere);
}

推理将贯穿分支。如果类型不清楚是基于哪个分支进行的,则类型系统将对选项进行多项类型划分,直到下一次分配。

import Toybox.Lang;

function process(a as Boolean) as Boolean? {
 var x = null;

 if(a) {
     x = true;
 }
 // At this point, x is now the poly type
 // Boolean or Null
 return x;
}

当值具有已知类型定义时,类型检查器将验证是否允许方法调用。

import Toybox.Lang;

class A {
 function foo() {};
 function bar() {};
}

function process() {
 var a = new A();
 a.foo(); // Allowed
 a.bar(); // Allowed
 a.fonz(); // Not allowed
}

对于容器类型,还可以将类型绑定到初始化值。这将控制可分配给容器的内容,但允许本地具有任何值。

import Toybox.Lang;

function example() {
 var a = {} as Dictionary<String, String>;
 a["key"] = "value" // <-- 对a值的赋值必须服从类型

 a = null; // <-- a是Any,可以赋值为null
}
返回值和 Void

默认情况下,函数返回 Any。如果你将类型绑定到函数返回值,类型检查器将确保你返回该类型的值。

import Toybox.Lang;

function isTrue() as Boolean {
    return "true"; // Not allowed
}

如果您的函数没有返回值,您可以使用该Void类型。这将确保函数不会返回值,并且如果函数尝试分配函数的返回值,则会出现错误。

function example() as Void {
 // 函数逻辑
 Sys.println("This function returns no value.");
}
Any 和类型歧义

任何没有绑定类型的函数参数都将是 Any 类型。参数的歧义将渗透到表达式中与其交互的任何成员。如果您提供所有类型定义,类型检查可以保护它们免受多种常见错误的影响。但是,只要有一点点歧义就会阻止任何级别的检查。以这个例子为例,一个函数有结果检查,但没有参数类型a

import Toybox.Lang;

function foo(a) as Integer? {
    // a is of type Any, so Monkey Types can't identify what doThis() is being called
    var x = a.doThis();
    // x is of type Any, so we can't know what the result type is
    var y = x + 3;
    // What is Y? What is Why? What is Love?
    return y;
}

因为a是 Any,Monkey Types 无法对它的任何成员做出任何确定,并且通过代理 Monkey Types 无法对访问这些成员的结果做出确定。

类型转换

关键字as还可以用于表达式中,将值转换为另一种类型。如果类型系统不清楚该类型,这会很有用。

import Toybox.WatchUi;

function process(a as View) {
    (a as MyView).specialMyViewMethod();
}
运行时类型检查(设置之间的兼容性应该考虑)

Monkey Types 的目标之一是不增加任何运行时开销。这使得 Monkey Types 可以在所有兼容 Connect IQ 的产品上直接使用,但在运行时类型检查方面确实会增加成本。简而言之:虽然在编译时您可以访问富有表现力的类型系统,instanceof但在运行时,has限制与以前相同。对于涉及多类型具体类型的情况,这可以很好地工作。

import Toybox.Lang;

function example(x as Number or Float) as Boolean {
    switch(x) {
        case instanceof Number:
            doNumberImpl(x);
            break;
        case instanceof Float:
            doFloatImpl(x);
            break;
    }
}

不幸的是,并非所有情况都能通过这种方式解决。例如,假设我们有以下情况:

typedef Nimble as interface {
 function isNimble() as Boolean;
};

typedef Quick as interface {
 function isQuick() as Boolean;
};

function handleCandleStick(jack as Nimble or Quick) {
 if(jack instanceof Nimble and jack instanceof Quick) {
     if(jack.isNimble() and jack.isQuick() and jack has :jumpOverCandleStick) {
         jack.jumpOverCandleStick();
     }
 }
}

handleCandleStick在接口的情况下Nimble,和Quick是词法类型,并且仅在编译时存在。这将导致编译器错误,因为instanceof只能用于具体类,而不能用于词法类型。在这种情况下,我们可以使用来has解决这个问题。

function handleCandleStick(jack as Nimble or Quick) {
 if(jack has :isNimble and jack has :isQuick and jack has :jumpOverCandleStick) {
     if(jack.isNimble() and jack.isQuick()) {
         jack.jumpOverCandleStick();
     }
 }
}
如果分裂(强制类型转换)

在 Java 等语言中,对象的类型被假定为声明的类型。这可能会导致一些非常多余的强制类型转换或生成大量不必要的局部变量,以向编译器传达某些内容与声明的类型不符的信息。

public boolean foo(SomeInterfaceType x) {
    if(x instanceof SomeConcreteType) {
        // My life will just be easier if I make
        // a new variable, even though it should
        // be possible to assume that x is
        // a SomeConcreteType
        SomeConcreteType y = (SomeConcreteType)x;
        // Do operations on y
    }
}

Monkey C 类型系统将利用 if-splitting,其中分支表达式会导致变量的类型在真和假情况下发生变化。

import Toybox.Lang;

public function foo(x as Number?) as Boolean {
 if(x != null) {
     // 在这个块中,假设x是Number且不为空
 } else {
     // 在这个块中假设x为空
 }
}

==、!= 和instanceof运算符将根据以下规则改变类型

type==!=instanceof!instanceof
AnyIgnoreIgnoreinstanceof类型突变Ignore
ConcreteIgnoreIgnoreinstanceof类型突变Ignore
Poly如果 == 是null,则突变为 Null 类型如果 != 是null,则变异为 poly 类型减nullinstanceof类型突变将类型变异为多边形类型减去类型instanceof
InterfaceIgnoreIgnoreinstanceof类型突变Ignore
ContainerIgnoreIgnoreIgnoreIgnore
DictionaryIgnoreIgnoreIgnoreIgnore
Enum转换为枚举值类型IgnoreIgnoreIgnore
CallbackIgnoreIgnoreIgnoreIgnore
NullIgnoreIgnoreIgnoreIgnore

表达式也可以使用 && 和 || 运算符进行修改。使用 && 运算符,修改将贯穿整个表达式,并随着表达式的继续而进一步修改。

import Toybox.Lang;

typedef Addable as Number or Float or Long or Double or String;

public function foo(x as Addable?) {
 //在第一个子句中,修改x以删除null
	//从多边形类型。在第二个子句中,新多类型
	//被修改为String具体类型。
 if(x != null &&
    x instanceof String) {
     //在这个块中假设x是一个字符串
 }
}

使用 || 运算符,将创建一个新的多项式类型,其中包含两个运算符的结果

import Toybox.Lang;

typedef Addable as Number or Float or Long or Double or String;

public function foo(x as Addable?) {
 if(x instanceof Number ||
    x instanceof Float) {
     // Within this block assume x is a Number or Float
 }
}

当对成员变量进行 if-splitting 时,如果调用函数,所有类型变异都将被删除。

类型模块和类

类成员变量默认绑定到 Any 类型。与局部变量不同,成员变量不是基于赋值进行类型推断的。向成员变量添加类型支架并将名称添加到枚举中将允许更严格的类型检查。常量基于赋值进行类型推断。

class Example {
    // Member variable
    private var _x as Number = 0;

    // Enum values can be explicitly assigned, or by default will
    // be numerically incremented values.
    enum NamedEnum {
        NAMED_ENUM;
    }

    // Constants assume their type by assignment
    private const _constant = "Constant";
}

如果添加类型脚手架,则必须初始化变量或允许其为null。以下示例将导致编译器错误:

import Toybox.Lang;
import Toybox.System;

// Don't shoot
class Messenger {
 private var _message as String;

 public function shareTheMessage() as Void {
     System.println(_message);
 }
}

错误的原因在于_message被声明为字符串,但单独放置时却将其初始化为null。模块变量要么必须在声明时初始化,要么允许为null,而对象成员也可以在函数中初始化initialize。以下代码可解决该错误:

import Toybox.Lang;
import Toybox.System;

// Don't shoot
class Messenger {
 private var _message as String;

 public function initialize() {
    // Initialize message
     _message = "";
 }

 public function shareTheMessage() {
     System.println(_message);
 }
}
类型和继承

扩展类时,类型系统将使用以下规则:

  1. 如果你从父函数扩展一个具有相同数量参数的函数,但不添加类型修饰,则参数和返回值的类型将逐字从父实现转移
  2. 如果你从具有相同数量参数的父函数扩展函数并添加类型修饰,则必须完全匹配参数数量和类型修饰,否则编译器会出错

这使得现有的 Monkey C 代码可以扩展Toybox类型以利用类型检查,而无需添加任何类型修饰。

应用范围类型检查

类型检查器会尝试验证从模块或类中提取的任何成员是否在与调用者相同的所有应用程序范围内可用。如果开发人员确信他们的代码在应用程序范围内是安全的,而类型检查器仍然抱怨,则可以分别使用注释 或 为后台或 Glance 范围禁用此检查:typecheck(disableBackgroundCheck):typecheck(disableGlanceCheck)要禁用后台和 Glance 范围的检查,请使用注释:typecheck([disableBackgroundCheck, disableGlanceCheck])

异常和错误

Monkey C 支持结构化异常处理,用于处理非致命错误并可从中恢复。Java 和 Javascript 开发人员应该熟悉以下语法:

try {
    // Code to execute
}
catch( ex instanceof AnExceptionClass ) {
    // Code to handle the throw of AnExceptionClass
}
catch( ex ) {
    // Code to catch all execeptions
}
finally {
    // Code to execute when
}

您可以使用throw关键字来引发异常。

创建异常

如果您要创建自己的例外情况,请遵循以下规则:

  • 延长Toybox.Lang.Exception
  • 在初始化程序中初始化超类
  • 将字符串消息分配给mMessage成员变量

例如,应用程序特定的异常可以定义如下:

class AppSpecificException extends Lang.Exception {
    //! Constructor
    //! @param msg Message explaining cause
    function initialize(msg) {
        Exception.initialize();
        self.mMessage = msg;
    }
}
错误

由于 Monkey C 使用动态类型,因此编译器无法检查许多错误。如果错误的严重程度足够高,它将引发致命的 API 错误并导致您的应用在运行时终止。这些错误无法被捕获。

注解

Monkey C 允许将符号与类或模块方法和变量关联起来。这些符号当前写入debug.xml编译器生成的文件中,但将来可能会用于添加新功能而无需更改 Monkey C 语法:

(:debug) class TestMethods
{
    (:test) static function testThisClass( x )
}
:background

表示后台进程可用的代码块。

:debug

使用此注释修饰的代码块将不会在编译时包含在发布版本中。

:glance

表示在 Glance 模式下运行时可用的代码块。

:release

使用此注释修饰的代码块将不会在编译时包含在调试版本中。

:test

表示用作 Run No Evil 单元测试并在编译时从应用程序中排除的测试用例。

:typecheck

用此注释修饰的代码块可以指示类型检查器执行/避免某些检查。

:initialized

用此注释修饰的成员变量指示类型检查器可以安全地假定该变量在被引用之前将被初始化。

编码约定

以下是 Monkey C 代码的指导原则:

我们
  • 模块和类采用驼峰式命名法,首字母大写。
  • 函数采用驼峰式命名法,且首字母始终为小写。
  • 私有类成员变量采用驼峰式命名法,第一个字符是下划线(_),然后第一个字母小写。
  • 公共类成员变量采用驼峰式命名规则,首字母小写。
  • 模块变量应采用驼峰式命名法,首字母小写
  • 枚举必须有一个公共前缀,例如COLOR_REDCOLOR_BLUE
  • 在 POMO(Plain Old Monkey C Objects)中,拥有所有公共成员是可以的。
来源
  • 每个 Monkey C 源文件放置一个类。
  • Monkey C 代码应使用空格对齐,每缩进级别四个空格。Monkey C 编辑器会自动将空格转换为制表符并删除尾随空格。
  • 定义模块、类、函数和枚举时,将左括号与定义放在同一行,将右括号与定义首字符对齐。
定义
  • 尽可能避免使用纯全局变量。
  • 因为模块不是纯粹的词汇,并且具有运行时内存成本,所以将类定义放入全局模块是可以接受的。
  • 避免在类定义中使用公共静态成员;而是将这些定义移到父模块中。
  • 在类初始化函数的第一行中,始终调用超类初始化。
样本

以下是一个示例:

class SampleName extends Toybox.Application.AppBase
{
    public var publicVar;
    private var _privateVar;

    function initialize() {
        AppBase.initialize();
    }
    // onStart() is called on application start up
    function onStart(state) {
    }

    // onStop() is called when your application is exiting
    function onStop(state) {
    }

    // Return the initial view of your application here
    function getInitialView(){
        return [new SampleNameView(), new SampleNameDelegate()];
    }
 }

编译器选项

以下是的命令行选项monkeyc

Short OptionLong OptionArgument描述
-d--device设备标识符构建设备可执行文件时所需。指定目标设备。
-e--package-app没有任何指定输出是IQ 用于上传到应用商店的文件。
-f--jungles用冒号分隔的 Jungle 文件路径列表必填。每个 Jungle 指定一个要包含的项目,其中可以包括一个 app 项目和多个 Monkey Barrier 项目。
-g--debug没有任何打印调试输出。
-h--help没有任何打印帮助信息。
-k--profile没有任何在可执行文件中包括分析器信息。当包含分析器信息的可执行文件在设备上运行后,设备将生成可在模拟器中进行分析的分析器信息。
-l--typecheck0 = 关闭,1 = 渐进,2 = 信息丰富,3 = 严格请参阅猴子类型 部分以了解更多信息。
-o--output要输出的文件必需。指定编译器的输出。
-O--optimization0 = 无,1 = 基本,2 = 快速优化,3 = 慢速优化,p = 性能优化,z = 代码空间优化默认1 用于调试构建,2 默认用于发布构建。数字级别可以以字母作为后缀,因此-O 2pz 是允许的参数。
-r--release没有任何不要在 PRG 中包含调试信息。
-t--unit-test没有任何在构建中包括单元测试。
-v--version没有任何打印编译器版本。
-w--warn没有任何默认关闭。显示编译器生成的构建警告。
-y--private-key开发者密钥路径必需。指定用于签署PRGIQ文件的开发人员密钥。
调试日志

如果您遇到编译器错误,monkeyc则需要生成日志以提供报告。以下是创建调试日志的选项:

Long OptionArgument描述
--debug-log-level0 = 错误,1 = 基本调试,2 = 中级调试,3 = 详细调试指定输出的详细程度。
--debug-log-output要生成的日志文件的路径指定要创建的日志文件的路径
--debug-log-device设备标识符允许在构建 Monkey Barrel 时将日志限制到特定设备。
特征控制选项

这些选项用于控制不同的功能:

Long OptionArgument描述
--disable-api-has-check-removal没有任何禁用优化 API 的检查。
--disable-v2-opcodes没有任何禁用 V2 操作码的生成。
Private Options

这些选项通常已经设置好,不需要使用:

Short OptionLong OptionArgument描述
-a--apidb路径至api.db指定 API 链接信息的路径。
-b--apimir路径至api.mir指定 API 类型信息的路径。
-i--import-dbg路径至api.debug.xmlAPI 调试信息的路径。
-p--project-info路径至projectInfo.xmlSDK 项目定义的路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值