今天回来补下10月的部分,简书9月10月的时候没法写文章,并且这段时间赶进度天天加班,所以留下了很多天没有更新过,大致记了下部分问题,慢慢补上去。
19.10.08
构建环信好友体系
环信自有的体系无法很轻易地扩展我们的业务,主要体现在主键关联、业务姓名、业务头像、好友关系、群关系等等,需要我们自己去设计一套关联方案使得环信体系与我们业务建立联系。
关联对后端来说比较容易,对两者主键进行关联即可建立两个对象间的联系,但对前端来说,要考虑一个实时更新的问题,即有更新则需尽可能快速反应到页面上,例如好友更新了一个姓名,在聊天列表中我希望尽可能快地获取到这个信息,再例如群里加入了一个人,我需要立刻就了解到这个人在我们业务中的信息。这类更新往往是一个被动知道的状态,即更新者不会也不可能进行广播消息,我们能获取的途径有两种,第一种像服务器请求这个人的最新信息,第二种通过环信消息系统。第一种的劣势很明显,因为是被动状态请求时机无法获知,同时会对服务器产生负担;第二种前面已经说了不可能进行广播,所以需要把握更新的时机。
目前项目中我们的设计如下(分为2步):
1.打开App时向服务器请求数据,进行一次好友更新维护,并同步到本地数据库。这个步骤有两个目的:a.断网状态保证能正常显示数据;b.处理无法实时更新时消息列表信息显示的问题。这个保证了历史状态(你更新的这一刻)最新。
2.每次发送消息时带上自身业务的识别信息放在ext中,当ext内容与缓存模型不一致时,更新缓存模型,并同步到数据库。我可能不知道群中新加入发言的那个人他在我们业务中是谁,但他肯定知道他自己是谁。发送者知道自身是谁,基于这点我们可以解决实时更新的问题,只要这个人给我发送消息,我就知道这个人是谁,在App运行中去实时更新这个人的状态。但他的弊端也是有的,就是无发言时我们无法更新其状态,这时就需要1来去解决这个问题。这个保证了实时状态的最新。
上述1和2的结合也会存在部分空档期:App运行阶段消息列表中某个人更新了状态并且未向你发送消息,那么他的状态更新则会推迟到下一次App启动。
19.10.09
外部业务如何并入现有业务线
这里还以环信的场景为例,在会话列表中,我们维护的不单单只是环信的一组会话列表,还有我们自有的业务场景,例如系统消息、红包信息快捷操作等等,当这些业务和环信这种第三方业务混在一起时我们应该如何进行操作?
操作前要明确一点,任何其它业务和自有业务的混合都应该是以自有业务为主,其它业务为辅,也就意味着如果强迫一方做出改变,那么改变的一方不应该是我们的业务体系。
我们需要考虑两点,一是方式二是时机。
方式指的是我们如何搭建一座桥梁,让三方业务通过这座桥梁并入到自有业务体系中,而又不影响二者的内在逻辑。比较好的方式就是模型的组合并入。以开头的场景来说,环信自有模型为A,我们自有业务模型为B,为了使A和B达到并线并统一外在,可以使用模型C,C中引用A或B,对外统一用模型C操作逻辑,区分则以引用的A或B的存在为条件。这样一来不改变也不打破原有的业务体系,通过组合包装,使得两个体系并入在一起。
时机指的是我们在何时进行并入操作。一般来说,这个时机是产生模型并且没有进行处理的时刻。还以上述场景为例,当我调用环信的方法拿到所有的会话列表时,用模型C包装原始模型,包装完毕之后环信的业务线就需要开始并入到我们自有的业务线中(也就是整合模型列表),此时环信业务停止,开展自有业务。业务停止并不意味着不再管环信的业务,而是以自己业务的模型去处理,不再以环信的模型处理其业务。
19.10.10
继承环信好友头像和姓名的更新
这部分三端讨论了不少时间,在之前的设计中又进行了一些优化和改进。
主要数据:好友群组等信息
次要数据:群成员头像姓名映射信息
1.本地数据库作为数据支撑。每次使用App时都会同步一遍主要数据(数据同步点1,主动),同步完成后进行增量更新,随后释放内存,每次取值从数据库中拿出。(此处数据库是做了优化,拿出过的值在一定时间内会保存在内存中,避免了反馈存取);
2.发送消息携带此刻此人的信息(数据同步点2,被动)。由于App只更新了一次数据信息,所以在App运行的过程中,需要合适的点去进行数据库的更新和同步,这个点就在信息的传递时机,例如收发消息,推送等等;
3.单个信息源查询(数据同步点2,被动)。当出现遗留数据未同步到数据库时,便调用单个信息源查询接口,查询单个信息,保存在数据库中。
19.10.11
Realm主键问题
createOrUpdateInRealm:withValue
这个方法作为增量更新方法的使用前提是需要指定了主键才能使用,如果不指定则会报错。
一个场景:每个用户在每个群中都有不同的昵称,如果我不想做多表关联如何使用这个方法?群ID或者用户ID此时并不是唯一,并不能作为主键使用。那么我们如何保证主键唯一性?可以使用群ID和用户ID拼接起作为主键,群ID和用户ID拼接之后值唯一,可以作为增量更新的依据。
19.10.14
后台返回的数据类型
不同的接口返回类型可能会有很多种,我们目前返回类型各种各样,并没有统一的界定。前两天的一次改动让我认识到这里的返回类型需要做一个统一的规定:全部使用字典类型。之前的一个接口由于简单,只需要一串id,后台直接把id抛了回来,以至于后期加字段时必须要重新改变数据结构或者重新写接口,因为当前的字符串类型无法进行扩展,这就大大增加了时间成本,对已经上线的App来说增加了不必要的风险。修改数据结构对于后台和前端来说都是非常不友好的一件事,不到万不得已不应该去修改结构,那么从一开始就使用最容易扩展的结构才是正确的选择。
19.10.15
layer.borderColor处在当前视图的最上层
有如下代码:
UIView *backView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 150)];
backView.backgroundColor = [UIColor lightGrayColor];
backView.layer.borderColor = [UIColor redColor].CGColor;
backView.layer.borderWidth = 5;
[self.view addSubview:backView];
//
UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 100, 100)];
maskView.backgroundColor = [UIColor yellowColor];
[backView addSubview:maskView];
达到的效果如图所示:
可以看到这么一个现象,父视图A如果给了border,那么在当前这个父视图上,此border的层级为最高,任何加载到A上的子视图都无法遮挡border。
那么子视图的border是否遵循层级关系呢?我给子视图加上border,效果如下:
可以看到,子视图的border并没有因为层级关系处在上层而遮挡到父视图的border,父视图的border依旧在层级最高位。
那么单纯的Layer是否会影响到呢?猜测是不会,因为view其实操作的就是layer,为了严谨一点还是测试了下,结果是不会影响。
可以看出,border并没有遵循普通的视图层级关系,父视图的border总是凌驾于当前加到这个父视图的所以视图之上。如果想遮挡到这个view的border,就要从他的父视图入手,代码改为下面这样:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 150)];
[self.view addSubview:view];
UIView *backView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 150, 150)];
backView.backgroundColor = [UIColor lightGrayColor];
backView.layer.borderColor = [UIColor redColor].CGColor;
backView.layer.borderWidth = 5;
[view addSubview:backView];
//
UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 100, 100)];
maskView.backgroundColor = [UIColor yellowColor];
maskView.layer.borderWidth = 5;
maskView.layer.borderColor = [UIColor greenColor].CGColor;
[view addSubview:maskView];
还有一个比较值得注意的地方,borderd的宽度是往内扩展的,我们打开层级关系很容易看到这个:
19.10.16
iOS13之后persent默认不再全屏
iOS13之后的persent默认是分页形式而不是全屏形式,相比全屏的模式,分页模式下即使没有dismiss的按钮,下滑操作同样可以触发这个效果,也就是用户可以随意关闭这个弹出的页面,这对于一些需要强制用户选择的地方产生了一些问题,而且分页模式下,多个弹窗会像卡片一样错层叠加状态,越上层的越往下偏移,这和UI上的差距也有点明显。
对于改动来说,一开始我想在基类中重写persent的方法,但由于persent的模式和push执行又不太一样,有点问题,所以舍弃了,目前我在基类VC中写了一个自定义的persent方法,在这个方法中去判断系统版本,进行全屏化设置。
19.10.17
梳理不同枚举下页面的展示和操作问题
枚举一旦过多,如果组织不好代码便会非常冗杂。如何组织这些代码让枚举即使多样也不会产生混乱?我觉得主要从以下几步操作:
1.提取不同和相同
枚举的意义在于区分不同的状态,那么提取不同部分就是首要的任务,利用switch case区分不同。对于相同的部分,统一写好接口进行处理;
2.组织流程,让分支分开与汇流
每种页面都有其不同的业务流程,如果把这个业务流程比作一条水流,那么枚举就好像一个又一个的石头,切开这部分水流,然后再次汇到了一起。我们页面的流程也是这样,分支判断语句负责分流然后汇总。
现在的项目里有一个文章发布页面,这个页面的业务流就是发布填充到页面上的数据,根据上面的方式,我们要做的像下面这样:
1.由于存在正常、草稿、审核修改三种枚举状态,那么这里的主要不同存在两处,一处是数据源,另外一处是在执行返回操作时是否保存操作,剩下的例如UI布局、文本校验、上传文件等等操作都是相同的;
2.整个业务流程来说,像如下分部:a数据流入->b整合数据 ->cUI基本交互 ->d发布或保存,我们根据枚举,a和d是主要的分流部分,那么对a我们进行三种数据流入的区分,区分之后把数据汇入到b进行整合,在d处再次进行分流,根据不同的枚举进行操作(例如修稿草稿类的把当前草稿进行删除),最后汇集到发布或者保存草稿的状态。
19.10.18
重用状态下对不可重用模型的依赖
tableview的重用机制使得无法利用自身去标记事件,需要借助外界不可重用的对象,我们比较常用的就是Model对象,若干个Model放在数组中对应若干个cell。
由于cell本身是重用的,在需要特殊标识时无法通过自身的去进行判断,必须要借助model这类不重用对象进行标识。例如我在cell上展开了一个下拉图,如果不进行标识,那么在重用机制下,滑动到上面或下面某个区域时这个cell会再次被重用到,那么这个下拉图还是原来的状态。因此基于重用机制的控件的处理必须要用非重用的模型进行标记处理。
19.10.21
Lottie兼顾性能作出无损效果的动画
像上面的这种动画,打开文件可以看到这个gif是由141张静态图片组合成的,直接使用Imageview的动画组或者第三方去加载gif的话是非常消耗资源的,甚至在一级界面UI常驻的情况下,gif会大量占用内存而且无法释放掉,很容易会收到内存警告。一些情况下,gif的加载甚至比视频文件还要占用资源。
Lottie是一个支持多平台的动画库,使用json文件配合素材实现动画效果,Lottie的动画核心基础是CAKeyframeAnimation 关键帧动画,因此它的优势在于对内存资源的占用非常小,通过GPU和很少的CPU进行调度。它的制作也非常简单,UI使用AE等工具绘制好动画之后直接导出json文件即可,基本无损还原动画。
官方Git:https://github.com/airbnb/lottie-ios
官方Git目前没有提供OC版本的,我在网上收集了个:https://www.jianshu.com/p/b2c0d1109c4d
需要注意一点的是,虽然Lottie对内存消耗的很小,但是会占用一部分CPU,如果动画一直循环,则CPU的占用无法停止,多个动画则会产生占用上的叠加,虽然CPU在没有满负荷的时候并不会产生明显的感觉,但长时间使CPU处在高位有一点会明显,那就是手机会发热,这点在动画上可能并不是很明显,之前做视频格式转换使用FFmpeg时非常明显(占用CPU进行转换),1分钟90%左右的占用会使手机明显升温,耗电量明显加速。
19.10.22
列表加载大图对内存的影响
在做一个列表的时候,我发现滑动时候产生了卡顿,前两页还好点,当我加载的越多,卡顿越明显,看了一下内存也是在嗖嗖嗖地网上涨,滑个七八页的时候就收到了内存警告,排除了其他原因,最后锁定到了加载的图片上,这里出了问题。我把要加载的链接放到浏览器里,好家伙,基本都是1M以上,我这一梭子滑下去就要加载20M+,连续滑来滑去那还受得了。
之前对加载大图的认知是放在了查看高清大图上,先把图片下载到本地,再以引用的方式加载,防止内存崩溃掉。在tableview上倒是很少注意,因为一般上传的图片也经过了压缩,不会出现很大的图片,这次的图片不知道是怎么弄上去的,应该是某一端没有做限制,导致传的图片比较大。因为图片目前都是在OSS保存着,所以解决这个问题也很方便,直接对图片进行fill裁剪,裁剪出ImageView三倍的宽高即可。
我用XR测试了一下,在列表页面想要保持流畅的滑动,加载的主图(头像之类小的不算在内)个数应该小于6个,每个大小保持在200KB以下,副图比如头像之类的,大小最好控制在50KB以下。
如果没有使用OSS这类的存储,对于这种情况需要强制输入端限制上传文件的大小,对于已经存在的大图,可以采用下载的方式,统一写一个方法,对下载好的图片,大于500kb的进行裁剪压缩处理,加载压缩过的图片。
从这里也可以看出OSS这类云端存储的一些优势,对于图片的各种裁剪提供了很多便利的方式,包括对圆角的处理也很方便,如果后期能加上一些滤镜效果的话就更棒了。
19.10.23
HUD的二次封装问题
今天发现之前封装的HUD似乎有点问题,多网络请求时没法消失。
改进了一下,设计了两个方案:
1.加载到window上,全局保持一个HUD单例,只要记得hidden就不会出现不消失的情况,但这种情况的劣势也非常的明显,那就是HUD出现时全局无法操作,所以这个暂时抛弃。
2.加载到某个VC上,这个VC上最多只有一个HUD对象。这个方案比加载到window好很多,左滑手势可以生效,还可以返回上级页面不至于卡住。在设计上我直接在baseVC中加入两个方法,showHudWithText:和hidden。show方法中会懒加载一个hud(被属性指针持有),保证这个VC只拥有一个hud对象,不会反复alloc多个。这样一来只要show和hidden成对出现,就能保证这个唯一的hud对象的消失。
另外还有若干的方案,例如和网络请求绑定、用单独的工具类持有等,前者适用范围过窄,后者是一个不错的方法。这个问题其实只要保证操作对象hud是一个唯一的就可以(至少是当前唯一),才不会出现并发问题。
19.10.24
数据流入阻塞
这个是最近测试时候的问题,在之前ERP项目中也会存在。
流程1-10,要测试的部分是789,但由于这样或者那样的原因,导致流程卡死在5,并且短时间内无法解决。目前的项目中很多次出现了这样的问题,为此浪费了大量的时间。由于前期后台的业务并没有梳理清晰,每次做出改动都影响到了前期的流程,一旦影响,那么流程直接卡死后续的无法测试。
回顾一下之前的ERP项目,流程也是非常的长,每个节点的关联性也非常地强,但阻塞流程的情况却很少,后台的老哥们确实做出了很大的贡献。当时我们的后台管理比较严格,建表命名关联都是由同一个人在操作,其他人去写逻辑,这保证了基础框架的稳定,同时在数据上不会出现过多的冗余。而目前的工程,后台对于核心部分的管理过于分散,每个人都可以操作核心业务,这使得当前的代码十分混乱,每个人都写自己的一套代码,没有在一个统一的框架和约束下,使得代码各种冗余复杂,牵一发动全身。另外之前的项目中我们自动化测试引入的非常早,随后一直保持了与Master分支一致的迭代,这使得我们再效率上大大提升,主要体现在快速校验主流程已提测接口、快速创建可用数据、快速模拟异常情况等等。而目前的工程来说,由于后台的接口不稳定,修补频率过高,引入自动化测试可能出现加重工作负担的情况,所以也暂时搁置一边。
目前这个问题,估计后台需要对主流程的代码进行重构才能解决。
19.10.25
给collectionView添加个头视图
collectionView和tableview不同,collectionView是没有一个独立的头视图的,但是我们可以自己造一个出来。看了网上的几个方案,我整理出了一个比较好的方案措施,并且已经实际进行了验证,并且这个效果对继承与scrollView的视图通用。
我们知道,scrollView有一个属性是contentInset,在这里能起到布局起始位置的作用,例如下面这样会向下偏移150进行布局:
_myCollectionView.contentInset = UIEdgeInsetsMake(150, 0, 0, 0);
这样在这个collectionView起始位置就预留了150高度的位置,但是按照正常布局来说,设置的frame是会从150以上进行布局的,这个时候就需要设置负值来使其在150以内的区域,像下面这样:
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, -150, self.view.width, 150)];
[self.myCollectionView addSubview:headerView];
需要注意一点,采用这样方式之后,mjheader之类的刷新控件位置需要重新设置不然会被遮挡。
19.10.28
处理非一般的App启动三方数据登入问题
一般来说,第三方登入或者绑定都会在首页这个位置进行操作,手上有一个app比较特殊,是根据角色来区分App内tabbar上的Item的个数,也就是这个App可能不存在首页这个概念,但当时没有注意这个问题,还是按照普通App的模式,在首页进行第三方登入和信息的绑定,在提测时遇到了IM消息接收不到,极光推送无法到达的情况。那么现在就面临了一个问题,并没有一个确定的业务流汇总的页面,如何处理三方数据登入问题呢?
既然找不到汇总点,那么就从分支点进行处理:
例如登录成功之后,跳转到A或B或C页面,那么这个分支点就可以一次三方数据登入的操作,先执行登入再执行跳转,这是登录流程的处理;正常打开App时,也去找这个分支点,例如在AppDelegate中,通过已登录用户的权限来进行判断时先进行三方登录操作。
这种App的流程和一般正常的流程有些差别,正常的流程是在汇总点处理事务,而上面这种则是在分支点处理事务,由于分支点有多个,所以要处理多个地方。总之,对流程把控来说,分支点和汇总点往往是处理事务比较好的切入点。
19.10.29
git stash
stash是git一个非常好用的命令,不过开发的时候发现同事用的并不是很多。这个命令的作用是将当前的变更保留在一个独立的区域,清空当前的工作环境。一个比较实用的场景:我在某个分支上写了一些代码,但突然来了一些紧急需求,需要切换到另外的分支去处理事务,这个时候就可以用git stash命令将当前分支的更改保存到独立的区域,然后切换到需要处理的分支,待需要处理的分支处理完毕后,切回一开始的分支,应用之前stash保存的变更即可。
19.10.30
整合自身数据源和三方数据源
这里指的是拖入第三方的数据源与自身的数据源由于模型的不同,如何进行一个数据源的同步,在各种操作之后还能保持一致性。这个问题在之前环信处理的时候已经提过一次,进行数据源的并线,至于采取自有向第三方并线还是第三方向自有并线,要看其规模,如果自有的model涉及面非常小,可以向第三方并线,举一个最近的例子。
在文章发布VC中,我需要一组图片进行发布,这里的图片视图页面是个第三方控件,用的模型是B,这个发布VC中我可能出现输入型数据源,即草稿带入或者审核驳回之后服务器带入,这个时候我需要处理模型的差异,假设自有模型为A,三方模型为B,由于模型A涉及面非常小,这个时候为了简化设计我们可以直接向三方模型B中组合A,即B中持有一个A,在输入数据源时创建A的同时创建B持有A,这样共用一组数据源,从输入到UI操作到提交,数据源都保持一致性。
当然,从自有模型到三方数据源中的模型大多还需要进行一次转换才可以直接被使用,这里我选择的是遵从三方原始的赋值方式,例如这个图片拖拽页面三方用的是Image赋值,由于有被驳回的情况存在,我的模型可能出现只有url的场景,这个时候我采取的是url下载转化成Image,而不是在其源码中进行修改用url赋值,这也遵循了封闭原则。
19.10.31
多参数接口统一外化参数模型
最近在整理一些之前的代码,在整合部分接口的过程中发现接口的参数会出现很多问题:不是多了就是少了,改动接口参数之后全局的接口都要相应地区调整。这个问题在对某个视图赋值的时候比较常见,比如上个版本我只需要显示加入的人数,接口中我只预留了peopleNum这个参数,但在下个版本中又需要添加活动时间的显示,这样一来就需要对接口进行改动,在开放封闭的原则下这显然是设计有问题的。
对于这种UI类赋值的接口来说,考虑到后期的扩展情况,接口在设计时使用某个类的模型对象作为参数显然是更合理的,后期的扩展不需要更改接口本身。
而对于工具类的接口来说,接口在设计之初,所需的参数和返回值便是可以确定的,这类接口反而不应该使用可变的外化模型作为参数,而是使用确定参数,更加明确接口要表达的含义。并且作为工具类的接口,所需的参数应该是最直接不需要二次加工的参数,除非是泛型或者反射机制,否则不应该在方法的实现中再去判断参数类型再去进行转化分类。
19.11.1
collectionView flowLayout剩下一个Item时自动居中问题
系统默认的flowlayout在某个分区仅存在一个item存在时会自动将其居中,需要手动修改改变其位置,为了修正这个问题我写了个layout继承自flowlayout,
在.h文件中我预留了一个距左边的距离参数leftOffest
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray <UICollectionViewLayoutAttributes *>*attributes = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *att in attributes) {
if (att.indexPath.row == 0) {
att.frame = CGRectMake(self.sectionInset.left + self.leftOffest, att.frame.origin.y, att.frame.size.width, att.frame.size.height);
}
}
return attributes;
}
一开始我以attributes.count == 1的方式来判断仅剩下一个item,后来发现这个现象在每个分区都会存在,所以直接用row == 0去判断。
需要注意的一点是:在实际操作中我发现这个方法不单单对item生效,对分组的头和尾也会生效。
19.11.4
NSDateFormatter对性能严重消耗
NSDateFormatter这个对象非常消耗性能,之前测试过,初始化时消耗的时间远远高于设置日期格式时的时间。
在静态页面上这个消耗可能在体验上不是很明显,但在tableview或者collectionView上面就非常直观的感受到了这个卡顿现象。
如何处理这种初始化消耗性能的对象在滑动视图上的问题呢?
对于这种初始化时消耗大量资源的对象,单例无疑是一个非常好的选择,单例初始化一个对象,全局使用,减少了不必要的消耗。
上面的初始化性能消耗是可以避免的,NSDateFormatter在使用时stringFromDate/dateFromString这两个方法也很消耗性能,但这里似乎无法做出好的优化,只能尽可能地做缓存,减少调用。
项目里有一处是显示发布时间,需要根据时间戳和当前时间的对比进行两次转化:时间戳->真实时间->当前需要展示的时间方案。由于是在滑动视图上,我做了如下的优化方案:
1.单例保持NSDateFormatter对象的初始化;
2.在视图滑动停止时开始进行时间转换操作,滚动时不进行操作;
2.在模型中加入realDate字段,转换后的要展示的时间进行一次缓存,有则直接从缓存中取,不再进行stringFromDate的操作。
之前考虑过一个方案,就是异步处理这些耗时操作,但在cell重用状态下,异步操作反而会出现更多异常的情况,所以暂时只在滑动停止时进行了处理,这里还需要研究一下。
19.11.5
带返回参数的block和带block参数的block
有空梳理下,老是把这两个弄混。。。
1.带返回参数的block,即时回调结果,像下面这样:
typedef CGFloat(^speedBlock)(CGFloat x);
@interface ViewController ()
@property (nonatomic,copy) speedBlock speed;
@end
self.speed = ^CGFloat(CGFloat x) {
return 20 + x;
};
CGFloat result = self.speed(10);
NSLog(@"%f",result);
很简单,打印出30,这种方式一般用的多一点,用于 【即时】 回调出结果的情况。
2.带block参数的block,用于执行某个结果,像下面这样:
typedef void(^numActionBlock)(void(^)(NSInteger num));
@interface ViewController ()
@property (nonatomic,copy) numActionBlock numAction;
@end
NSInteger startNum = 123;
self.numAction = ^(void(^act)(NSInteger num)) {
act(startNum + 2);
};
self.numAction(^(NSInteger num) {
NSLog(@"%ld",num);
});
最简单的对比来说,在使用场景上,第一种block适用于即时返回计算值或者比较结果的场景,偏向于即时“结果”的获取,获取之后直接在当前函数方法中使用,,例如下载进度、快速枚举等;第二种block适合执行某个操作的场景,同时也可以控制回调执行时间,例如延迟执行某个方法时第一种block显然不合适。
19.11.6
无网络时缓存点赞等网络请求
产品有个需求是用户在无网络状态时的点赞操作缓存起来,等待有网络时统一提交给后台。
这个需求的路子绕的有多远,整理下抛给产品:
先说下正常流程:
1.在无网络状态下,保留一个请求缓存的队列,里面保存着每组请求接口和参数,当有网络时异步进行请求处理;
2.假如现在有20个操作需要同步到网络,那么这20个请求如何进行处理?并行or串行?如果某个失败了是进行轮询还是放弃请求?
异常流程:
1.目前我们的请求全都是校验token的,token超时如何处理?客户端本身是不知道token超时的,需要发出请求之后给予回应中;
2.产生错误返回时是否屏蔽错误信息?正常看来肯定不能莫名其妙突然弹出一个“文章已被删除”之类的信息;
3.若产生失败,但本地数据又和服务器数据不同步,以哪一方为准?
抛给产品,估计最后这个需求不了了之。。。
19.11.7
正常超时和异常超时
正常超时指的是客户端发出请求超出一定时间后服务器无任何响应操作,此时服务器无响应,之后的一段时间服务器也无响应,异常超时指的是客户端达到一定时间后认为其超时,但在若干时间后服务器给出了应答。简单地说,超时时间给的太短,但为了不影响用户体验,一般来说我们都会给个15s左右的超时限制,太长的时间未响应本身就是程序上的问题。
如何避免这类情况的发生?我们从事件发生原因来看,造成这种现象的原因无外乎是服务器进行了复杂耗时的操作,常见的比如多表查询、下载上传资源文件、服务器等待另一服务端响应等等。
1.多表查询。后台在表结构设计上需要下点功夫,尤其是ERP类型的业务,业务关联性非常强,前端发送请求时可以适当增加参数辅助后台快速查询;
2.上传下载资源文件。这个本就不应该在服务器上进行操作,耗费服务器资源,包括用服务器进行视频转码操作,这些都应该避免,资源文件的操作尽量采用第三方云端服务,例如阿里OSS,七牛云等;
3.服务器等待另一端服务器响应。这种一般是进行了跨服务器业务,同样地,这个业务如果简单完全可以在前端进行操作,但如果涉及安全方面则必须要有自己的服务器操作,这个时间是无法避免的,而且协调起来不那么容易。一个比较妥协的方案就是异步无限期等待,发起之后就异步挂起,响应回来之后全局弹出。
19.11.8
涉及UI类第三方库尽量使用拖入工程的方式
TZImagePickerController这个相册选择的第三方相信很多人都用过,我们这个工程中也不例外,集中使用了pod管理。由于部分UI需要高度自定义,所以直接就在源代码中进行了修改,为了防止有误操作导致版本变更,强制指定了固定了版本。但即使这样,还是出现了一些问题,(git对pod操作的一些忽略,兼容13迫不得已需要升级三方版本等等),最终不得不重新将其拖入工程中进行修改,不再使用Pod管理。
对于这类UI类型的库,如果预见后期需要对UI有各种改动,比较稳妥的方式就是拖入工程而不是使用pod管理,一旦要修改库的源代码,使用pod管理反而会增加很多麻烦,误操作被新版本覆盖时甚至丢失代码。但拖入工程的方式也有个很大的弊端,就是系统产生兼容性问题时需要手动修改。相比较来说,拖入操作更可控一些,出了问题也很容易定位到。
19.11.11
带上梯子让终端pod飞起
pod的操作越来越慢,没有梯子往往需要等待很久最后给你来个超时。之前保存的那个链接打不开了,手动记录一下方式:
1.你要有个梯子,找到本地socket5地址;
2.在终端输入一下命令,强制终端走梯子:export https_proxy="本地socket5地址"
3.这个命令只对当前的终端窗口有效,关闭则需要重新设置。
19.11.13
反射模式使问题复杂化
目前的项目中多次用model进行cell的反射,由于要依靠Model,产生了很多“无用”状态的Model,仅仅是为了反射到对应的cell,这类model文件数量还不算少,造成了文件的冗余。
回到为什么使用反射模式的问题上,反射能消除分支节点的if...else...判断,加强代码的扩展性,保证了开放封闭的原则,在会出现大量if...else...的地方使用效果很好,但对于确定性的比较少量的分支节点判断,使用反射模式反而会使得问题有些复杂化,主要就体现在上面这种情况,搭建反射的资源过度冗余,对于这类问题以后要注意。
19.11.14
persent和push的执行
persent和push在代码执行上有着差别,persent是逐行执行,push是有点类似于FMDB的事务提交方式,把这部分都设置完成之后才处理。
有下面代码:
- (void)action {
QViewController *vc = [[QViewController alloc] init];
[self presentViewController:vc animated:YES completion:nil];
// [self.navigationController pushViewController:vc animated:YES];
NSLog(@"222");
}
在QVC的ViewDidLoad中我打印了111,上述代码先打印111,后打印222。改变代码如下
- (void)action {
QViewController *vc = [[QViewController alloc] init];
// [self presentViewController:vc animated:YES completion:nil];
[self.navigationController pushViewController:vc animated:YES];
NSLog(@"222");
}
上面代码先打印222,后打印111。上面简单的例子已经足以说明present和push执行时在生命周期上的区别:对于present的VC,在present代码之后再赋值就已经迟了,而push则不会。
19.11.15
今天开始需要周六上班来延长春节假期了,工期比较赶暂时休整一段。