Tip

WKWebView内存监控

WKWebView是多进程组件,会从App内存中分离到单独的进程中。当WKWebView的内存超过系统分配的内存时,WKWebView浏览器会出现崩溃白屏,同时App只会收到系统通知而不是崩溃。

因为业务中不可避免需要用到WKWebView,这时候需要对其内存监控,但是XCode运行时的App内存并不包含它,只能从other process这一项来大致估计内存占用,最好是通过别的手段来进行查看app内出现的WKWebview进程数和其内存占用。目前了解了两种:

  1. Instruments。
    1. 在Instruments的Activity Monitor中,选中要查看的应用,点击红圈开始录制,查看Live Processes面板。
    2. 在下方的过滤栏中搜索apple.Webkit,就能过滤出当前应用内的Webkit进程。然后查看对应的内存占用信息。
  2. Safari开发者选项。
    1. 打开Safari 开发选项,选择对应WKWeb网页,选择「时间线」面板,点击「编辑」并勾选「内存」这一项。
    2. 点击红色圆点开始录制,然后查看到该WKWeb网页的内存占用,可以具体到JavaScript、图像、页面等的分别占用情况。

两者可以结合着一起看,虽然前者指的是WebKit进程的内存,后者指的是WKWebView当前页面的内存占用。

Instruments

Safari

Review

Instruments Tutorial: Getting Started「Instruments入门教程」

因为最近有需要对App内的Webview进行内存分析,但又不怎么会使用Instruments,就看了这篇raywenderlich上的关于Instruments的教程。

关于性能分析,Instruments还是比较重要的工具。这篇文章以一个Flicker图片App为Demo,讲了几个性能分析的点,做了些笔记如下:

  1. 「Timer Profiler」分析时间性能:
    1. 原理:在测量时间间隔内,暂停程序执行,并在每个运行的线程上判断堆栈顶部的方法。以此来推断每个方法的大概时间。
    2. 功能:显示Call Tree,显示一个应用中执行各个方法所花费的时间。
    3. 选项:下方Call Tree的几种选项
      1. Separate by State. 按生命周期状态进行分组,用于检查app在什么时间做多少工作。
      2. Separate by Thread. 按线程进行分离。用于分析哪些县城负责最大量的CPU使用。
      3. Invert Call Tree. 逆向调用树。用于展示堆栈跟踪的最近的帧。
      4. Hide System Libraries. 隐藏系统库。只需要关心CPU在自己的代码上花费的时间。
      5. Flattern Recursion. 铺平递归。用于显示递归函数,即调用自身的函数,在每个堆栈跟踪中只有一个条目,而不是多次。
      6. Top Functions. 启用该功能,Instruments会将某个功能耗费的时间视为该功能直接所花费的时间的总和,加上该功能调用的方法所花费的时间。用来直接评估哪个部分最耗时。
    4. Tip:如果是本地编译的代码,则可以双击条目,会跳转代码,然后再点击右上角的Xcode按钮,则会跳转到项目中。
  2. 「Allocations」内存泄露分析:
    1. 内存泄漏的两种类型
      1. 真正的内存泄漏:当对象不再被任何对象引用时,仍然被分配了内存。这段内存就一直都不能被重复利用。比如即使有ARC,但是当两个对象彼此保持强引用时,就能够阻止释放另外一个对象,发生内存泄漏。
      2. 无限的内存增长:连续分配内存且从来不进行释放,会耗尽内存。iOS上,系统会终止App。
    2. Allocations能够提供程序创建的所有对象的详细信息和对应内存,以及每个对象的retain count。
    3. 【Generation Analysis】:打点分析。点击下方「Mark Generation」,面板会插红旗🚩。多次执行一个操作,并进行Mark,然后查看内存是否义无限的方式增长「Growth」。对Growth进行排序,有针对性的对增长最严重的地方进行分析。
    4. 【模拟内存警告⚠️】:使用方式 Instruments - Document - Simulate Memory Warning。然后在代码中对didReceiveMemoryWarningNotification进行内存释放。
    5. 同 Timer Profile,可以点开详细的信息,并跳转到自己的代码。
  3. 「Allocations」强引用分析:
    1. Allocations 面板中有 #Persistent 和 #Transient 两列, #Persistent 计算当前存在于内存中的每中类型的对象数,#Transient显示已存在但是已经被释放的对象数。前者占用了内存,后者则没有。
    2. 可以通过进入退出进入退出,然后查看引用计数,就可以分析出哪些对象是被保持了强引用。
    3. Xcode中有Visual Memory Debugger,可以用来诊断内存泄露和Retain Count。
  4. 「Visual Memory Debugger」
    1. 前提设置:Xcode scheme editor - Diagnostics - Malloc Stack - Live Allocations Only
    2. 多执行几次操作,在Debug 导航器中观察。
    3. Visual Memory Debugger 会暂停应用,并显示当前内存中对象的可视化表示和它们之间的引用。
      1. 左侧边栏「Heap contents 堆内容」,显示应用暂停时内存中分配的所有类型和实例。小数字表示内存中该类型实例的数目。
      2. 主窗口「Memory Graph 内存图表」,显示内存中对象的视觉表示。对象之间的箭头表示它们之间的引用关系(强/弱)。
      3. 右侧边栏「Memory Inspector 内存检查器」,包括类名和层次结构等细节,以及引用是强引用还是弱引用。

像Instruments这种性能测试工具,本身应该成为日常开发流程中的一部分,通过在发布前运行Instruments,确保已经尽可能多地捕捉到内存管理和性能上的问题。

Algorithm

54. 螺旋矩阵

描述:

给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。

思路:

处理好上下左右边界值,以及处理好终止条件。

时间复杂度:O(mn)

空间复杂度:O(1)

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
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int left = 0, right = matrix[0].size()-1, top = 0, bottom = matrix.size()-1;
vector<int> res;
while(left <= right || top <= bottom) {
// left2right
for (int i=left;i<=right;i++){
res.push_back(matrix[top][i]);
}
top += 1;
if (top > bottom) break;
// top2bottom
for (int i=top;i<=bottom;i++){
res.push_back(matrix[i][right]);
}
right -= 1;
if (right < left) break;
// right2left
for (int i=right;i>=left;i--){
res.push_back(matrix[bottom][i]);
}
bottom -=1;
if (bottom < top) break;
// bottom2top
for (int i=bottom;i>=top;i--){
res.push_back(matrix[i][left]);
}
left += 1;
if (left > right) break;
}
return res;
}
};

以后开始尝试用Swift刷题,多了解了解基本数据结构的用法。

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
40
41
class Solution {
func spiralOrder(_ matrix: [[Int]]) -> [Int] {
var left = 0, right = matrix[0].count - 1, top = 0, bottom = matrix.count-1
var res = [Int]()
while(left <= right || top <= bottom) {
// left2right
for i in left...right {
res.append(matrix[top][i])
}
top = top + 1
if (top > bottom) {
break
}
// top2bottom
for i in top...bottom {
res.append(matrix[i][right])
}
right = right - 1
if (right < left) {
break
}
// right2left
for i in stride(from: right, through: left, by: -1) {
res.append(matrix[bottom][i])
}
bottom = bottom - 1
if(bottom<top) {
break
}
// bottom2top
for i in stride(from: bottom, through: top, by: -1) {
res.append(matrix[i][left])
}
left = left + 1
if(left>right) {
break
}
}
return res;
}
}