简介
是什么?
Expression 是一个用于在 Apple 和 Linux 平台运行时评估表达式的 Swift 框架
Expression 库分为两部分
-
Expression
类,类似于 Foundation 内置的NSExpression
类,但提供了更好的自定义运算符支持、更 Swift 适配的 API 和更优越的性能。 -
AnyExpression
,Expression 的扩展,用于处理任意类型,并提供了对常见类型(如String
、Dictionary
、Array
和Optional
)的额外内置支持。
为什么?
在许多情况下,能够在运行时评估一个简单的表达式是非常有用的。一些示例可以在库中包含的示例应用中看到。
- 科学计算器
- CSS样式颜色字符串解析器
- 基本的布局引擎,类似于AutoLayout
但是还有其他可能的用途,例如:
- 电子表格应用
- 配置(例如,使用配置文件中的表达式来避免数据重复)
- 简单脚本语言的底层
(如果您发现其他用例,请告知我,我会添加它们。)
通常这类计算会涉及到将JavaScript、Lua等大型解释型语言嵌入到您的应用程序中。Expression避免了这种开销,并且由于减少了任意代码注入或无限循环、缓冲区溢出等风险,因此也更加安全。
Expression速度快、轻量级、经过充分测试,完全使用Swift编写。在评估简单表达式方面,它比使用JavaScriptCore更快(请参阅基准应用以进行科学比较。)
如何做到的?
Expression通过将表达式字符串解析为符号树,然后在运行时对其进行评估来完成工作。每个符号映射到一个Swift闭包(函数),在评估期间执行。存在表示常用数学运算的内建函数,或者您也可以提供您自己的自定义函数。
虽然Expression
类只与Double
值一起工作,但AnyExpression使用NaN boxing技术,通过IEEE浮点规范中未使用的位模式来引用任意数据。
用法
安装
Expression
类封装在单个文件中,所有公共函数前缀或名称空间都已定义,因此您只需将Expression.swift
文件拖放到项目中即可使用。如果您想使用AnyExpression扩展,则还需要包含AnyExpression.swift
文件。
如果您喜欢,有一个框架您可以导入,其中包含Expression
和AnyExpression类。您可以通过拖放或使用CocoaPods、Carthage或Swift Package Manager自动安装此框架。
要使用CocoaPods安装Expression,请将以下内容添加到您的Podfile中:
pod 'Expression', '~> 0.13'
要使用Carthage安装,请将在您的Cartfile中添加此内容:
github "nicklockwood/Expression" ~> 0.13
要使用Swift Package Manager安装,请将此内容添加到您的Package.swift文件中的dependencies:
部分:
.package(url: "https://github.com/nicklockwood/Expression.git", .upToNextMinor(from: "0.13.0")),
集成
您可以通过传递一个包含表达式字符串以及(可选的)以下一个或多个参数来创建一个Expression
实例
- 一组配置选项 - 用于启用或禁用某些功能
- 一个命名的常量字典 - 这是指定预定义常量最简单的方式
- 一个命名的数组常量字典 - 这是指定预定义相关值数组的简单方式
- 一个符号和
SymbolEvaluator
函数的字典 - 这允许您提供自定义变量、函数或操作符
然后,通过调用evaluate()
方法来计算结果。
注意:给定Expression
或AnyExpression
实例的evaluate()
函数是线程安全的,这意味着您可以从多个线程并发调用它。
默认情况下,Expression已实现大多数标准数学函数和操作符,因此,如果您的应用需要支持额外的函数或变量,您只需要提供自定义符号字典。您可以混合和匹配实现,因此如果同时拥有一些自定义常量或数组以及一些自定义函数或操作符,您可以提供单独的常量字典和符号字典。
以下是一些示例
// Basic usage:
// Only using built-in math functions
let expression = Expression("5 + 6")
let result = try expression.evaluate() // 11
// Intermediate usage:
// Custom constants, variables and and functions
var bar = 7 // variable
let expression = Expression("foo + bar + baz(5) + rnd()", constants: [
"foo": 5,
], symbols: [
.variable("bar"): { _ in bar },
.function("baz", arity: 1): { args in args[0] + 1 },
.function("rnd", arity: 0): { _ in arc4random() },
])
let result = try expression.evaluate()
// Advanced usage:
// Using the alternative constructor to dynamically hex color literals
let hexColor = "#FF0000FF" // rrggbbaa
let expression = Expression(hexColor, pureSymbols: { symbol in
if case .variable(let name) = symbol, name.hasPrefix("#") { {
let hex = String(name.characters.dropFirst())
guard let value = Double("0x" + hex) else {
return { _ in throw Expression.Error.message("Malformed color constant #\(hex)") }
}
return { _ in value }
}
return nil // pass to default evaluator
})
let color: UIColor = {
let rgba = UInt32(try expression.evaluate())
let red = CGFloat((rgba & 0xFF000000) >> 24) / 255
let green = CGFloat((rgba & 0x00FF0000) >> 16) / 255
let blue = CGFloat((rgba & 0x0000FF00) >> 8) / 255
let alpha = CGFloat((rgba & 0x000000FF) >> 0) / 255
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}()
请注意,evaluate()
函数可能会抛出错误。如果表达式格式不正确,或者引用了未知符号,则会在评估过程中自动抛出错误。在上面的颜色示例中,自定义符号实现也可能抛出应用程序特定的错误。
对于像第一个示例这样的简单、硬编码的表达式,没有抛出错误的可能,但如果您接受用户输入的表达式,您必须始终确保捕获和处理错误。Expression生成的错误消息详细且易于阅读(但未本地化)。
do {
let result = try expression.evaluate()
print("Result: \(result)")
} catch {
print("Error: \(error)")
}
当使用constants
、arrays
和symbols
字典时,错误消息生成由Expression库自动处理。如果您需要支持动态符号解码(如前面颜色示例中所示),您可以使用更复杂的init(impureSymbols:pureSymbols)
初始化器。
init(impureSymbols:pureSymbols)
初始化器接受一对查找函数,该函数接受一个Symbol
并返回一个SymbolEvaluator
函数。此接口非常强大,因为它允许您动态解析符号(如在颜色示例中的十六进制颜色常量),而不需要预先创建所有可能值的字典。
对于每个符号,您的查找函数可以返回一个SymbolEvaluator
函数,或者返回nil。如果您不识别符号,您应该返回nil,使其可以被默认评估器处理。如果两个查找函数均不匹配符号,并且它不是标准数学或布尔函数之一,则evaluate()
将抛出错误。
在某些情况下,你可能识别出一个符号,但一定要确认它是错误的,这是一个提供比Expression默认生成的更具体的错误消息的机会。以下示例匹配一个具有1个参数(意味着它只接受一个参数)的一元函数bar
。这只会匹配单个参数的bar
的调用来忽略 Summon calls。参数为0个或者多个参数。
switch symbol {
case .function("bar", arity: 1):
return { args in args[0] + 1 }
default:
return nil // pass to default evaluator
}
因为bar
是一个自定义函数,我们知道它应该只接受一个参数,因此在错误数量的参数被调用时抛出一个错误比返回nil(表示该函数不存在)更为有用。这将看起来像这样
switch symbol {
case .function("bar", let arity):
guard arity == 1 else {
return { _ in throw Expression.Error.message("function bar expects 1 argument") }
}
return { arg in args[0] + 1 }
default:
return nil // pass to default evaluator
}
注意:Expression的新版本可以正确报告这样的简单可变参数错误,因此这是一个略微牵强附会的例子,但对于其他类型的错误,这种方法可能是有用的,例如当参数超出范围或类型不正确时。
符号
表达式由数字文字和符号组成,这些都是Expression.Symbol
枚举类型的实例。内建的数学和布尔库定义了一些标准符号,但你也可以自由定义自己的。
Expression.Symbol
枚举支持以下符号类型
变量
.variable(String)
这是一个代表表达式中常量或变量的字母数字标识符。标识符可以是任何以字母、下划线(_)、美元符号($)、井号(@)或井号/英镑符号(#)开头的一系列字母和数字的序列。
和Swift一样,Expression允许在标识符中使用Unicode字符,如emoji和科学符号。与Swift不同,Expression的标识符也可以包含点(.)作为分隔符,这在命名空间(如在Layout应用程序示例中演示的)中很有用。
解析器也接受引号字符串作为标识符。可以使用单引号、双引号或反引号。由于Expression
只处理数字值,因此将这些字符串标识符映射到数字的责任就交给了你的应用程序(如果你使用AnyExpression,那么这会自动处理)。
与常规标识符不同,引号标识符可以包含任何Unicode字符,包括空格。可以使用反斜杠(\')来转义换行符、引号和其他特殊字符。转义序列为您解码,但保留外部引号,以便您可以将字符串与其他标识符区分开来。
最后,未引号的标识符允许以单个引号结束,因为这是数学中表示修改过的值的常用符号。在标识符的其他地方出现的引号将视为名称的结束。
要验证给定的字符串是否可以作为标识符安全使用,可以使用Expression.isValidIdentifier()
方法。
操作符
.infix(String)
.prefix(String)
.postfix(String)
这些符号代表的是操作符。操作符可以由一个或多个字符组成,它可以包含几乎所有不与有效标识符名称冲突的符号,但有一些例外。
-
逗号(,)本身是一个有效的操作符,但不能作为较长字符序列的一部分。
-
方括号字符
[
、圆括号(()、花括号({)及其对应字符是保留的,不能用作操作符。 -
操作符可以以一个或多个点(。)或破折号(-)开始,但是点或破折号不能出现在其他任何字符之后。以下是可以的:
...
,..<
,.
,-
,--
,-=
,-->
但是以下是不可以的:
+.
,>.<
,*-
,-+-
,<--
,.-
,-.
要验证给定的字符序列是否安全用作操作符,可以使用Expression.isValidOperator()
方法。
您可以通过后缀/前缀变体或相反的方式覆盖现有的中缀操作符。歧义取决于操作符周围的空白(这与Swift使用的相同的方法)。
任何有效的标识符也可以用作中缀操作符,通过将其放置在两个操作数之间,或者作为后缀操作符,通过将其放置在操作数之后。例如,在处理距离逻辑时,您可以将m
和cm
定义为后缀操作符,或者使用and
作为布尔&&
操作符的更易于阅读的替代选项。
操作符优先级遵循标准的BODMAS顺序,乘法/除法的优先级高于加法/减法。前缀操作符比后缀操作符优先级高,后缀操作符比中缀操作符优先级高。目前无法指定自定义操作符的优先级 - 所有操作符都具有等于加法和减法的优先级。
支持标准布尔操作符,并遵循正常的优先级规则,但是有一个例外,即短路的(其中右侧参数可能不会根据左侧参数进行计算)是不支持的。解析器还会识别三元?:
操作符,将a ? b : c
视为具有三个参数的单个中缀操作符。
函数
.function(String, arity: Arity)
函数符号通过名称和Arity
定义,其中Arity
是它所期望的参数数量。类型是enum,可以设置为exactly(Int)
或atLeast(Int)
,以表示变长函数。给定的函数名称可以多次与不同的arity重载。
注意: Arity
符合ExpressibleByIntegerLiteral
,因此对于固定参数数量的函数,您只需编写.function("foo", arity: 2)
,而不是.function("foo", arity: .exactly(2))
。
通过在括号中调用其名称并指定逗号分隔的参数序列来调用函数。如果不匹配任何指定的arity变体,将抛出arityError
。
由于函数符号必须有名称,因此直接在表达式中使用匿名函数(例如存储在变量中,或由其他函数返回的函数)是不可能的。
这种语法支持存在。但是,如果您实现了函数调用操作符 .infix("()")
,它接受一个或多个参数,其中第一个参数被视为将被调用的函数。这一特性在 Expression
中(其中值都是数值)作用有限,但 AnyExpression 使用这种方法以提供对 [匿名函数(#anonymous-functions)
数组
.array(String)
数组符号代表可以按索引访问的值的序列。在表达式中通过名称后跟方括号中的索引参数引用数组符号。
使用数组与 Expression
的最简单方法是使用 arrays
初始化参数传递一个常量数组值。对于变量数组,您可以通过 symbols
参数返回一个 .array()
符号实现。
Expression 还支持类似于 Swift 的数组文字语法,如 [1, 2, 3]
和任意表达式的索引索引,如 (a + b)[c]
。数组文字映射到数组文字构造符符号 .function("[]", arity: .any)
,而索引映射到数组索引运算符 .infix("[]")
。
由于 Expression
不能与非数值类型一起工作,因此数组文字构造符和数组索引运算符在 Expression
中没有默认实现。然而,这两个都在 AnyExpression 的标准符号库中实现。
性能
缓存
默认情况下,Expression 缓存解析的表达式。表达式缓存的大小无限。在大多数应用程序中,这几乎永远不会成为问题 - 表达式很小,即使是您能想到的最复杂的表达式也可能不到 1KB,所以需要很多表达式才会造成内存压力 - 但如果您出于某种原因确实需要回收已缓存表达式的内存,可以通过调用 flushCache()
方法来完成。
Expression.flushCache())
flushCache()
方法接受一个可选的字符串参数,因此您也可以从缓存中删除特定的表达式,而不会清除其他表达式。
Expression.flushCache(for: "foo + bar"))
如果您更喜欢更细粒度的缓存控制,可以不缓存预先解析的表达式,然后从预先解析的表达式创建 Expression 实例,如下所示
let expressionString = "foo + bar"
let parsedExpression = Expression.parse(expressionString, usingCache: false)
let expression = Expression(parsedExpression, constants: ["foo": 4, "bar": 5])
通过在上面的代码中将 usingCache
参数设置为 false
,我们避免了将表达式添加到全局缓存。您也可以根据自己实现自己的缓存,通过存储解析的表达式并重新使用它,这可能在某些情况下比内置缓存更高效(例如,如果您是单线程代码,可以避免线程管理)。
Expression.parse() 方法的第二种变体接受一个 String.UnicodeScalarView.SubSequence
和可选的终止分隔符字符串列表。这可以用于匹配更长字符串中的嵌入式表达式,并在达到分隔符时保持在正确的字符序列 startIndex
位置以便继续解析。
let expressionString = "lorem ipsum {foo + bar} dolor sit"
var characters = String.UnicodeScalarView.SubSequence(expression.unicodeScalars)
while characters.popFirst() != "{" {} // Read up to start of expression
let parsedExpression = Expression.parse(&characters, upTo: "}")
let expression = Expression(parsedExpression, constants: ["foo": 4, "bar": 5])
优化
默认情况下,尽可能地优化表达式以提高评估效率。常见的优化包括用字面值替代常量、以及当所有参数都是常量时用结果替换纯函数或运算符。
优化器以增加初始化时间为代价,缩短了评估时间,对于那些只会评估一次或两次的表达式,这种权衡可能不值得,这时您可以使用 options
参数禁用优化。
let expression = Expression("foo + bar", options: .noOptimize, ...)
另一方面,如果您的表达式被评估数百或数千次,您将希望充分利用优化器来提高应用程序的性能。为确保获得 Expression 优化器的最佳效益,请遵循以下指南:
-
始终通过
constants
或arrays
参数而不是在symbols
字典中以变量形式传递常量值。常量值可以内联,而变量必须在每次函数评估时重新计算,以防其发生变化。 -
如果您的自定义函数和运算符全部是 纯 的,即它们没有副作用,并且对于给定的参数值始终返回相同的输出,那么您应该为您的表达式设置
pureSymbols
选项。此选项告诉优化器,如果所有参数都是常量,则可以安全地内联symbols
字典中的任何函数或运算符。请注意,pureSymbols
选项不影响变量或数组符号,因为它们永远不会内联。 -
如果您的表达式可能包含常量值,但不是所有可能值都可以预先计算,例如在十六进制颜色示例中的编码值,或必须在深层对象图中查找的任意键路径,您可以使用
init(pureSymbols:)
初始化器解码或查找所需的特定值。
标准库
数学符号
默认情况下,Expression 支持一些基本的数学函数、运算符和常量,这些对任何特定应用程序都非常有用。
如果您使用自定义符号字典,您可以直接覆盖任何默认符号,或者通过不同数量的参数数(齐次性)重载默认函数。您未显式覆盖的来自标准库的任何符号仍然可用。
要显式禁用标准库中的单个符号,您可以覆盖它们并抛出异常
let expression = Expression("pow(2,3)", symbols: [
.function("pow", arity: 2): { _ in throw Expression.Error.undefinedSymbol(.function("pow", arity: 2)) }
])
try expression.evaluate() // this will throw an error because pow() has been undefined
如果您正在使用init(impureSymbols:pureSymbols:)
初始化器,您可以返回nil
以回退到标准库函数和运算符,对未识别的符号进行还原。如果您不希望在自己的表达式中提供对标准库函数的访问,则对于未识别的符号抛出错误而不是返回nil
。
let expression = Expression("3 + 4", pureSymbols: { symbol in
switch symbol {
case .function("foo", arity: 1):
return { args in args[0] + 1 }
default:
return { _ in throw Expression.Error.undefinedSymbol(symbol) }
}
})
try expression.evaluate() // this will throw an error because no standard library operators are supported, including +
以下是当前支持的数学符号
常量
pi
中缀运算符
+ - / * %
前缀运算符
-
函数
// Unary functions
sqrt(x)
floor(x)
ceil(x)
round(x)
cos(x)
acos(x)
sin(x)
asin(x)
tan(x)
atan(x)
abs(x)
log(x)
// Binary functions
pow(x,y)
atan2(x,y)
mod(x,y)
// Variadic functions
max(x,y,[...])
min(x,y,[...])
布尔符号
除了数学,表达式还支持布尔逻辑,遵循C约定,零为假,任何非零值都为真。默认布尔符号不是默认启用,但您可以使用.boolSymbols
选项启用它们
let expression = Expression("foo ? bar : baz", options: .boolSymbols, ...)
与数学符号一样,可以使用symbols
初始化程序参数单独覆盖或禁用给定表达式的所有标准布尔运算符。
以下是当前支持的布尔符号
常量
true
false
中缀运算符
==
!=
>
>=
<
<=
&&
||
前缀运算符
!
三元运算符
?:
AnyExpression
用法
AnyExpression
的使用几乎与Expression
类完全相同,有以下例外
AnyExpression
的SymbolEvaluator
函数接受并返回Any
而不是Double
- 在创建
AnyExpression
时默认启用布尔符号和运算符 - 为
AnyExpression
构造函数没有单独的arrays
参数。如果要将数组或字典常量传递,可以像任何其他值类型一样将其添加到constants
字典中 AnyExpression
支持[匿名函数](#anonymous-functions),可以是类型Expression.SymbolEvaluator
或AnyExpression.SymbolEvaluator
的任何值- 您还可以使用常量字典将
Expression.SymbolEvaluator
或AnyExpression.SymbolEvaluator
函数传递到AnyExpression
,它们的行为就像普通函数符号一样
您可以按照以下方式创建和评估AnyExpression
实例
let expression = AnyExpression("'hello' + 'world'")
let result: String = try expression.evaluate() // 'helloworld'
请注意字符串字面量使用单引号(')。任何表达式 AnyExpression
支持单引号或双引号作为字符串字面量,这两者之间没有区别,只是单引号在 Swift 字符串字面量中不需要转义。
由于 AnyExpression
的 evaluate()
方法有一个泛型返回类型,您需要告诉它期望的类型。在上面的例子中,我们通过指定 result
变量的显式类型来做到这一点,但您也可以使用 as
操作符(不使用 ! 或 ?)来做。
let result = try expression.evaluate() as String
evaluate
函数在类型上具有一定的宽容性,所以如果(例如)表达式返回布尔值,但您指定 Double
作为期望的类型,类型将自动转换。但如果它返回字符串而您请求 Bool
,则会抛出类型不匹配错误。
当前支持的自定义转换类型有:
- T -> Optional
- Numeric -> Numeric
- Array -> Array
- Numeric -> Bool
- Bool -> Numeric
- Any -> String
符号
除了支持字符串字面量之外,AnyExpression
还扩展了 Expression
的标准库,增加了处理可选参数和空值的额外符号。
nil
- 空字面量??
- 空合并运算符
可选解包是自动进行的,因此目前不需要后缀 ?
或 !
运算符。 nil
(即 Optional.none
)和 NSNull
被同等对待,以避免在处理 JSON 或 Objective-C API 数据时产生混淆。
比较运算符(如 ==
和 !=)也被扩展到可应用于任何 Hashable
类型,而且可以使用 + 来进行字符串连接,就像上面的例子中所做的那样。
对于 array
符号,AnyExpression
可以使用任何 Hashable
类型作为索引。这意味着 AnyExpression
可以与 Dictionary
值一样与 Array
和 ArraySlice
一起使用。
字面量
如上所述,AnyExpression
支持使用单引号(')或双引号(")定界的引用字符串字面量。在字符串内部可以使用反斜杠(\)来转义特殊字符。
AnyExpression
支持使用方括号定义的数组字面量,例如 [1, 2, 3]
或 ['foo', 'bar', 'baz']
。数组字面量可以包含值类型和/或子表达式。
您还可以使用 ..<
和 ...
语法创建范围字面量。支持闭区间、半开区间和部分范围。范围与 Int
或 String.Index
值一起使用,并可用于与数组和字符串索引语法相结合,以进行切片操作。
匿名函数
除了普通的命名函数符号外,AnyExpression
还可以调用匿名函数,这些匿名函数是类型为 Expression.SymbolEvaluator
或 AnyExpression.SymbolEvaluator
的值,可以存储在常量中或从子表达式中返回。
您可以通过使用常量值而不是 .function()
符号将匿名函数传递给 AnyExpression
,但请注意,此方法不允许您通过自变量数量来重载具有相同名称的函数。
与函数符号不同,匿名函数不支持重载,但您可以在函数体内部使用 switch 语句来实现根据参数数量不同的行为。如果您传递了不支持数量的参数,还应抛出 arityMismatch
错误,因为这无法自动检测,例如。
let bar = { (args: [Any] throws -> Any in
switch args.count {
case 1:
// behavior 1
case 2:
// behavior 2
default:
throw Expression.Error.arityMismatch(.function("bar", arity: 2))
}
}
// static function foo returns anonymous function bar, which is called in the expression
let expression = AnyExpression("foo()(2)", symbols: [
.function("foo"): { _ in bar }
])
注意:由于假设匿名函数是不纯的,因此无论您是否使用 pureSymbols
选项,它们都不适合内联。
Linux 支持
AnyExpression 在 Linux 上工作,但有以下限制
AnyExpression
由于 Linux 基础的限制,不支持NSString
桥接。如果您想使用AnyExpression
与NSString
值,则必须在评估前后手动将它们转换为String
。
示例项目
基准测试
基准测试应用程序使用 Expression
、AnyExpression
、NSExpression
和 JavaScriptCore 的 JSContext
分别运行一组测试表达式。然后,它将解析和评估表达式的持续时间计时,并在表中显示结果。
时间以微秒(µs)或毫秒显示。每个类别中最快的结果以绿色显示,最慢的结果以红色显示。
为了得到准确的结果,应该在真机上运行 Benchmark 应用程序的开发模式。你可以下拉表格来刷新测试结果。测试在主线程上运行,因此刷新时屏幕会短暂锁定,请不要感到惊讶。
在我的测试中,Expression 的实现始终是最快的,而 JavaScriptCore 的实现始终是最慢的,无论是在初始设置还是初始化上下文之后的评估中。
计算器
关于这个没有太多要说的。这是一个计算器。你可以输入数学表达式,它会计算结果(如果输入无效,则显示错误)。
颜色
颜色示例演示了如何使用 AnyExpression
创建一个(大多数情况下)符合CSS规范的颜色解析器。它接受包含命名颜色、十六进制颜色或 rgb()
函数调用的字符串,并返回一个UIColor对象。
布局
这里事情变得有趣了:布局示例演示了一个简单但可用的布局系统,它支持视图坐标的任意表达式。
它在概念上类似于 AutoLayout,但是有一些重要区别
- 表达式可以是简单或复杂的。在 AutoLayout 中,每个约束都使用一个固定的公式,只有操作数是可互换的。
- 与在视图的属性之间应用任意数量的约束相比,每个视图只有一个大小和位置可以进行计算。
- 布局是确定性的。在解决冲突时不使用加权系统,并禁止循环引用。可以利用显式乘数实现加权关系。
示例视图的默认布局值已在 Storyboard 中设置,但你可以通过在应用中触摸视图并输入新值来实时编辑它们。
以下是需要注意的一些事项
- 每个视图都有一个定义其屏幕坐标的
top
、left
、width
和height
表达式。 - 视图有一个可选的
key
(类似于标签,但基于字符串)可用于从其他视图引用其属性。 - 任何视图的基于表达式的属性可以引用任何其他属性(同一视图或任何其他视图),甚至可以引用多个属性。
- 每个视图都有一个底部和右侧属性。这些属性是计算得出的,无法直接设置,但可以在表达式中使用。
- 循环引用(一个属性的值依赖于自身)是被禁止的,系统会检测到它们。
- 宽度和高度属性可以使用auto变量,这对于普通视图来说并没有什么有用的作用,但可以与文本标签一起使用,根据文本量计算给定宽度的最佳高度。
- 数值以屏幕点为单位衡量。百分比值相对于父视图的宽度和高度属性。
- 请记住,您可以使用min()和max()这样的函数来确保相对值不会高于或低于一个固定的阈值。
这只是一个玩具例子,但如果你喜欢这个概念,请查看GitHub上的Layout框架[https://github.com/schibsted/layout],它将这个想法提升到了更高的层次。
REPL
Expression REPL (读写打印循环) 是一个Mac命令行工具,用于评估表达式。与计算器示例不同,REPL基于AnyExpression,因此它允许使用任何可以用表达式语法表示为字面值的类型 - 而不仅仅是数字。
REPL中每行输入的表达式都独立评估。要共享表达式之间的值,您可以通过标识符名称后跟=然后是一个表达式来定义变量,例如:
foo = (5 + 6) + 7
命名变量(在这个例子中是“foo”)然后可以在随后的表达式中使用。
致谢
Expression框架主要是由Nick Lockwood完成的。
(贡献者完整列表)