七月总结

本月重新读了一遍《剑指Offer》,并把题目做了一遍。
本月在swift上学习了一些很好用的技巧,比如 mapfilterreduce。本月重点学习了 RunloopRuntimeblock 的底层细节,并且总结了一个 Method Swizzling 标准代码。

Week 27

July 02 to July 08

Algorithm:

[Leetcode - Medium] 240. Search a 2D Matrix II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func searchMatrix(_ matrix: [[Int]], _ target: Int) -> Bool {
if matrix.count == 0 || matrix[0].count == 0 {
return false
}
var row = 0, col = matrix[0].count - 1
while row < matrix.count && col >= 0 {
if matrix[row][col] == target {
return true
} else if matrix[row][col] > target {
col -= 1
} else if matrix[row][col] < target {
row += 1
}
}
return false
}

Review:

《Adopting Modern Objective-C》

instancetype

instancetype 代替 id 能够提高类型安全,考虑如下代码

1
2
3
4
5
+ (instancetype)factoryMethodA;
+ (id)factoryMethodB;

x = [[MyObject factoryMethodA] count];
y = [[MyObject factoryMethodB] count];

因为 instancetype 返回 +factoryMethodA 的类型 MyObject *, 因为 MyObject 没有 -count 方法, 所以编译器会报错:

1
main.m: ’MyObject’ may not respond to ‘count’

而 id 可以是任何类,编译器不会警告。

Properties

用 @property 定义变量,可以自动生成setter和getter方法

Enumeration Macros

用 NS_ENUM 和 NS_OPTIONS 代替 enum ,普通枚举用 NS_ENUM ,位枚举用 NS_OPTIONS 。

Object Initialization

在类中定义 designated initializers 并且用 NS_DESIGNATED_INITIALIZER 标记. 比如:

1
- (instancetype)init NS_DESIGNATED_INITIALIZER;

Automatic Reference Counting (ARC)

推荐使用ARC。

Tip:

  • Off-Screen Rendering 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。opengl里离屏渲染会单独在内存中创建一个屏幕外缓冲区并进行渲染,而屏幕外缓冲区跟当前屏幕缓冲区上下文切换是很耗性能的。
  • 圆角效果:圆角效果的优化老生常谈,产生性能问题的根源在于原生圆角效果带来的离屏渲染开销。通常我们推荐直接使用圆角的素材,或者提前在子线程将图片进行圆角裁剪,这两者原理相同。除此之外,还有一种思路是在需要圆角的视图最上层添加一个中空的圆角遮罩层,以此来做出圆角效果。这个遮罩层和被盖在下面的视图在显示时会由 GPU 进行图层混合,而图层混合的开销远小于离屏渲染。值得一提的是,由于圆角效果通常在一屏中频繁出现,所以这个遮罩的图片素材可以只加载一次,并且应用于每一个圆角视图,避免重复加载。
  • 阴影效果:值得注意的是系统原生的阴影实现要求 layermasksToBounds 值为 YES,所以原生的阴影效果和圆角是不兼容的。高效的阴影实现是为阴影指定 shadowPath,如果你还没用的话,不妨试一下。
  • 适时替换轻量控件:在合适的时候用 CALayer 替换 UIView,这确实有效,不过盲目替换往往会造成代码维护的困难。这里举两个适合的场景:绘制线条时,完全可以替换。以及静态展示图片时,将图片对象赋值给 layer 的 content 属性,也完全可以达到效果。
  • 图片解码:对于不同的图片格式,不同的解码算法,或者使用系统解码方法时的不同参数设置,都会影响解码性能,如果有这方面瓶颈的,不妨做多种尝试。

再说一个经典的例子:为了实现一个简单的画板需求,有人会在 UIView 上频繁调用 drawRect 方法进行新笔划的绘制,殊不知有一个天生的专用图层对象 CAShapeLayer 是很适合做这件事的。CAShapeLayer 不需要像普通 CALayer 一样创建寄宿图,不会造成巨量内存的使用,并且它使用了硬件加速。

