测试已测试 | ✓ |
语言编程语言 | Obj-CObjective C |
许可协议 | MIT |
发布时间上次发布 | 2016年8月 |
由Evan Maloney维护。
依赖 | |
MBToolbox | ~> 1.2.2 |
RaptureXML@Gilt | ~> 1.0.11 |
本仓库托管Mockingbird Data Environment,这是一个开源项目,由Gilt Groupe在Mockingbird Toolbox项目的基础上构建,为iOS和Mac OS X应用程序提供动态数据处理引擎。
Mockingbird Data Environment允许为任意数据模型和对象图指定名称并将它们存储在一个变量空间中,从中可以通过表达式提取值。
Mockingbird Data Environment特别适用于构建能够适应服务器端数据模型变化的应用程序。
对于iOS应用程序发布商,通常会有一个预装的旧版本用户群体。常常因为当前版本需要比他们的硬件支持的iOS更新的版本,所以用户可能会卡在旧版本上。在这种情况下,除非他们购买新设备,否则他们不会运行您最新的应用程序版本。
对于某些类型的应用程序,这不是一个大问题。但对于需要与基于网络的在线服务通信的应用程序,广泛的旧版本可能会导致服务的演变和维护变得困难。
最终,您将面临以下权衡之一
放弃对旧版本的支持,知道这可能存在无法与部分用户保持重要关系的风险吗?
承担随着时间的推移而增加的维护和运营大量旧版后端服务的费用和难度吗?
或者,让自己永远放弃后端系统的演变——或远远慢于您所希望的演变?
Mockingbird Data Environment的目的是让您免于做出这些妥协。
通常iOS应用程序的架构是,客户端应用和服务器必须事先就用于通信的数据架构达成一致。在客户端,编写原生代码实现从中提取有意义信息的逻辑。
这种架构的问题是客户端和服务器都需要深入了解数据模式。
有些人试图通过自动从数据模式生成客户端代码的解决方案来解决这个问题。虽然这无疑很方便,可以节省时间,但原始问题依然存在:数据格式的知识必须编译到客户端应用程序中,因此一旦应用程序发布后就不能更改。你仍然需要面对一些用户正在运行非常旧的应用程序版本,他们希望能够与你的服务进行通信。
理想情况下,数据的模型知识只需要在一个地方维护。遗留应用程序的长尾巴给我们一个充分理由避免将知识硬编码到客户端,因此我们应该寻找由服务器驱动的解决方案。
如果你考虑客户端应用程序真正需要了解的内容,那不是服务器发送的数据结构的细节,而是一组特定信息,这些信息嵌入在数据结构中。数据结构仅仅是应用程序需要与服务器通信的副产品。
例如,一个应用程序可能对产品列表感兴趣,但产品列表是从特定JSON结构下的三个级别的数据中组装而成的这一事实是偶然的。
Mockingbird数据环境允许你构建应用程序,其中服务器可以托管用于导航其返回的数据结构所需的所有逻辑。服务不仅可以返回数据模型,还可以返回一组用于从数据模型中提取有意义信息的表达式。
基本上,服务器的响应告诉客户端应用程序两件事:“这里有一个包含你需要的信息的数据结构,以及如何提取对你相关的部分。”
因为所有内容都托管在服务器上——无论是数据模型还是解释该数据模型的知识——所以每个安装的应用程序版本都可以在部署新的数据模型时自动适应。
每次你更改服务通信方式时,你不需要重新提交应用程序进行审查,也不需要运行多个后端版本来支持你的应用程序的旧版本。
让Mockingbird数据环境将你的原生代码与服务器端数据的细节解耦,你可以在自己的时间表上发展你的服务,而那些被遗忘的版本的应用程序将不断工作。
这种技术于2014年8月11日在iOSoho开发者研讨会上进行了更详细的讨论。(有关幻灯片,请参阅Gilt科技博客http://tech.gilt.com/post/94663143169/handling-changes-to-your-server-side-data-model。)
目前,将Mockingbird数据环境集成到项目中的唯一官方支持的方法是使用CocoaPods。
(你可以从MBDataEnvironment.xcworkspace
中构建一个实验性但不受支持的MBDataEnvironment.framework
,用于iOS 8。)
如果你还没有使用CocoaPods,可以在CocoaPods网站上找到文档来帮助你入门。
一旦你的CocoaPods启动并运行,你只需将以下行添加到你的Podfile
中,就可以将Mockingbird数据环境添加到你的项目中
pod 'MBDataEnvironment'
然后,从你的项目目录中执行以下命令来安装CocoaPod
pod install
重要:注意pod install
命令的输出。如果你以前没有使用Xcode工作区为你的项目,CocoaPods将为你创建一个包含你的项目以及任何已安装的CocoaPods的工作区。从现在开始,你将需要使用该工作区而不是旧的项目文件进行开发。
运行pod install
命令后,你将能够在项目中引用Mockingbird数据环境。
你应该使用“库头文件”导入注解来引用头文件。为了方便,你可以引用整体头文件,这确保了你将能够访问Mockingbird数据环境的整个公共API。
#import <MBDataEnvironment/MBDataEnvironment.h>
将来,我们可能会发布Mockingbird数据环境的二进制发布版,作为iOS框架;使用上述注解将允许你无缝过渡到使用框架。
在使用Mockingbird数据环境之前,你的应用程序首先加载一个MBEnvironment
实例。
这样做最简单的方法是加载默认环境
[MBEnvironment loadDefaultEnvironment];
默认环境包含使用Mockingbird数据环境所需的所有内容。
你也可以提供一个包含附加声明的清单文件,允许你在加载时自定义环境的初始状态。
清单是一个XML文件(通常命名为manifest.xml
),其中包含用于自定义和配置Mockingbird环境的MBML标记。
MBML是一个XML文档格式,其名称代表.mockingbird.markup.language。该格式在下面的MBML文件简介中描述。
如果你的应用程序在其资源中提供了manifest.xml
文件,则可以加载环境,而不是调用loadDefaultEnvironment
[MBEnvironment loadFromManifest];
加载环境后,你可以在变量空间中填充数据,并使用表达式与该数据进行交互。
Mockingbird表达式是包含零个或多个字面量或表达式标记的字符串。
根据上下文,Mockingbird表达式可以包含
将表达式解析为特定值(值)的过程称为评估。在评估过程中,字面量将被传递而不受影响,但遇到的任何表达式标记都将被它们在评估时的代表值所替换。
评估表达式的主要API是通过几个MBExpression
类方法
结果是使用以下方法评估的 | 是使用以下方法评估的 |
---|---|
NSString |
转换为字符串 |
NSNumber |
转换为数字 |
BOOL |
转换为布尔值 |
id 或 NSObject | 转换为对象 |
Mockingbird 表达式的一个可能用途是在填充用户界面视图时执行字符串替换,例如 UILabel
UILabel* greeting = // created elsewhere
greeting.text = [MBExpression asString:@"Hello, $userName!"];
上述代码中,greeting 标签的 text 属性将被设置为评估字符串表达式 "Hello, $userName!" 的结果。
这个特定的表达式由三部分组成:一个文本字面量("Hello,
"),接着是 Mockingbird 变量 userName
的引用,最后是另一个文本字面量("!
")。
当表达式被评估时,表达式的 "$userName
" 部分将被替换为活动变量空间中 userName
变量的值。
如果 userName
的值是 "Cooper
",那么标签将显示文本 "Hello, Cooper!
"。
在本文档中,记法
$variableName
可以根据上下文读作 "活动变量空间中名为 variableName 的变量",或者 "对活动变量空间中名为 variableName 的变量值的引用"。
变量空间 是 Mockingbird 变量值存储的地方。
任何本地运行时对象实例都可以存储在 Mockingbird 变量空间中。当一个对象存储在变量空间时,它与一个 变量名 组配,并被考虑为一个 变量值。
In Objective-C,变量值可以通过键值索引符号在变量空间中进行暴露
[MBVariableSpace instance][@"cats"] = @[@"Barrett", @"Duncan", @"Gabby"];
上述代码将 $cats
与包含三个项的 NSArray
(字符串 "Barrett
","Duncan
" 和 "Gabby
")关联。
你还可以使用 MBML 清单文件在环境加载时预先填充变量空间。使用清单文件允许你避免在每次应用程序启动时都需要程序性地填充变量空间中的常用值。
为了确保 Mockingbird 环境始终有相同值的 $cats
,你可以在清单中添加此 变量声明
<Var name="cats" type="list" mutable="F">
<Var literal="Barrett"/>
<Var literal="Duncan"/>
<Var literal="Gabby"/>
</Var>
mutable="F"
属性确保在运行时$cats
不能被其他值覆盖。但是,它并不能保证 underlying 对象实例的不可变性。
有关清单文件的更多信息,请参阅下面的 清单文件。
最基本的非字面量表达式形式是 简单变量引用,其中变量名前面有一个美元符号
$cats
当一个对象通过变量空间提供时,不仅对象实例本身可以通过表达式访问,其属性、任何可使用键值编码(KVC)访问的值,以及如果相关对象是数组或字典,其内容也可以访问。
您可以通过 变量子引用 访问这些子值,这些子引用可以采用以下两种形式之一:点访问器和方括号访问器。
您可以使用 点访问器 访问对象的属性值或 KVC 值。例如,要确定 $cats
数组中的对象数量,你会使用以下表达式
$cats.count
当评估该表达式时,Mockingbird表达式引擎会访问$cats
的底层NSArray
值的count
属性,并返回该值。在本例中,$cats.count
的值将是一个包含整数值3
的NSNumber
。
Mockingbird表达式引擎不直接处理原始值(非对象)。因此,原始类型被包装在其等效的Cocoa类中。
NSUInteger
和BOOL
等,就被包装在NSNumber
实例中。
要访问数组或字典中包含的项,您将使用方括号访问器
$cats[1]
上面的表达式会返回$cats
数组索引为1
的对象。由于数组索引是从0开始的,这实际上是数组中的第二个条目:字符串 "Duncan
"。
引用越界数组索引的表达式将返回值
nil
。
也可以将子引用链接在一起
$cats[1].length
此表达式返回$cats
数组中索引为1
的对象的length
属性值。
在本例中,返回的值将是数字6
,这是字符串 "Duncan
"的长度。
方括号访问器在字典中作用类似。例如
[MBVariableSpace instance][@"catGenders"] = @{@"Barrett": @"female",
@"Duncan": @"male",
@"Gabby": @"female"}];
在上面的代码中,我们创建了一个将猫名映射到性别的字典,并将其与变量$catGenders
关联。
要访问任何特定猫的性别,我们可以使用方括号记法来获取给定字典键关联的值
$catGenders[Barrett]
此表达式返回字符串 "female
"。
根据变量空间中的数据和您需要如何使用它们,MBExpression
类提供了一些方法来根据以下以下表达式类型之一评估表达式
如果您正在评估一个预期会产生字符串的表达式,您应该使用MBExpression
提供的asString:
类方法之一
NSString* gender = [MBExpression asString:@"$catGenders[Barrett]"];
将表达式$catGenders[Barrett]
作为字符串进行评估,确保返回值将是NSString
或nil
。
在此特定情况下,NSString
变量gender
将包含值"female
"。
如果底层对象不是
NSString
实例,则会尝试将值强制转换为字符串。
当编写字符串表达式时,您可以在表达式内包含文本字面量。您可以将文本字面量视为其结果与其原始形式相同的结果表达式。
当评估字符串表达式时,表达式内的任何文本字面量都会原样返回,而任何变量引用都将被评估并替换为其结果值。这被称为字符串插值。
例如,考虑以下表达式
NSString* barrett = [MBExpression asString:@"Barrett is a $catGenders[Barrett] cat."];
在这里,字符串"Barrett is a
"和"cat.
"是文本字面量,而"$catGenders[Barrett]
"是变量引用。
因为字符串"$catGenders[Barrett]
"代表变量引用,所以它将被其底层值替换;在这种情况下,字符串"female
"。
变量 NSString
的 barrett
的结果值是 "Barrett is a female cat.
"
如果你在变量空间中有,比如说一个 NSArray
或一个 NSDictionary
(如 $catGenders
的情况),你可以使用一个 对象表达式 来检索底层对象值。
NSDictionary* genders = [MBExpression asObject:@"$catGenders"];
NSDictionary
的变量 genders
现在引用的是与 $catGenders
相同的值。
因为
asObject:
方法返回的是通用对象类型id
,所以调用者有责任知道返回的对象类型,或者在使用之前进行适当的类型检查。
要检索数值变量的值或执行数学计算,你可以将表达式评估为 数值。
NSNumber* five = [MBExpression asNumber:@"5"];
five = [MBExpression asNumber:@"2 + 3"];
five = [MBExpression asNumber:@"8 - 3"];
five = [MBExpression asNumber:@"10 / 2"];
five = [MBExpression asNumber:@"2.5 * 2"];
five = [MBExpression asNumber:@"((5 * 2) - 5)"];
所有上面的表达式都返回一个包含值 5
的 NSNumber
。
正如你所看到的,数值表达式可以包含基本的数学表达式。支持的运算符有 +
、-
、*
、/
和 %
(取余)。运算符必须与周围的词元之间至少有一个空格字符。
默认情况下,在评估数学运算符时使用 C 语言顺序。可以使用括号分组来确保特定的评估顺序。
将表达式评估为数值也会处理将 Mockingbird 变量值转换为数值。在这里,NSNumber
变量 val
将包含整数值 5
。
[MBVariableSpace instance][@"five"] = @(5);
NSNumber* val = [MBExpression asNumber:@"$five"];
在上面的代码中,@(5)
符号确保 Mockingbird 变量被设置为 NSNumber
实例。然而,作为 Mockingbird 变量基础的实例不需要是 NSNumber
,只要表达式可以数值地评估。
在下面的代码中,我们正在将名为 five
的 Mockingbird 变量设置为一个包含文本 "5
" 的 NSString
。
[MBVariableSpace instance][@"five"] = @"5";
NSNumber* val = [MBExpression asNumber:@"$five"];
因为 Mockingbird 表达式引擎将尝试将非 NSNumber
类型的值转换为 NSNumber
,所以 val
将包含整数值 5
。
当表达式在一个 布尔上下文 中被评估时,Mockingbird 表达式引擎认识到一组额外的运算符和比较器,以便实现布尔逻辑。
布尔运算符 | 用途 |
---|---|
lVal -AND rVal 或 lVal && rVal |
逻辑与 运算符;将 lVal 作为布尔表达式评估,如果它为 true ,则评估 rVal 作为布尔表达式。运算符当且仅当 lVal 和 rVal 都为 true 时评估为 true 。 |
lVal -OR rVal 或 lVal || rVal |
逻辑或 运算符;将 lVal 和 rVal 评估为布尔表达式。当且仅当 lVal 或 rVal 为 true 时,运算符评估为 true 。 |
! rVal |
逻辑非 运算符;将 rVal 评估为布尔表达式并取其值。当 rVal 评估为 false 时,运算符评估为 true ,反之亦然。 |
布尔运算符可以组合成复合形式。
MBVariableSpace* vars = [MBVariableSpace instance];
vars[@"catName"] = @"Barrett";
BOOL isFemale = [MBExpression asBoolean:@"$catName -AND $catGenders[$catName] -AND $catGenders[$catName] == female"];
在这个例子中,BOOL
变量 isFemale
将持有值 YES
。
在布尔表达式中,也可以使用括号分组来确保特定的评估顺序。
除了预期的布尔运算符外,还支持一些布尔比较器。
比较器依赖于标准的 isEqual:
和 compare:
方法来完成其工作,但并不局限于这些方法。在某些情况下,可能会发生类型强制转换(例如,比较 NSString
与 NSNumber
时)。
布尔比较器 | 用途 |
---|---|
lVal -EQ rVal 或 lVal == rVal |
等于比较器。当 lVal 和 rVal 被视为等价时,运算符结果为 true 。 |
lVal -NE rVal 或者 lVal != rVal |
不等于比较器。当 lVal 和 rVal 被视为不等价时,运算符结果为 true 。 |
lVal -LT rVal 或者 lVal < rVal |
小于比较器。当 lVal 被视为小于 rVal 时,运算符结果为 true 。 |
lVal -LTE rVal 或者 lVal <= rVal 或者 lVal =< rVal |
小于或等于比较器。当 lVal 被视为小于或等于 rVal 时,运算符结果为 true 。 |
lVal -GT rVal 或者 lVal > rVal |
大于比较器。当 lVal 被视为大于 rVal 时,运算符结果为 true 。 |
lVal -GTE rVal 或者 lVal >= rVal 或者 lVal => rVal |
大于或等于比较器。当 lVal 被视为大于或等于 rVal 时,运算符结果为 true 。 |
例如
BOOL hasThreeCats = [MBExpression asBoolean:@"$cats.count == 3"];
BOOL hasAnyCats = [MBExpression asBoolean:@"$cats.count -GTE 1"];
BOOL theCatLady = [MBExpression asBoolean:@"!($cats.count -LT 7)"];
在这里,hasThreeCats
将是 YES
,hasAnyCats
同样会是 YES
,而 theCatLady
将是 NO
。
最后,可以在布尔表达式上下文中使用两个布尔文字
布尔文字 | 用途 |
---|---|
T |
布尔 true (或 YES ) |
F |
布尔 false (或 NO ) |
这些值工作方式可能正如你所期望的那样
BOOL tValue = [MBExpression asBoolean:@"T"];
BOOL fValue = [MBExpression asBoolean:@"F"];
变量 tValue
将是 YES
,而 fValue
将是 NO
。
这使得你可以将布尔值存储在字符串中,并且它可以按预期的布尔值进行评估
[MBVariableSpace instance][@"featureFlag"] = @"T";
// somewhere later on...
if ([MBExpression asBoolean:@"$featureFlag"]) {
// do something awesome with our great new feature
}
四种表达式类型——字符串、对象、数字 和 布尔——都以不同的方式处理它们的输入和输出。因此,在 字符串上下文 中评估的表达式可能与在 布尔上下文 中评估的相同表达式产生不同的结果。同样,在 数字上下文 中有效的表达式可能在其他任何上下文中无效。
例如,像 -EQ
、-LTE
、-GT
等这样的标记仅在布尔表达式上下文中有效。当遇到不在布尔上下文中的情况时,表达式引擎将这些值视为文本文字。
如果你发现表达式没有按预期执行,请检查表达式上下文是否与你的假设一致。有关诊断问题的额外提示,请参阅下文中的 调试表达式。
有时,你可能想要混合上下文。例如,你可能想在包含数学计算的 UILabel
中显示一个字符串。或者你可能想在包含一些条件文本的消息中显示一个消息。
你可以使用以下表示法在任意其他表达式上下文中嵌入数字表达式:#(
numeric expression )
。
例如
[MBExpression asString:@"There are #(10 - 3) days in the week."];
这个表达式包括三个部分:文本文字 "There are
",然后是数字表达式 #(10 - 3)
,之后是另一个文本文字:"days in the week.
"。
如往常一样,文本文字按原样返回,但数字表达式将被如此评估,结果得到 7
。因此,上面的表达式将返回字符串 "There are 7 days in the week.
"。
您可以在非布尔上下文中使用以下符号来评估布尔表达式: ^if(
布尔表达式 |
真结果 [ |
假结果 ] )
。
当遇到此符号时,布尔表达式 在布尔上下文中进行评估,并根据结果返回 真结果 或 假结果。
[MBExpression asString:@"You ^if($user.isAdmin|have|do not have) admin privileges"];
上述表达式的返回值取决于 $user.isAdmin
的 布尔上下文值。如果评估结果为 true
,则返回值将是 "您有管理员权限
";否则,将返回 "您没有管理员权限
"。
因为 真结果 或 假结果 可以是空字符串,所以上述表达式可以稍作修改,使结果更为简洁
[MBExpression asString:@"You ^if($user.isAdmin||do not )have admin privileges"];
这也等价于
[MBExpression asString:@"You ^if(!$user.isAdmin|do not |)have admin privileges"];
因为整个 假结果 子句是可选的,所以上述表达式可以通过移除最后的管道符号进一步缩短一位
[MBExpression asString:@"You ^if(!$user.isAdmin|do not )have admin privileges"];
当使用字符串插值时,表达式内的
nil
值被视为空字符串。
在某些情况下,一个表达式中可能包含您不想被视为表达式一部分的字符。
例如,考虑以下字符串表达式
It will cost $total for your $quantity tickets to the Ke$ha concert
$
字符表示变量引用的开始,所以虽然 $total
和 $quantity
正确识别为变量,但在名称 Ke$ha
中的文本 "$ha
" 将被错误地解释为变量引用。
假设 $ha
没有值,则该表达式的输出将是我们不希望的结果 "Ke concert
" 而不是 "Ke$ha concert
"。
为了避免这个问题,您可以使用 引号 来确保表达式的某些部分不被评估。
要在表达式中引用一段文本,请使用以下符号: ^q(
要引用的文本 )
。括号内的文本被视为文本字面量,并在评估包含引号的表达式时按原样返回。
下面是如何使用引号来修复上述表达式的方法
It will cost $total for your $quantity tickets to the ^q(Ke$ha) concert
当这个表达式被评估时,返回的字符串将正确地包含文本 "Ke$ha
",而不是仅仅包含 "Ke
"。
与引号类似的一种技术是使用 转义序列 来表示无法单独表示的字符。
例如,字符串 "$$
" 是代表美元符号 $
的转义序列。就像使用引号来确保 "Ke$ha
" 被正确处理一样,这个转义序列也可以使用。
It will cost $total for your $quantity tickets to the Ke$$ha concert
以下转义序列被支持
转义序列 | 表示 |
---|---|
$$ |
$ |
## |
# |
^^ |
^ |
\n |
换行符 |
\t |
制表符 |
迄今为止,我们已看到一种变量引用形式,即 简单变量引用。此符号只能在变量名称被视为 标识符 时使用。标识符是如下构造的名称
第一个字符可以是大写 (A-Z
) 或小写 (a-z
) 字母字符或下划线 (_
)。
后续字符可以是大写 (A-Z
) 或小写 (a-z
) 字母字符,数字 (0-9
),下划线 (_
),连字符 (-
), 或冒号 (:
)。
在有些情况下,简单的符号无法用于引用给定的变量,因为该变量的名称不是一个有效的标识符。
在其他情况下,您可能希望确保解析器能够识别变量名称与字面量或另一个表达式之间的边界。
对于这些情况,提供了一些额外的变量引用符号。
花括号变量引用表示法可用于防止表达式引擎将变量引用之后的字符解释为引用本身的一部分。该表示法通过在大括号内包围变量名称来使用。
花括号表示法与简单表示法类似,它仅接受但也合法的标识符作为变量名称。表达式 $userName
和 ${userName}
都引用同一个变量,但它们使表达式评估器对变量引用之后的字符有不同的处理。
假设您有一个变量 $lastName
,它包含一个人的姓氏,并且您想构造一个像“Butterfields 处得怎么样?”这样的消息,该消息使用复数形式的姓氏。
您不能使用未引用的表示法(例如,“How are the $lastNames doing?
”),因为 $lastName
后的 s
字符会被解释为变量名称的一部分。并非引用变量 $lastName
后跟字符字面量 ,实际上表达式引用了一个完全不同的变量:
$lastNames
。
花括号表示法允许您将变量引用与应视为字面量任何周围的文本分开。使用这种表示法,表达式可以这样编写
How are the ${lastName}s doing?
这个表达式将被解释为文本字面量 "How are the
" 后跟 $lastName
的值,然后是另一个文本字面量 "s doing?
"。
方括号变量引用表示法可用于访问名称不是有效标识符的 Mockingbird 变量。
例如,空格字符和美元符号($
)不是有效标识符字符。通常,在解析变量名称时遇到空格时,表达式引擎会假设该空格表示变量名称的结束。当表达式内遇到美元符号时,表达式引擎会假设它表示新变量引用的开始。
由于 Mockingbird 变量名称不 一定要 是标识符,所以方括号表示法存在是为了允许访问那些名称不是标识符的变量。
假设您的应用程序连接到一个后端系统,该系统引入了一个名为 total $
的 Mockingbird 变量。您可以使用以下表达式来引用该变量的值
$[total $]
使用方括号表示法允许在闭合括号后进行子引用。假设变量 total $
是一个字符串,以下表达式将返回该字符串的长度
$[total $].length
与方括号表示法一样,括号变量引用表示法可用于访问名称不是有效标识符的 Mockingbird 变量。就像花括号表示法一样,括号表示法可以防止表达式引擎误将相邻的字符解释为变量引用的一部分。
为了看到区别,考虑这两个表达式
$[total $].length
$(total $).length
上面的表达式使用的是方括号表示法,允许闭合括号后的子引用。下方的表达式使用的是括号表示法,不允许子引用。
由于括号表示法不允许子引用,因此任何出现在闭合括号之后的文本将被视为新语句的开始。在这种情况下,因为 ".length
" 不是任何类型的有效表达式,它被解释为文本字面量。
所以,当使用 $[total $].length
时,会获取 total $
的 length
属性值;而使用 $(total $).length
将会获取 total $
的值后面跟着文本字面量 ".length
"。
表示法 | 变量名称 | 示例 | 带子引用的示例 | 后续子引用 |
---|---|---|---|---|
简单 | 必须是一个标识符 | $variableName |
$string.length |
允许 |
花括号 | 必须是一个标识符 | ${variableName} |
${string.length} |
不被识别 |
方括号 | 可以包含除 [ 或 ] 之外的任意字符 |
$[variable name] |
$[a string].length |
允许 |
括号 | 可以包含除 ( 或 ) 之外的任意字符 |
$(variable name) |
不可能 | 不被识别 |
MBML函数允许在Mockingbird表达式中调用原生的Objective-C代码。函数可以接受零个或多个输入参数,并且可以返回对象实例作为结果。
当评估包含函数调用的表达式时,实现函数的方法将被执行,并且该方法返回的值(如果有)将作为函数的结果产生。函数实现返回的值可以在表达式内部进一步操作。
函数调用以一个感叹号字符(^
)开始,其后跟着函数名,并以括号内零个或多个用管道符分隔的参数列表结束。
例如,要调用一个创建数组的函数,可以编写
^array(pizza|pasta|sushi|lobster)
如果这个表示法看起来很熟悉,那是因为在上面的文档中你已经遇到了两个函数——《code>^if()` 和 ^q()`。
在这个例子中,^array()
函数返回一个包含四个项目的 NSArray
,即字符串 "pizza
"、"pasta
"、"sushi
" 和 "lobster
"。
可以使用与变量引用相似的方式,在函数的返回值中使用子引用
^array(pizza|pasta|sushi|lobster)[3]
此表达式产生值 "lobster
"。
Mockingbird数据环境几乎预装了200个内置功能,并且可以通过MBModule
添加更多,或者通过自行实现。
包含的函数列表可以在这个MBDataEnvironmentModule.xml
文件中看到;它们使用 `
函数自身在其实现类中有文档说明
MBMLCollectionFunctions
MBMLDataProcessingFunctions
MBMLDateFunctions
MBMLDebugFunctions
MBMLEncodingFunctions
MBMLEnvironmentFunctions
MBMLFileFunctions
MBMLFontFunctions
MBMLGeometryFunctions
MBMLLogicFunctions
MBMLMathFunctions
MBMLRegexFunctions
MBMLResourceFunctions
MBMLRuntimeFunctions
MBMLStringFunctions
有关如何实现MBML函数的说明,请参阅MBMLFunction
类文档
面对一个不符合你期望的表达式时,首先检查你的应用控制台日志。当未处理的表达式错误发生时,将信息写入控制台,描述问题。
如果这没有帮助,你可能需要进一步调查。Mockingbird数据环境提供了一些工具来帮助你进行调试。
MBMLDebugFunctions
类提供了对调试表达式有用的MBML函数。
其中一些是穿透过函数,允许您将一个表达式(或其部分)包裹在函数调用中,该调用将调试信息写入控制台,但不会以其他方式干扰使用的表达式。这类函数可以在表达式中变量引用或子引用的边界处插入,而不会影响表达式本身的结果。
考虑以下表达式
$dictionary[$key].propertyValue.anotherValue[$arrayIndex]
如果这个表达式不是返回预期的结果,可能有许多原因。也许$dictionary
、$key
或$index
具有意外的值。或者,可能是.propertyValue
或.anotherValue
子引用返回了nil
。
要检查表达式的内部,可以将其中一部分或全部包裹在一个或多个^log()
调用中。
^log()
函数接受一个对象表达式作为参数。当函数被调用时,它会将调试信息记录到控制台,显示输入表达式及其在评估时的值。然后该函数返回该值。
在这里,上述表达式的四个部分被包裹在对^log()
的调用中
^log(^log($dictionary)[^log($key)]).propertyValue.anotherValue[^log($arrayIndex)]
当评估这个表达式时,每个对^log()
的调用都会导致将调试信息写入控制台
首先,^log($dictionary)
将打印出$dictionary
的值。
如果$dictionary
不是nil
,则接着会打印出$key
的调试信息。
然后,^log(^log($dictionary)[^log($key)])
将打印出关于$dictionary[$key]
的信息到控制台(因为包含的^log()
调用是穿透过,可以被忽略)。
最后,如果表达式中的中间值都不是nil
,则^log($arrayIndex)
将打印出$arrayIndex
的值。
^test()
函数与^log()
类似,只是它接受一个布尔表达式,并返回在该布尔上下文中评估该表达式的结果。
此函数非常适合测试布尔表达式的部分。它可以如下使用
^test($Network.isWifiConnected) -AND ^test($Device.isRetina)
当上述表达式被评估时,关于布尔表达式$Network.isWifiConnected
的调试信息将被记录到控制台。如果$Network.isWifiConnected
评估为true
,则还将记录关于$Device.isRetina
的调试信息。
由于布尔
-AND
操作符是短路的,所以只有在左操作符评估为true
时才会评估右操作符。
当MBDataEnvironment
作为DEBUG
构造的一部分编译时,^debugBreak()
函数是可用的。
当在一个包含^debugBreak()
调用的表达式上进行评估时,如果代码在Xcode中运行,将触发一个调试断点,使您到达调试器的命令提示符。从那里,您可以通过在调试器中键入命令来评估表达式。
^debugBreak()
函数接受一个输入消息,在断入调试器之前将其记录到控制台。
重要:如果
^debugBreak()
在DEBUG
构建中调用,但应用程序未在Xcode调试器中运行,则应用程序将崩溃。
^tokenize()
函数对对象上下文中的输入表达式进行标记化,并将生成的标记打印到控制台。
标记化显示了Mockingbird如何解释您的表达式。
例如
^tokenize(This $aint.no ^party())
上述表达式将产生类似以下控制台输出的输出
--> Tokens for object expression "This $aint.no ^party()":
<MBMLLiteralToken@0x7f8ac03aaea0: "This " {0, 5}>
<MBMLVariableReferenceToken@0x7f8abce042a0: "$aint" {5, 5}> containing 1 token:
0: <MBMLObjectSubreferenceToken@0x7f8ac0670a00: ".no" {10, 3}>
<MBMLLiteralToken@0x7f8ac036f4a0: " " {13, 1}>
<MBMLFunctionCallToken@0x7f8ac0645bb0: "^party()" {14, 8}>
与 ^log()
和 ^test()
一样,此函数可以用于包装更大的表达式的一部分或全部。
^tokenizeBoolean()
函数类似于 ^tokenize()
,但它接受一个布尔表达式作为输入参数。
^tokenizeBoolean(This != ^party())
上述表达式将产生类似以下控制台输出的输出
--> Tokens for boolean expression "This != ^party()":
<MBMLInequalityTestToken@0x7fa93fb5fbe0: "!=" {5, 2}> containing 2 tokens:
0: <MBMLLiteralToken@0x7fa93fb45970: "This" {0, 4}>
1: <MBMLFunctionCallToken@0x7fa93fb4af80: "^party()" {8, 8}>
^tokenizeMath()
函数可以对数学表达式进行标记,但与其他的 ^tokenize()
和 ^tokenizeBoolean()
相似。
^tokenizeMath(($user.firstName.length + $user.lastName.length) + 1)
上述表达式将产生类似以下控制台输出的输出
--> Tokens for math expression "($user.firstName.length + $user.lastName.length) + 1":
<MBMLAdditionOperatorToken@0x7fa940048b70: "+" {49, 1}> containing 2 tokens:
0: <MBMLMathGroupingToken@0x7fa9400a87d0: "($user.firstName.length + $user.lastName.length)" {0, 48}> containing 1 token:
0: <MBMLAdditionOperatorToken@0x7fa93ae86fd0: "+" {23, 1}> containing 2 tokens:
0: <MBMLVariableReferenceToken@0x7fa93faf9400: "$user" {0, 5}> containing 2 tokens:
0: <MBMLObjectSubreferenceToken@0x7fa940049ac0: ".firstName" {5, 10}>
1: <MBMLObjectSubreferenceToken@0x7fa93fae4340: ".length" {15, 7}>
1: <MBMLVariableReferenceToken@0x7fa93aeb8f70: "$user" {25, 5}> containing 2 tokens:
0: <MBMLObjectSubreferenceToken@0x7fa93aea8d90: ".lastName" {30, 9}>
1: <MBMLObjectSubreferenceToken@0x7fa93fa52f60: ".length" {39, 7}>
1: <MBMLNumericLiteralToken@0x7fa94007d100: "1" {51, 1}>
可以使用 Mockingbird 数据环境提供的 .lldbinit
文件,将评估表达式的功能添加到 Xcode 中。
LLDB 是 Xcode 使用的调试器,可以通过在您的家目录中的 .lldbinit
文件中添加命令来扩展。
为了在调试器中评估表达式,将此项目附带的可用 .lldbinit
文件复制到您的家目录中。 (或者,如果您已经有了 .lldbinit
文件,则可以将我们 .lldbinit
文件中的条目添加到您的文件中。)
然后,重新启动您应用程序的调试会话,在 Xcode 中的 (lldb)
提示时,您可以使用以下命令评估表达式
命令 | 名称 | 用途 | 示例 |
---|---|---|---|
pe |
print epression | 评估一个对象表达式并打印结果 | pe $user |
pec |
print expression class | 评估一个对象表达式并打印结果类 | pec $user.guid |
pse |
print tring expression | 评估一个字符串表达式并打印结果 | pse Hello,$user.lastName! |
pbe |
print boolean expression | 评估一个布尔表达式并打印结果 | pbe $user.isLoggedIn |
pne |
print numeric expression | 评估一个数值或数学表达式并打印结果 | pne(50 % 3) |
可以通过使用 MBML 文件来修改 MBEnvironment
的状态。
MBML 是一种基于 XML 的文档格式,它提供了声明新变量值、函数和其他状态的一种机制。如果您总是想将某些变量或函数放在环境中,您可以在 MBML 中声明它们。
如果将 manifest.xml
文件包含在您应用程序的主资源包中,您可以使用它来配置环境的初始状态。您只需要使用以下方式加载环境
[MBEnvironment loadFromManifest];
还有一个 loadFromManifest...
方法变体,允许您将清单文件存储在其他位置或将其命名不同于 manifest.xml
。
MBML 文档中最外层的 XML 标签是 <MBML>
标签。此标签内包含各种类型的声明。文档的结构如下
<MBML modules="...">
<!-- declarations -->
</MBML>
modules
属性是可选的。如果存在,属性值可能是一个逗号分隔的类列表,这些类符合 MBModule
协议。模块为 Mockingbird 数据环境提供额外功能,并随环境一起加载。
模块可以挂钩到 MBML 解析过程,允许在语言中引入新的标签。例如,MBEventHandling 项目增加了监听 NSNotification
事件并在响应中执行操作的能力。这些 监听器 和 操作 是通过 MBEventHandlingModule
添加的 MBML 标签声明的。
要包括此模块,需要将 MBEventHandling
代码链接到您的应用程序,通常通过将其包含在您的 Podfile
中来实现。然后,您可以在配置文件的起始 <MBML>
标签中指定模块。
<MBML modules="MBEventHandlingModule">
当加载包含此声明的配置文件时,MBEventHandling
功能将在环境中可用。
尽管任何包含的模块都可能引入额外的 MBML 声明,但仅凭本身,Mockingbird 数据环境提供了声明以下内容的能力
包含 —— 配置文件之所以这么命名,是因为在更大的应用程序中,将应用程序的离散部分拆分为单独的 MBML 文件,并将这些文件从配置文件中 包含 是一种最佳实践。包含声明指定了在加载数据环境时应加载的 MBML 文件。
变量 —— 可以在 MBML 中设置 Mockingbird 变量的值。
函数 —— 通过引入新的 MBML 函数,可以扩展 Mockingbird 表达式的功能。
MBML 文件可以 包含 另一个文件。当一个文件被包含时,该文件中声明的任何内容都将作为在遇到 <Include>
标签时的声明来处理。
<Include>
标签需要 file
属性,并接受可选的 if
属性
file
属性指定要包含的 MBML 文件名称。
如果存在,if
属性的值将被评估为一个布尔表达式,并且指定的文件只有在表达式评估为 true
时才会被包含。
例如
<Include file="onboarding.xml" if="$isFirstLaunch"/>
<Include file="landing.xml"/>
根据上述声明,如果 $ isFirstLaunch
在布尔环境下评估为 true
,则将包含 onboarding.xml
文件,而 landing.xml
文件将无条件包含。此外,如果包含 onboarding.xml
,它包含的任何声明都将处理在 landing.xml
的声明之前。
<Include>
标签总是先于 MBML 文件中的任何其他声明进行处理。
使用 <Var>
标签来声明 Mockingbird 变量的值。
有三种类型的变量声明是可能的
具体的 —— 变量名称 与 Objective-C 对象实例相关联,该实例代表具体变量的 值。具体变量的初始值是在其声明处理时设置的。
单例 —— 变量名称 与返回单例对象实例的类方法相关联。该方法返回的对象代表单例变量的 值。单例变量的值在声明处理时一次性设置。
动态的 —— 变量名称 与 Mockingbird 表达式相关联。每当引用该变量时,关联的表达式都会被评估,并且该表达式产生的值代表动态变量的 值。
<Var>
标签提供支持以指定诸如 NSString
、NSNumber
、NSArray
、NSDictionary
和 BOOL
等类型的显式值。
还可以使用 MBML 函数创建其他类型,并且可以通过 Mockingbird 表达式引用其他源中的值。
可以使用 literal
属性指定具体文本字面量。在字面量中,表达式不会被求值,因此不需要进行任何转义。
<Var name="currency" literal="$USD"/>
上面的声明将 $currency
设置为一个包含 "$USD
" 的 NSString
。
如果文字表达式不足以满足需求,可以使用与对象表达式一起使用的 value
属性,该表达式将在处理声明时被评估。变量的值被设置为表达式产生的值。
<Var name="price" value="#(99.99)"/>
<Var name="priceLabel" value="${currency}${price}"/>
<Var name="appLaunchTime" value="^currentTime()"/>
上面第一行中的值使用 #(
... )
表示法包围,因此它被评估为一个数值表达式。因此,将 $price
设置为一个包含值 99.99
的 NSNumber
。
在第二行中,value
属性包含了一个引用两个值的表达式:${currency}
和 ${price}
。每当在表达式顶部级别引用多个值时,就会使用字符串插值,因此产生的值将是一个 NSString
。在这种情况下,产生的值是 "$USD99.99
"。
第三行将 $appLaunchTime
设置为企业返回的值 ^currentTime()
,这将是一个 NSDate
。
<Var>
标签总是会从文档顶部向下处理。因此,<Var>
声明 只能 引用它之上的或任何所包含文件(因为包含顺序为首先)中声明的值。尝试引用尚未处理的声明变量将失败。
可以使用 boolean
属性使用布尔值声明变量。
<Var name="useLargeImageSizes" boolean="$Network.isWifiConnected -AND $Device.isRetina"/>
boolean
属性的值会被评估为一个布尔表达式,其结果用作变量的值。
在上面的示例中,如果 $Network.isWifiConnected
和 $Device.isRetina
都评估为 true
,则 $useLargeImageSizes
将被设置为 true
。
在实际应用程序中,应将
$useLargeImageSizes
声明为 动态变量,而不是 具体变量。请参阅下面的 动态变量 部分以了解原因。
可以使用 type="list"
属性声明数组。其中一种用途可以是声明美国各州的列表。
<Var name="states" type="list" mutable="F">
<Var literal="Alabama"/>
<Var literal="Alaska"/>
<Var literal="Arizona"/>
...
</Var>
上面的声明将产生一个包含指定文字字面量的 NSArray
。请注意,内层的 <Var>
标签没有 name
属性,因为数组只是未命名值的列表。
映射 - NSDictionary
实例 - 可以使用类似的方式声明,使用 type="map"
属性。此声明显示了州名和它们相应的邮政编码之间的映射。
<Var name="statesToPostalCodes" type="map" mutable="F">
<Var name="Alabama" literal="AL"/>
<Var name="Alaska" literal="AK"/>
<Var name="Arizona" literal="AZ"/>
...
</Var>
在生成的 NSDictionary
中,name
属性的值指定了 字典键,而 literal
属性的值指定了相关的 字典值。
尽管上面的示例只是使用
literal
属性,但列表或映射内的嵌套<Var>
也可以接受任何boolean
或value
属性。
您可以使用具有 type="singleton"
属性的 <Var>
声明来将单例对象实例暴露给变量空间。
单例声明需要一个指定提供单例的类名的 class
属性,以及一个指定(无参数)返回单例实例的类方法名的 method
属性。
<Var name="UIApplication" type="singleton" class="UIApplication" method="sharedApplication"/>
上述声明可以在 《MBDataEnvironmentModule.xml》文件中找到,它通过变量 $UIApplication
暴露了方法 [UIApplication sharedApplication]
返回的值。
由于单例变量的值是在处理 <Var>
标签时设置的,因此此类变量声明只能用于真正的单例,它们必须如下所述
期望单例实例在整个运行应用程序的生命周期内存在。
单例访问器方法返回的值在应用程序的生命周期内绝不能改变。
动态变量将一个 表达式 与一个变量名相关联,而不是与一个特定的 值。当引用动态变量时,Mockingbird 会评估关联的表达式,并由该表达式产生值成为动态变量产生的值。
在上面的 布尔值 的具体变量部分,我们看到了以下对 $useLargeImageSizes
的声明
<Var name="useLargeImageSizes" boolean="$Network.isWifiConnected -AND $Device.isRetina"/>
因为这个声明不是动态的,所以 $useLargeImageSizes
是在处理 <Var>
标签时设置的。因此,$useLargeImageSizes
的值将始终反映了 MBML 首次加载时 $Network.isWifiConnected
的值。
我们想要的是 $useLargeImageSizes
总是反映 $Network.isWifiConnected
的 当前 值。要做到这一点,我们可以简单地将 $useLargeImageSizes
声明为一个 动态变量
<Var name="useLargeImageSizes" type="dynamic" boolean="$Network.isWifiConnected -AND $Device.isRetina"/>
为了理解具体变量和动态变量之间的区别,请考虑以下两个声明
<Var name="inThePast" value="^currentTime()"/>
<Var name="alwaysNow" type="dynamic" value="^currentTime()"/>
这两个声明都将一个变量名与 ^currentTime()
函数返回的 NSDate
关联起来。
然而,$inThePast
的具体声明确保在声明变量时只计算 value
属性中的表达式一次。除非显式更改基础值,否则每次引用 $inThePast
时,其值将与最初处理声明时的值相同。
$alwaysNow
变量则不同:每次引用 $alwaysNow
时都会计算 value
属性中的表达式。所以 $alwaysNow
的值总是从一个新的调用 ^currentTime()
函数中获取的 NSDate
。
MBML 函数使用 <Function>
标签来声明。以下是一些从 《MBDataEnvironmentModule.xml》文件中摘录的示例
<Function class="MBMLCollectionFunctions" name="mutableCopy" method="mutableCopyOf" input="object"/>
<Function class="MBMLDataProcessingFunctions" name="filter" input="pipedExpressions"/>
<Function class="MBMLDateFunctions" name="currentTime" input="none"/>
<Function class="MBMLStringFunctions" name="q" input="raw"/>
声明和实现 MBML 函数超出了本文档的范围。关于 <Function>
声明及其使用方法的详细信息,请参阅 《MBMLFunction》类 的文档。
如果您想了解更多关于 Mockingbird 数据环境如何工作的信息,一个好的起点是以下类的 API 文档
MBVariableSpace
MBEnvironment
MBExpression
MBDataModel
MBMLFunction
MBStringConversions
MBScopedVariables
当然,您可以自由地深入研究 代码!
Mockingbird数据环境是Gilt Groupe为其iOS开发的Mockingbird库开源项目的一部分。
多年来,Gilt Groupe一直使用和改进Mockingbird库作为其众多iOS项目的基座平台。
Mockingbird最初以AppFramework的形式出现,最初由Jesse Boyes创建。
AppFramework在Gilt Groupe找到了归宿,最终发展为Mockingbird库。
近年来,Mockingbird库由Gilt Groupe的首席iOS工程师Evan Coyne Maloney开发和维护。
对于XML解析,Mockingbird数据环境使用了一个自定义分支,这个分支是基于RaptureXML项目,由John Blanco创建。
Mockingbird库和Mockingbird数据环境©版权所有 2009-2015,Gilt Groupe。
许可协议为MIT许可协议。