专栏名称: 知识小集
目录
相关文章推荐
51好读  ›  专栏  ›  知识小集

「 iOS 知识小集 」2018 · 第 24 期

知识小集  · 掘金  ·  · 2018-08-07 23:38

正文

阅读 7

「 iOS 知识小集 」2018 · 第 24 期

上周公众号发布的以下文章:

本期知识小集的主要内容包括:

  • 再谈数组、集合、字典与 hash、isEqual 方法的关联
  • 使用 Keychain 存储登录态需要注意的一个坑
  • 给 UIView 添加阴影
  • 比较三种网络框架上传图片过程中的不同点?
  • 通过 runtime 控制导航栏的 hidden 属性
  • 关于 IAP 丢单的处理

再谈数组、集合、字典与 hash、isEqual 方法的关联

作者 : halohily

我们或多或少了解,Objective-C 中的 NSArray、NSSet、NSDictionary 与 NSObject 及其子类对象的 hash、isEqual 方法有许多联系,这篇小集讲一下其中的一些细节。

NSArray 允许添加重复元素,添加元素时不查重,所以不调用上述两个方法。在移除元素时,会对当前数组内的元素进行遍历,每个元素的 isEqual 方法都会被调用(使用 remove 方法传入的元素作为参数),所有返回真值的元素都被移除。在字典中,不涉及 hash 方法。

NSSet 不允许添加重复元素,所以添加新元素时,该元素的 hash 方法会被调用。若集合中不存在与此元素 hash 值相同的元素,则它直接被加入集合,不调用 isEqual 方法;若存在,则调用集合内的对应元素的 isEqual 方法,返回真值则判等,不加入,处理结束。若返回 false,则判定集合内不存在该元素,将其加入。

从集合中移除元素时,首先调用它的 hash 方法。若集合中存在与其 hash 值相等的元素,则调用该元素的 isEqual 方法,若真值则判等,进行移除;若不存在,则会依次调用集合中每个元素的 isEqual 方法,只要找到一个返回真值的元素,就进行移除,并结束整个过程。(所以这样会有其他满足 isEqual 方法但却被漏掉未被移除的元素)。调用 contains 方法时,过程类似。

因此,若某自定义对象会被加入到集合或作为字典的 key 时,需要同时重写 isEqual 方法和 hash 方法。这样,若集合中某元素存在,则调用它的 contains 和 remove 方法时,可以在 O(1) 完成查询。否则,查询它的时间复杂度提升为 O(n)。

值得注意的是,NSDictionary 的键和值都是对象类型即可。但是被设为键的对象需要遵守 NSCopying 协议。

使用 Keychain 存储登录态需要注意的一个坑

作者 : KANGZUBIN

今天要讨论的这个问题你可能永远都不会遇到,而且绝大部分情况下你很难在开发中事先预料到它未来可能会发生,但是一旦不幸发生了,可能就是一个很严重的线上问题,惨痛教训。

我们通常会在 Keychain(钥匙串)中存储一些密码、用户登录态等敏感数据,一是可以提高保存数据的安全性;二是当用户卸载 App 后重新安装,可以自动登录保留上次的登录态;三是同一开发者账号下的不同 App,如果是采用同一套账户体系,就可以通过 Keychain Groups 共享登录态。

我们的 App 之前都是只把用户的登录态保存在 Keychain 中,并在 App 启动时去读取它,这一直也都没什么问题。前一段时间我们的 App 由于业务合规的原因审核被拒,按照苹果的要求不得不把 App 从公司的 A 开发者账号转让到 B 开发者账号下(公司旗下有很多不同主体的开发者账号),转让过程很顺利,但发版后短时间内收到大面积的用户反馈说,更新新版本后提示“登录失效,需要重新登录”。

原因很容易就可以猜到,App 从 A 转让到 B,就无法读取保存在 A 账号下的 Keychain 数据了,用户更新版本覆盖安装后,打开 App 也就无法获取之前的登录态了。

而且对于这种已经发生的问题,我们似乎也没有什么有效的补救措施,临时加急再发一版似乎也解决不了问题,因为之前的 Keychain 数据就是读取不到了,总不能再把 App 转让回去吧,😂

那么如何未雨绸缪预防以后再发生这种因为转让 App 导致存储在 Keychain 中的登录态丢失读取不到呢?(虽然出现转让 App 的概率非常低)

我们在新版本中采用了一种兼容的方法:把用户的登录态同时加密存储在本地缓存(Sandbox)和 Keychain 中,在 App 启动时,优先从 Keychain 中读取,如果 Keychain 中取不到,就从本地缓存中取(然后再把本地缓存的同步到 Keychain 中,因为即使 App 转让了,用户更新版本覆盖安装后 Sandbox 中的数据是不会变的),如果两处都取不到,就认为未登录。

你有没有更好的解决方案?欢迎留言讨论。

