0%

OC的block对外部变量的捕获

OC中,block捕获的值是block**初始化时上下文**中变量的瞬时值,也就是一个常量

1
2
3
4
5
6
7
8
9
10
-(void)testBlock {
NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld", i);
}
i += 1;
NSLog(@"before block %ld", i); // 2
block(); // 1
NSLog(@"after block %ld", i); // 2
}

捕获__block修饰的变量时,将变量捕获到堆区,外部的修改会影响内部捕获的值(其实是捕获了内存地址)

1
2
3
4
5
6
7
8
9
10
-(void)testBlock {
__block NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld", i);
}
i += 1;
NSLog(@"before block %ld", i); // 2
block(); // 2
NSLog(@"after block %ld", i); // 2
}
阅读全文 »

编译耗时的检查

  1. Swift的表达式和函数的编译时长检查

    build settingOther Swift Flags中设置:

    1
    2
    3
    4
    // 编译耗时超过 100ms 的函数或表达式给出警告
    -Xfrontend -warn-long-function-bodies=100

    -Xfrontend -warn-long-expression-type-checking=100
  2. 查看总耗时

    在终端执行如下命令,重启Xcode,编译后可以看到总耗时:

    1
    defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

编译优化怎么做

针对OC的优化——重点在于减少无效的引用

  1. pch文件:删除使用不多的引用
  2. .h文件:减少.h文件中的引用,将.h中导入改为声明#import改为@class
    阅读全文 »

对于编译型语言来说,从源码到可执行文件需要经过编译、汇编和链接三个步骤。编译器接收源代码,输出目标代码(也就是汇编代码),汇编器接收汇编代码,输出由机器码组成的目标文件(二进制格式,.o 后缀),最后链接器将各个目标文件链接起来,执行重定位,最终生成可执行文件。

iOS的编译器LLVM

LLVM采用三相设计:

  • 前端负责词法分析、语法分析、生成中间代码;
  • 后端以中间代码作为输入,进行与架构无关的代码优化,接着针对不同架构生成不同的机器码。
  • 前后端依赖统一格式的中间代码(IR),使得前后端可以独立的变化。新增一门语言只需要修改前端,而新增一个 CPU 架构只需要修改后端即可

Objective-C/C/C++ 使用的编译器前端是Clang,swift 是swiftc,后端都是 LLVM。

阅读全文 »

基本知识

App启动分为冷启动和热启动,启动时间由两个阶段构成:

  • pre-main阶段:main()函数之前所需要的时间
  • main()main()之后需要的时间

分析每个阶段做了做什么

  1. App启动后,首先,系统内核(Kernel)创建一个进程。

  2. 加载可执行文件。(可执行文件是指Mach-O格式的文件,也就是App中所有.o文件的集合体)这时,能获取到dyld(dyld是苹果的动态链接器)的路径。

  3. 加载dyld,主要分为4步:

    (1)load dylibs:这一阶段dyld会分析应用依赖的dylib,找到其Mach-O文件,打开和读取这些文件并验证其有效性,接着会找到代码签名注册到内核,最后对dylib的每一个segment调用mmap()(在Linux中,使用mmap()用来在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系)。

    (2)rebase/bind:进行rebase指针调整和bind符号绑定。

    (3)ObjC setup:runtime运行时初始化。包括ObjC相关Class的注册、category注册、selector唯一性检查等。

    阅读全文 »

AutoreleasePool底层涉及的数据结构:__AtAutoreleasePoolAutoreleasePoolPage

调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的。

AutoreleasePoolPage结构

  • 每个AutoreleasePoolPage对象占用4096个字节

  • 存放Page自身的成员变量autorelease对象

  • Page自身是结构

  • Page与Page之间通过parentchild指针连接成双向链表

    阅读全文 »

在使用swift原生api对字符串进行处理时,发现其处理效率特别低。经过测试对比发现,处理大量数据时,相同长度的数组和字符串,数组的处理能力可以达到字符串的几十倍。测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func testStringCount() {
let string = String(Array(repeating: "a", count: 1000))
print(111, Date.now.timeIntervalSince1970)
var a = 0
for _ in 0 ..< 100000 {
a = string.count
}
print(222, Date.now.timeIntervalSince1970)
}

func testArrayCount() {
let array = Array(repeating: "a", count: 1000)
print(333, Date.now.timeIntervalSince1970)
var a = 0
for _ in 0 ..< 100000 {
a = array.count
}
print(444, Date.now.timeIntervalSince1970)
}

打印结果如下:

阅读全文 »

Category的底层结构

编译后的分类信息都保存在category_t结构体中(定义在objc-runtime-new.h中):

Category的加载处理过程

Category中的方法是在程序运行时通过runtime动态地将分类中的方法合并到类对象、元类对象中。

  1. 通过Runtime加载某个类的所有Category数据(方法、属性、协议);
  2. 把所有Category的方法、属性、协议数据,合并到一个大数组中,参与编译的Category数据,会放在数组的前面
  3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

Category的实现原理

  • Category编译之后的底层结构是struct category_t
  • 里面存储着分类的对象方法、类方法、属性、协议信息。
  • 在程序运行时,runtime会将Category的数据合并到类信息中(类对象、元类对象中)
阅读全文 »

卡顿检测的方案根据线程是否相关分为两大类:

  • 检测任务耗时
  • 检测主线程是否能响应任务

与主线程相关的检测方案:

  • fps
  • ping
  • RunLoop

与主线程不相关的检测方案:

  • stack backtrace
  • msgSend Observe
阅读全文 »

题目描述

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

1
L0  L1    Ln - 1  Ln

请将其重新排列后变为:

1
L0  Ln  L1  Ln - 1  L2  Ln - 2  

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例

1
2
输入:head = [1,2,3,4]
输出:[1,4,2,3]
1
2
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

提示:

阅读全文 »

题目描述

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例1

1
2
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例2

1
2
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

提示

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 10
  • -100 <= matrix[i][j] <= 100

题解

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
42
43
func spiralOrder(_ matrix: [[Int]]) -> [Int] {
if matrix.isEmpty || matrix[0].isEmpty { return [] }
var res = [Int]()
var left = 0
var top = 0
var bottom = matrix.count - 1
var right = matrix[0].count - 1

while left <= right && top <= bottom {
// 左上到右上
for col in left ... right {
res.append(matrix[top][col])
}
// 右上到右下
if top + 1 <= bottom { // for...in...写法需要判断top+1和bottom的大小
for row in top+1 ... bottom {
res.append(matrix[row][right])
}
}
// left == right || top == bottom是最后一行或最后一列
if left < right && top < bottom {
// 右下到左下
var col = right - 1
while col >= left {
res.append(matrix[bottom][col])
col -= 1
}

var row = bottom - 1
while row > top {
// 左下到左上
res.append(matrix[row][left])
row -= 1
}
}
left += 1
right -= 1
top += 1
bottom -= 1
}

return res
}