UIKit Inside: frame bounds position anchorPoint center

iOS 中UIView的属性:frameboundscenter以及CALayer的属性:positionanchorPoint与视图的位置与大小相关,理解这些属性是进行 iOS 视图编码的基础。

下面从汇编角度看一下这些属性的实现以及相互关系。

1 frame

frame定义了视图在父视图坐标系下的位置与大小。

上图中红色UIViewframe为 {x: 50, y: 50, width: 100, height: 200}。

如果访问viewframe属性,汇编代码如下:

armasm 复制代码
;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]

从代码可以看到,访问UIViewframe属性,实际上就是访问UIView对应的CALayerframe属性。CALayerframe汇编代码如下:

armasm 复制代码
;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)

方法_ivarDescriptionNSObject的私有方法,任何一个继承自NSObject的对象,都可以使用它输出自己的实例变量成员,包括继承过来的。从上面的输出可以看到,CALayer对象首地址偏移0x10字节处是一个void指针,它指向一个名为layer的对象:

这个layer对象很重要,从后面可以知道,layer对象偏移0x48处存储着CALayerposition值,偏移0x58处存储着CALayer.bounds.origin值,偏移0x68处存储着CALayer.bounds.size值:

代码注释 3 处tbnz是一个测试跳转指令,如果CALayer设置过anchorPoint,就跳转到当前代码偏移<+112>字节处执行。

代码注释 4 处是如果没有设置过CALayer对象的anchorPoint属性,就使用默认的值 (0.5, 0.5),默认值被存储在浮点数寄存器d0d1中。

代码注释 5 b指令是一个跳转指令,获取到默认的anchorPoint值之后,就跳转到当前代码偏移<+124>字节处执行。

代码注释 6 7 处正是注释 3 处跳转过来要执行的代码,是调用[CALayer anchorPoint]方法去获取设置的anchorPoint值,方法返回的结果会被存储在浮点数寄存器d0d1中。

总之,经过代码注释 3 4 5 6 7处的代码,浮点寄存器d0d1已经存储了CALayer对象的anchorPoint属性值,其中d0存储anchorPoint.xd1存储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.xd8寄存器存储着bounds.size.width,因此这里实际上是在计算frame.origin.x:

\[frame.origin.x = position.x - anchorPoint.x * bounds.size.width \]

代码注释 11 同理,相当于:\(d11 = d3 - d1 * d9\)。由于d3寄存器存储着position.yd1寄存器存储着anchorPoint.yd9寄存器存储着bounds.size.height,因此这里实际上是在计算frame.origin.y:

\[frame.origin.y = position.y - anchorPoint.y * bounds.size.height \]

从上面的公式可以看出,UIViewframe本质上是由CALayer对象的positionanchorPointbounds.size计算而来

代码注释 12 处 4 条fmov指令将浮点寄存器d10 d11 d8 d9的值赋值到浮点数寄存器d0 d1 d2 d3,以变用来符合ARM函数返回值调用约定。这样一来,UIViewframe值为 {x: d0, y: d1, width: d3, height: d4}。

代码注释 13 retab执行函数返回指令。