另外,有很多人通过 Keychain 来存储设备唯一标示符,也需要注意这个问题。

关于 Keychain 如何使用,可以参考苹果官方文档: GenericKeychain ,而关于 Keychain 滥用问题的讨论,可以看 V2EX 的 这个帖子

给 UIView 添加阴影

作者 : Lefe_x

给 UIView 添加阴影看似简单,如果操作不当也可能会浪费你一些时间。有时候明明添加了阴影可是在 UI 上却没显示出来,尤其涉及到 cell 复用的情况。这里总结几条阴影不显示的原因:

  • 是否设置了 masksToBounds 为 YES,设置为 masksToBounds=YES,阴影不显示;
  • 设置阴影时 view 的 frame 是否为 CGRectZero,如果是,即使设置阴影后修改 frame 不为 CGRectZero 时,也不会显示阴影;
  • 使用自动布局时往往会遇到 frame 为 CGRectZero 时设置阴影无效,这时可以使用 layoutIfNeeded 方法;

通过 layer 设置阴影

// 阴影的颜色
self.imageView.layer.shadowColor = [UIColor blackColor].CGColor;
self.imageView.layer.shadowOpacity = 0.8;
// 阴影的圆角
self.imageView.layer.shadowRadius = 1;
// 阴影偏离的位置 (100, 50) x 方向偏离 100,y 偏离 50 正向,如果是负数正好为相反的方向
self.imageView.layer.shadowOffset = CGSizeMake(3, 4);
复制代码

通过 shadowPath 设置阴影

通过这种方式设置的阴影可以自定义阴影的形状,它会使用在 layer 上设置的属性,比如 shadowRadius。

UIEdgeInsets edges = UIEdgeInsetsMake(15, 10, 15, 10);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(-edges.left, -edges.top, CGRectGetWidth(self.imageView.frame) + edges.left + edges.right, CGRectGetHeight(self.imageView.frame) + edges.top + edges.bottom)];
self.imageView.layer.shadowPath = path.CGPath;
复制代码

比较三种网络框架上传图片过程中的不同点?

作者 : 陈满iOS

AFNetworking 上传图片的步骤是利用图片设置到 request 的 HTTPBodyStream 中去,然后利用带有图片的 request 新建 task 上传。HYNetworking 内部实现上传图片的时候,其实就是采用 AFNetworking 关于上传图片的 API,都是 AFNetworking 里面一个 API。XMNetworking 上传图片请求也是基于 AFNetworking 上传进行的封装,不过比 HYNetworking 更加隐晦而已,另外它封装了上次图片数组的方法。

AFNetworking

  1. 压缩转换:UIImage 实例对象通过 UIImageJPEGRepresentation (压缩)转换为 NSData,下面称之为 imageData。
  2. 信息整合:将 imageData 与文件名 fileName,文件路径 name,类型名 mimeType 整合成图片模型(AFHTTPBodyPart)的一个对象 bodyPart 中去。
  3. 添加图片模型:将上面新建好的图片模型对象 bodyPart,向图片输入流(AFMultipartBodyStream)的对象 bodyStream 的数组属性(HTTPBodyParts)添加。
  4. 设置 request 的 HTTPBodyStream 属性为 bodyStream:封装为 requestByFinalizingMultipartFormData
  5. 将图片模型对象 formData 用 AFNetwork 的 POST 请求与 uploadTaskWithStreamedRequest 方法进行上传。

HYBNetworking

  1. 压缩转换:UIImage 实例对象通过 UIImageJPEGRepresentation 压缩转换为 NSData,下面称之为 imageData。
  2. 信息整合:利用 AFNetwork的appendPartWithFileData,将 imageData 与文件名 fileName,文件路径 name,类型名 mimeType 整合成图片模型(AFStreamingMultipartFormData)的一个对象 formData 中去。
  3. 将图片模型对象 formData 用 AFNetwork 的 POST 请求与 uploadTaskWithStreamedRequest 方法进行上传。

XMNetworking

  1. 压缩转换:UIImage 实例对象通过 UIImageJPEGRepresentation 压缩转换为 NSData,下面称之为 imageData。
  2. 信息整合:利用 AFNetwork的appendPartWithFileData,将 imageData 与文件名 fileName,文件路径 name,类型名 mimeType 整合成图片模型(XMUploadFormData)的一个对象 formData 中去。
  3. 添加图片模型:向管理器的图片模型数组 uploadFormDatas 添加上面新建好的图片模型对象 formData。
  4. 遍历图片模型数组,获得图片模型,利用 AFNetwork 的 POST 请求与 uploadTaskWithStreamedRequest 方法进行上传。

【总结】 可见,上面三种框架都是基于 AFNetworking 进行的封装,实质的流程还是一样的。上传图片的流程图如下所示。







请到「今天看啥」查看全文