当前位置: 首页 > news >正文

石家庄网站建设电话/市场推广方案范文

石家庄网站建设电话,市场推广方案范文,b2b2c网站开发,网站建设中国的发展来源:bang 链接:http://blog.cnbang.net/tech/3262/ 问题 一般一个 iOS APP 做的事就是:请求数据->保存数据->展示数据,一般用 Sqlite 作为持久存储层,保存从网络拉取的数据,下次读取可以直接从 Sqli…

来源:bang

链接:http://blog.cnbang.net/tech/3262/


问题


一般一个 iOS APP 做的事就是:请求数据->保存数据->展示数据,一般用 Sqlite 作为持久存储层,保存从网络拉取的数据,下次读取可以直接从 Sqlite DB 读取。我们先忽略从网络请求数据这一环节,假设数据已经保存在 DB 里,那我们要做的事就是,ViewController 从 DB 取数据,再传给 view 渲染:



这是最简单的情况,随着程序变复杂,多个 ViewController 都要向 DB 取数据,ViewController本身也会因为数据变化重新去 DB 取数据,会有两个问题:


  • 数据每次有变动,ViewController 都要重新去DB读取,做 IO 操作。

  • 多个 ViewController 之间可能会共用数据,例如同一份数据,本来在 Controller1 已经从 DB 取出来了,在 Controller2 要使用得重新去 DB 读取,浪费 IO。



对这里做优化,自然会想到在 DB 和 VC 层之间再加一层 cache,把从 DB 读取出来的数据 cache 在内存里,下次来取同样的数据就不需要再去磁盘读取 DB 了。



几乎所有的数据库框架都做了这个事情,包括微信读书开源的 GYDataCenter,CoreData,Realm 等。但这样做会导致一个问题,就是数据的线程安全问题。


按上面的设计,Cache层会有一个集合,持有从DB读取的数据。



除了 VC 层,其他层也会从cache取数据,例如网络层。上层拿到的数据都是对 cache 层这里数据的引用:



可能还会在网络层子线程,或其他一些用于预加载的子线程使用到,如果某个时候一条子线程对这个 Book1 对象的属性进行修改,同时主线程在读这个对象的属性,就会 crash,因为一般我们为了性能会把对象属性设为nonatomic,是非线程安全的,多线程读写时会有问题:


//Network

WRBook *book = [WRCache bookWithId:@10000];

book.fav = YES;   //子线程在写

[book save];

 

//VC1

WRBook *book = [WRCache bookWithId:@10000];

self.view.title = book.title;   //主线程在读


可以通过这个测试看到 crash 场景:


@interface TestMultiThread : NSObject

@property (nonatomic) NSArray *arr;

@end

@implementation TestMultiThread

@end

TestMultiThread *obj = [[TestMultiThread alloc] init];

for (int i = 0; i < 100000; i ++) {

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        id a = obj.arr;

    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        obj.arr = [NSArray arrayWithObject:@"b"];

    });

}


解决方案


对这种情况,一般有三种解决方案:


1. 加锁


既然这个对象的属性是非线程安全的,那加锁让它变成线程安全就行了。可以给每个对象自定义一个锁,也可以直接用 OC 里支持的属性指示符 atomic:


@property (atomic) NSArray *arr;


这样就不用担心多线程同时读写的问题了。但在APP里大规模使用锁很可能会导致出现各种不可预测的问题,锁竞争,优先级反转,死锁等,会让整个APP复杂性增大,问题难以排查,并不是一个好的解决方案。


2. 分线程cache


另一种方案是一条线程创建一个 cache,每条线程只对这条线程对应的 cache 进行读写,这样就没有线程安全问题了。CoreData 和 Realm 都是这种做法,但这个方案有两个缺点:


  • a.使用者需要知道当前代码在哪条线程执行。

  • b.多条线程里的 cache 数据需要同步。


CoreData 在不同线程要创建自己的 NSManagedObjectContext,这个 context 里维护了自己的 cache,如果某条子线程没有创建 NSManagedObjectContext,要读取数据就需要通过 performBlockAndWait: 等接口跑到其他线程去读取。如果多个 context 需要同步 cache 数据,就要调用它的 merge 方法,或者通过 parent-children context 层级结构去做。这导致它多线程使用起来很麻烦,API 友好度极低。


Realm 做得好一点,会在线程 runloop 开始执行时自动去同步数据,但如果线程没有 runloop 就需要手动去调 Realm.refresh() 同步。使用者还是需要明确知道代码在哪条线程执行,避免在多线程之间传递对象。


3.数据不可变


我们的问题是多线程同时读写导致,那如果只读不写,是不是就没有问题了?数据不可变指的就是一个数据对象生成后,对象里的属性值不会再发生改变,不允许像上述例子那样 book.fav = YES 直接设置,若一个对象属性值变了,那就新建一个对象,直接整个替换掉这个旧的对象:


//WRCache

@implementation WRCache

+(void) updateBookWithId:(NSString *)bookId params:(NSDictionary *)params

{

    [WRDBCenter updateBookWithId:@10000 params:{@fav: @(YES)}]; //更新DB数据

    WRBook *book = [WRDBCenter readBookWithId:bookId]; //重新从DB读取,新对象

    [self.cache setObject:book forKey:bookId];  //整个替换cache里的对象

}

@end


self.book = [WRCache bookWithId:@10000];

// book.fav = YES;   //不这样写

[WRCache updateBookWithId:@10000 params:{@fav: @(YES)}]; //在cache里整个更新

self.book = [WRCache bookWithId:@10000];   //重新读取对象


这样就不会再有线程安全问题,一旦属性有修改,就整个数据重新从DB读取,这些对象的属性都不会再有写操作,而多线程同时读是没问题的。


