UIKit Inside: frame bounds position anchorPoint center
iOS 中UIView
的属性:frame
、bounds
、center
以及CALayer
的属性:position
、anchorPoint
与视图的位置与大小相关,理解这些属性是进行 iOS 视图编码的基础。
下面从汇编角度看一下这些属性的实现以及相互关系。
1 frame
frame
定义了视图在父视图坐标系下的位置与大小。
上图中红色UIView
的frame
为 {x: 50, y: 50, width: 100, height: 200}。
1.1 frame 读取
如果访问view
的frame
属性,汇编代码如下:
;UIKitCore`-[UIView(Geometry) frame]:
...
; 1. x0 寄存器存储 UIView 的 CALayer 对象指针
0x1bae62384 <+44>: mov x0, x8
; 2. bl 指令调用 objc_msgSend 方法,也就是调用 [CALayer frame]
0x1bae62388 <+48>: bl 0x1bc590f00 ; objc_msgSend$frame
...
上面代码注释 1 寄存器x0
存储的是UIView
对应的CALayer
对象指针,在控制台输出的结果如下:
(lldb) po $x0
<CALayer:0x280ea0f40; position = CGPoint (100 150); bounds = CGRect (0 0; 100 200); delegate = <UIView: 0x10380dd70; frame = (50 50; 100 200); backgroundColor = UIExtendedSRGBColorSpace 1 0 0 1; layer = <CALayer: 0x280ea0f40>>; allowsGroupOpacity = YES; backgroundColor = <CGColor 0x282aa9680> [<CGColorSpace 0x282aa0cc0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 1 0 0 1 )>
注释 2 的bl
指令是函数调用指令,相当于x64
汇编中的call
指令。这个调用指令调用objc_msgSend
方法,而ARM
里面,函数的第一个参数由x0
寄存器传递,objc_msgSend
第一个参数是self
,因此这里相当于调用[CALayer frame]
。
从代码可以看到,访问UIView
的frame
属性,实际上就是访问UIView
对应的CALayer
的frame
属性。CALayer
的frame
汇编代码如下:
;QuartzCore`-[CALayer frame]:
...
; 1. x0 寄存器 CALayer 对象指针暂存到寄存器 x21
0x1ba335b6c <+32>: mov x21, x0
...
; 2. x21 寄存器存储着 CALayer 对象指针,ldr 指针是 LOAD 内存操作,读取从 CALayer 对象开始偏移 0x10 字节的内容,
; 也就是读取内存地址 (x21 + 0x10) 的内容,存到 x20 寄存器
0x1ba335ba4 <+88>: ldr x20, [x21, #0x10]
0x1ba335ba8 <+92>: ldr w8, [x20, #0x34]
;3. tbnz 是一个测试跳转指令,如果有设置过 CALayer 的 anchorPoint,就跳转到当前代码偏移 <+112> 字节处执行,
; 也就是去调用 [CALayer anchorPoint] 方法
0x1ba335bac <+96>: tbnz w8, #0x18, 0x1ba335bbc ; <+112>
; 4. 如果没有设置过 CALayer 对象的 anchorPoint 属性,那么就使用默认值 (0.5, 0.5)
0x1ba335bb0 <+100>: fmov d1, #0.50000000
0x1ba335bb4 <+104>: fmov d0, #0.50000000
; 5. b 指令是跳转指令,跳转到当前代码偏移 <+124> 字节处执行
0x1ba335bb8 <+108>: b 0x1ba335bc8 ; <+124>
; 6. 如果设置过 CALayer 对象的 anchorPoint 属性,就会调用 [CALayer anchorPoint] 方法,
; x21 寄存器存储着 CALayer 对象指针,这里传递给 x0 寄存器,作为 objc_msgSend 方法第一个参数
0x1ba335bbc <+112>: mov x0, x21
; 7. bl 指令调用 objc_msgSend 方法,方法的返回结果存储在 d0 与 d1 寄存器中
0x1ba335bc0 <+116>: bl 0x1ba680aa0 ; objc_msgSend$anchorPoint
...
; 8. 读取内存地址 (x20 + 0x68) 处 16 字节的内容,前 8 字节存到到寄存器 d8,后 8 字节存放到寄存器 d9,
; 这里实际上读取的是 CALayer 对象 bounds 属性的 width 与 height
0x1ba335bd0 <+132>: ldp d8, d9, [x20, #0x68]
...
; 9. 读取内存地址 (x20 + 0x48) 处 16 字节的内容,前 8 字节存到寄存器 d2,后 8 字节存放到寄存器 d3,
; 这里时机上读取的是 CALayer 对象的 position 属性
0x1ba335bdc <+144>: ldp d2, d3, [x20, #0x48]
; 10. fmsub 指令的操作是: 将寄存器 d0 与 d8 内容相乘,然后使用寄存器 d2 内容减去前面的乘积,最后将结果存储到寄存器 d10,
; 也就是相当于 d10 = d2 - d0 * d8,
; 也就是相当于 UIView.frame.origin.x = CALayer.position.x - anchorPoint.x * CALayer.bounds.width
0x1ba335be0 <+148>: fmsub d10, d0, d8, d2
; 11. 同注释 9,相当于 d11 = d3 - d1 * d9,
; 也相当于 UIView.frame.origin.y = CALayer.position.y - anchorPoint.y * CALayer.bounds.height
0x1ba335be4 <+152>: fmsub d11, d1, d9, d3
...
; 12. 下面 4 条 fmov 指令将寄存器 d10 d11 d8 d9 的值赋值给寄存器 d0 d1 d2 d3 作为返回值,
; d0 = origin.x d1 = origin.y d2 = size.width d3 = size.height
0x1ba335c48 <+252>: fmov d0, d10
0x1ba335c4c <+256>: fmov d1, d11
0x1ba335c50 <+260>: fmov d2, d8
0x1ba335c54 <+264>: fmov d3, d9
...
// 13. 函数返回
0x1ba335c70 <+292>: retab
上面代码注释 1 将 x0
寄存器的值暂存到 x21
寄存器,也就是 x21
寄存器也存储着 CALayer
对象指针。
代码注释 2 ldr
指令是ARM
里面加载内存的 LOAD 指令,也就是将内存里面的值加载到对应寄存器,相当于从内存地址 (x21 + 0x10)
处加载值。由于寄存器x21
存储的是CALayer
对象指针,这里相当于从CALayer
对象首地址偏移0x10
字节,然后将此处内存内容加载到寄存器x20
:
那CALayer
对象偏移0x10
字节处存放的是什么呢?使用LLDB
调试命令po [$x21 _ivarDescription]
输出CALayer
对象的实例变量成员:
(lldb) po [$x21 _ivarDescription]
<CALayer: 0x28065fa60>:
in CALayer:
_attr (struct _CALayerIvars): {
refcount (int): 4
magic (unsigned int): 1279351122
layer (void*): 0x1031053f0
_objc_observation_info (void*): 0x0
}
in NSObject:
isa (Class): CALayer (isa, 0x10000020f2845e3)
方法_ivarDescription
是 NSObject
的私有方法,任何一个继承自NSObject
的对象,都可以使用它输出自己的实例变量成员,包括继承过来的。从上面的输出可以看到,CALayer
对象首地址偏移0x10
字节处是一个void
指针,它指向一个名为layer
的对象:
这个layer
对象很重要,从后面可以知道,layer
对象偏移0x48
处存储着CALayer
的position
值,偏移0x58
处存储着CALayer.bounds.origin
值,偏移0x68
处存储着CALayer.bounds.size
值:
代码注释 3 处tbnz
是一个测试跳转指令,如果CALayer
设置过anchorPoint
,就跳转到当前代码偏移<+112>
字节处执行。
代码注释 4 处是如果没有设置过CALayer
对象的anchorPoint
属性,就使用默认的值 (0.5, 0.5),默认值被存储在浮点数寄存器d0
与d1
中。
代码注释 5 b
指令是一个跳转指令,获取到默认的anchorPoint
值之后,就跳转到当前代码偏移<+124>
字节处执行。
代码注释 6 7 处正是注释 3 处跳转过来要执行的代码,是调用[CALayer anchorPoint]
方法去获取设置的anchorPoint
值,方法返回的结果会被存储在浮点数寄存器d0
与d1
中。
总之,经过代码注释 3 4 5 6 7处的代码,浮点寄存器d0
与d1
已经存储了CALayer
对象的anchorPoint
属性值,其中d0
存储anchorPoint.x
,d1
存储anchorPoint.y
。
代码注释 8 处读取地址(x20 + 0x68)
处的内容。由于寄存器x20
存储着上图中的layer
对象指针,因此这里读取的是CALayer
对象的bounds.size
值。其中,浮点数寄存器d8
存储bounds.size.width
,浮点数寄存器d9
存储bounds.size.height
。
代码注释 9 处读取地址(x20 + 0x48)
处的内容。由于寄存器x20
存储这上图中的layer
对象指针,因此这里读取的是CALayer
对象的position
值。其中,浮点数寄存器d2
存储position.x
,浮点数寄存器d3
存储position.y
。
代码注释 10 处fmsub
指令的操作是: \(d10 = d2 - d0 * d8\)。由于d2
寄存器存储着position.x
,d0
寄存器存储着anchorPoint.x
,d8
寄存器存储着bounds.size.width
,因此这里实际上是在计算frame.origin.x
:
代码注释 11 同理,相当于:\(d11 = d3 - d1 * d9\)。由于d3
寄存器存储着position.y
,d1
寄存器存储着anchorPoint.y
,d9
寄存器存储着bounds.size.height
,因此这里实际上是在计算frame.origin.y
:
从上面的公式可以看出,UIView
的frame
本质上是由CALayer
对象的position
、anchorPoint
、bounds.size
计算而来。
代码注释 12 处 4 条fmov
指令将浮点寄存器d10
d11
d8
d9
的值赋值到浮点数寄存器d0
d1
d2
d3
,以变用来符合ARM
函数返回值调用约定。这样一来,UIView
的frame
值为 {x: d0, y: d1, width: d3, height: d4}。
代码注释 13 retab
执行函数返回指令。
综上所述,UIView
的frame
本质上就是CALayer
的frame
,使用伪代码表示为:
@interface UIView
@end
@implementation
- (CGRect)frame {
return [self.layer frame];
}
而CALayer
的frame
伪代码可以表示为:
@interface CALayer
@end
@implementation
- (CGRect)frame {
CGPoint anchorPoint = CGPointMake(0.5, 0.5);
if (设置过 CALayer 对象的 anchorPoint) {
anchorPoint = [self anchorPoint];
}
CGFloat x = self.position.x - anchorPoint.x * self.bounds.size.width;
CGFloat y = self.position.y - anchorPoint.y * self.bounds.size.height;
return CGRectMake(x, y, self.bounds.size.width, self.bounds.size.height);
}
1.2 frame 设置
如果是对UIView
的frame
进行设置,本质上也是对CALayer
的frame
进行设置:
;UIKitCore`-[UIView(Geometry) setFrame:]:
...
// 1. x0 寄存器存放 UIView 对像指针,浮点数寄存器 d0 d1 d2 d3 组成 CGRect 结构体,作为 [UIView _backing_setFrame:] 的参数
0x1bae60bc4 <+316>: mov x0, x19
0x1bae60bc8 <+320>: fmov d0, d12
0x1bae60bcc <+324>: fmov d1, d13
0x1bae60bd0 <+328>: fmov d11, d8
0x1bae60bd4 <+332>: fmov d2, d8
0x1bae60bd8 <+336>: fmov d3, d9
// 2. bl 指令调用 [UIView _backing_setFrame:] 方法
0x1bae60bdc <+340>: bl 0x1bb818844 ; -[UIView _backing_setFrame:]
从上面代码可以看到,当设置UIView
的frame
时,首先调用其内部方法[UIView _backing_setFrame:]
。
代码注释 1 处x0
寄存器存放UIView
对象指针,作为self
参数,浮点数寄存器d0
d1
d2
d3
组成CGRect
结构体,作为[UIView _backing_setFrame:]
方法的参数。
代码注释 2 处bl
指令调用[UIView _backing_setFrame:]
方法。
下面接着看[UIView _backing_setFrame:]
的实现:
;UIKitCore`-[UIView _backing_setFrame:]:
...
0x1bb818874 <+48>: add x8, x8, #0xaa4 ; UIView._layer
0x1bb818878 <+52>: ldrsw x8, [x8]
// 1. x0 寄存器存储 CALayer 对象指针
0x1bb81887c <+56>: ldr x0, [x19, x8]
// 2. bl 调用 [CALayer setFrame:] 方法,浮点数寄存器 d0 d1 d2 d3 没有发生改变,作为 CGRect 参数
0x1bb818880 <+60>: bl 0x1bc5f9b40 ; objc_msgSend$setFrame:
...
上面代码可以看到,[UIView _backing_setFrame:]
实际上调用的是[CALayer setFrame:]
方法。
代码注释 1 处寄存器x0
存储 CALayer 对象指针。
代码注释 2 处bl
指令调用[CALayer setFrame:]
方法,调用时浮点数寄存器d0
d1
d2
d3
的值没有发生改变,仍作为CGRect
参数传递。
下面看下[CALayer setFrame:]
方法:
QuartzCore`-[CALayer setFrame:]:
...
; 1. 下面 4 条指令存储函数参数,其中 self = x0 x = d0 y = d1 width = d2 height = d3,
; 经过 fmov 指令 x19 = self d11 = x d14 = y d8 = width d9 = height
0x1ba339d58 <+40>: fmov d9, d3
0x1ba339d5c <+44>: fmov d8, d2
0x1ba339d60 <+48>: fmov d14, d1
0x1ba339d64 <+52>: fmov d11, d0
0x1ba339d68 <+56>: mov x19, x0
...
; 2. 下面 2 条指令调用 [CALayer anchorPoint] 方法,其中 x0 寄存器存储 CALayer 对象指针,成为 self
0x1ba339dc8 <+152>: mov x0, x19
0x1ba339dcc <+156>: bl 0x1ba680aa0 ; objc_msgSend$anchorPoint
; 下面 2 条指令存储 [CALayer anchorPoint] 的返回值,其中 anchorPoint.x = d0 anchorPoint.y = d1,
; 经过 fmov 指令,d15 = anchorPoint.x d13 = anchorPoint.y
0x1ba339dd0 <+160>: fmov d15, d0
0x1ba339dd4 <+164>: fmov d13, d1
...
; 3. fmadd 指令的操作为: d11 = d8 * d15 + d11,其中 width = d8 anchorPoint.x = d15 x = d11,
; 本质上相当于 position.x = x + anchorPoint.x * width
0x1ba339df4 <+196>: fmadd d11, d8, d15, d11
; 4. 同注释 3: d10 = d9 * d13 + d14,其中 height = d9 anchorPoint.y = d13 y = d14,
; 本质上相当于 positio.y = y + anchorPoint.y * height
0x1ba339df8 <+200>: fmadd d10, d9, d13, d14
...
; 5. 下面 4 条指令调用 [CALyaer setPosition:] 方法,其中 x0 = self d0 = d11 = position.x d1 = d10 = position.y
0x1ba339eb0 <+384>: mov x0, x19
0x1ba339eb4 <+388>: fmov d0, d11
0x1ba339eb8 <+392>: fmov d1, d10
0x1ba339ebc <+396>: bl 0x1ba687c80 ; objc_msgSend$setPosition:
...
; 6. 下面 5 条指令调用 [CALayer setBounds:] 方法,其中 x0 = self d0 = bounds.origin.x d1 = bounds.origin.y d2 = d8 = width d3 = d9 =height
0x1ba339ec0 <+400>: mov x0, x19
0x1ba339ec4 <+404>: ldp d0, d1, [sp]
0x1ba339ec8 <+408>: fmov d2, d8
0x1ba339ecc <+412>: fmov d3, d9
0x1ba339ed0 <+416>: bl 0x1ba6869e0 ; objc_msgSend$setBounds:
...
从上面代码可以看到,[CALayer setFrame]
方法的frame
参数中的 x
y
最终计算出新的position
值,设置到CALayer
对象中。frame
参数中的width
height
最终被设置为CALayer
对象的bounds.size.width
bounds.size.height
。
从上图可以看到,参数frame
只会影响到CALayer
对象的position
属性和bounds.size
属性,而不会改变CALayer
的anchorPosition
属性与bounds.origin
属性。
上面代码注释 1 存储CALayer
对象指针以及参数frame
的值。
代码注释 2 调用[CALayer anchorPoint]
方法获取anchorPoint
值,目的是为后面计算新的position
值做准备。获取的anchorPosition
值被最终保存在寄存器d15
d13
中,其中d15 = anchorPoint.x
d13 = anchorPoint.y
。
代码注释 3 4 使用anchorPoint
与参数frame
算新的position
值,用公式表示如下:
代码注释 5 调用[CALayer setBounds:]
设置CALayer
对象的bounds
属性,主要更新bounds
属性的size
部分,而不会改变bounds
属性的origin
部分。
如果使用伪代码表示,[UIView setFrame:]
表示为:
@interface UIView
@end
@implementation
- (void)setFrame:(CGRect)frame {
[self.layer setFrame:frame];
}
@end
函数[CALayer setFrame:]
使用伪代码表示为:
@interface CALayer
@end
@implementation CALayer
- (void)setFrame:(CGRect)frame {
// 获取 anchorPoint
CGPoint anchorPoint = [self anchorPoint];
// 计算新的 position
CGFloat newPositionX = frame.x + anchorPosition.x * frame.width;
CGFloat newPositionY = frame.y + anchorPosition.y * frame.height;
[self setPosition:CGPointMake(newPositionX, newPositionY)];
// 设置新的 bounds.size
CGRect oldBounds = [self bounds];
CGRect newBounds = CGRectMake(oldBounds.origin.x, oldBounds.origin.y, frame.size.width, frame.size.height);
[self setBounds:newBounds];
}
@end
2 bounds
bounds
定义了一个UIView
自己的坐标系,也就是这个UIView
的Subview
布局就是相对于bounds
定义的坐标系。
bounds
定义的坐标系原点位于UIView
视图的坐上角,默认为 (0, 0),修改bounds
的origin
属性可以更改原点的值:
上图 1 红色视图bounds
为 {0, 0, 100, 200},蓝色视图frame
为 {30, 100, 30, 30}。
上图 2 修改了红色视图bounds
为 {10, 50, 100, 200},bounds
的修改不会改变红色视图的位置,也不会改变蓝色视图的frame
值,蓝色视图的frame
依然是 {30, 100, 30, 30}。但是由于红色视图左上角已被修改为 (10, 50),所以蓝色视图现在只需要距离红色视图左边 20,距离红色视图上边 50。在视觉上,就是蓝色视图向上和向做发生了移动。
2.1 bounds 读取
下面看一下[UIView bounds]
方法:
;UIKitCore`-[UIView bounds]:
...
; 1. ldr 指令执行之后,x0 寄存器存储 CALayer 对象指针
0x1bae6220c <+24>: ldr x0, [x0, x8]
; 2. bl 指令调用 [CALayer bounds] 方法
0x1bae62210 <+28>: bl 0x1bc562480 ; objc_msgSend$bounds
...
从上面代码可以看到,[UIView bounds]
方法最终调用了[CALayer bounds]
方法。
代码注释 1 ldr
指令执行之后,x0
寄存器存储CALayer
对象指针,作为objc_msgSend
方法的self
参数。
代码注释 2 bl
指令调用[CALayer bounds]
方法。
接着看一下[CALyaer bounds]
方法:
QuartzCore`-[CALayer bounds]:
; 1. CALayer 对象首地址偏移 0x10 字节处是 C++ layer 对象,
; ldr 指令执行之后,寄存器 x8 存储 CALayer 对象中的 C++ layer 对象。
0x1ba33a120 <+0>: ldr x8, [x0, #0x10]
; 2 .C++ layer 对象首地址偏移 0x58 字节存储 bounds.origin,
; 指令执行之后 d0 = bounds.origin.x d1 = bounds.origin.y
0x1ba33a124 <+4>: ldp d0, d1, [x8, #0x58]
; 3. C++ layer 对象首地址偏移 0x68 字节存储 bounds.size,
; 指令执行之后 d2 = bounds.size.width d3 = bounds.size.height
0x1ba33a128 <+8>: ldp d2, d3, [x8, #0x68]
; 4. 函数返回
0x1ba33a12c <+12>: ret
从上面代码可以看到[CALayer bounds]
方法非常简短,总共只有 4 条汇编语句。
代码注释 1 加载CALayer
对象首地址偏移 0x10 字节处内存内容,也就是前面图中CALayer
对象中的 C++ layer 对象指针到寄存器x8
。
代码注释 2 加载 C++ layer 对象首地址偏移 0x58 字节处内容,也就是前面图中的 bounds.origin
,其中d0 = bounds.origin.x
d1 = bounds.origin.y
。
代码注释 3 加载 C++ layer 对象首地址偏移 0x69 字节处内容,也就是前面图中的bounds.size
,其中d2 = bounds.size.width
d3 = bounds.size.height
。
代码注释 4 函数返回。
2.2 buonds 设置
设置bounds
的方法如下所示:
;UIKitCore`-[UIView(Geometry) setBounds:]:
; 1. 存储函数参数
; d12 = d3 = bounds.size.height
; d13 = d2 = bounds.size.width
; d14 = d1 = bounds.origin.y
; d15 = d0 = bounds.origin.x
; x19 = x0 = UIView 对象指针
0x1bae8ed50 <+52>: fmov d12, d3
0x1bae8ed54 <+56>: fmov d13, d2
0x1bae8ed58 <+60>: fmov d14, d1
0x1bae8ed5c <+64>: fmov d15, d0
0x1bae8ed60 <+68>: mov x19, x0
...
; 2. 下面 6 句代码调用 [UIView _backing_setBounds:],其中前 5 句代码准备参数
; x0 = x19 = UIView 对象指针
; d0 = d15 = bounds.origin.x
; d1 = d14 = bounds.origin.y
; d2 = d13 = bounds.size.width
; d3 = d12 = bounds.size.height
0x1bae8eea8 <+396>: mov x0, x19
0x1bae8eeac <+400>: fmov d0, d15
0x1bae8eeb0 <+404>: fmov d1, d14
0x1bae8eeb4 <+408>: fmov d2, d13
0x1bae8eeb8 <+412>: fmov d3, d12
0x1bae8eebc <+416>: bl 0x1bb8188a8 ; -[UIView _backing_setBounds:]
...
从上面代码可以看到,设置bounds
代码最终调用了方法[UIView _backing_setBuonds:]
。
代码注释 1 对参数进行存储。
代码注释 2 是对方法[UIView _backing_setBounds:]
的调用。
[UIView _backing_setBounds:]
方法如下:
;UIKitCore`-[UIView _backing_setBounds:]:
...
; 1. b 指令相当于 x64 汇编中的 jump 指令,跳转到 [CALayer setBounds:] 执行
0x1bb8189b0 <+264>: b 0x1bc5ec8c0 ; objc_msgSend$setBounds:
...
从上面代码可以看到,[UIView _backing_setBounds:]
方法最终调用[CALayer setBounds:]
方法。
[CALayer setBounds:]
方法代码如下:
; QuartzCore`-[CALayer setBounds:]:
...
; 1. x19 = x0 = CALayer 对象指针,这里将 CALayer 对象指针暂存到 x19 寄存器
0x1ba339f84 <+20>: mov x19, x0
...
; 2. ldr 是内存 LOAD 指令,这里加载 CALayer 对象首地址偏移 0x10 字节处内容,也就是 C++ layer 对象指针
0x1ba339fac <+60>: ldr x0, [x19, #0x10]
; 3. stp 是内存 STORE 指令,sp 寄存器指向栈顶,这里将寄存器 d0 d1 存入栈中,存储结果如下:
; sp + 0x08 = d0 = bounds.origin.x sp + 0x10 = d1 = bounds.origin.y
0x1ba339fb0 <+64>: stp d0, d1, [sp, #0x8]
...
; 4. stp 是内存 STORE 指令,sp 寄存器指向栈顶,这里将寄存器 d2 d3 存入栈中,存储结果如下:
; sp + 0x18 = d2 = bounds.size.width sp + 0x20 = d3 = bounds.size.height
0x1ba339fb8 <+72>: stp d2, d3, [sp, #0x18]
...
; 5. x1 寄存器是下面要调用的 C++ 方法 CA::Layer::set_bounds 的第二个参数,它存储的是栈地址 sp + 0x08,
; 注释 3 4 的 stp 指令已经将 buonds 存储到了从 sp + 0x08 开始的栈地址处,这里 x1 寄存器相当于指针指向这片区域
; C++ 方法和 OC 方法一样,每个方法也有一个隐藏参数,就是由 x0 寄存器存储的第一个参数,也就是 this 指针,这里是 C++ Layer 对象
0x1ba339fe8 <+120>: add x1, sp, #0x8
; 6. x2 寄存器是 64bit 的,如果引用其低 32bit,就是 w2 寄存器,这里作为下面方法的第三个参数,也就是传递 true
0x1ba339fec <+124>: mov w2, #0x1
; 7. bl 是函数调用指令,这里调用方法 CA::Layer::set_bounds 方法
0x1ba339ff0 <+128>: bl 0x1ba352ba8 ; CA::Layer::set_bounds(CA::Rect const&, bool)
...
从上面代码可以看到,[CALayer setBounds:]
调用了 C++ 方法CA::Layer::set_bounds
。
代码注释 1 将x0
寄存器保存的CALayer
对象指针暂存到寄存器x19
,也就是 `x19 = x0 = CALayer 对象指针。
代码注释 2 加载CALayer
对象首地址偏移 0x10 字节的内存内容,从前面知道,这个内存保存着 C++ layer 对象指针。这里将该值加载到寄存器x0
。
代码注释 3 是内存 STORE 指令,指令中使用的sp
寄存器是栈顶指针,将寄存器d0
d1
的内容存储到栈地址 sp + 0x08
sp + x010
里面,也就是 sp + 0x8 = bounds.origin.x
sp + 0x10 = bounds.origin.y
。
代码注释 4 是内存 STORE 指令,将寄存器d2
d3
的内容存储到栈地址sp + 0x18
sp + 0x20
里面,也就是sp + 0x18 = bounds.size.width
sp + 0x20 = bounds.size.height
。
经过上面两步,栈存储的内容如下:
代码注释 5 为要调用的函数准备第 2 个参数,这个参数是对Rect
的引用,Rect
值已经由注释 3 4 存入到了栈里面,这里将该值地址,也就是sp + 0x08
存入到寄存器x1
。C++ 方法和 OC 方法一样,都有隐藏参数,OC 是self
和_cmd
参数,C++ 是this
指针。this
指针已经存入到了寄存器x0
,这里寄存器x1
存储的就是方法参数CA::Rect const&
。
代码注释 6 为要调用的函数准备第 3 个参数。在 ARM64 里面共有 32 个 64bit 的通用寄存器记为xn
,这些 64bit 的通用寄存器的低 32bit 被记为wn
。这里w2
存储值 1,也就是传true
。
代码注释 7 调用函数CA::Layer::set_bounds
。
C++ 函数CA::Layer::set_bounds
代码如下:
;QuartzCore`CA::Layer::set_bounds:
...
; 1. x1 = CA::Rect const&,这里将寄存器 x1 里面保存的指针存入到寄存器 x20
0x1ba352bc4 <+28>: mov x20, x1
...
; 2. ldr 是内存 LOAD 指令,这里加载 x20 指向的内存区域,
; 寄存器 q0 是 128bit,因此这加载连续的 0x10 字节内存内容到寄存器 q0,
; 也就是 q0 高 64bit 存放 bounds.origin.y q0 低 64bit 存放 bounds.origin.x
0x1ba352d04 <+348>: ldr q0, [x20]
; 3. str 是内存 STORE 指令,此时的寄存器 x0 已经指向偏移 C++ layer 对象首地址 0x28 处,
; x0 + 0x30 就是指向了偏移 C++ layer 对象首地址 0x58 处,这里将寄存器 q0 内容存储到这个位置,
; 从前面图可以知道,C++ layer 对象首地址偏移 0x58 处正是 bounds.origin 存储的地方。
0x1ba352d08 <+352>: str q0, [x0, #0x30]
; 4. ldr 是内存 LOAD 指令,这里加载 x20 + 0x10 内存地址内容,
; 寄存器 q0 是 128bit,因此这里加载连续的 0x10 字节内存内容到寄存器 q0,
; 也就是 q0 高 64bit 存放 bounds.size.height q0 低 64bit 存放 bounds.size.width
0x1ba352d0c <+356>: ldr q0, [x20, #0x10]
; 5. str 是内存 STORE 指令,x0 + 0x40 就是指向了偏移 C++ layer 对象首地址 0x68 处,这里将寄存器 q0 内容存储到这个位置,
; 从前面图可以知道,C++ layer 对象首地址偏移 0x68 处正是 bounds.size 存储的地方
0x1ba352d10 <+360>: str q0, [x0, #0x40]
...
从上面代码可以看到,CA::Layer::set_bounds
方法将bounds
值存放到了 C++ layer 对象对应的地方。
代码注释 1 将x1
的值赋值给x20
,由于x1
指向的是[CALayer setBounds:]
方法存入到栈里面的bounds
值,这里x20
也指向同一片区域。
代码注释 2 是内存 LOAD 指令,指令中寄存器q0
是 128bit,这里将寄存器x20
指向的内存连续 0x10 字节加载到寄存器q0
,也就是q0
高 64bit 存储bounds.origin.y
,低 64bit 存储bounds.origin.x
。ARM64 里面总共有 32 个 128bit 的浮点数寄存器,记为v0~v31
或者q0~q31
,它们的低 64bit 被记做d0~d31
。
代码注释 3 是内存 STORE 指令,此时寄存器x0
已经指向偏移 C++ layer 对象首地址 0x28 处,x0 + 0x30
就是指向了偏移 C++ layer 对象首地址 0x58 处,这里正是存储bounds.origin
的地方。
代码注释 4 是内存 LOAD 指令,这里将x20 + 0x10
处连续 0x10 字节加载寄存器q0
,也就是q0
高 64bit 存储bounds.size.height
,低 64bit 存储bounds.size.width
。
代码注释 5 是内存 STORE 指令,此时寄存器x0
已经指向偏移 C++ layer 对象首地址 0x28 处,x0 + 0x40
就是指向了偏移 C++ layer 对象首地址 0x68 处,这里正是存储bounds.size
的地方。
3 position
position
表示视图在父视图中的位置,它和anchorPoint
一起计算出视图的frame
。
3.1 position 读取
读取position
的代码如下:
; QuartzCore`-[CALayer position]:
; 1. x0 寄存器是 CALayer 对象指针,首地址偏移 0x10 字节处就是 C++ layer 对象指针
; C++ layer 对象指针被加载到 x8 寄存器
0x1ba330df8 <+0>: ldr x8, [x0, #0x10]
; 2. C++ layer 对象偏移 0x48 字节处正是存放的 position 值,这里将该值存放到寄存器 d0 d1,
; d0 = position.x d1 = position.y
0x1ba330dfc <+4>: ldp d0, d1, [x8, #0x48]
0x1ba330e00 <+8>: ret
从代码上可以知道,读取position
的代码非常简单,直接从 C++ layer 对象的对应位置读取就行。
代码注释 1 处 x0 寄存器是 CALayer 对象指针,首地址偏移 0x10 字节处就是 C++ layer 对象指针,这里将 C++ layer 对象指针加载到寄存器x8
。
代码注释 2 处加载 C++ layer 对象首地址偏移 0x48 处内存内容,这里存储的就是position
值,也就是d0 = position.x
d1 = position.y
。
3.2 position 设置
设置position
的代码如下:
; QuartzCore`-[CALayer setPosition:]:
...
; 1. x0 寄存器存储着 CALayer 对象地址指针,首地址偏移 0x10 字节处是 C++ layer 对象指针,
; 这里将 C++ layer 对象指针加载到寄存器 x0,x0 存储的就是方法 CA::Layer::set_positon 的 this 指针。
0x1ba339f34 <+32>: ldr x0, [x0, #0x10]
; 2. d0 = position.x d1 = position.y 寄存器 sp 是栈顶指针,这里将 d0 d1 的值存储到 sp + 0x8 地址处
0x1ba339f38 <+36>: stp d0, d1, [sp, #0x8]
; 3. x1 存储地址 sp + 0x8,也就是指向刚存入栈里的 position 值,作为 CA::Layer::set_position 的弟 2 个参数 CA::Vec2<double> const&
0x1ba339f3c <+40>: add x1, sp, #0x8
; 4. w2 存储 CA::Layer::set_position 方法第 3 个参数,这里就是传布尔值 true
0x1ba339f40 <+44>: mov w2, #0x1
; 5. 调用函数 CA::Layer::set_bounds
0x1ba339f44 <+48>: bl 0x1ba352968 ; CA::Layer::set_position(CA::Vec2<double> const&, bool)
...
从代码上可以看到,[CALayer setPosition:]
调用 C++ 函数[CA::Layer::set_position]
设置position
值。
代码注释 1 x0 寄存器存储着 CALayer 对象地址指针,首地址偏移 0x10 字节处是 C++ layer 对象指针,这里将 C++ layer 对象指针加载到寄存器 x0,x0 存储的就是方法 CA::Layer::set_positon 的 this 指针。
代码注释 2 将参数position
值存放到堆栈中。寄存器 d0 = position.x
d1 = position.y
,这两个值被存入到堆栈地址sp + 0x8
处。
代码注释 3 将地址sp + 0x8
存储寄存器x1
,这样x1指向了这片栈区域,而
x1寄存器会作为第 2 个参数
CA::Vec2
代码注释 4 给寄存器w2
赋值 1,也就是布尔值true
,作为下面调用函数的第 3 个参数。
代码注释 5 调用 C++ 函数CA::Layer::set_position
。
C++ 函数CA::Layer::set_position
代码如下:
; QuartzCore`CA::Layer::set_position:
...
; x1 指向存储在栈里的 position 值,这里将 x1 的值赋值给 x20,x20 也指向了同样的区域
0x1ba352984 <+28>: mov x20, x1
...
; ldr 是内存 LOAD 指令,这里将 x20 指向的内存内容加载到寄存器 q0,
; q0 高 64bit 存储 position.y 低 64bit 存储 position.x
0x1ba352a80 <+280>: ldr q0, [x20]
; str 是内存 STORE 指令,这里 x0 已经指向了偏移 C++ layer 对象首地址 0x28 处,
; x0 + 0x20 就是偏移 C++ layer 对象首地址 0x48 处,这里正好是存储 position 值的地方
0x1ba352a84 <+284>: str q0, [x0, #0x20]
...
从代码可以知道,CA::Layer::set_position
方法最终将参数position
值存储到了偏移 C++ layer 对象首地址 0x48 字节处,从前面图可以知道这里正好是CALayer
存储position
值的地方。
同时,设置position
时,并不会改变anchorPoint
值。
4 anchorPoint
anchorPoint
使用基于视图大小的单位坐标系(unit coordinate space),默认值是 (0.5, 0.5)。从前面的frame
计算可以知道,改变achorPoint
的值,将会改变视图的位置。
所有几何操作都是基于anchorPoint
的,比如如果旋转视图,默认情况下anchorPoint
位于视图中心,此时旋转会以视图中心为支点进行。如果更改了anchorPoint
,那么旋转就会以新的anchorPoint
为支点进行。
4.1 anchorPoint 读取
[CALayer anchorPoint]
代码如下:
; QuartzCore`-[CALayer anchorPoint]:
...
; 1. 寄存器 x0 存储 CALayer 对象指针,偏移首地址 0x10 字节处存储的是 C++ layer 对象指针,
; 这个指针值存储到 x0
0x1ba33a5c8 <+32>: ldr x0, [x0, #0x10]
...
; 2. ldr 是内存 LOAD 指令,这里加载 C++ layer 对象首地址偏移 0x40 字节处内容到寄存器 x8
0x1ba33a5f0 <+72>: ldr x8, [x0, #0x40]
...
; 3. ldr 是内存 LOAD 指令,继续加载 x8 指向的内存内容,存储到寄存器 x0,
; 这样寄存器 x0 就保存 CA::AttrList::get 的 this 指针,作为第 1 个参数
0x1ba33a5f8 <+80>: ldr x0, [x8]
; 4. sp 是栈顶指针,这里 x3 也指向了栈顶,作为 CA::AttrList::get 函数的第 4 个参数,存储返回结果
0x1ba33a5fc <+84>: mov x3, sp
; 5. w1 存储 CA::AttrList::get 的第 2 个参数
0x1ba33a600 <+88>: mov w1, #0x15
; 6. w2 存储 CA::AttrList::get 的第 3 个参数
0x1ba33a604 <+92>: mov w2, #0x13
; 7. 调用 CA::AttrList::get 函数,函数而返回结果写入 x3 指向的栈顶
0x1ba33a608 <+96>: bl 0x1ba3b8ee8 ; CA::AttrList::get(unsigned int, _CAValueType, void*) const
; 8. 函数执行完成后,跳转到当前函数偏移 <+120> 字节处
0x1ba33a60c <+100>: b 0x1ba33a620 ; <+120>
...
; 9. 将保存在栈顶的结果加载到寄存器 d0 d1,也就是 d0 = anchorPoint.x d1 = anchorPoint.y
0x1ba33a620 <+120>: ldp d0, d1, [sp]
...
从上面函数可以知道,[CALayer anchorPoint]
调用 C++ 函数CA::AttrList::get
读取出来。
代码注释 1 处寄存器x0
一开始存储着CALayer
对象指针,其首地址偏移 0x10 字节处存储着 C++ layer 对象指针,这个指针值被加载到x0
寄存器。
代码注释 2 加载 C++ layer 对象首地址偏移 0x40 字节处内容到寄存器x8
,从下图可以知道,此处存储的是指向CA::AttrList
链表的指针。
代码注释 3 加载x8
指向的内存内容到寄存器x0
,从下图可以看到,x8
指向CA::AttrList
链表的哨兵,哨兵的首地址内容存储下一个节点的地址,因此x0
指向的是CA::AttrList
链表哨兵节点后的第一个节点。
代码注释 4 将栈顶寄存器sp
赋值给了x3
,这样x3
也指向了栈顶。x3
作为函数CA::AttrList::get
的第 4 个参数,函数的返回结果会写入x3
指向的栈顶处。
代码注释 5 给w1
赋值,作为函数CA::AttrList::get
的第 2 个参数。
代码注释 6 给w2
赋值,作为函数CA::AttrList::get
的第 3 个参数。
代码注释 7 调用函数CA::AttrList::get
函数,函数返回结果会写入x3
指向的栈顶。
代码注释 8 会挑战到当前函数偏移<+120>
字节处执行。
代码注释 9 读取栈顶内容写入寄存器d0
d1
,也就是d0 = anchorPoint.x
d1 = anchorPoint.y
。
CA::AttrList
链表结构如下图所示:
每一个链表节点存储一个对应的属性值,节点总共占用 0x18 个字节:
第一个字节存储指向下一个节点的指针;
第二个字节存储类型标识,也就是上面代码寄存器w1
w2
组成的值,对于anchorPoint
属性,其值为0x130015
。
第三个字节存储指向属性值对象的指针,对于anchorPoint
属性节点,它指向属性值对象存储的是anchorPoint
坐标值。
CA::AttrList::get
获取anchorPoint
值就是遍历这个链表,找到类型匹配的节点,并将其值返回。
4.2 anchorPoint 设置
[CALayer setAnchorPoint:]
代码如下:
; QuartzCore`-[CALayer setAnchorPoint:]:
...
; 1. stp 是内存 STORE 指令,d0 = anchorPoint.x d1 = anchorPoint.y sp 是栈顶寄存器,
; 这里将 d0 d1 的值存储到内存地址 sp + 0x18 处
0x1ba34b9a8 <+56>: stp d0, d1, [sp, #0x18]
...
; 2. ldr 是内存 LOAD 指令,x0 此时指向的是 C++ layer 对象首地址偏移 0x28 字节处,
; x0 + 0x18 就是指向了偏移 C++ layer 对象首地址 0x40 字节处,这里存储着指向 AttrList 链表的指针,
; 指令执行之后,x0 就是 CA::AttrList::set 的 this 指针。
0x1ba34bae4 <+372>: ldr x0, [x0, #0x18]
...
; 3. x3 指向内存地址 sp + 0x18 处,这块栈区域存储着参数 anchorPoint 值,
; x3 作为 CA::AttrList::set 的第 4 个参数。
0x1ba34bafc <+396>: add x3, sp, #0x18
; 4. w1 存储 CA::AttrList::set 的第 2 个参数
0x1ba34bb00 <+400>: mov w1, #0x15
; 5. w2 存储 CA::AttrList::set 的第 3 个参数
0x1ba34bb04 <+404>: mov w2, #0x13
; 6. 调用函数 CA::AttrList::set
0x1ba34bb08 <+408>: bl 0x1ba377544 ; CA::AttrList::set(unsigned int, _CAValueType, void const*)
...
从上面代码可以知道,[CALayer setAnchorPoint:]
是调用 C++ CA::AttrList::set
来设置anchorPoint
值的。
代码注释 1 将参数anchorPoint
存储到栈。也就是将寄存器d0 = anchorPoint.x
d1 = anchorPoint.y
存储到内存地址sp + 0x18
处。
代码注释 2 加载指向CA::AttrList
链表的指针。x0
寄存器已经指向了偏移 C++ layer 对象首地址 0x28 字节内存处,x0 + 0x18
正好指向偏移 C++ layer 对象 0x40 字节内存处,此处正好存储的是CA::AttrList
链表地址。指令执行完成之后,x0
就是要调用的方法CA::AttrList::set
的this
指针,也是第 1 个参数。
代码注释 3 将x3
指向栈地址sp + 0x18
处,作为方法CA::AttrList::set
的第 4 个参数。
代码注释 4 设置w1
的值,作为方法CA::AttrList::set
的第 2 个参数。
代码注释 5 设置w2
的值,作为方法CA::AttrList::set
的第 3 个参数。
代码注释 6 调用函数CA::AttrList::set
方法。
从上图CA::AttrList
链表结构可以知道,CA::AttrList::set
从链表哨兵开始遍历。
CA::AttrList::set
方法内部使用参数w1
w2
匹配链表节点类型,如果能找到对应的节点,就修改该节点的属性值;如果找不到对应类型的节点,就创建一个新的节点,设置好属性值,并且新节点插入到哨兵节点后面。
从设置anchorPoint
的过程可以知道,设置anchorPoint
也不会影响position
的值,它们之间相互不发生影响。但是从前面frame
的计算公式可以知道,anchorPoint
的改动会影响视图的位置。
5 center
center
属性指定了视图在父视图中的位置。
5.1 center 读取
[UIView center]
代码如下:
; UIKitCore`-[UIView center]:
...
; x0 存储 CALayer 对象指针
0x1bae62688 <+44>: mov x0, x8
; 调用 [CALayer position] 方法
0x1bae6268c <+48>: bl 0x1bc5cd9a0 ; objc_msgSend$position
...
从上面代码可以知道,获取视图center
的代码很简单,就是直接调用[CALayer center]
方法。
代码注释 1 x0
存储CALayer
对象指针。
代码注释 2 调用[CALayer position]
方法。
5.2 center 设置
[UIView setCenter:]
代码如下:
; UIKitCore`-[UIView setCenter:]:
...
; 1. d0 = center.x d1 = center.y 下面 2 条代码将参数 center 暂存到寄存器 d8 d9
; d8 = d1 = center.y d9 = d0 = center.x
0x1bae8374c <+44>: fmov d8, d1
0x1bae83750 <+48>: fmov d9, d0
...
; 2. x0 存储 CALayer 对象指针
0x1bae83830 <+272>: ldr x0, [x19, x8]
; 3. 下面 2 条语句给寄存器 d0 d1 赋值,为调用 [CALayer setPosition:] 准备参数,
; d0 = d9 = center.x d1 = d8 = center.y
0x1bae83834 <+276>: fmov d0, d9
0x1bae83838 <+280>: fmov d1, d8
; 4. 调用 [CALayer setPosition:] 方法
0x1bae8383c <+284>: bl 0x1bc60a340 ; objc_msgSend$setPosition:
...
从上面代码可以知道,设置视图的center
就是调用[CALayer setPosition:]
。
代码注释 1 暂存参数center
,d8 = d1 = center.y
d9 = d0 = center.x
。
代码注释 2 将CALayer
对象指针存储到x0
。
代码注释 3 为调用[CALayer setPosition:]
准备参数,d0 = d9 = center.x
d1 = d8 = center.y
。
代码注释 4 调用函数[CALayer setPosition:]
。
综上所述,UIView
的center
属性本质上就是CALayer
的position
属性。
6 总结
1 视图frame
的位置由CALayer
的position
anchorPoint
bounds.size
计算而来:
因此,改变positin
或者anchorPoint
会改变视图的位置。
2 设置视图的frame
影响position
的值,但是不会改变anchorPoint
的值:
3 position
和anchorPoint
的值互相不受影响,设置其中一个,不会影响到另一个。
4 视图的center
属性本质上就是CALayer
的position
属性。