iOS 中UIView
的屬性:frame
、bounds
、center
以及CALayer
的屬性:position
、anchorPoint
與檢視的位置與大小相關,理解這些屬性是進行 iOS 檢視編碼的基礎。
下面從彙編角度看一下這些屬性的實現以及相互關係。
1 frame
frame
定義了檢視在父檢視座標系下的位置與大小。
上圖中紅色UIView
的frame
為 {x: 50, y: 50, width: 100, height: 200}。
如果訪問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);
}
同理,如果是對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。在視覺上,就是藍色檢視向上和向做發生了移動。
下面看一下[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 函式返回。
設定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 裡面,x2
暫存器是 64bit 的,如果想引用其低 32bit,就使用w2
。這裡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
。
讀取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
。
設定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
為支點進行。
[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
值就是遍歷這個連結串列,找到型別匹配的節點,並將其值返回。
[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
屬性指定了檢視在父檢視中的位置。
[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]
方法。
[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
屬性。