UI 性能优化时,我们常常需要实时监测帧率。帧率监测工具 YYFPSLabel 的实现原理:使用 CADisplayLink,在每帧的回调事件中,计数器 c 加一,并且累计时间间隔 t 也进行更新。当时间间隔够 1 秒后,使用 c/t 计算出过去 1 秒的帧率,而后计数器清零,时间戳更新为当前时间戳,再重复之前步骤。因此 YYFPSLabel 的帧率更新周期在 1 秒左右。

Share:

Swift 内部参数名 和 外部参数名

1
2
3
4
5
6
7
8
// 不加任何参数名,直接写参数值
<函数名>(参数值,参数值...);

// 方法调用第一个参数不写参数名,后面的全部要写。
<实例>.<方法名>(参数值,参数名:参数值,参数名:参数值...);

// 类初始化所有参数都需要加参数名
<类初始化>(参数名:参数值,参数名:参数值...);

默认值

  • 如果使用默认值,调用的时候,默认值对应的参数必须写参数名。这里影响的主要是函数和方法调用,因为类初始化本来就要写全参数名。
  • 如果使用默认值并且默认值不是出现在最后,那调用的时候必须写全所有参数。

强制指定参数名
如果你想强制要求调用时必须加参数名,可以在声明的时候给参数加上外部参数名:

1
2
3
4
func test(outName name: String, outAge age: Int) {
...
}
test(outName: "asd", outAge: 2)

如果外部参数名和内部参数名一样,可以直接在参数名前加#

1
2
3
4
func test(#name: String, #age: Int) {
...
}
test(outName: "asd", outAge: 2)

强制取消参数名
对于需要参数名的函数,你也可以在参数名前加_来强制取消参数名:

1
2
3
4
5
6
7
8
class Test {
func test(name: String, _ age: Int) {
...
}
}

var test = Test()
test.test("123", 3)

Week 28

July 09 to July 15

Algorithm:

[Leetcode - Medium] 113. Path Sum II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func pathSum(_ root: TreeNode?, _ sum: Int) -> [[Int]] {
var result = [[Int]](), path = [Int]()
dfs(root, sum, &path, &result)
return result
}

private func dfs(_ root: TreeNode?, _ sum: Int, _ path: inout [Int], _ result: inout [[Int]]) {
guard let root = root else {
return
}
path.append(root.val)
if root.val == sum && root.left == nil && root.right == nil {
result.append(path)
}
dfs(root.left, sum - root.val, &path, &result)
dfs(root.right, sum - root.val, &path, &result)
path.removeLast()
}

Review:

《What’s Next for Mobile at Airbnb》

本文介绍Airbnb决定放弃React Native后的下一步工作。

主要是提出了一个新的框架MvRx,新框架借鉴了很多React Native的特性,同时使原生程序在build时可以加快速度。

Tip:

ABI 及 ABI稳定性

ABI是Application Binary Interface的缩写,它是一个规范,通过这个规范,所有被独立编译的二进制实体才能被链接在一起并执行。这些二进制实体必须在一些很低层的细节上达成一致,例如:如何调用函数,如何在内存中表示数据甚至是如何存储以及访问metadata。ABI是平台相关的,因为它关注的这些底层细节会受到不同的硬件架构以及操作系统的的影响。

ABI稳定是指把ABI锁定在某种形式,以至于未来的编译器都可以生成遵从这种形式的二进制实体。

ABI的稳定性仅会影响到外部可见的公共接口和符号的不变性。而内部使用的符号、调用约定以及内存格局仍旧可以修改而不会破坏ABI约定。

import 引号与尖括号

一句话概括:带尖括号的语句是用来导入系统文件的,而带引号的语句则说明导入的是项目本地的头文件。

双引号是用于本地的头文件,需要指定相对路径,尖括号是全局的引用,其路径由编译器提供,如引用系统的库。cocoapod中引入的第三方库是作为系统库的,需要使用尖括号。

Share:

Tagged Pointer

为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。对于64位程序,引入Tagged Pointer后,相关逻辑能减少一半的内存占用,以及3倍的访问速度提升,100倍的创建、销毁速度提升。

将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。