但这种方案有个缺陷,就是数据修改后,会在 cache 层整个替换掉这个对象,但这时上层扔持有着旧的对象,并不会自动把对象更新过来:



所以怎样让上层更新数据呢?有两种方式,push 和 pull。


a. push


push 的方式就是 cache 层把更新 push 给上层,cache对整个对象更新替换掉时,发送广播通知上层,这里发通知的粒度可以按需求斟酌,上层监听自己关心的通知,如果发现自己持有的对象更新了,就要更新自己的数据,但这里的更新数据也是件挺麻烦的事。


举个例子,读书有一个想法列表WRReviewController,存着一个数组 reviews,保存着想法 review 数据对象,数组里的每一个 review 会持有这个这个想法对应的一本书,也就是 review.book 持有一个 WRBook 数据对象。然后这时 cache 层通知这个 WRReviewController,某个 book 对象有属性变了,这时这个 WRReviewController 要怎样处理呢?有两个选择:


  • 遍历 reviews 数组,再遍历每一个 review 里的 book 对象,如果更新的是这个 book 对象,就把这个 book 对象替换更新。

  • 什么都不管,只要有数据更新的通知过来,所有数据都重新往 cache 层读一遍,重新组装数据,界面全部刷新。


第一种是精细化的做法,优点是不影响性能,缺点是蛋疼,工作量增多,还容易漏更新,需要清楚知道当前模块持有了哪些数据,有哪些需要更新。第二种是粗犷的做法,优点是省事省心,全部大刷一遍就行了,缺点是在一些复杂页面需要组装数据,会对性能造成较大影响。


b. pull


另一种 pull 的方式是指上层在特定时机自己去判断数据有没有更新。


首先所有数据对象都会有一个属性,暂时命名为 dirty,在 cache 层更新替换数据对象前,先把旧对象的 dirty 属性设为 YES,表示这个旧对象已经从 cache 里被抛弃了,属于脏数据,需要更新。然后上层在合适的时候自行去判断自己持有的对象的 dirty 属性是否为 YES,若是则重新在 cache 里取最新数据。


实际上这样做发生了多线程读写 dirty 属性,是有线程安全问题的,但因为 dirty 属性读取不频繁,可以直接给这个属性的读写加锁,不会像对所有属性加锁那样引发各种问题,解决对这个 dirty 属性读写的线程安全问题。


这里主要的问题是上层应该在什么时机去 pull 数据更新。可以在每次界面显示 -viewWillAppear 或用户操作后去检查,例如用户点个zan,就可以触发一次检查,去更新zan的数据,在这两个地方做检查已经可以解决90%的问题,剩下的就是同个界面联动的问题,例如 iPad 邮件左右两栏两个 controller,右边详情点个收藏,左边列表收藏图标也要高亮,这种情况可以做特殊处理,也可以结合上面 push 的方式去做通知。


push 和 pull 两种是可以结合在一起用的,pull 的方式弥补了 push 后数据全部重新读取大刷导致的性能低下问题,push 弥补了 pull 更新时机的问题,实际使用中配合一些事先制定的规则或框架一起使用效果更佳。


总结


对于 APP 缓存数据线程安全问题,分线程 cache 和数据不可变是比较常见的解决方案,都有着不同的实现代价,分线程 cache 接口不友好,数据不可变需要配合单向数据流之类的规则或框架才会变得好用,可以按需选择合适的方案。



http://www.lbrq.cn/news/843463.html

相关文章:

  • 深圳住房和建设局网站认租申请/软文网站发布平台
  • 怎么给自己做网站吗/seo会被取代吗
  • 全屏响应式网站模板/个人开发app可以上架吗
  • 外贸大型门户网站建设/自己开网店怎么运营
  • 东莞市永铭装饰有限公司/搜索引擎优化服务公司哪家好
  • 菜单宣传网站怎么做/网络广告推广方法
  • wordpress网站如何添加栏目/模拟搜索点击软件
  • 南阳专业做网站公司/百度搜索指数排行
  • 做一个微网站平台/百度推广业务员
  • 有服务器了怎么做网站/apple私人免费网站怎么下载
  • 帝国cms建站实例教程/百度快照客服
  • 分类信息网站做淘客/seo资源网站排名
  • 营销型网站建设制作推广/市场调研方案
  • 在线做漫画网站/上海网络营销有限公司
  • 白帽网站/seo搜索引擎优化推广
  • 成都网站建设公司好做吗/搜索引擎营销的原理
  • 新郑整站优化/免费自己建网站
  • 我做的网站怎么是危险网站/网站seo关键词排名推广
  • 上海网站关键词排名优化报价/长沙百度快速排名
  • 网站建设企业站模板/个人网上卖货的平台
  • 个人博客网站制作教程/seo关键词排优化软件
  • 营销网站/百度seo关键词怎么做
  • 网站商业授权/软文广告经典案例
  • 开发网站需要多少人/百度软文推广怎么做
  • 响应式网站怎么做/俄罗斯搜索引擎入口
  • 没有基础学做网站/sem和seo是什么职业岗位
  • 北京做百度网站建设/中视频自媒体平台注册
  • 动态网站系统的5个组成部分/深圳整站seo
  • 湖南网红网站建设有限公司/最经典的营销案例
  • asp网站免费完整源码/药品销售推广方案
  • AI问答-Token:在人工智能领域,Token 是模型处理文本的核心单元 / 最小可处理片段
  • AI辅助Python编程30天速成
  • 基于多智能体强化学习的医疗检索增强生成系统研究—MMOA-RAG架构设计与实现
  • 纯CSS轮播
  • 大模型-量化技术
  • 设计模式之工厂模式:对象创建的智慧之道