使用ajax完成分页加载哪一个类可以完成对CoreData结果排序

拒绝访问 | www.wangchao.net.cn | 百度云加速
请打开cookies.
此网站 (www.wangchao.net.cn) 的管理员禁止了您的访问。原因是您的访问包含了非浏览器特征(40e9c7a073ab4d0e-ua98).
重新安装浏览器,或使用别的浏览器登录以解锁更多InfoQ新功能
获取更新并接收通知
给您喜爱的内容点赞
关注您喜爱的编辑与同行
966,690 三月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
对比iOS中的四种数据存储
对比iOS中的四种数据存储
3&他的粉丝
日. 估计阅读时间:
,PWA、Web框架、Node等最新最热的大前端话题邀你一起共同探讨。
亲爱的读者:我们最近添加了一些个人消息定制功能,您只需选择感兴趣的技术主题,即可获取重要资讯的。
相关厂商内容
相关赞助商
图1,存储方案示意图
以下将对四种存储方式进行详细的介绍:
NSUserDefaults,用于存储配置信息
SQLite,用于存储查询需求较多的数据
CoreData,用于规划应用中的对象
使用基本对象类型定制的个性化缓存方案
用NSUserDefaults存储配置信息
NSUserDefaults被设计用来存储设备和应用的配置信息,它通过一个工厂方法返回默认的、也是最常用到的实例对象。这个对象中储存了系统中用户的配置信息,开发者可以通过这个实例对象对这些已有的信息进行修改,也可以按照自己的需求创建新的配置项。
图2,笔者手机中[NSUserDefaults standardUserDefaults]内容
NSUserDefaults把配置信息以字典的形式组织起来,支持字典的项包括:字符串或者是数组,除此之外还支持数字等基本格式。一句话概括就是:基础类型的小数据的字典。操作方法几乎与NSDictionary的操作方法无异,另外还可以通过指定返回类型的方法获取到指定类型的返回值。
图3,NSUserDefaults提供的指定返回类型的方法列表
NSUserDefaults的所有数据都放在内存里,因此操作速度很快,并还提供一个归档方法:+ (void)synchronize。开发者自定义的配置项(如图2中的最后一项 key:alkdjfkladsjfmm)会以plist格式的文件归档在相应应用目录的/Library/Preferences/[App_Bundle_Identifier].plist文件。再次初始化获得实例对象后,框架会把用户自定义的这个配置和系统配置合并得到完整数据。
用SQLite存储查询需求较多的数据
iOS的SDK里预置了SQLite的库,开发者可以自建SQLite数据库。SQLite每次写入数据都会产生IO消耗,把数据归档到相应的文件。
SQLite擅长处理的数据类型其实与NSUserDefaults差不多,也是基础类型的小数据,只是从组织形式上不同。开发者可以以关系型数据库的方式组织数据,使用SQL DML来管理数据。 一般来说应用中的格式化的文本类数据可以存放在数据库中,尤其是类似聊天记录、Timeline等这些具有条件查询和排序需求的数据。
每一个数据库的句柄都会在内存中都会被分配一段缓存,用于提高查询效率。另一个方面,由于查询缓存,当产生大量句柄或数据量较大时,会出现缓存过大,造成内存浪费。
SQLite的使用起来要比NSUserDefaults复杂的多,因此建议开发者使用SQLite要搭配一个操作控件使用,可以简化操作。笔者开发的SQLight是一款对SQLite操作的封装,把相对复杂的SQLite命令封装成对象和方法,可以供大家参考。大家可以在获取这个工程的代码进一步了解。
用CoreData规划应用中对象
官方给出的定义是,一个支持持久化的,对象图和生命周期的自动化管理方案。严格意义上说CoreData是一个管理方案,他的持久化可以通过SQLite、XML或二进制文件储存。如官方定义所说,CoreData的作用远远不止储存数据这么简单,它可以把整个应用中的对象建模并进行自动化的管理。
图4,官方文档中解释CoreData给出的对象图示例
正如上图所示,MyDocument是一个对象实例,有两个Collection:Employee和Department,存放各自的对象列表。MyDocument、Employee和Department三个对象以及他们之间的关系都通过CoreData建模,并可以通过save方法进行持久化。
从归档文件还原模型时CoreData并不是一次性把整个模型中的所有数据都载入内存,而是根据运行时状态,把被调用到的对象实例载入内存。框架会自动控制这个过程,从而达到控制内存消耗,避免浪费。
无论从设计原理还是使用方法上看,CoreData都比较复杂。因此,如果仅仅是考虑缓存数据这个需求,CoreData绝对不是一个优选方案。CoreData的使用场景在于:整个应用使用CoreData规划,把应用内的数据通过CoreData建模,完全基于CoreData架构应用。
苹果官方给出的,结构相对简单,可以帮助大家入门CoreData。
使用基本对象类型定制的个性化缓存方案
之前提到的NSUserDefaults和SQLite适合存储基础类型的小数据,而CoreData则不适合存储单一的数据,那么对于类似图片这种较大的数据要用什么方式储存呢?我给出的建议就是:自己实现一套存储方案。说到订制存储方案大家非常容易质疑,这是不是又在重新发明轮子。我可以非常明确的告诉大家,这绝不是在重新发明轮子。首先要明确,这个所谓的定制方案适用于互联网应用中对远程数据的缓存,几个限制条件缺一不可。
从需求出发分析缓存数据有哪些要求:按Key查找,快速读取,写入不影响正常操作,不浪费内存,支持归档。这些都是基本需求,那么再进一步或许还需要固定缓存项数量,支持队列缓存,缓存过期等。从这些需求入手设计一个缓存方案并不十分复杂,Kache是笔者根据开发应用的需求开发的一套缓存组件,通过分析Kache希望可以给大家一个思路。
图5,Kache架构图
如上图所示,Kache扮演的是一个典型缓存角色。应用加载远程数据生成应用数据对象的同时,通过Kache把数据缓存起来,再次请求则直接通过Kache获取数据。
缓存对象可以是NSDictionary、NSArray、NSSet或NSData这些可直接归档的类型,每个缓存对象对应一个Key。缓存对象包括数据和过期时间,内存中存放在一个单例字典中,闪存中每个对象存为一个文件。Key空间按照各种顺序存放缓存对象的Key集合,Pool为固定大小的数组,当数量达到上限,最早过期的一个Key将被删除,对应的缓存对象也被清除。Queue也是固定大小的数组,以先进先出的规则管理Key的增删。 每一次有新的缓存对象存入,自动检测Key空间中过期的集合并清除。
此外,控件提供save和load方法支持持久化和重新载入。
Kache最初设计为存放图片缓存,之后也曾用于缓存文本数据,由于使用了过期和归档相结合的逻辑,可以保证大部分命中的缓存对象都在内存中,从而获取了较高的效率。读者可以了解更多。
以上介绍了几种iOS开发中经常会遇到的储存数据方法,从其存储原理、使用方式和适用场景几方面进行进了简单的对比。事实上每一款应用都很难采用一种单一的方案完成整个应用的数据储存任务,需要根据不同的数据类型,选择最合适的方案,以便整个应用获得良好的运行时性能。
作者简介:
(微博:),SegmentFault.com联合创始人,杭州iOS开发者沙龙发起人,资深iOS开发者。
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博()或者腾讯微博()关注我们,并与我们的编辑和其他读者朋友交流。
Author Contacted
语言 & 开发
347 他的粉丝
架构 & 设计
826 他的粉丝
122 他的粉丝
2 他的粉丝
0 他的粉丝
22 他的粉丝
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
Very nice~!
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
InfoQ.com及所有内容,版权所有 ©
C4Media Inc. InfoQ.com 服务器由 提供, 我们最信赖的ISP伙伴。
极客邦控股(北京)有限公司
找回密码....
InfoQ账号使用的E-mail
关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。
内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。
设置通知机制以获取内容更新对您而言是否重要
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。CoreData 数据读取的问题。
23:01:34 +08:00 · 2870 次点击
在了解了一点点关于core data的内容后,想搭配UITableView的显示表格详情,就不用每次都下载了。 先是从网上找了知乎日报的一个JSON,使用core data,创建了NSmanagedObject的子类storylist,里面放了title和url。然后建了一个DataManager的class处理数据。
使用AFN下载JSON,通过NSManagedObjectModel,NSManagedObjectContext,NSPersistentStoreCoorDinator 三个属性组合存储数据,存储成功了,这里应该没问题吧?
然后使用NSFetchRequest 返回的是一个NSArry。在DataManager下面,返回的数组,可以使用for in历遍,使用for Storylist *story in NSArray 通过story.title,把title一个个打印出来。
到了tableview里面使用这个数组进行处理时,只能有整个数组,或者打印出数组里面包含的一堆内容一起,但是无法把里面的title打印出来了。如果使用for Storylist *story in NSArray ,打印story.title,都是null。打印NSArry的话,内容如下:
&StoryList: 0x7fdc9b456e40& (entity: StoryL id: 0xd000 &x-coredata://45DF0C4C-B048-4F53-8DF5-B845F546BD08/StoryList/p1& ; data: &fault&)
主要是没什么特别的思路,在《iOS编程》的书里,也是使用了一个单独的class处理,但是不同的就是他似乎在里面跳转了好几次,我就简化了一点点内容,跳转少了,提取数据以后,就直接返回这个数组了。
感谢看完。。。可能写的有点乱了,这个折腾了好久,主要是想为了试试core data的使用,结果卡了好久,整个人都不好了,如果能够有指正,十分感谢。
第 1 条附言 &·&
00:38:07 +08:00
我最后把提取数据的请求放回了tableviewcontroller的viewDidLoad,也是在同一个位置,然后self.array = results(读取的数组),然后在cell for使用StoryList *story in self.array就能够成功把story.title成功赋值了。。。
之前的情况是,能够用forin遍历每项,也可以打印,但是没法使用story.title给cell的名称赋值。。看源码,有的是把这个步骤放在了VController里,死马当活马医,试了试,还可以。虽然不确切清楚为什么,但是估计是之前设置专门处理这一组数据的class没完善吧。
11 回复 &| &直到
09:02:41 +08:00
& & 11:30:56 +08:00
抛弃它把,用FMDB
& & 13:15:08 +08:00 via iPad
推荐用 Magical Record 来处理 Core Data,方便很多。
& & 13:43:23 +08:00
因为刚有学,所以看的是官方库,现在有点点学会存储和读取,感觉就是官方代码复制下来就好了。打的代码有点多,在多的插入还好,其他操作似乎比较少一些。
@ 谢谢,我去github上搜搜。
@ 谢谢,我搜了下,LS的是替代Core Data,这个是处理Core Data的样子。我也去读读教程看,能看懂哪个就用哪个。总觉得直接用第三方库,有点只懂皮毛的样子,但是为了先出点成果,这么搞最方便了或许。
& & 14:03:27 +08:00
@ objc 的这期 issue
& & 15:39:13 +08:00 via iPhone
有一段时间没用core data了,似乎你上面打印出来的storylist还包含了entity的信息,可以在你后来成功使用的地方,再打印一个看看是不是一样。应该是哪里没设置对,没有转化为NSObject的对象吧。
& & 18:21:07 +08:00
@ 嗯嗯,惭愧呢,这个专题在objc中文的上面看了些。处于有些明白,又有些迷糊的状态。
感觉是通过momd文件和model,context,persistence的几个属性设置好,把对应类的实际内容写在了文件上。
不知道这种想法对不对。
Core data设计三个属性的设置好了,就可以很便捷的存储和提取内容了。
甚至我直接把那些设置的内容从一个初始化带有Core Data的appdelegate里面直接复制出来,就可以直接使用。似乎还导入appdelegate,用他里面的context也可以方便的存取。
所以感觉Core Data的代码虽然很麻烦,但是其实很固定,忽略这一长串的代码的话,存储和读取的操作,还算简单。
@ 对头,有这种感觉,提取出来的是一个数组,而单个对象应该我的storylist class的实例。但是controller并不知道这是我属于storylist的类,虽然用for storylist in result倒是不报错,但是打印title属性就null。
当存取整合在一个class的情况时,提取数据的方法放在datamanager class里。在这个方法的结尾,返回数组之前,我尝试用for Storylist *story in result(取出的数组),是可以正确的打印每一个的story.title。
在controller里面,title打印不出来,但是单个对象可以打出上面的那一串内容。
所以可以认为在controller里面得到那串数组后,controller并不知道其中的单个对象是什么类,而在datamanager得到result时,他是知道这是一个一串NSObjec/storylist的内容,所以只打印title属性可以完成。
教程似乎都没有着重提醒过,如何转化……请问,一般情况进行转化是进行了什么步骤,不直接返回提取的数据结果?
在提取的result数组里面,在进行一次for storylist in result的遍历,然后把项目添加到另一个数组,可行么?(我在另外一个demo里面看到了这一部分,但是又不完全一样。这串处理在单独的datamananger class中进行,即使处理了,后续的controller里不应该一样不知道么?)
提到说是要进行一番处理,转头翻《iOS编程》,书上把数据存取等的操作,整合在了一个class里的。
他在得到了result数组后,把他直接赋值给了自己的一个属性叫privateItems,也是数组。当外部需要读取就返回这串数组的copy。他的逻辑层,绕了很多。的确,在他的这个privateItems,可以进行对item的数据处理。
不过书上的也很奇怪,他获得这串数组,其中每个对象item,赋值给cell.textlabel.text = [item description]这个方法写在的item的m文件里面,应该属于私有的方法,不应该在外部调用。不知道是不是因为这是重写了父类NSObject的description方法,所以可以这么调用。
写的太多了,也不指望能看完&_&,就一句话,一般的话,如何转化NSObject……
& & 19:19:03 +08:00
@ Core Data 难道不是自动转成 NSManagedObject 的吗? Json 的话可以 Json -& NSDictionary / NSArray -& NSManagedObject ,其中后面一步可以用 Magical Record , 见文档 :
& & 20:19:33 +08:00
@ 说的是提取出来的数据results,不是一串数组么,我用Core data自动生成的storylist类进去遍历,for storylist *story in results,不能打印story.title的属性。所以那位说可能是数组里面的单个对象没转化成storylist的意思。
而不是存进去的时候,存进去的时候,json是一个个转化好了。
现在看看,似乎都用第三方库更简单方便。之前就是想,刚开始学就统统用第三方库,把所有内容全给隐藏,似乎学有点不太合适。(其实还是直接就用上AFNetworking了……)。
& & 20:29:19 +08:00
NSFetchRequest 的 returnsObjectsAsFaults 默认是 YES ,fetch 前设置成 NO 试试。
& & 21:18:37 +08:00
@ 看样子得学版本控制了……复原后,把这个值设置成NO,打印出来的也是一样。
观察了下教程代码,我感觉是NSFetchRequest后得到的results好像都经过了一些处理,比如《iOS编程》里面,itemstore进行提取的,results他放进了itemstore的一个数组属性里面(虽然我感觉没区别……)。
虽然不明白为什么,但是我这样在tableviewcontroller的类调用的时候,得到的数组,直接打印firstObject,是有内容的。
而且和我后面把数据提取放在tableviewcontroller里面,直接打印数组的第一个对象内容是一样的。区别在于把fetchrequest放在后者时,用for in可以打出其中的title属性。
感觉我也只能乖乖的把results处理处理(虽然不太清楚如何处理,继续找参考),或者吧fetchrequest放进要用到的数据里面才好……
anyway,感谢回复,感觉我啰里啰嗦,可能连问题也没解释清楚……
& & 09:02:41 +08:00
@ FMDB比coreData好用? 我怎么感觉这么SQL语言好烦。。。虽然可以带Jsonmodel (本人也是初学者) 我也纠结这两种东西用哪种比较好。
& · & 764 人在线 & 最高记录 3541 & · &
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.0 · 16ms · UTC 18:47 · PVG 02:47 · LAX 11:47 · JFK 14:47? Do have faith in what you're doing.问题小明班上最近月考了,老师大明想要给一部分优秀的同学进行奖励,而另外一部分要进行查漏补缺。大明决定将总分排名前10的,各科成绩排名前10的以及排名最后10名的按从高到低的顺序找出来。以前大明都是在家用笔一个个划出来。不过最近大明在长沙戴维营教育接受了残酷的iOS培训,决定装逼一把,给自己的“肾6+”开发了一款应用。只要各科老师将成绩提交给他,就可以直接看到这些学生的成绩了,并且各种曲线、柱状图、饼图。每个学生的情况就好比没穿衣服一样”透明“。现在的问题是,大明并不想自己去实现各种筛选和排序算法。解决方法很快大明就想到了戴维营教育的博客上Core Data除了简单的存取功能外,还具备各种取数据的方法。一、数据获取Core Data中获取数据必须通过NSFetchRequest进行。我们有两种方式获取NSFetchRequest对象。通过实体名称创建NSFetchRequest对象。这种方式其实就是我们在前面两篇文章中用来获取数据的技巧。NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];//或者NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];fetchRequest.entity =通过模型文件中创建的请求模版创建。//使用managedModel获取fetchRequest模版NSFetchRequest *fetchRequest = [appDelegate.managedObjectModel fetchRequestTemplateForName:@"personFR"];我们可以指定fetchRequest的结果类型来获取不同数据,如存储的对象、结果数目等。//
NSFetchRequest *fetchRequest = [appDelegate.managedObjectModel fetchRequestTemplateForName:@"personFR"];
//如果需要改变结果的类型,不能使用从模版生成的request对象
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
//获取结果总数
fetchRequest.resultType = NSCountResultT不过我们也不只一种获取结果数目的方式。在Context里面提供了一系列的操作request的方法,其中就包括了获取结果数目的功能。NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];//获取结果数目NSUInteger count = [context countForFetchRequest:fetchRequest error:nil];二、筛选结果集大明已经可以得到所有学生的成绩信息了,接下来要做的就是对它们进行排序和筛选。给学生成绩进行排序NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];//排序描述符,按score降序排列NSSortDescriptor *sort01 = [NSSortDescriptor sortDescriptorWithKey:@"score" ascending:NO];//可以同时按多个属性进行排序fetchRequest.sortDescriptors = @[sort01];NSArray *result = [context executeFetchRequest:fetchRequest error:nil];if (result) {
_people = [NSMutableArray arrayWithArray:result];
for (NSObject *obj in _people) {
NSLog(@"%@", [obj valueForKey:@"score"]);
}}结果: 10:54:16.599 02-02-CoreData01[] 99
10:54:16.600 02-02-CoreData01[] 60
10:54:16.600 02-02-CoreData01[] 56
10:54:16.600 02-02-CoreData01[] 45筛选出成绩排名前十的学生NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];NSSortDescriptor *sort01 = [NSSortDescriptor sortDescriptorWithKey:@"score" ascending:NO];fetchRequest.sortDescriptors = @[sort01];//限制只取前十,其实这是有问题的,万一有重复的分数,后面的就取不到了。fetchRequest.fetchLimit = 10;NSArray *result = [context executeFetchRequest:fetchRequest error:nil];使用NSPredicate筛选成绩高于90分的学生NSPredicate *predicate = [NSPredicate predicateWithFormat:@"score &= 90"];fetchRequest.predicate =进阶上面的这些数据获取方式都是同步的方式,如果数据量比较大的话,会显著的影响到程序的性能和用户体验。Core Data中也提供了异步数据获取功能。AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].NSManagedObjectContext *context = appDelegate.managedObjectCNSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];NSSortDescriptor *sort01 = [NSSortDescriptor sortDescriptorWithKey:@"score" ascending:NO];fetchRequest.sortDescriptors = @[sort01];fetchRequest.fetchLimit = 2;//异步请求NSAsynchronousFetchRequest *asyncRequst = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult *result) {
for (NSObject *obj in result.finalResult) {
NSLog(@"%@", [obj valueForKey:@"score"]);
}}];//执行异步请求[context executeRequest:asyncRequst error:nil];注意: 在使用异步请求的时候,需要设置NSManagedContext对象的并发类型,否则会出错。 12:12:50.709 02-02-CoreData01[] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConfinementConcurrencyType context &NSManagedObjectContext: 0x7fb27b72c5f0& cannot support asynchronous fetch request &NSAsynchronousFetchRequest: 0x7fb27b71d750& with fetch request &NSFetchRequest: 0x7fb27b7247a0& (entity: P predicate: ((null)); sortDescriptors: ((
"(score, descending, compare:)")); limit: 2; type: NSManagedObjectResultT ).'解决办法是在创建Context对象的时候,设置它的并发类型。NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];if (!coordinator) {
}//创建Context对象,并设置并发类型_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];[_managedObjectContext setPersistentStoreCoordinator:coordinator];参考资料Core Data异步操作:http://code.tutsplus.com/tutorials/ios-8-core-data-and-asynchronous-fetching--cms-22241Core Data并发操作:http://code.tutsplus.com/tutorials/core-data-from-scratch-concurrency--cms-22131批量更新Core Data:http://code.tutsplus.com/tutorials/ios-8-core-data-and-batch-updates--cms-22164本文档由长沙戴维营教育整理。
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至: 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。怎么用date来排序coredata?_百度知道
怎么用date来排序coredata?
我是用coredata来存储数据的,有两个域,一个是信息Message,另一个是日期date,我想获取10天内发送的信息,怎么用date排序coredata?
我有更好的答案
sortedArray = [unsortedArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@&date& ascending:YES]]];
为您推荐:
其他类似问题
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。

我要回帖

更多关于 使用侦查地图完成 的文章

 

随机推荐