特点:

  • Tagged Pointer专门用来存储小的对象,例如 NSNumber 和 NSDate
  • Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free 。
  • 在内存读取上有着3倍的效率,创建时比以前快106倍。

Tagged Pointer的引入也带来了问题,即Tagged Pointer因为并不是真正的对象,而是一个伪对象,所有对象都有 isa 指针,而Tagged Pointer其实是没有的。

Week 29

July 16 to July 22

Algorithm:

[Leetcode - Medium] 179. Largest Number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func largestNumber(_ nums: [Int]) -> String {
guard nums.count > 0 else {
return ""
}
guard nums.filter({$0 > 0}).count > 0 else {
return "0"
}
var strNums = nums.map { value in
String(value)
}
strNums.sort { (str1, str2) -> Bool in
let combine1 = str1 + str2
let combine2 = str2 + str1
return combine1 > combine2
}
return strNums.reduce("", +)
}

Review:

iOS — Identifying Memory Leaks using the Xcode Memory Graph Debugger

Xcode memory graph debugger可以帮助找到和修复循环引用与内存泄露。当被激活时,会暂停app运行,展现当前堆中的对象,对象的关系,对象间的引用。

Tip:

Swift Guide to Map Filter Reduce

Map

Use map to loop over a collection and apply the same operation to each element in the collection.
Map

Filter

Use filter to loop over a collection and return an Array containing only those elements that match an include condition.
Filter

Reduce

Use reduce to combine all items in a collection to create a single new value.
Reduce

Share:

UITableView style设置PlainGrouped的区别

  1. SectionHeaderSectionFooterPlain 时是悬停的,在 Grouped 不悬停
  2. Grouped 样式的 SectionHeader 是自带间隔的,如果要取消间隔需要在相关代理方法中减去

Week 30

July 23 to July 29

Algorithm:

[Leetcode - Medium] 109. Convert Sorted List to Binary Search Tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func sortedListToBST(_ head: ListNode?) -> TreeNode? {
if head == nil {
return nil
}
if head?.next == nil {
return TreeNode(head!.val)
}
var slow = head, fast = head, pre = head
while fast != nil && fast?.next != nil {
pre = slow
slow = slow?.next
fast = fast?.next?.next
}
pre?.next = nil
let root = TreeNode(slow!.val)
root.left = sortedListToBST(head)
root.right = sortedListToBST(slow?.next)
return root
}

Review:

mock in iOS

mock,通过制造模拟真实对象行为的假对象,来对程序功能进行测试或调试。

从设计思路上来说,interface 是抽象出一套行为接口或者属性,且并不关心实现者是否存在具体实现上的差异。而 mock 需要模拟对象和真实对象两者具有相同的行为和属性,以及一致的行为实现。interface 更适用于模块解耦、功能扩展相关的工作;而 mock 更多的应用在调试、测试等方面的工作。

mock 并不是一种特定的操作或者编程手段,它更像是一种剖析工程细节来解决特殊环境下难题的解决思路。无论如何,如果我们想要继续在开发领域上继续深入,必然要学会从更多的角度和使用更多的工具来理解和解决开发上的难题,而 mock 绝对是一种值得学习的开发思想

Tip:

NSDecimalNumber, An object for representing and performing arithmetic on base-10 numbers.
可以方便地处理货币相关的计算,包括加减乘除等。可以通过 NSDecimalNumberHandler 对运算的处理策略进行设置,比如四舍五入,数据溢出,除零等异常。

Share:

Method Swizzling 注意事项及代码示例

  • Swizzling 应该总在 +load 中执行。 +load 会在类初始加载时调用, +initialize 方法是以懒加载的方式被调用的。
  • Swizzling 应该总是在 dispatch_once 中执行
  • Swizzling+load 中执行时,不要调用 [super load]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

// 判断原有类中是否有要替换的方法的实现
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
// 当前类中没有要替换方法的实现,需要在父类中去寻找
// 用method_getImplementation去获取class_getInstanceMethod里面的方法实现。然后再进行class_replaceMethod来实现Swizzling。
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
// 当前类中有要替换方法的实现,所以可以直接进行替换
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
// 不会造成死循环
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end