这可能是最详细的monkeyc开发手册!篇幅较长全文15000字,
建议根据目录选择性观看,可以收藏开发时查找(每个功能点都有示例)
大标题后括号中有标注希望对你有所帮助!!!
程序的类型
Connect IQ 系统中有五种应用类型:
- 表盘- 这是 Garmin 可穿戴设备的主屏幕。它们可以是简单的钟表,也可以是包含数十种健康和健身统计数据的复杂数据屏幕。
- 数据字段- 数据字段是 Garmin 活动体验的插件。它们允许计算新指标或允许将新数据带入锻炼。
- 小部件- 小部件是可以从主屏幕启动的微型应用程序。它们旨在提供一目了然的信息访问。
- 设备应用程序- 设备应用程序是最强大的应用程序类型,并提供对系统的完全访问权限。
- 音频内容提供商- 音频内容提供商是音乐可穿戴设备上媒体播放器的插件,它们在媒体和第三方内容服务之间架起了桥梁。
API 和应用权限
应用类型定义了应用的用户上下文。例如,表盘由于在低功耗模式下运行,因此受到许多限制。为了强制执行这些限制,Connect IQ 虚拟机将根据您的应用类型限制可用的 API。
模块名称 | 数据字段 | 表盘 | 小部件 | 应用程序 | 音频内容提供商 | API 级别 |
---|---|---|---|---|---|---|
Toybox.Activity | ✓ | ✓ | ✓ | 1.0.0 | ||
Toybox.ActivityMonitor * | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.ActivityRecording * | ✓ | 1.0.0 | ||||
Toybox.Ant * | ✓ | ✓ | ✓ | ✓ | 1.0.0 | |
Toybox.Application | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.Attention | ✓ | ✓ | ✓ | ✓ | 1.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.Graphics | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.Lang | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.Math | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.Media | ✓ | 3.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.StringUtil | ✓ | ✓ | ✓ | ✓ | ✓ | 1.3.0 |
Toybox.System | ✓ | ✓ | ✓ | ✓ | ✓ | 1.3.0 |
Toybox.Test | ✓ | ✓ | ✓ | ✓ | ✓ | 2.1.0 |
Toybox.Time | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.Timer | ✓ | ✓ | ✓ | ✓ | 1.0.0 | |
Toybox.UserProfile * | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.WatchUi | ✓ | ✓ | ✓ | ✓ | ✓ | 1.0.0 |
Toybox.Weather | ✓ | ✓ | ✓ | ✓ | ✓ | 3.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
语句,或import
Java™、Ruby 或 Python™ 中的 。using
语句在词汇上将模块带入我们的名称空间。 在子句之后using
,我们可以通过其缩写名称(在本例中为 )来引用模块System
。Toybox
是 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!"
}
字段访问级别
有三个访问级别—— public
、protected
和private
。
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
其中一种不可变类型(Number
,Float
,Char
,Long
,Double
)String
,那么它将返回对象本身。否则它将返回一个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);
}
您还可以使用关键字将模块纳入您的作用域级别using
。using
允许通过符号将模块导入到另一个类或模块中:
using Toybox.System;
function foo() {
System.print( "Hello" );
}
该as
子句提供了一种在范围内为模块分配不同名称的方法。这对于缩短模块名称或当您只是不同意我们的命名方案时很有用:
using Toybox.System as Sys;
function foo() {
Sys.print( "Hello" );
}
import
和using
之间的区别很微妙。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-
true
和false
- 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 '可能看起来像关键字,但它们实际上是文字和操作符;不能在程序中使用它们作为标识符。
as | const | enum | has | module | self | using |
break | continue | extends | hidden | private | static | var |
case | default | finally | if | protected | switch | while |
catch | do | for | instanceof | public | throw | |
class | else | function | me | return | try |
声明变量
所有局部变量必须使用关键字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
运算符
优先级 | 操作员 | 描述 |
---|---|---|
1 | new | 创建 |
! | 逻辑非 | |
~ | 按位非 | |
( ) | 函数调用 | |
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" );
}
如果调用对象的实例,请在调用前面加上对象和“ .
”。
访问类成员public
和protected
变量时,应使用以下格式之一进行访问:
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 提供了两种工具来进行运行时类型检查 —instanceof
和has
。
该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
在定义类型时使用该子句即可。
- 其类型绑定到 poly 类型中的一种类型的值
- 绑定到多类型的值,其类型在目标类型的定义范围内
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 语言有两种原生容器类型,Array
和Dictionary
。
虽然 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→ | Any | Concrete | Poly | Interface | Container | Dictionary | Enum | Callback | Null |
---|---|---|---|---|---|---|---|---|---|
Any | True | True | True | True | True | True | True | True | True |
Concrete | Maybe | 如果 B 是 A 或者扩展了 A,则为 True | 如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 False | False | 如果 A 是字典或数组 | 如果 A 是字典则为可能,否则为假 | 如果枚举值类型与 A 匹配则为 True,否则为 False | False | False |
Poly | Maybe | 如果 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 |
Interface | Maybe | 如果 B 是包含接口 A 的所有成员的对象,则为 True,否则为 False | 也许如果 poly 包含匹配的类型 | 如果 B 的接口包含 A 中的所有成员,则为 True。 | 如果类数组或字典包含接口的所有成员则为 True,否则为 False | 如果类 Dictionary 包含接口的所有成员,则为 True,否则为 False | False | False | False |
Container | Maybe | False | 如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 False | False | 如果容器类型和键/值类型完全匹配则为 True,否则为 False | False | False | False | False |
Dictionary | Maybe | False | 如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 False | False | 如果所有键都与键类型匹配,并且所有值都与值类型匹配(如果适用),则为 True,否则为 False | 如果所有键都与键类型匹配,并且所有值都与值类型匹配(如果适用),则为 True,否则为 False | False | False | False |
Enum | Maybe | 如果枚举值 B 的值类型与具体类型 A 匹配,则为 True | 如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 False | False | False | False | 如果枚举类型匹配则为 True,否则为 false | False | False |
Callback | Maybe | False | 如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 False | False | False | False | False | 如果函数签名匹配则为 True,否则为 False | False |
Null | Maybe | False | 如果 B 中的一个多项式类型与 A 匹配,则为 Maybe,否则为 False | False | False | False | False | False | True |
根据您对歧义的接受程度,类型检查器可以在三个不同的级别运行。
- 静默- 类型匹配失败被标记为错误,但歧义被忽略
- 警告- 类型匹配失败被标记为错误,歧义被标记为警告
- 错误- 类型匹配失败和歧义被标记为错误
编译消除歧义的代码库可以发现明显的类型错误,而编译出现歧义错误时的代码则需要在整个代码中添加类型支架。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 |
---|---|---|---|---|
Any | Ignore | Ignore | instanceof 类型突变 | Ignore |
Concrete | Ignore | Ignore | instanceof 类型突变 | Ignore |
Poly | 如果 == 是null ,则突变为 Null 类型 | 如果 != 是null ,则变异为 poly 类型减null 。 | instanceof 类型突变 | 将类型变异为多边形类型减去类型instanceof |
Interface | Ignore | Ignore | instanceof 类型突变 | Ignore |
Container | Ignore | Ignore | Ignore | Ignore |
Dictionary | Ignore | Ignore | Ignore | Ignore |
Enum | 转换为枚举值类型 | Ignore | Ignore | Ignore |
Callback | Ignore | Ignore | Ignore | Ignore |
Null | Ignore | Ignore | Ignore | Ignore |
表达式也可以使用 && 和 || 运算符进行修改。使用 && 运算符,修改将贯穿整个表达式,并随着表达式的继续而进一步修改。
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); } }
类型和继承
扩展类时,类型系统将使用以下规则:
- 如果你从父函数扩展一个具有相同数量参数的函数,但不添加类型修饰,则参数和返回值的类型将逐字从父实现转移
- 如果你从具有相同数量参数的父函数扩展函数并添加类型修饰,则必须完全匹配参数数量和类型修饰,否则编译器会出错
这使得现有的 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_RED、COLOR_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 Option | Long Option | Argument | 描述 |
---|---|---|---|
-d | --device | 设备标识符 | 构建设备可执行文件时所需。指定目标设备。 |
-e | --package-app | 没有任何 | 指定输出是IQ 用于上传到应用商店的文件。 |
-f | --jungles | 用冒号分隔的 Jungle 文件路径列表 | 必填。每个 Jungle 指定一个要包含的项目,其中可以包括一个 app 项目和多个 Monkey Barrier 项目。 |
-g | --debug | 没有任何 | 打印调试输出。 |
-h | --help | 没有任何 | 打印帮助信息。 |
-k | --profile | 没有任何 | 在可执行文件中包括分析器信息。当包含分析器信息的可执行文件在设备上运行后,设备将生成可在模拟器中进行分析的分析器信息。 |
-l | --typecheck | 0 = 关闭,1 = 渐进,2 = 信息丰富,3 = 严格 | 请参阅猴子类型 部分以了解更多信息。 |
-o | --output | 要输出的文件 | 必需。指定编译器的输出。 |
-O | --optimization | 0 = 无,1 = 基本,2 = 快速优化,3 = 慢速优化,p = 性能优化,z = 代码空间优化 | 默认1 用于调试构建,2 默认用于发布构建。数字级别可以以字母作为后缀,因此-O 2pz 是允许的参数。 |
-r | --release | 没有任何 | 不要在 PRG 中包含调试信息。 |
-t | --unit-test | 没有任何 | 在构建中包括单元测试。 |
-v | --version | 没有任何 | 打印编译器版本。 |
-w | --warn | 没有任何 | 默认关闭。显示编译器生成的构建警告。 |
-y | --private-key | 开发者密钥路径 | 必需。指定用于签署PRG 或IQ 文件的开发人员密钥。 |
调试日志
如果您遇到编译器错误,monkeyc
则需要生成日志以提供报告。以下是创建调试日志的选项:
Long Option | Argument | 描述 |
---|---|---|
--debug-log-level | 0 = 错误,1 = 基本调试,2 = 中级调试,3 = 详细调试 | 指定输出的详细程度。 |
--debug-log-output | 要生成的日志文件的路径 | 指定要创建的日志文件的路径 |
--debug-log-device | 设备标识符 | 允许在构建 Monkey Barrel 时将日志限制到特定设备。 |
特征控制选项
这些选项用于控制不同的功能:
Long Option | Argument | 描述 |
---|---|---|
--disable-api-has-check-removal | 没有任何 | 禁用优化 API 的检查。 |
--disable-v2-opcodes | 没有任何 | 禁用 V2 操作码的生成。 |
Private Options
这些选项通常已经设置好,不需要使用:
Short Option | Long Option | Argument | 描述 |
---|---|---|---|
-a | --apidb | 路径至api.db | 指定 API 链接信息的路径。 |
-b | --apimir | 路径至api.mir | 指定 API 类型信息的路径。 |
-i | --import-dbg | 路径至api.debug.xml | API 调试信息的路径。 |
-p | --project-info | 路径至projectInfo.xml | SDK 项目定义的路径。 |