优先级能自定义怎么增加难度增加多个吗,目前只能选

这里有三个步骤去定义一个自定义操作符:
命名你的运算符
选择一种类型
设置它的优先级和结合性
现在你必须选择一个字符作为你的运算符。自定义运算符可以以/、=、-、+、!、*、%、&、&、&、|、^、~或者Unicode字符开始。这个给了你一个很大的范围去选择你的运算符。但是别太高兴,选择的时候你还必须考虑重复输入的时候更少的键盘键入次数。
由于运算符定义是全局的,所以你要小心的选择你的自定义运算符的优先级和结合性。
//: Playground - noun: a place where people can play
import UIKit
infix operator +++ {
associativity left precedence 140
func +++(left:[Int],right:[Int]) -& [Int]{
var sum = [Int](count: left.count, repeatedValue: 0)
assert(left.count == right.count,"vector of same length only")
for (key,_) in left.enumerate() {
sum[key] = left[key] + right[key]
return sum
print("\([2,3,4] +++ [1,2,3])")
定义一个中缀/二元操作符,它有两个操作数并且位于操作符两侧
命名操作符为+++
设置结合性为left,表明该操作符在相同优先级时候,将使用操作符的顺序从左到右结合
设置优先级为140,这个是和Int加法有相同的优先级,这些优先级可以在查看。
Swift重载和自定义运算符
swift3.0自定义运算符
swift_重载和自定义运算符
Swift3.0学习笔记-Basic Operators(基本运算符)
Swift 自定义运算符
Swift 运算符重载
Kotlin语法基础,运算符
没有更多推荐了,当前位置:
/ 请问优先级能自定义增加多个吗,目前只能选择1、2、3、4
请问优先级能自定义增加多个吗,目前只能选择1、2、3、4
8.0.1 Windows一键安装包
客户端浏览器
提问者: xl
可以在 后台-自定义 中维护。你的浏览器禁用了JavaScript, 请开启后刷新浏览器获得更好的体验!
按 两个文本字段 t1和t2 分词搜索,然后按搜索得分、一个数值型字段d1、 一个逻辑型字段 b1 来排序输出,权重如下:
score * 30%
1. 直接 addSort() 只能按多个字段排序,并且有优先级,无法设置权重;
2. 自定义 function score
如何处理 d1 的归一化问题? 如何处理 b1 的逻辑值问题?
**更新**:
d1 是一个从 0 到 几十亿的超大数字;
b1 目前想到按照 0/1 处理,或者 -1/1处理
主要方案有两种:
1. function score
原理都差不多,主要区别是后者要开script,而且可以先在es里写好脚本,查询时指定脚本名即可。
权重方面重点在于自己的设计,以及合理地按照业务需求使用归一化函数,实现没有问题。
&query&: {
&function_score&: {
&functions&: [
&script_score&: {
&params&: {
&param1&: 0.3,
&param2&: 0.5,
&param3&: 0.2
&script&: &_score *param1+ doc['d1'].value * param2 + doc['b1'].value * param23&
&query&: {
&match&: {
&brief&: &智能&
&score_mode&: &first&
_score 就是es对doc的打分,d1和bi需要是数值类型。param1,2,3可以不用赋值的形式,直接写在script里面也可以。
官方文档在这里:
使用script排序
官方现在建议使用function_score了,
介绍下function_score
要回复问题请先或
Another developer !
浏览: 15254
关注: 8 人亚马逊促销活动Promotion②:Money Off(满减折扣)的设置教程-雨果网
热门搜索词
亚马逊促销活动Promotion②:Money Off(满减折扣)的设置教程
海猫跨境作者:海猫跨境
满减、折扣是放之四海皆有效的促销手段,虽然亚马逊对卖家有诸多限制,但这个促销方式却是允许的,对亚马逊的卖家而言,这对提升商品销量、打造爆款都是极好的。今天小编来讲讲亚马逊的Money&Off要怎么设置和设置过程要注意的那些坑。(点击进入&&)
一、什么是Money Off?
Money&off是亚马逊免费促销(Promotion)方式中的一种满减及折扣活动,也就是我们平时常常遇到的类似“满多少减多少”的常规促销方式。
二、什么情况下使用Money Off?
促销方式嘛,自然是促销的情况下使用。一般情况下,促销的时机分为以下三种:
1、新品上市
2、打造热销款或者爆款
3、赠送样品以取得Review
需要说明的是,只有拥有,卖家才能使用Promotion。
三、如何设置Money Off
1)全面认识一下Money Off(Percentage Off)设置页面
设置Money Off时,先进入卖家后台。美国站点,在advertising下拉菜单中选择Promotion,进入下图。而英国站点,则是在inventory下拉菜单中选择Manage&Promotion进入。
进入以上页面后,再点击Create,进入Money Off(Percentage Off)促销规则设置页面
Money Off促销规则设置共分为三个步骤:
STEP1:为促销方式(Conditions)的设置,根据你的实际促销活动来填写;
Step2:活动时间(Scheduling)的设置;
Step3:附加选项(Additional&Options)的设置。
2)接下来我们分别来看看每一步小细节是如何设置的。
1、Step1:Conditions设置
这里主要需要填写Buyer purchases、Purchased Items、Buyer gets、Applies to以及Advanced&Options五项内容。下面依次来看如何填写。
(1)Buyer purchases
这个下拉菜单里有三种选项,可以根据实际的促销活动进行选择,具体见下文说明。但需要提醒卖家的是,在设置 promotion时,必须要记得Step1(Conditions)的第一选项 Buyer purchases ,它决定着整个 Step 1 promotion的设定,填写的优先顺序是要从上到下,因为上层的设置都会影响到下方的选项。
A:At least this quantity of items(至少购物X样商品):
表示此促销方案只有在顾客购买X样商品时方适用,后面的框内您必须填入数字。
B:At least amount (in $)(至少X金额):
表示此促销方案只有在顾客购买至少X金额的商品时方适用,即买家最少要花费X费用才能享受此促销活动。
C:For every quantity of items purchased(每X样商品):
表示买家一次购买多少个商品就可以有优惠。假如设定一次购买5个某商品有优惠,那一次购买5个该商品的买家就会有优惠。
(2)Purchased Items
卖家在这选择哪些产品才享有Promotion,就是选择要参与促销的产品。当你只想对在售商品中的部分商品做促销的话,是需要先创建产品列表,以便系统可以识别出来是哪一些产品包含在促销的内容里面。那系统如何找到这些商品呢,你可以通过给系统提供SKU列表、ASIN列表、品牌名称等,也就是说,你可以从不同的角度出发来创建你想做促销的产品列表。
(3)Buyer gets:对买家的优惠,也是促销的点之所在。
Percent off:打折,即享受多少折的折扣优惠。比如,如果你想打九折,后面框内就填上数字10,想打95折,后面框内填上5。
(4)Applies to:哪些产品可以有这个Promotion,同样有2个选项:
A:purchased items(购买的商品)。一般默认的就是该选项。
B:qualifying Item(指定的商品)。如果选择了这个选项,点击select anasin,表示当买家购买了某个选定的产品后才能享受优惠。
(5)Advanced Options:高级设置。在这里可以根据自己的需要,添加多个促销的区间。比如满50减5元,满100减15元等。
2、Step2:Scheduling设置
这个比较简单,就是促销的起止时间。如下图。这里需要注意的一点就是,促销活动创建之后4小时才会生效。此外,这个时间是美国的时间,也就是说要到美国的这个时间才会生效。当然,你可以到管理促销去查看目前这个促销是pengding还是active状态。
(1)Start Date:开始时间
(2)End Date:结束时间
(3)Internal Description:促销识别名称,用来区分促销活动
(4)Tracking ID:促销追踪编码。这个不会显示给买家,仅供卖家内部使用。
3、Step3:Additional Options设置
这个附加选项部分主要用来完善我们的促销活动。
点开,如图:
(1)Claim Code:促销优惠码。主要是用来限制买家使用以取得优惠。勾选后,顾客在结账时需要输入优惠码才能享受促销优惠。点开进行设置。
A:one redemption per customer:勾选后表示每位买家只能使用一次优惠码。
B:Claim Code:可以自己输入,或者系统生成。
C:Claim Code&Combinability:优惠码类型。有三个选项,分别是preferential(优先使用)、Unrestricted(无限制)及Exclusive(排他性)。建议新手卖家还是选择系统默认的Exclusive,意思就是卖家使用了这个优惠码后就不能享受其他的促销活动了,当然,进行过亚马逊培训的卖家就没有这个顾虑了。
(2)Customize messaging:卖家自定义信息。创建给买家的信息,并设置展示的先后顺序。需要设置的内容主要包括以下几项:
1):Checkout display text:结算时显示的文字。
2):short display text:短显示文本。搜索页面时显示的信息。
3):Detail page display&text:商品详情页面显示文本。勾选后,商品详情页里会显示促销信息,否则不显示。如果不勾选的话那么每位到你店里的访客都能享受优惠。这点需要注意,这里一般适合清理库存时使用。
4)Purchased Items display text:需购买商品显示文本。即显示需要买家购买的商品信息
5)Detail page display text(详情页面显示出的促销信息):有2个选项:
A:Standard text(标准文本):系统自己推荐的促销文本信息
B:Customized text(自定义文本):自己编辑促销信息
6)Display precedence(此促销的优先级):数字越小,此促销越优先生效。适用于同时有多个促销活动进行活动排序时。
4、预览并提交
在设置好以上促销的所有信息后,在促销活动页面的最下方有一个“Review”,点击查看,对所创建的促销活动有一个整理的预览检查,确认无误后,点击“Submit”。此时,就完成了促销活动的创建。
四、亚马逊Money Off(满减折扣)的设置那些坑
关于促销码设置主要是在money off设置的第三步Additional&Options。这里面有一些比较关键的点需要我们注意,尤其是当你的促销是针对某一个特定的群体(比如为了索要Reviews而寻找的们),这时尤其需要注意,否则你很容易亏得血本无归!
一、Claim Code(优惠码)
(1)Claim Code:后面的复选框需要勾选上,这样,顾客在结账时就需要输入优惠码才能享受促销优惠。
(2)One redemption per&customer:表示一个买家是否只能使用一次优惠码。后面勾选上,即表示一个买家只可购买一单促销。但是要记住,如果不想亏得血本无归,仅仅只勾选这里是不够的!
(3)Claim Code:这个即为买家购买时需要输入的优惠码,你可以自己设定,也可以随机设定。
(4)Claim Code&Combinability:优惠码类型,用来区分促销的优先级、排他性等。当多个不同的促销活动同时进行时,您可以通过创建不同的优惠码来指定组合逻辑。优惠码类型共有3种:
A:Preferential:优先使用型优惠码。表示同一笔订单中,买家最多只能使用一个Preferential优惠码,但它可以和Unrestricted无限制型优惠码同时使用。同一笔订单中,买家若符合多个优先型优惠码,系统将自动选择一个最佳折扣。
B:Unrestricted:无限制型优惠码。表示同一笔订单中,买家可以同时使用多个无限制型折扣码。
C:Exclusive:排他型优惠码,也叫独用型优惠码,若勾选了它,则表示买家购买时不可与任何优先型或无限制型优惠码搭配使用。这也是新手卖家做促销时,为避免出错,最常被建议选用的一种优惠码类型。
二、Customize messaging(买家自定义信息)
这部分主要是创建和/或自定义必选或可选的信息类型,包括条款与条件,并设置促销的优先级,以及在商品详情页或结算页显示的促销内容。
上图中被圈出来的部分。如果你做的是定向促销,比如上文说的,是为了索取review而专为特定人群(top&reviewers等)做的促销活动,那该项后面的复选框必须取消勾选(系统默认是勾选状态),这一点非常非常重要。很多商家折就折在这里。如果因为马虎或者不懂而没有取消勾选,那你的优惠码就会出现在你的listing详情页面上,也就是说,所有人都将会看到你的优惠码,用优惠码来参与促销活动,那你的库存也许眨眼间就为零了,到时候哭都来不及。所以,请谨记这一点!
正常的促销设置基本到此结束,可是,如果是用于索要Reviews或针对某个特定人群给以超过折扣换评价的促销,这里完成提交之后,请记住,还并没完!。还有一步很重要,很关键!那就是促销码的管理。
三、管理促销码
进入Promotion页面后,点击”Manage&Promotions”,在“Search”处可查看所有促销,点击“Active”查看正在进行的促销,也可点击“Pending”查看尚未开始的促销活动,找到你刚刚设置的促销活动,点击促销活动名称,进入促销详情页。
在促销详情页面,可以看到“Manage claim code”(管理促销码)按钮。
点击“Manage claim code”进入以下页面(见下图),在Group&Name中输入便于自己识别的名称,在Quantity中,输入你计划送出用来做测评的产品数量,然后,点击Create按钮。到此时,页面下边会出现你刚刚设置的Claim&Code Group, 点击右边的“Download”下载。下载的文件里面就是对应于刚刚设置的数量的一个长串促销码。
此时的促销码,是一次性促销码了,此时你可以把这个促销码发送给接受邀请的Reviewers,也不用再担心被人恶意分享出去重复使用而造成额外的损失了。
下一篇:。
以上内容属作者个人观点,不代表雨果网立场!
深挖用户“金矿”都有那些绝招?关注雨果网官方微信公众号【cifnews】, 快速抢占行业商机。扫描下方二维码,获取大咖PPT!(获取PPT:雨果网App->圈子->干货铺 )
技巧式跟卖,谨慎型防关联,经济使用FBA,与大卖互动畅聊如何提升单量。
订阅每周精选
下载雨果网客户端
2013 雨果网 闽ICP备号-1
请正确填写手机号码或邮箱!
请输入内容
扫描二维码下载雨果网APP数据存储开发指南 & Objective-C
数据存储(LeanStorage)是 LeanCloud 提供的核心功能之一,它的使用方法与传统的关系型数据库有诸多不同。下面我们将其与传统数据库的使用方法进行对比,让大家有一个初步了解。
下面这条 SQL 语句在绝大数的关系型数据库都可以执行,其结果是在 Todo 表里增加一条新数据:
INSERT INTO Todo (title, content) VALUES ('工程师周会', '每周工程师会议,周一下午 2 点')
使用传统的关系型数据库作为应用的数据源几乎无法避免以下步骤:
插入数据之前一定要先创建一个表结构,并且随着之后需求的变化,开发者需要不停地修改数据库的表结构,维护表数据。
每次插入数据的时候,客户端都需要连接数据库来执行数据的增删改查(CRUD)操作。
使用 LeanStorage,实现代码如下:
AVObject *todo = [AVObject objectWithClassName:@&Todo&];
[todo setObject:@&工程师周会& forKey:@&title&];
[todo setObject:@&每周工程师会议,周一下午2点& forKey:@&content&];
[todo saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 存储成功
// 失败的话,请检查网络环境以及 SDK 配置是否正确
使用 LeanStorage 的特点在于:
不需要单独维护表结构。例如,为上面的 Todo 表新增一个 location 字段,用来表示日程安排的地点,那么刚才的代码只需做如下变动:
AVObject *todo = [AVObject objectWithClassName:@&Todo&];
[todo setObject:@&工程师周会& forKey:@&title&];
[todo setObject:@&每周工程师会议,周一下午2点& forKey:@&content&];
[todo setObject:@&会议室& forKey:@&location&];// 只要添加这一行代码,云端就会自动添加这个字段
[todo saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 存储成功
// 失败的话,请检查网络环境以及 SDK 配置是否正确
数据可以随用随加,这是一种无模式化(Schema Free)的存储方式。
所有对数据的操作请求都通过 HTTPS 访问标准的 REST API 来实现。
我们为各个平台或者语言开发的 SDK 在底层都是调用统一的 REST API,并提供完整的接口对数据进行增删改查。
LeanStorage 在结构化数据存储方面,与 DB 的区别在于:
Schema Free/Not free 的差异;
数据接口上,LeanStorage 是面向对象的(数据操作接口都是基于 Object 的),开放的(所有移动端都可以直接访问),DB 是面向结构的,封闭的(一般在 Server 内部访问);
数据之间关联的方式,DB 是主键外键模型,LeanStorage 则有自己的关系模型(Pointer、Relation 等);
LeanStorage 支持两种存储类型:
我们将按照顺序逐一介绍各类的使用方法。
开启调试日志
在应用开发阶段,你可以选择开启 SDK 的调试日志(debug log)来方便追踪问题。调试日志开启后,SDK 会把网络请求、错误消息等信息输出到 IDE 的日志窗口,或是浏览器 Console 或是 LeanCloud 控制台的
// 放在 SDK 初始化语句 [AVOSCloud setApplicationId:] 后面,只需要调用一次即可
[AVOSCloud setAllLogsEnabled:YES];
在应用发布之前,请关闭调试日志,以免暴露敏感数据。
AVObject 是 LeanStorage 对复杂对象的封装,每个 AVObject 包含若干属性值对,也称键值对(key-value)。属性的值是与 JSON 格式兼容的数据。通过 REST API 保存对象需要将对象的数据通过 JSON 来编码。这个数据是无模式化的(Schema Free),这意味着你不需要提前标注每个对象上有哪些 key,你只需要随意设置 key-value 对就可以,云端会保存它。
AVObject 支持以下数据类型:
= [NSNumber numberWithInt:2015];
= [NSString stringWithFormat:@&%@ 年度音乐排行&, number];
= [NSDate date];
= [@&短篇小说& dataUsingEncoding:NSUTF8StringEncoding];
= [NSArray arrayWithObjects:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
number, @&数字&,
string, @&字符串&,
= [AVObject objectWithClassName:@&DataTypes&];
[object setObject:boolean
forKey:@&testBoolean&];
[object setObject:number
forKey:@&testInteger&];
[object setObject:string
forKey:@&testString&];
[object setObject:date
forKey:@&testDate&];
[object setObject:data
forKey:@&testData&];
[object setObject:array
forKey:@&testArray&];
[object setObject:dictionary forKey:@&testDictionary&];
[object saveInBackground];
此外,NSDictionary 和 NSArray 支持嵌套,这样在一个 AVObject 中就可以使用它们来储存更多的结构化数据。
注意,时间类型在云端将会以 UTC 时间格式存储,但是客户端在读取之后会做转化成本地时间。
我们不推荐在 AVObject 中使用 NSData 来储存大块的二进制数据,比如图片或整个文件。每个 AVObject 的大小都不应超过 128 KB。如果需要储存更多的数据,建议使用 。
若想了解更多有关 LeanStorage 如何解析处理数据的信息,请查看专题文档《》。
构建一个 AVObject 可以使用如下方式:
// objectWithClassName 参数对应控制台中的 Class Name
AVObject *todo = [AVObject objectWithClassName:@&Todo&];
// 或调用实例方法创建一个对象
AVObject *todo = [[AVObject alloc] initWithClassName:@&Todo&];
// 以上两行代码完全等价
每个 objectId 必须有一个 Class 类名称,这样云端才知道它的数据归属于哪张数据表。
Class 类名称(ClassName)必须以字母开头,只能包含字母、数字和下划线。
现在我们保存一个 TodoFolder,它可以包含多个 Todo,类似于给行程按文件夹的方式分组。我们并不需要提前去后台创建这个名为 TodoFolder 的 Class 类,而仅需要执行如下代码,云端就会自动创建这个类:
AVObject *todoFolder = [[AVObject alloc] initWithClassName:@&TodoFolder&];// 构建对象
[todoFolder setObject:@&工作& forKey:@&name&];// 设置名称
[todoFolder setObject:@1 forKey:@&priority&];// 设置优先级
[todoFolder saveInBackground];// 保存到云端
创建完成后,打开 ,点开 TodoFolder 类,就可以看到刚才添加的数据。除了 name、priority(优先级)之外,其他字段都是数据表的内置属性。
该对象唯一的 Id 标识
该对象的权限控制,实际上是一个 JSON 对象,控制台做了展现优化。
该对象被创建的 UTC 时间
该对象最后一次被修改的时间
也叫键或 key,必须是由字母、数字或下划线组成的字符串。自定义的属性名,不能以双下划线 __ 开头,也不能与以下系统保留字段和内置属性重名(不区分大小写)。
ACL、className、createdAt、objectId、updatedAt
可以是字符串、数字、布尔值、数组或字典。
为提高代码的可读性和可维护性,建议使用驼峰式命名法(CamelCase)为类和属性来取名。类,采用大驼峰法,如 CustomData。属性,采用小驼峰法,如 imageUrl。
使用 CQL 语法保存对象
LeanStorage 提供了类似 SQL 语法中的 Insert 方式保存一个对象,例如保存一个 TodoFolder 对象可以使用下面的代码:
// 执行 CQL 语句实现新增一个 TodoFolder 对象
[AVQuery doCloudQueryInBackgroundWithCQL:@&insert into TodoFolder(name, priority) values('工作', 1) & callback:^(AVCloudQueryResult *result, NSError *error) {
// 如果 error 为空,说明保存成功
AVObject 对象在保存时可以设置选项来快捷完成关联操作,可用的选项属性有:
fetchWhenSave
createupdate
对象成功保存后,自动返回其在云端的最新值。create 操作返回该对象的所有属性,update 操作只返回被更新了的属性的最新值。用法请参考 。
当 query 中的条件满足后,对象才能被更新,否则系统会放弃更新,并返回错误码 305。通过 query 指定的条件仅对已存在的对象生效,如果用于保存新对象,则不生效。开发者原本可以通过 AVQuery 和 AVObject 分两步来实现这样的逻辑,但如此一来无法保证操作的原子性从而导致并发问题。该选项可以用来判断多用户更新同一对象数据时可能引发的冲突。用法请参考 。
每个被成功保存在云端的对象会有一个唯一的 Id 标识 objectId,因此获取对象的最基本的方法就是根据 objectId 来查询:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query getObjectInBackgroundWithId:@&558e20cbe4beb36c& block:^(AVObject *object, NSError *error) {
// object 就是 id 为 558e20cbe4beb36c 的 Todo 对象实例
除了使用 AVQuery,还可以采用在本地构建一个 AVObject 的方式,通过接口和 objectId 把数据从云端拉取到本地:
// 第一个参数是 className,第二个参数是 objectId
AVObject *todo =[AVObject objectWithClassName:@&Todo& objectId:@&558e20cbe4beb36c&];
[todo fetchInBackgroundWithBlock:^(AVObject *avObject, NSError *error) {
NSString *title = avObject[@&title&];// 读取 title
NSString *content = avObject[@&content&]; // 读取 content
获取 objectId
每一次对象存储成功之后,云端都会返回 objectId,它是一个全局唯一的属性。
AVObject *todo = [AVObject objectWithClassName:@&Todo&];
[todo setObject:@&工程师周会& forKey:@&title&];
[todo setObject:@&每周工程师会议,周一下午2点& forKey:@&content&];
[todo setObject:@&会议室& forKey:@&location&];// 只要添加这一行代码,云端就会自动添加这个字段
[todo saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 存储成功
NSLog(@&%@&,todo.objectId);// 保存成功之后,objectId 会自动从云端加载到本地
// 失败的话,请检查网络环境以及 SDK 配置是否正确
访问对象的属性
访问 Todo 的属性的方式为:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query getObjectInBackgroundWithId:@&558e20cbe4beb36c& block:^(AVObject *object, NSError *error) {
// object 就是 id 为 558e20cbe4beb36c 的 Todo 对象实例
int priority = [[object objectForKey:@&priority&] intValue];
NSString *location = [object objectForKey:@&location&];
NSString *title = object[@&title&];
NSString *content = object[@&content&];
// 获取三个特殊属性
NSString *objectId = object.objectId;
NSDate *updatedAt = object.updatedAt;
NSDate *createdAt = object.createdAt;
请注意以上代码中访问三个特殊属性 objectId、createdAt、updatedAt 的方式。
如果访问了并不存在的属性,SDK 并不会抛出异常,而是会返回空值。
默认属性是所有对象都会拥有的属性,它包括 objectId、createdAt、updatedAt。
对象第一次保存到云端的时间戳。该时间一旦被云端创建,在之后的操作中就不会被修改。
对象最后一次被修改(或最近一次被更新)的时间。
注:应用控制台对 createdAt 和 updatedAt 做了在展示优化,它们会依据用户操作系统时区而显示为本地时间;客户端 SDK 获取到这些时间后也会将其转换为本地时间;而通过 REST API 获取到的则是原始的 UTC 时间,开发者可能需要根据情况做相应的时区转换。
多终端共享一个数据时,为了确保当前客户端拿到的对象数据是最新的,可以调用刷新接口来确保本地数据与云端的同步:
// 使用已知 objectId 构建一个 AVObject
AVObject *anotherTodo = [AVObject objectWithClassName:@&Todo& objectId:@&b2febec4b35ed7&];
// 然后调用刷新的方法,将数据从云端拉到本地
[anotherTodo fetchInBackgroundWithBlock:^(AVObject *object, NSError *error) {
// 此处调用 fetchInBackgroundWithBlock 和 refreshInBackgroundWithBlock 效果一样。
在更新对象操作后,对象本地的 updatedAt 字段(最后更新时间)会被刷新,直到下一次 save 操作,updatedAt 的最新值才会被同步到云端,这样做是为了减少网络流量传输。
同步指定属性
目前 Todo 这个类已有四个自定义属性:priority、content、location 和 title。为了节省流量,现在只想刷新 priority 和 location 可以使用如下方式:
AVObject *theTodo = [AVObject objectWithClassName:@&Todo& objectId:@&564df4f3006ad1&];
NSArray *keys = [NSArray arrayWithObjects:@&priority&, @&location&,nil];// 指定刷新的 key 数组
[theTodo fetchInBackgroundWithKeys:keys block:^(AVObject *object, NSError *error) {
// theTodo 的 priority 和 location 属性的值就是与云端一致的
NSString *priority = [object objectForKey:@&priority&];
NSString *location = object[@&location&];
刷新操作会强行使用云端的属性值覆盖本地的属性。因此如果本地有属性修改,刷新操作会丢弃这些修改。
LeanStorage 上的更新对象都是针对单个对象,云端会根据有没有 objectId 来决定是新增还是更新一个对象。
假如 objectId 已知,则可以通过如下接口从本地构建一个 AVObject 来更新这个对象:
// 第一个参数是 className,第二个参数是 objectId
AVObject *todo =[AVObject objectWithClassName:@&Todo& objectId:@&558e20cbe4beb36c&];
// 修改属性
[todo setObject:@&每周工程师会议,本周改为周三下午3点半。& forKey:@&content&];
// 保存到云端
[todo saveInBackground];
更新操作是覆盖式的,云端会根据最后一次提交到服务器的有效请求来更新数据。更新是字段级别的操作,未更新的字段不会产生变动,这一点请不用担心。
按条件更新对象
query 可以按照指定条件去更新对象——当条件满足时,执行更新;条件不满足时,不执行更新。
例如:用户的账务账户表 Account 有一个余额字段 balance,同时有多个请求要修改该字段值,为避免余额出现负值,只有满足 balance &= 当前请求的数值 这个条件才允许修改,否则提示「余额不足,操作失败!」。
NSInteger amount = -100;
AVObject *account = [[AVQuery queryWithClassName:@&Account&] getFirstObject];
[account incrementKey:@&balance& byAmount:@(amount)];
AVQuery *query = [[AVQuery alloc] init];
[query whereKey:@&balance& greaterThanOrEqualTo:@(-amount)];
AVSaveOption *option = [[AVSaveOption alloc] init];
option.query =
option.fetchWhenSave = YES;
[account saveInBackgroundWithOption:option block:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(@&当前余额为:%@&, account[@&balance&]);
} else if (error.code == 305) {
NSLog(@&余额不足,操作失败!&);
使用 CQL 语法更新对象
LeanStorage 提供了类似 SQL 语法中的 Update 方式更新一个对象,例如更新一个 TodoFolder 对象可以使用下面的代码:
// 执行 CQL 语句实现更新一个 TodoFolder 对象
[AVQuery doCloudQueryInBackgroundWithCQL:@&update TodoFolder set name='家庭' where objectId='558e20cbe4beb36c'& callback:^(AVCloudQueryResult *result, NSError *error) {
// 如果 error 为空,说明保存成功
更新计数器
这是原子操作(Atomic Operation)的一种。
为了存储一个整型的数据,LeanStorage 提供对任何数字字段进行原子增加(或者减少)的功能。比如一条微博,我们需要记录有多少人喜欢或者转发了它,但可能很多次喜欢都是同时发生的。如果在每个客户端都直接把它们读到的计数值增加之后再写回去,那么极容易引发冲突和覆盖,导致最终结果不准。此时就需要使用这类原子操作来实现计数器。
假如,现在增加一个记录查看 Todo 次数的功能,一些与他人共享的 Todo 如果不用原子操作的接口,很有可能会造成统计数据不准确,可以使用如下代码实现这个需求:
AVObject *theTodo = [AVObject objectWithClassName:@&Todo& objectId:@&564df4f3006ad1&];
[theTodo setObject:[NSNumber numberWithInt:0] forKey:@&views&]; //初始值为 0
[theTodo saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// 原子增加查看的次数
[theTodo incrementKey:@&views&];
// 保存时自动取回云端最新数据
theTodo.fetchWhenSave =
[theTodo saveInBackground];
// 也可以使用 incrementKey:byAmount: 来给 Number 类型字段累加一个特定数值。
[theTodo incrementKey:@&views& byAmount:@(5)];
[theTodo saveInBackground];
// 因为使用了 fetchWhenSave 选项,saveInBackground 调用之后,如果成功的话,对象的计数器字段是当前系统最新值。
更新数组也是原子操作。使用以下方法可以方便地维护数组类型的数据:
addObject:forKey:
addObjectsFromArray:forKey:
将指定对象附加到数组末尾。
addUniqueObject:forKey:
addUniqueObjectsFromArray:forKey:
如果数组中不包含指定对象,将该对象加入数组,对象的插入位置是随机的。
removeObject:forKey:
removeObjectsInArray:forKey:
从数组字段中删除指定对象的所有实例。
例如,Todo 对象有一个提醒时间 reminders 字段,是一个数组,代表这个日程会在哪些时间点提醒用户。比如有个拖延症患者把闹钟设为早上的 7:10、7:20、7:30:
-(NSDate*) getDateWithDateString:(NSString*) dateString{
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@&yyyy-MM-dd HH:mm:ss&];
NSDate *date = [dateFormat dateFromString:dateString];
-(void)addReminders{
NSDate *reminder1= [self getDateWithDateString:@& 07:10:00&];
NSDate *reminder2= [self getDateWithDateString:@& 07:20:00&];
NSDate *reminder3= [self getDateWithDateString:@& 07:30:00&];
NSArray *reminders =[NSArray arrayWithObjects:reminder1, reminder1,reminder3, nil];// 添加提醒时间
AVObject *todo = [AVObject objectWithClassName:@&Todo&];
[todo addUniqueObjectsFromArray:reminders forKey:@&reminders&];
[todo saveInBackground];
假如某一个 Todo 完成了,用户想要删除这个 Todo 对象,可以如下操作:
[todo deleteInBackground];
删除对象是一个较为敏感的操作。在控制台创建对象的时候,默认开启了权限保护,关于这部分的内容请阅读《》。
使用 CQL 语法删除对象
LeanStorage 提供了类似 SQL 语法中的 Delete 方式删除一个对象,例如删除一个 Todo 对象可以使用下面的代码:
// 执行 CQL 语句实现删除一个 Todo 对象
[AVQuery doCloudQueryInBackgroundWithCQL:@&delete from Todo where objectId='558e20cbe4beb36c'& callback:^(AVCloudQueryResult *result, NSError *error) {
// 如果 error 为空,说明保存成功
为了减少网络交互的次数太多带来的时间浪费,你可以在一个请求中对多个对象进行创建、更新、删除、获取。接口都在 AVObject 这个类下面:
// 批量创建、更新
+ (BOOL)saveAll:(NSArray *)objects error:(NSError **)
+ (void)saveAllInBackground:(NSArray *)objects
block:(AVBooleanResultBlock)
// 批量删除
+ (BOOL)deleteAll:(NSArray *)objects error:(NSError **)
+ (void)deleteAllInBackground:(NSArray *)objects
block:(AVBooleanResultBlock)
// 批量获取
+ (BOOL)fetchAll:(NSArray *)objects error:(NSError **)
+ (void)fetchAllInBackground:(NSArray *)objects
block:(AVArrayResultBlock)
批量设置 Todo 已经完成:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSArray&AVObject *& *todos =
for (AVObject *todo in todos) {
todo[@&status&] = @1;
[AVObject saveAllInBackground:todos block:^(BOOL succeeded, NSError *error) {
if (error) {
// 网络错误
// 保存成功
不同类型的批量操作所引发不同数量的 API 调用,具体请参考 。
细心的开发者已经发现,在所有的示例代码中几乎都是用了异步来访问 LeanStorage 云端,形如 xxxxInBackground 的方法都是提供给开发者在主线程调用用以实现后台运行的方法,因此开发者在主线程可以放心的调用这种命名方式的函数。另外,需要强调的是:回调函数的代码是在主线程执行。
离线存储对象
大多数保存功能可以立刻执行,并通知应用「保存完毕」。不过若不需要知道保存完成的时间,则可使用 saveEventually 来代替。
它的优点在于:如果用户目前尚未接入网络,saveEventually 会缓存设备中的数据,并在网络连接恢复后上传。如果应用在网络恢复之前就被关闭了,那么当它下一次打开时,LeanStorage 会再次尝试保存操作。
所有 saveEventually(或 deleteEventually)的相关调用,将按照调用的顺序依次执行。因此,多次对某一对象使用 saveEventually 是安全的。
AVRelation(已弃用)
以下文档仅供还在使用 Relation 的开发者做参考。对关联数据进行查询、排序等复杂操作,请使用 。
对象可以与其他对象相联系。如前面所述,我们可以把一个 AVObject 的实例 A,当成另一个 AVObject 实例 B 的属性值保存起来。这可以解决数据之间一对一或者一对多的关系映射,就像关系型数据库中的主外键关系一样。
例如,一个 TodoFolder 包含多个 Todo,可以用如下代码实现:
AVObject *todoFolder = [[AVObject alloc] initWithClassName:@&TodoFolder&];// 构建对象
[todoFolder setObject:@&工作& forKey:@&name&];// 设置名称
[todoFolder setObject:@1 forKey:@&priority&];// 设置优先级
AVObject *todo1 = [[AVObject alloc] initWithClassName:@&Todo&];
[todo1 setObject:@&工程师周会& forKey:@&title&];
[todo1 setObject:@&每周工程师会议,周一下午2点& forKey:@&content&];
[todo1 setObject:@&会议室& forKey:@&location&];
AVObject *todo2 = [[AVObject alloc] initWithClassName:@&Todo&];
[todo2 setObject:@&维护文档& forKey:@&title&];
[todo2 setObject:@&每天 16:00 到 18:00 定期维护文档& forKey:@&content&];
[todo2 setObject:@&当前工位& forKey:@&location&];
AVObject *todo3 = [[AVObject alloc] initWithClassName:@&Todo&];
[todo3 setObject:@&发布 SDK& forKey:@&title&];
[todo3 setObject:@&每周一下午 15:00& forKey:@&content&];
[todo3 setObject:@&SA 工位& forKey:@&location&];
[AVObject saveAllInBackground:@[todo1,todo2,todo3] block:^(BOOL succeeded, NSError *error) {
if (error) {
// 出现错误
// 保存成功
AVRelation *relation = [todoFolder relationForKey:@&containedTodos&];// 新建一个 AVRelation
[relation addObject:todo1];
[relation addObject:todo2];
[relation addObject:todo3];
// 上述 3 行代码表示 relation 关联了 3 个 Todo 对象
[todoFolder saveInBackground];// 保存到云端
Pointer 只是个描述并没有具象的类与之对应,它与 AVRelation 不一样的地方在于:AVRelation 是在一对多的「一」这一方(上述代码中的一指 TodoFolder)保存一个 AVRelation 属性,这个属性实际上保存的是对被关联数据多的这一方(上述代码中这个多指 Todo)的一个 Pointer 的集合。而反过来,LeanStorage 也支持在「多」的这一方保存一个指向「一」的这一方的 Pointer,这样也可以实现一对多的关系。
简单的说, Pointer 就是一个外键的指针,只是在 LeanCloud 控制台做了显示优化。
现在有一个新的需求:用户可以分享自己的 TodoFolder 到广场上,而其他用户看见可以给与评论,比如某玩家分享了自己想买的游戏列表(TodoFolder 包含多个游戏名字),而我们用 Comment 对象来保存其他用户的评论以及是否点赞等相关信息,代码如下:
AVObject *comment = [[AVObject alloc] initWithClassName:@&Comment&];// 构建 Comment 对象
[comment setObject:@1 forKey:@&likes&];// 如果点了赞就是 1,而点了不喜欢则为 -1,没有做任何操作就是默认的 0
[comment setObject:@&这个太赞了!楼主,我也要这些游戏,咱们团购么?& forKey:@&content&];// 留言的内容
// 假设已知了被分享的该 TodoFolder 的 objectId 是 5590cdfde4b00f7adb5860c8
[comment setObject:[AVObject objectWithClassName:@&TodoFolder& objectId:@&5590cdfde4b00f7adb5860c8&] forKey:@&targetTodoFolder&];
// 以上代码的执行结果是在 comment 对象上有一个名为 targetTodoFolder 属性,它是一个 Pointer 类型,指向 objectId 为 5590cdfde4b00f7adb5860c8 的 TodoFolder
获取 Pointer 对象
当 Todo 拥有一个字段叫做 TodoFolder 的 Pointer 类型的属性,在获取 Todo 的对象的同时,想一并把被关联的 TodoFolder 也拉取到本地:
AVObject *todo = [AVObject objectWithClassName:@&Todo& objectId:@&5735aae7c4c9710060fbe8b0&];
[todo fetchInBackgroundWithKeys:@[@&todoFolder&] block:^(AVObject * _Nullable todoObject, NSError * _Nullable error) {
AVObject *todoFolder = [todoObject objectForKey:@&todoFolder&];
NSLog(@&%@&, todoFolder);
更多内容可参考 。
地理位置是一个特殊的数据类型,LeanStorage 封装了 AVGeoPoint 来实现存储以及相关的查询。
首先要创建一个 AVGeoPoint 对象。例如,创建一个北纬 39.9 度、东经 116.4 度的 AVGeoPoint 对象(LeanCloud 北京办公室所在地):
AVGeoPoint *point = [AVGeoPoint geoPointWithLatitude:39.9 longitude:116.4];
假如,添加一条 Todo 的时候为该 Todo 添加一个地理位置信息,以表示创建时所在的位置:
[todo setObject:point forKey:@&whereCreated&];
同时请参考 。
序列化和反序列化
在实际的开发中,把 AVObject 当做参数传递的时候,会涉及到复杂对象的拷贝的问题,因此 AVObject 也提供了序列化和反序列化的方法:
AVObject *todoFolder = [[AVObject alloc] initWithClassName:@&TodoFolder&];// 构建对象
[todoFolder setObject:@&工作& forKey:@&name&];// 设置名称
[todoFolder setObject:@1 forKey:@&priority&];// 设置优先级
[todoFolder setObject:[AVUser currentUser] forKey:@&owner&];// 这里就是一个 Pointer 类型,指向当前登录的用户
NSMutableDictionary *serializedJSONDictionary = [todoFolder dictionaryForObject];//获取序列化后的字典
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:serializedJSONDictionary options:0 error:&err];//获取 JSON 数据
NSString *serializedString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];// 获取 JSON 字符串
// serializedString 的内容是:{&name&:&工作&,&className&:&TodoFolder&,&priority&:1}
反序列化:
NSMutableDictionary *objectDictionary = [NSMutableDictionary dictionaryWithCapacity:10];// 声明一个 NSMutableDictionary
[objectDictionary setObject:@&工作& forKey:@&name&];
[objectDictionary setObject:@1 forKey:@&priority&];
[objectDictionary setObject:@&TodoFolder& forKey:@&className&];
AVObject *todoFolder = [AVObject objectWithDictionary:objectDictionary];// 由 NSMutableDictionary 转化一个 AVObject
[todoFolder saveInBackground];// 保存到云端
很多开发者在使用 LeanStorage 初期都会产生疑惑:客户端的数据类型是如何被云端识别的?
因此,我们有必要重点介绍一下 LeanStorage 的数据协议。
先从一个简单的日期类型入手,比如在 Objective-C 中,默认的日期类型是 NSDate,下面会详细讲解一个
NSDate 是如何被云端正确的按照日期格式存储的。
为一个普通的 AVObject 的设置一个 NSDate 的属性,然后调用保存的接口:
Objective-C SDK 在真正调用保存接口之前,会自动的调用一次序列化的方法,将 NSDate 类型的数据,转化为如下格式的数据:
&__type&: &Date&,
&iso&: &T18:02:52.249Z&
然后发送给云端,云端会自动进行反序列化,这样自然就知道这个数据类型是日期,然后按照传过来的有效值进行存储。因此,开发者在进阶开发的阶段,最好是能掌握 LeanStorage 的数据协议。如下表介绍的就是一些默认的数据类型被序列化之后的格式:
序列化之后的格式
{&__type&: &Date&,&iso&: &T18:02:52.249Z&}
{&__type&: &Bytes&,&base64&:&utf-8-encoded-string}&
{&__type&:&Pointer&,&className&:&Todo&,&objectId&:&55aed48f0c1845c&}
AVRelation
{&__type&: &Relation&,&className&: &Todo&}
文件存储也是数据存储的一种方式,图像、音频、视频、通用文件等等都是数据的载体。很多开发者也习惯把复杂对象序列化之后保存成文件,比如 JSON 或 XML 文件。文件存储在 LeanStorage 中被单独封装成一个 AVFile 来实现文件的上传、下载等操作。
文件上传是指开发者调用接口将文件存储在云端,并且返回文件最终的 URL 的操作。
文件上传成功后会在系统表 _File 中生成一条记录,此后该记录无法被再次修改,包括
中的数据。所以如需更新该文件的记录内容,只能重新上传文件,得到新的 id 和 URL。
如果 _File 表打开了 ,该记录才可以被删除。
从数据流构建文件
AVFile 支持图片、视频、音乐等常见的文件类型,以及其他任何二进制数据,在构建的时候,传入对应的数据流即可:
NSData *data = [@&我的工作经历& dataUsingEncoding:NSUTF8StringEncoding];
AVFile *file = [AVFile fileWithData:data name:@&resume.txt&];
上例将文件命名为 resume.txt,这里需要注意两点:
不必担心文件名冲突。每一个上传的文件都有惟一的 ID,所以即使上传多个文件名为 resume.txt 的文件也不会有问题。
给文件添加扩展名非常重要。云端通过扩展名来判断文件类型,以便正确处理文件。所以要将一张 PNG 图片存到 AVFile 中,要确保使用 .png 扩展名。
从本地路径构建文件
大多数的客户端应用程序都会跟本地文件系统产生交互,常用的操作就是读取本地文件,如下代码可以实现使用本地文件路径构建一个 AVFile:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:@&LeanCloud.png&];
AVFile *file = [AVFile fileWithLocalPath:imagePath error:&error];
从网络路径构建文件
从一个已知的 URL 构建文件也是很多应用的需求。例如,从网页上拷贝了一个图像的链接,代码如下:
AVFile *file =[AVFile fileWithRemoteURL:[NSURL URLWithString:@&http://ww3.sinaimg.cn/bmiddle/596beavm5tg20bq06m7wi.gif&]];
会产生实际上传的流量,并且文件最后是存在云端,而
的文件实体并不存储在云端,只是会把文件的物理地址作为一个字符串保存在云端。
上传的操作调用方法如下:
[file uploadWithCompletionHandler:^(BOOL succeeded, NSError *error) {
NSLog(@&%@&, file.url);//返回一个唯一的 Url 地址
上传进度监听
一般来说,上传文件都会有一个上传进度条显示用以提高用户体验:
[file uploadWithProgress:^(NSInteger percentDone) {
// 上传进度数据,percentDone 介于 0 和 100。
} completionHandler:^(BOOL succeeded, NSError *error) {
// 成功或失败处理...
客户端 SDK 接口可以下载文件并把它缓存起来,只要文件的 URL 不变,那么一次下载成功之后,就不会再重复下载,目的是为了减少客户端的流量。
[file downloadWithProgress:^(NSInteger number) {
//下载的进度数据,number 介于 0 和 100。
} completionHandler:^(NSURL * _Nullable filePath, NSError * _Nullable error) {
// filePath 是文件下载到本地的地址
请注意代码中下载进度数据的读取。
图像缩略图
保存图像时,如果想在下载原图之前先得到缩略图,方法如下:
AVFile *file = [AVFile fileWithRemoteURL:[NSURL URLWithString:@&文件-url&]];
[file getThumbnail:YES width:100 height:100 withBlock:^(UIImage *image, NSError *error) {
图片缩略图只支持华北节点的应用,华东和北美节点不支持。
文件元数据
AVFile 的 metaData 属性,可以用来保存和获取该文件对象的元数据信息。metaData 一旦保存到云端就无法再次修改。
NSError *error =
AVFile *file = [AVFile fileWithLocalPath:@&文件-本地-路径& error:&error];
if (!error) {
[file.metaData setObject:@(100) forKey:@&width&];
[file.metaData setObject:@(100) forKey:@&height&];
[file.metaData setObject:@&LeanCloud& forKey:@&author&];
使用 Pointer 字段类型将 AVFile 关联到 AVObject 对象的一个字段上:
AVFile *file = [AVFile fileWithRemoteURL:[NSURL URLWithString: @&http://ww3.sinaimg.cn/bmiddle/596beavm5tg20bq06m7wi.gif&]];
AVObject *todo = [AVObject objectWithClassName:@&Todo&];
[todo setObject:file forKey:@&girl&];
[todo setObject:@&明星& forKey:@&topic&];
[todo saveInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
查询的时候需要额外的 include 一下:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKey:@&topic& equalTo:@&明星&];
[query includeKey:@&girl&];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
AVObject *todo = objects[0];
AVFile *file = [todo objectForKey:@&girl&];
NSString *url = file.
当文件较多时,要把一些不需要的文件从云端删除:
默认情况下,文件的删除权限是关闭的,需要进入 ,选择菜单 其他 & 权限设置 & delete 来开启。
[file deleteWithCompletionHandler:^(BOOL succeeded, NSError *error) {
AVFile 也提供了清除缓存的方法:
//清除当前文件缓存
- (void)clearPersistentC
//类方法, 清除所有缓存
+ (BOOL)clearAllPersistentC
启用 HTTPS 域名
如果希望使用 HTTPS 域名来访问文件,需要进入 ,勾选 启用 https 域名。HTTPS 文件流量无免费的使用额度,收费标准将在该选项开启时显示。
「启用 https 域名」会影响到 API 返回的文件地址是 HTTPS 还是 HTTP 类型的 URL。需要注意的是,即使没有启用这一选项,终端仍然可以选择使用 HTTPS URL 来访问文件,但由此会产生 HTTPS 流量扣费。
在启用文件 HTTPS 域名之后,之前已保存在 _File 表中的文件的 URL 会自动被转换为以 HTTPS 开头。如果取消 HTTPS 域名,已经改为 HTTPS 域名的文件不会变回到 HTTP。
LeanCloud 即时通讯组件也使用 AVFile 来保存消息的图片、音频等文件,并且把文件的地址写入到了消息内容中。当文件 HTTPS 域名被开启后,之前历史消息中的文件地址不会像 _File 表那样被自动转换,而依然保持 HTTP。
iOS 9 适配
从 iOS 9 开始,Apple 默认屏蔽 HTTP 访问,只支持 HTTPS 访问。LeanCloud 除了文件的 getData 之外的 API 都支持通过 HTTPS 访问。
如果你仍然需要 HTTP 访问,例如即时通讯消息中,你可以为项目配置访问策略来允许 HTTP 访问,从而解决这个问题。方法如下:
在项目中额外配置一下该接口的访问策略。选择项目的
Info.plist,右击以 Source Code 的方式打开。在 plist -& dict 节点中加入以下文本:
&key&NSAppTransportSecurity&/key&
&key&NSExceptionDomains&/key&
&key&clouddn.com&/key&
&key&NSIncludesSubdomains&/key&
&key&NSTemporaryExceptionAllowsInsecureHTTPLoads&/key&
或者在 Target 的 Info 标签中修改配置:
如果是美国节点,请把上面的 clouddn.com 换成 amazonaws.com。
你也可以根据项目需要,允许所有的 HTTP 访问,更多可参考《》。
设置自定义文件域名
LeanCloud 提供公用的二级域名来让开发者及其用户能够便捷地访问到存储在云端的文件。但由于受网络法规的管控与限制,我们无法 100% 保证该公用域名随时可用。因此,强烈建议开发者使用自定义域名来访问自己的文件,以避免公用域名不可用之时应用的体验会受到影响。请前往
设置自定义域名。
中国节点的文件存储服务自带 。
美国节点没有现成的 CDN 加速访问服务,需要用户自行配置()。
以 CloudFront 加速服务为例,配置过程如下:
阅读官方指南 。
创建一个 AWS 账户,开始使用 CloudFront 服务和付费。
S3 的公共访问权限(read permission)已经被配置好,可以跳过指南中有关 S3 配置的部分(。
CloudFront 配置中的 Origin Domain Name 请从 AVFile 的 URL 中获取,其他均可保持默认。
AVQuery 是构建针对 AVObject 查询的基础类。每次查询默认最多返回 100 条符合条件的结果,要更改这一数值,请参考 。
示例数据结构
熟悉本文所使用的相关数据表结构将有助于更好地理解后面的内容。
Todo(待办事项)
事项的详细内容
与事项相关的图片
处理该事项的地点
0 优先级最高,最迫切需要完成。
设置提醒日期和时间
0 未完成,1 已完成
事项的标题(简短描述)
该事项被浏览过的次数
whereCreated
AVGeoPoint
该事项被创建时的地理定位
TodoFolder(待办事项的分组)
containedTodos
所包含的 Todo,与表 Todo 相关联。
分组的名称,如家庭、会议。
分组的所有者或创建人,指向表 _User
该分组的优先级别,0 优先级最高。
标签,与表 Tag 相关联。
Comment(待办事项分组的评论)
评论的内容
点了赞就是 1,点了不喜欢为 -1,没有做任何操作就为 0(默认)。
targetTodoFolder
相关联的待办事项分组,指向表 TodoFolder 的 objectId
Tag(待办事项分组的标签)
标签的名称,如今日必做、老婆吩咐、十分重要等。
targetTodoFolder
相关联的待办事项分组,指向表 TodoFolder 的 objectId
创建查询实例
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
最基础的用法是根据 objectId 来查询对象:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query getObjectInBackgroundWithId:@&558e20cbe4beb36c& block:^(AVObject *object, NSError *error) {
// object 就是 id 为 558e20cbe4beb36c 的 Todo 对象实例
AVQuery 方法
notEqualTo
greaterThan
greaterThanOrEqualTo
lessThanOrEqualTo
利用上述表格介绍的逻辑操作的接口,我们可以很快地构建条件查询。
例如,查询优先级小于 2 的所有 Todo :
[query whereKey:@&priority& lessThan:@2];
以上逻辑用 SQL 语句表达为 select * from Todo where priority & 2。LeanStorage 也支持使用这种传统的 SQL 语句查询。具体使用方法请移步至 。
查询优先级大于等于 2 的 Todo:
[query whereKey:@&priority& greaterThanOrEqualTo:@2];
查询 boolean 值时,很多开发者会错误地使用 0 和 1,正确方法为:
[query whereKey:@&booleanTest& equalTo:@(YES)];
多个查询条件
当多个查询条件并存时,它们之间默认为 AND 关系,即查询只返回满足了全部条件的结果。建立 OR 关系则需要使用 。
在简单查询中,如果对一个对象的同一属性设置多个条件,那么先前的条件会被覆盖,查询只返回满足最后一个条件的结果。例如要找出优先级为 0 和 1 的所有 Todo,错误写法是:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKey:@&priority& equalTo:@0];
[query whereKey:@&priority& equalTo:@1];
// 如果这样写,第二个条件将覆盖第一个条件,查询只会返回 priority = 1 的结果
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
正确作法是使用
来构建这种条件。
字符串查询
前缀查询类似于 SQL 的 LIKE 'keyword%' 条件。因为支持索引,所以该操作对于大数据集也很高效。
// 找出开头是「早餐」的 Todo
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKey:@&content& hasPrefix:@&早餐&];
包含查询类似于 SQL 的 LIKE '%keyword%' 条件,比如查询标题包含「李总」的 Todo:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKey:@&title& containsString:@&李总&];
不包含查询可以使用正则匹配查询的方式来实现。例如,查询标题不包含「机票」的 Todo:
AVQuery *query = [AVQuery queryWithClassName:@"Todo"];
[query whereKey:@"title" matchesRegex:@"^((?!机票).)*&&];
但是基于正则的模糊查询有两个缺点:
当数据量逐步增大后,查询效率将越来越低。
没有文本相关性排序
因此我们推荐使用
功能。它基于搜索引擎技术构建,提供更强大的搜索功能。
当一个对象有一个属性是数组的时候,针对数组的元数据查询可以有多种方式。例如,在
一节中我们为 Todo 设置了 reminders 属性,它就是一个日期数组,现在我们需要查询所有在 8:30 会响起闹钟的 Todo 对象:
-(NSDate*) getDateWithDateString:(NSString*) dateString{
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@&yyyy-MM-dd HH:mm:ss&];
NSDate *date = [dateFormat dateFromString:dateString];
-(void)queryRemindersContains{
NSDate *reminder= [self getDateWithDateString:@& 08:30:00&];
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
// equalTo: 可以找出数组中包含单个值的对象
[query whereKey:@&reminders& equalTo:reminder];
查询包含 8:30 和 9:30 这两个时间点响起闹钟的 Todo:
-(NSDate*) getDateWithDateString:(NSString*) dateString{
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@&yyyy-MM-dd HH:mm:ss&];
NSDate *date = [dateFormat dateFromString:dateString];
-(void)queryRemindersContainsAll{
NSDate *reminder1= [self getDateWithDateString:@& 08:30:00&];
NSDate *reminder2= [self getDateWithDateString:@& 09:30:00&];
// 构建查询时间点数组
NSArray *reminders =[NSArray arrayWithObjects:reminder1, reminder1, nil];
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKey:@&reminders& containsAllObjectsInArray:reminders];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
注意这里是包含关系,假如有一个 Todo 会在 8:30、9:30 和 10:30 响起闹钟,它仍然是会被查询出来的。
查询「全不包含」的情况:
[query whereKey:@&reminders& notContainedIn:reminders];
假设用户可以有选择地为 Todo 上传图片来做标注,要想找出那些已有图片的 Todo:
// 存储一个带有图片的 Todo 到 LeanCloud 云端
AVFile *aTodoAttachmentImage = [AVFile fileWithURL:@(&http://www.zgjm.org/uploads/allimg/__1.jpg&)];
AVObject *todo = [AVObject objectWithClassName:@&Todo&];
[todo setObject:aTodoAttachmentImage forKey:@&images&];
[todo setObject:@&记得买过年回家的火车票!!!& forKey:@&content&];
[todo saveInBackground];
// 使用非空值查询获取有图片的 Todo
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKeyExists:@&images&];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
// objects 返回的就是有图片的 Todo 集合
// 使用空值查询获取没有图片的 Todo
[query whereKeyDoesNotExist:@&images&];
关联数据查询也可以通俗地理解为关系查询,关系查询在传统型数据库的使用中是很常见的需求,因此我们也提供了相关的接口来满足开发者针对关联数据的查询。
首先,我们需要明确关系的存储方式,再来确定对应的查询方式。
Pointer 查询
小节介绍的存储方式:每一个 Comment 都会有一个 TodoFolder 与之对应,用以表示 Comment 属于哪个 TodoFolder。现在我已知一个 TodoFolder,想查询所有的 Comnent 对象,可以使用如下代码:
AVQuery *query = [AVQuery queryWithClassName:@&Comment&];
[query whereKey:@&targetTodoFolder& equalTo:[AVObject objectWithClassName:@&TodoFolder& objectId:@&5590cdfde4b00f7adb5860c8&]];
AVRelation 查询
假如用户可以给 TodoFolder 增加一个 Tag 选项,用以表示它的标签,而为了以后拓展 Tag 的属性,就新建了一个 Tag 对象,如下代码是创建 Tag 对象:
AVObject *tag = [[AVObject alloc] initWithClassName:@&Tag&];// 构建对象
[tag setObject:@&今日必做& forKey:@&name&];// 设置名称
[tag saveInBackground];
而 Tag 的意义在于一个 TodoFolder 可以拥有多个 Tag,比如「家庭」(TodoFolder) 拥有的 Tag 可以是:今日必做、老婆吩咐、十分重要。实现创建「家庭」这个 TodoFolder 的代码如下:
AVObject *tag1 = [[AVObject alloc] initWithClassName:@&Tag&];// 构建对象
[tag1 setObject:@&今日必做& forKey:@&name&];// 设置 Tag 名称
AVObject *tag2 = [[AVObject alloc] initWithClassName:@&Tag&];// 构建对象
[tag2 setObject:@&老婆吩咐& forKey:@&name&];// 设置 Tag 名称
AVObject *tag3 = [[AVObject alloc] initWithClassName:@&Tag&];// 构建对象
[tag3 setObject:@&十分重要& forKey:@&name&];// 设置 Tag 名称
AVObject *todoFolder = [[AVObject alloc] initWithClassName:@&TodoFolder&];// 构建对象
[todoFolder setObject:@&家庭& forKey:@&name&];// 设置 Todo 名称
[todoFolder setObject:@1 forKey:@&priority&];// 设置优先级
AVRelation *relation = [todoFolder relationForKey:@&tags&];// 新建一个 AVRelation
[relation addObject:tag1];
[relation addObject:tag2];
[relation addObject:tag3];
[todoFolder saveInBackground];// 保存到云端
查询一个 TodoFolder 的所有 Tag 的方式如下:
AVObject *todoFolder = [AVObject objectWithClassName:@&TodoFolder& objectId:@&5661047dddb299ad5f460166&];
AVRelation *relation = [todoFolder relationForKey:@&tags&];
AVQuery *query = [relation query];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
// objects 是一个 AVObject 的 NSArray,它包含所有当前 todoFolder 的 tags
反过来,现在已知一个 Tag,要查询有多少个 TodoFolder 是拥有这个 Tag 的,可以使用如下代码查询:
AVObject *tag = [AVObject objectWithClassName:@&Tag& objectId:@&b204d55d3b7b89&];
AVQuery *query = [AVQuery queryWithClassName:@&TodoFolder&];
[query whereKey:@&tags& equalTo:tag];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
// objects 是一个 AVObject 的 NSArray
// objects 指的就是所有包含当前 tag 的 TodoFolder
关于关联数据的建模是一个复杂的过程,很多开发者因为在存储方式上的选择失误导致最后构建查询的时候难以下手,不但客户端代码冗余复杂,而且查询效率低,为了解决这个问题,我们专门针对关联数据的建模推出了一个详细的文档予以介绍,详情请阅读《》。
关联属性查询
中保存 Comment 的 targetTodoFolder 属性一样,假如查询到了一些 Comment 对象,想要一并查询出每一条 Comment 对应的 TodoFolder 对象的时候,可以加上 include 关键字查询条件。同理,假如 TodoFolder 表里还有 pointer 型字段 targetAVUser 时,再加上一个递进的查询条件,形如 include(b.c),即可一并查询出每一条 TodoFolder 对应的 AVUser 对象。代码如下:
AVQuery *commentQuery = [AVQuery queryWithClassName:@&Comment&];
[commentQuery orderByDescending:@&createdAt&];
commentQuery.limit = 10;
[commentQuery includeKey:@&targetTodoFolder&];// 关键代码,用 includeKey 告知云端需要返回的关联属性对应的对象的详细信息,而不仅仅是 objectId
[commentQuery includeKey:@&targetTodoFolder.targetAVUser&];// 关键代码,同上,会返回 targetAVUser 对应的对象的详细信息,而不仅仅是 objectId
[commentQuery findObjectsInBackgroundWithBlock:^(NSArray *comments, NSError *error) {
// comments 是最近的十条评论, 其 targetTodoFolder 字段也有相应数据
for (AVObject *comment in comments) {
// 并不需要网络访问
AVObject *todoFolder = [comment objectForKey:@&targetTodoFolder&];
AVUser *avUser = [todoFolder objectForKey:@&targetAVUser&];
此外需要格外注意的是,假设对象有一个 Array 类型的字段 todoArray 内部是 Pointer 类型:
[pointer1, pointer2, pointer3]
可以用 include 方法获取数组中的 pointer 数据,例如:
[query includeKey:@&todoArray&];
query.include(&todoArray&);
query.include('todoArray');
但是 Array 类型的 include 操作只支持到第一层,不支持 include(b.c) 这种递进关联查询。
select 也具备使用 dot 符号 . 来进行级联操作:
[query selectKeys:@&targetTodoFolder.targetAVUser.username&];
query.selectKeys(Arrays.asList(&targetTodoFolder.targetAVUser.username&));
query.select(['targetTodoFolder.targetAVUser.username']);
查询点赞超过 20 次的 TodoFolder 的 Comment 评论(注意查询针对的是 ),使用内嵌查询接口就可以通过一次查询来达到目的。
// 构建内嵌查询
AVQuery *innerQuery = [AVQuery queryWithClassName:@&TodoFolder&];
[innerQuery whereKey:@&likes& greaterThan:@20];
// 将内嵌查询赋予目标查询
AVQuery *query = [AVQuery queryWithClassName:@&Comment&];
// 执行内嵌操作
[query whereKey:@&targetTodoFolder& matchesQuery:innerQuery];
[query findObjectsInBackgroundWithBlock:^(NSArray *comments, NSError *error) {
// comments 就是符合超过 20 个赞的 TodoFolder 这一条件的 Comment 对象集合
// 注意如果要做相反的查询可以使用
[query whereKey:@&targetTodoFolder& doesNotMatchQuery:innerQuery];
// 如此做将查询出 likes 小于或者等于 20 的 TodoFolder 的 Comment 对象
与普通查询一样,内嵌查询默认也最多返回 100 条记录,想修改这一默认请参考 。
如果所有返回的记录没有匹配到外层的查询条件,那么整个查询也查不到结果。例如:
-- 找出积分高于 80、region 为 cn 的玩家记录
NAME IN (SELECT NAME
score & 80)
AND region = 'cn'
LeanCloud 云端使用的并非关系型数据库,无法做到真正的联表查询,所以实际的处理方式是:先执行内嵌/子查询(和普通查询一样,limit 默认为 100,最大
1000),然后将子查询的结果填入主查询的对应位置,再执行主查询。
如果子查询匹配到了 100 条以上的记录(性别等区分度低的字段重复值往往较多),且主查询有其他查询条件(region = 'cn'),那么可能会出现没有结果或结果不全的情况,其本质上是子查询查出的 100 条记录没有满足主查询的其他条件。
我们建议采用以下方案进行改进:
确保子查询的结果在 100 条以下,如果在 100 - 1000 条的话请在子查询末尾添加 limit 1000。
将需要查询的字段冗余到主查询所在的表上;例如将 score 冗余到 Player 表上,或者将 region 添加到 GameScore 上然后只查 GameScore 表。
进行多次查询,每次在子查询上添加
来遍历所有记录(注意 skip 的值较大时可能会引发性能问题,因此不是很推荐)。
地理位置查询
地理位置查询是较为特殊的查询,一般来说,常用的业务场景是查询距离 xx 米之内的某个位置或者是某个建筑物,甚至是以手机为圆心,查找方圆多少范围内餐厅等等。LeanStorage 提供了一系列的方法来实现针对地理位置的查询。
查询位置附近的对象
Todo 的 whereCreated(创建 Todo 时的位置)是一个 AVGeoPoint 对象,现在已知了一个地理位置,现在要查询 whereCreated 靠近这个位置的 Todo 对象可以使用如下代码:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
AVGeoPoint *point = [AVGeoPoint geoPointWithLatitude:39.9 longitude:116.4];
query.limit = 10;
[query whereKey:@&whereCreated& nearGeoPoint:point];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSArray&AVObject *& *nearbyTodos =// 离这个位置最近的 10 个 Todo 对象
在上面的代码中,nearbyTodos 返回的是与 point 这一点按距离排序(由近到远)的对象数组。注意:如果在此之后又使用了 orderByAscending: 或 orderByDescending: 方法,则按距离排序会被新排序覆盖。
查询指定范围内的对象
要查找指定距离范围内的数据,可使用 whereWithinKilometers 、 whereWithinMiles 或 whereWithinRadians 方法。
例如,我要查询距离指定位置,2 千米范围内的 Todo:
[query whereKey:@&whereCreated&
nearGeoPoint:point withinKilometers:2.0];
使用地理位置需要注意以下方面:
每个 AVObject 数据对象中只能有一个 AVGeoPoint 对象的属性。
地理位置的点不能超过规定的范围。纬度的范围应该是在 -90.0 到 90.0 之间,经度的范围应该是在 -180.0 到 180.0 之间。如果添加的经纬度超出了以上范围,将导致程序错误。
iOS 8.0 之后,使用定位服务之前,需要调用 [locationManager requestWhenInUseAuthorization] 或 [locationManager requestAlwaysAuthorization] 来获取用户的「使用期授权」或「永久授权」,而这两个请求授权需要在 info.plist 里面对应添加 NSLocationWhenInUseUsageDescription 或 NSLocationAlwaysUsageDescription 的键值对,值为开启定位服务原因的描述。SDK 内部默认使用的是「使用期授权」。
组合查询就是把诸多查询条件合并成一个查询,再交给 SDK 去云端查询。方式有两种:OR 和 AND。
OR 操作表示多个查询条件符合其中任意一个即可。 例如,查询优先级是大于等于 3 或者已经完成了的 Todo:
AVQuery *priorityQuery = [AVQuery queryWithClassName:@&Todo&];
[priorityQuery whereKey:@&priority& greaterThanOrEqualTo:[NSNumber numberWithInt:3]];
AVQuery *statusQuery = [AVQuery queryWithClassName:@&Todo&];
[statusQuery whereKey:@&status& equalTo:[NSNumber numberWithInt:1]];
AVQuery *query = [AVQuery orQueryWithSubqueries:[NSArray arrayWithObjects:statusQuery,priorityQuery,nil]];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
// 返回 priority 大于等于 3 或 status 等于 1 的 Todo
注意:OR 查询中,子查询中不能包含地理位置相关的查询。
AND 操作将满足了所有查询条件的对象返回给客户端。例如,找到创建于
之间的 Todo:
NSDate *(^dateFromString)(NSString *string) = ^(NSString *string) {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@&yyyy-MM-dd&];
return [dateFormatter dateFromString:string];
AVQuery *startDateQuery = [AVQuery queryWithClassName:@&Todo&];
[startDateQuery whereKey:@&createdAt& greaterThanOrEqualTo:dateFromString(@&&)];
AVQuery *endDateQuery = [AVQuery queryWithClassName:@&Todo&];
[endDateQuery whereKey:@&createdAt& lessThan:dateFromString(@&&)];
AVQuery *query = [AVQuery andQueryWithSubqueries:[NSArray arrayWithObjects:startDateQuery,endDateQuery,nil]];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
可以对新创建的 AVQuery 添加额外的约束,多个约束将以 AND 运算符来联接。
查询结果数量和排序
获取第一条结果
例如很多应用场景下,只要获取满足条件的一个结果即可,例如获取满足条件的第一条 Todo:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKey:@&priority& equalTo:@0];
[query getFirstObjectInBackgroundWithBlock:^(AVObject *object, NSError *error) {
// object 就是符合条件的第一个 AVObject
限定返回数量
为了防止查询出来的结果过大,云端默认针对查询结果有一个数量限制,即 limit,它的默认值是 100。比如一个查询会得到 10000 个对象,那么一次查询只会返回符合条件的 100 个结果。limit 允许取值范围是 1 ~ 1000。例如设置返回 10 条结果:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
NSDate *now = [NSDate date];
[query whereKey:@&createdAt& lessThanOrEqualTo:now];//查询今天之前创建的 Todo
query.limit = 10; // 最多返回 10 条结果
设置 skip 这个参数可以告知云端本次查询要跳过多少个结果。将 skip 与 limit 搭配使用可以实现翻页效果。例如,在翻页中每页显示数量为 10,要获取第 3 页的对象:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
NSDate *now = [NSDate date];
[query whereKey:@&createdAt& lessThanOrEqualTo:now];//查询今天之前创建的 Todo
query.limit = 10; // 最多返回 10 条结果
query.skip = 20;
// 跳过 20 条结果
上述方法的执行效率比较低,因此不建议广泛使用。建议选用 createdAt 或者 updatedAt 这类的时间戳进行。
返回指定属性/字段
通常列表展现的时候并不是需要展现某一个对象的所有属性,例如,Todo 这个对象列表一般展现的是 title 以及 content,在设置查询时可以告知云端需要返回的属性或字段有哪些,这样既满足需求又节省流量,还可以提高一部分的性能:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query selectKeys:@[@&title&, @&content&]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
for(AVObject *avObject in objects){
NSString *title = avObject[@&title&];// 读取 title
NSString *content = avObject[@&content&]; // 读取 content
// 如果访问没有指定返回的属性(key),则会报错,在当前这段代码中访问 location 属性就会报错
NSString *location = [avObject objectForKey:@&location&];
所指定的属性或字段也支持 Pointer 类型。例如,获取 Todo 这个对象的所有者信息(owner 属性,Pointer 类型),仅展示这个所有者的 username:
[query selectKeys:@[@&owner.username&]];
统计总数量
通常用户在执行完搜索后,结果页面总会显示出诸如「搜索到符合条件的结果有 1020 条」这样的信息。例如,查询一下今天一共完成了多少条 Todo:
AVQuery *query = [AVQuery queryWithClassName:@&Todo&];
[query whereKey:@&status& equalTo:@&1&];
[query countObjectsInBackgroundWithBlock:^(NSInteger number, NSError *error) {
if (!error) {
// 查询成功,输出计数
NSLog(@&今天完成了 %ld 条待办事项。&, number);
// 查询失败
对于数字、字符串、日期类型的数据,可对其进行升序或降序排列。
// 按时间,升序排列
[query orderByAscending:@&createdAt&];
// 按时间,降序排列
[query orderByDescending:@&createdAt&];
一个查询可以附加多个排序条件,如按 priority 升序、createdAt 降序排列:
[query addAscendingOrder:@&priority&];
[query addDescendingOrder:@&createdAt&];
CQL 是 LeanStorage 独创的使用类似 SQL 语法来实现云端查询功能的语言,具有 SQL 开发经验的开发者可以方便地使用此接口实现查询。
分别找出 status = 1 的全部 Todo 结果,以及 priority = 0 的 Todo 的总数:
NSString *cql = [NSString stringWithFormat:@&select * from %@ where status = 1&, @&Todo&];
[AVQuery doCloudQueryInBackgroundWithCQL:cql callback:^(AVCloudQueryResult *result, NSError *error)
NSLog(@&results:%@&, result.results);
cql = [NSString stringWithFormat:@&select count(*) from %@ where priority = 0&, @&Todo&];
[AVQuery doCloudQueryInBackgroundWithCQL:cql callback:^(AVCloudQueryResult *result, NSError *error)
NSLog(@&count:%lu&, (unsigned long)result.count);
通常查询语句会使用变量参数,为此我们提供了与 Java JDBC 所使用的 PreparedStatement 占位符查询相类似的语法结构。
查询 status = 0、priority = 1 的 Todo:
NSString *cql = [NSString stringWithFormat:@&select * from %@ where status = ? and priority = ?&, @&Todo&];
NSArray *pvalues =
@[@0, @1];
[AVQuery doCloudQueryInBackgroundWithCQL:cql pvalues:pvalues callback:^(AVCloudQueryResult *result, NSError *error) {
if (!error) {
// 操作成功
NSLog(@&%@&, error);
目前 CQL 已经支持数据的更新 update、插入 insert、删除 delete 等 SQL 语法,更多内容请参考 。
缓存一些查询的结果到磁盘上,这可以让你在离线的时候,或者应用刚启动,网络请求还没有足够时间完成的时候可以展现一些数据给用户。当缓存占用了太多空间的时候,LeanStorage 会自动清空缓存。
默认情况下的查询不会使用缓存,除非你调用接口明确设置启用。例如,尝试从网络请求,如果网络不可用则从缓存数据中获取,可以这样设置:
AVQuery *query = [AVQuery queryWithClassName:@&Post&];
query.cachePolicy = kAVCachePolicyNetworkElseC
//设置缓存有效期
query.maxCacheAge = 24*3600;
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// 成功找到结果,先找网络再访问磁盘
// 无法访问网络,本次查询结果未做缓存
为了满足多变的需求,SDK 默认提供了以下几种缓存策略:
含义及解释
kAVCachePolicyIgnoreCache
(默认缓存策略)查询行为不从缓存加载,也不会将结果保存到缓存中。
kAVCachePolicyCacheOnly
查询行为忽略网络状况,只从缓存加载。如果没有缓存结果,该策略会产生 AVError。
kAVCachePolicyCacheElseNetwork
查询行为首先尝试从缓存加载,若加载失败,则通过网络加载结果。如果缓存和网络获取行为均为失败,则产生 AVError。
kAVCachePolicyNetworkElseCache
查询行为先尝试从网络加载,若加载失败,则从缓存加载结果。如果缓存和网络获取行为均为失败,则产生 AVError。
kAVCachePolicyCacheThenNetwork
查询先从缓存加载,然后从网络加载。在这种情况下,回调函数会被调用两次,第一次是缓存中的结果,然后是从网络获取的结果。因为它会在不同的时间返回两个结果,所以该策略不能与 findObjects 同时使用。
缓存相关的操作
检查是否存在缓存查询结果:
BOOL isInCache = [query hasCachedResult];
删除某一查询的任何缓存结果:
[query clearCachedResult];
删除查询的所有缓存结果:
[AVQuery clearAllCachedResults];
设定缓存结果的最长时限:
query.maxCacheAge = 60 * 60 * 24; // 一天的总秒数
查询缓存也适用于 AVQuery 的辅助方法,包括 getFirstObject 和 getObjectInBackground。
查询性能优化
影响查询性能的因素很多。特别是当查询结果的数量超过 10 万,查询性能可能会显著下降或出现瓶颈。以下列举一些容易降低性能的查询方式,开发者可以据此进行有针对性的调整和优化,或尽量避免使用。
不等于和不包含查询(无法使用索引)
通配符在前面的字符串查询(无法使用索引)
有条件的 count(需要扫描所有数据)
skip 跳过较多的行数(相当于需要先查出被跳过的那些行)
无索引的排序(另外除非复合索引同时覆盖了查询和排序,否则只有其中一个能使用索引)
无索引的查询(另外除非复合索引同时覆盖了所有条件,否则未覆盖到的条件无法使用索引,如果未覆盖的条件区分度较低将会扫描较多的数据)
用户系统几乎是每款应用都要加入的功能。除了基本的注册、登录和密码重置,移动端开发还会使用手机号一键登录、短信验证码登录等功能。LeanStorage 提供了一系列接口来帮助开发者快速实现各种场景下的需求。
AVUser 是用来描述一个用户的特殊对象,与之相关的数据都保存在 _User 数据表中。
用户的属性
用户名、密码、邮箱是默认提供的三个属性,访问方式如下:
NSString *currentUsername = [AVUser currentUser].// 当前用户名
NSString *currentEmail = [AVUser currentUser]. // 当前用户的邮箱
// 请注意,以下代码无法获取密码
NSString *currentPassword = [AVUser currentUser].//
currentPassword 是 nil
请注意代码中,密码是仅仅是在注册的时候可以设置的属性(这部分代码可参照 ),它在注册完成之后并不会保存在本地(SDK 不会以明文保存密码这种敏感数据),所以在登录之后,再访问密码这个字段是为空的。
自定义属性
用户对象和普通对象一样也支持添加自定义属性。例如,为当前用户添加年龄属性 age:
[[AVUser currentUser] setObject:[NSNumber numberWithInt:25] forKey:@&age&];
[[AVUser currentUser] saveInBackground];
很多开发者会有这样的疑问:「为什么我不能修改任意一个用户的属性?」
因为很多时候,就算是开发者也不要轻易修改用户的基本信息,例如用户的手机号、社交账号等个人信息都比较敏感,应该由用户在 App 中自行修改。所以为了保证用户的数据仅在用户自己已登录的状态下才能修改,云端对所有针对 AVUser 对象的数据操作都要做验证。
例如,先为当前用户增加一个 age 属性,保存后再更改它的值:
[[AVUser currentUser] setObject:[NSNumber numberWithInt:25] forKey:@&age&];
[[AVUser currentUser] saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
[[AVUser currentUser] setObject:[NSNumber numberWithInt:27] forKey:@&age&];
[[AVUser currentUser] saveInBackground];
AVUser 的自定义属性在使用上与 AVObject 没有本质区别。
手机号码注册
一些应用为了提高首次使用的友好度,一般会允许用户浏览一些内容,直到用户发起了一些操作才会要求用户输入一个手机号,而云端会自动发送一条验证码的短信给用户的手机号,最后验证一下,完成一个用户注册并且登录的操作,例如很多团购类应用都有这种用户场景。
首先调用发送验证码的接口:
[AVSMS requestShortMessageForPhoneNumber:@&& options:nil callback:^(BOOL succeeded, NSError * _Nullable error) {
// 发送失败可以查看 error 里面提供的信息
然后在 UI 上给与用户输入验证码的输入框,用户点击登录的时候调用如下接口:
[AVUser signUpOrLoginWithMobilePhoneNumberInBackground:@&& smsCode:@&123456& block:^(AVUser *user, NSError *error) {
// 如果 error 为空就可以表示登录成功了,并且 user 是一个全新的用户
用户名和密码注册
采用「用户名 + 密码」注册时需要注意:密码是以明文方式通过 HTTPS 加密传输给云端,云端会以密文存储密码,并且我们的加密算法是无法通过所谓「彩虹表撞库」获取的,这一点请开发者放心。换言之,用户的密码只可能用户本人知道,开发者不论是通过控制台还是 API 都是无法获取。另外我们需要强调在客户端,应用切勿再次对密码加密,这会导致重置密码等功能失效。
例如,注册一个用户的示例代码如下(用户名 Tom 密码 cat!@#123):
AVUser *user = [AVUser user];// 新建 AVUser 对象实例
user.username = @&Tom&;// 设置用户名
user.password =
@&cat!@#123&;// 设置密码
user.email = @&&;// 设置邮箱
[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 注册成功
// 失败的原因可能有多种,常见的是用户名已经存在。
我们建议在可能的情况下尽量使用异步版本的方法,这样就不会影响到应用程序主 UI 线程的响应。
如果注册不成功,请检查一下返回的错误对象。最有可能的情况是用户名已经被另一个用户注册,错误代码 ,即 _User 表中的 username 字段已存在相同的值,此时需要提示用户尝试不同的用户名来注册。同样,邮件 email 和手机号码 mobilePhoneNumber 字段也要求在各自的列中不能有重复值出现,否则会出现 、 错误。
开发者也可以要求用户使用 Email 做为用户名注册,即在用户提交信息后将 _User 表中的 username 和 email 字段都设为相同的值,这样做的好处是用户在忘记密码的情况下可以直接使用「」功能,无需再额外绑定电子邮件。
关于自定义邮件模板和验证链接,请参考《》。
设置手机号码
微信、陌陌等流行应用都会建议用户将账号和一个手机号绑定,这样方便进行身份认证以及日后

我要回帖

更多关于 环世界自定义优先级 的文章

 

随机推荐