综上所述,UIViewframe本质上就是CALayer的`frame,使用伪代码表示为:

ObjectiveC 复制代码
@interface UIView

@end

@implementation

- (CGRect)frame {
	return [self.layer frame];
}

CALayerframe伪代码可以表示为:

ObjectiveC 复制代码
@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);
}

同理,如果是对UIViewframe进行设置,本质上也是对CALayerframe进行设置:

armasm 复制代码
;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:]

从上面代码可以看到,当设置UIViewframe时,首先调用其内部方法[UIView _backing_setFrame:]

代码注释 1 处x0寄存器存放UIView对象指针,作为self参数,浮点数寄存器d0 d1 d2 d3组成CGRect结构体,作为[UIView _backing_setFrame:]方法的参数。

代码注释 2 处bl指令调用[UIView _backing_setFrame:]方法。

下面接着看[UIView _backing_setFrame:]的实现:

armasm 复制代码
;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:]方法:

armasm 复制代码
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属性,而不会改变CALayeranchorPosition属性与bounds.origin属性。

上面代码注释 1 存储CALayer对象指针以及参数frame的值。

代码注释 2 调用[CALayer anchorPoint]方法获取anchorPoint值,目的是为后面计算新的position值做准备。获取的anchorPosition值被最终保存在寄存器d15 d13中,其中d15 = anchorPoint.x d13 = anchorPoint.y

代码注释 3 4 使用anchorPoint 与参数frame算新的position值,用公式表示如下:

\[position.x = x + anchorPoint.x * width \]

\[position.y = y + anchorPoint.y * height \]

代码注释 5 调用[CALayer setBounds:]设置CALayer对象的bounds属性,主要更新bounds属性的size部分,而不会改变bounds属性的origin部分。

如果使用伪代码表示,[UIView setFrame:]表示为:

ObjectiveC 复制代码
@interface UIView 

@end

@implementation

- (void)setFrame:(CGRect)frame {
	[self.layer setFrame:frame];
}

@end

函数[CALayer setFrame:]使用伪代码表示为:

ObjectiveC 复制代码
@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自己的坐标系,也就是这个UIViewSubview布局就是相对于bounds定义的坐标系。

bounds定义的坐标系原点位于UIView视图的坐上角,默认为 (0, 0),修改boundsorigin属性可以更改原点的值:

上图 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。在视觉上,就是蓝色视图向上和向做发生了移动。

下面看一下[UIView bounds]方法:

armasm 复制代码
;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]方法:

armasm 复制代码
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 函数返回。

设置bounds的方法如下所示:

armasm 复制代码
;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:]方法如下:

armasm 复制代码
;UIKitCore`-[UIView _backing_setBounds:]:
...
; 1. b 指令相当于 x64 汇编中的 jump 指令,跳转到 [CALayer setBounds:] 执行
0x1bb8189b0 <+264>: b      0x1bc5ec8c0   ; objc_msgSend$setBounds:
...

从上面代码可以看到,[UIView _backing_setBounds:]方法最终调用[CALayer setBounds:]方法。

[CALayer setBounds:]方法代码如下:

armasm 复制代码
; 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 里面,x2寄存器是 64bit 的,如果想引用其低 32bit,就使用w2。这里w2存储值 1,也就是传true

代码注释 7 调用函数CA::Layer::set_bounds

C++ 函数CA::Layer::set_bounds代码如下:

armasm 复制代码
;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

读取position的代码如下:

armasm 复制代码
; 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

设置position的代码如下:

armasm 复制代码
; 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 const&`传给下面要调用的 C++ 函数。

代码注释 4 给寄存器w2赋值 1,也就是布尔值true,作为下面调用函数的第 3 个参数。

代码注释 5 调用 C++ 函数CA::Layer::set_position

C++ 函数CA::Layer::set_position代码如下:

armasm 复制代码
; 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为支点进行。

[CALayer anchorPoint]代码如下:

armasm 复制代码
; 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值就是遍历这个链表,找到类型匹配的节点,并将其值返回。

[CALayer setAnchorPoint:]代码如下:

armasm 复制代码
; 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::setthis指针,也是第 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属性指定了视图在父视图中的位置。

[UIView center]代码如下:

armasm 复制代码
; 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]方法。

[UIView setCenter:]代码如下:

armasm 复制代码
; 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 暂存参数centerd8 = d1 = center.y d9 = d0 = center.x

代码注释 2 将CALayer对象指针存储到x0

代码注释 3 为调用[CALayer setPosition:]准备参数,d0 = d9 = center.x d1 = d8 = center.y

代码注释 4 调用函数[CALayer setPosition:]

综上所述,UIViewcenter属性本质上就是CALayerposition属性。

6 总结

1 视图frame的位置由CALayerposition anchorPoint bounds.size计算而来:

\[frame.origin.x = position.x - anchorPoint * bounds.size.width \]

\[frame.origin.y = position.y - anchorPoint * bounds.size.height \]

因此,改变positin或者anchorPoint会改变视图的位置。

2 设置视图的frame影响position的值,但是不会改变anchorPoint的值:

\[position.x = frame.origin.x + anchorPoint.x * frame.size.width \]

\[posotion.y = frame.origin.y + anchorPoint.y * frame.size.height \]

3 positionanchorPoint的值互相不受影响,设置其中一个,不会影响到另一个。

4 视图的center属性本质上就是CALayerposition属性。

相关推荐
昉钰4 个月前
CSS基础学习之元素定位(6)
前端·css·学习·元素定位·position