Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save madordie/dbeac4f6eee4d9659f547594f0f75160 to your computer and use it in GitHub Desktop.
Save madordie/dbeac4f6eee4d9659f547594f0f75160 to your computer and use it in GitHub Desktop.
探究UIScrollView的嵌套时手势作用在谁身上的问题
char __cdecl -[UIScrollViewPanGestureRecognizer _canTransferTrackingFromParentPagingScrollView](UIScrollViewPanGestureRecognizer *self, SEL a2)
{
double v2; // xmm0_8
double v3; // xmm1_8
UIScrollView *v4; // rax
void *v5; // rax
void *v6; // r13
void *v7; // rax
void *v8; // r14
UIView *v9; // rax
__int64 v10; // rbx
bool v11; // r15
char v12; // r12
char v14; // al
_BOOL8 v15; // rcx
_BOOL8 v16; // rdx
double v17; // xmm0_8
double v18; // xmm0_8
__int128 v19; // [rsp+0h] [rbp-90h]
__int128 v20; // [rsp+10h] [rbp-80h]
__int128 v21; // [rsp+20h] [rbp-70h]
__int128 v22; // [rsp+30h] [rbp-60h]
double v23; // [rsp+40h] [rbp-50h]
double v24; // [rsp+48h] [rbp-48h]
double v25; // [rsp+50h] [rbp-40h]
double v26; // [rsp+58h] [rbp-38h]
BOOL v27; // [rsp+60h] [rbp-30h]
char v28; // [rsp+66h] [rbp-2Ah]
char v29; // [rsp+67h] [rbp-29h]
v4 = -[UIScrollViewPanGestureRecognizer scrollView](self, "scrollView");
v5 = (void *)objc_retainAutoreleasedReturnValue(v4);
v6 = v5;
v7 = objc_msgSend(v5, "_parentScrollView");
v8 = (void *)objc_retainAutoreleasedReturnValue(v7);
if ( (unsigned __int8)objc_msgSend(v8, "isPagingEnabled")
&& (unsigned __int8)objc_msgSend(v6, "_transfersScrollToContainer") )
{
v27 = 0;
v9 = -[UIGestureRecognizer view](self, "view");
v10 = objc_retainAutoreleasedReturnValue(v9);
-[UIScrollViewPanGestureRecognizer translationInView:](self, "translationInView:", v10);
v25 = v2;
v26 = v3;
objc_release(v10);
v29 = (unsigned __int64)objc_msgSend(v6, "_canScrollX");
v28 = (unsigned __int64)objc_msgSend(v6, "_canScrollY");
if ( (unsigned __int8)objc_msgSend(v8, "_canScrollX") )
v27 = (unsigned __int8)objc_msgSend(v8, "isScrollEnabled") != 0;
if ( (unsigned __int8)objc_msgSend(v8, "_canScrollY") )
v11 = (unsigned __int8)objc_msgSend(v8, "isScrollEnabled") != 0;
else
v11 = 0;
if ( v6 )
{
objc_msgSend_stret(&v21, (const char *)v6, "contentInset");
objc_msgSend_stret(&v19, (const char *)v6, "bounds");
}
else
{
v2 = 0.0;
v22 = 0LL;
v21 = 0LL;
v20 = 0LL;
v19 = 0LL;
}
objc_msgSend(v6, "contentSize", v19);
v24 = v2;
v23 = v3;
v14 = (unsigned __int64)objc_msgSend(v6, "_currentlyAbuttedContentEdges", v19);
LODWORD(v15) = 0;
LODWORD(v16) = 0;
if ( v29 )
{
v17 = v24 + *((double *)&v21 + 1) + *((double *)&v22 + 1);
v16 = v17 <= *(double *)&v20;
LOBYTE(v16) = v17 > *(double *)&v20;
}
if ( v28 )
{
v18 = v23 + *(double *)&v21 + *(double *)&v22;
v15 = v18 <= *((double *)&v20 + 1);
LOBYTE(v15) = v18 > *((double *)&v20 + 1);
}
if ( v14 & 0xA && v27 && (_DWORD)v16 || (v12 = 0, v14 & 5) && (_DWORD)v15 && v11 == 1 )
{
v12 = 1;
if ( (!v27 || v25 <= 0.0 || !(v14 & 2))
&& (!v27 || v25 >= 0.0 || !(v14 & 8))
&& (v26 <= 0.0 || !(v14 & 1) || v11 != 1) )
{
v12 = 0;
}
}
}
else
{
v12 = 0;
}
objc_release(v8);
objc_release(v6);
return v12;
}
char __cdecl -[UIScrollViewPanGestureRecognizer _shouldTryToBeginWithEvent:](UIScrollViewPanGestureRecognizer *self, SEL a2, id a3)
{
double v3; // xmm0_8
double v4; // xmm1_8
__int64 event; // rbx
UIScrollView *v6; // rax
void *scrollView; // rbx
bool canScrollX; // r14
bool v9; // r14
bool canScrollY; // r15
UIView *v11; // rax
__int64 v12; // rbx
void *v13; // r15
void *v15; // rax
__int64 v16; // rax
__int64 v17; // r14
__int64 v18; // rax
void *v19; // rax
void *v20; // rbx
__int64 v21; // r14
void *v22; // r15
void *v23; // rax
void *v24; // rbx
void *v25; // rax
void *v26; // rbx
double v27; // xmm1_8
void *v28; // rdx
void *v29; // rbx
void *v30; // r14
bool v31; // bl
__int64 v32; // rdx
__int64 v33; // rcx
_BOOL8 v34; // r15
char v35; // r14
bool v36; // zf
char v37; // bl
UIScrollViewPanGestureRecognizer *v38; // [rsp+0h] [rbp-90h]
__objc2_class *v39; // [rsp+8h] [rbp-88h]
UIScrollViewPanGestureRecognizer *v40; // [rsp+10h] [rbp-80h]
NSObject v41; // [rsp+18h] [rbp-78h]
double v42; // [rsp+20h] [rbp-70h]
double v43; // [rsp+28h] [rbp-68h]
double v44; // [rsp+30h] [rbp-60h]
void *v45; // [rsp+38h] [rbp-58h]
__int64 v46; // [rsp+40h] [rbp-50h]
double v47; // [rsp+48h] [rbp-48h]
__int64 v48; // [rsp+50h] [rbp-40h]
void *v49; // [rsp+58h] [rbp-38h]
char v50; // [rsp+66h] [rbp-2Ah]
char v51; // [rsp+67h] [rbp-29h]
event = objc_retain(a3);
v38 = self;
v39 = &OBJC_CLASS___UIScrollViewPanGestureRecognizer;
if ( (unsigned __int8)objc_msgSendSuper2(
&v38,
"_shouldTryToBeginWithEvent:",
event,
self,
&OBJC_CLASS___UIScrollViewPanGestureRecognizer) )
{
v48 = event;
v6 = -[UIScrollViewPanGestureRecognizer scrollView](self, "scrollView");
scrollView = (void *)objc_retainAutoreleasedReturnValue(v6);
if ( (unsigned __int8)objc_msgSend(scrollView, "_canScrollX") )
canScrollX = (unsigned __int8)-[UIPanGestureRecognizer _canPanHorizontally](self, "_canPanHorizontally") != 0;
else
canScrollX = 0;
v49 = scrollView;
if ( (unsigned __int8)objc_msgSend(scrollView, "_canScrollY") )
canScrollY = (unsigned __int8)-[UIPanGestureRecognizer _canPanVertically](self, "_canPanVertically") != 0;
else
canScrollY = 0;
v51 = (unsigned __int64)-[UIPanGestureRecognizer _willScrollX](self, "_willScrollX");
v50 = (unsigned __int64)-[UIPanGestureRecognizer _willScrollY](self, "_willScrollY");
v11 = -[UIGestureRecognizer view](self, "view");
v12 = objc_retainAutoreleasedReturnValue(v11);
-[UIScrollViewPanGestureRecognizer translationInView:](self, "translationInView:", v12);
v42 = v3;
v43 = v4;
objc_release(v12);
if ( (canScrollY || canScrollX) != 1 || !canScrollX && canScrollY && !v50 || !canScrollY && !v51 && canScrollX )
goto LABEL_12;
v13 = v49;
if ( !(*((_BYTE *)self + 432) & 0x10) )
{
LABEL_51:
v9 = (unsigned __int8)objc_msgSend(v13, "_delegateShouldPanGestureTryToBegin") != 0;
event = v48;
goto LABEL_13;
}
v15 = objc_msgSend(v49, "_actingParentScrollView");
v16 = objc_retainAutoreleasedReturnValue(v15);
v17 = v16;
if ( v16 )
{
v18 = objc_retain(v16);
}
else
{
v19 = objc_msgSend(v13, "_parentScrollView");
v18 = objc_retainAutoreleasedReturnValue(v19);
}
v20 = (void *)v18;
objc_release(v17);
v45 = v20;
if ( !(unsigned __int8)-[UIScrollViewPanGestureRecognizer _isParentScrollView:consideringEvent:](
self,
"_isParentScrollView:consideringEvent:",
v20,
v48) )
{
LABEL_50:
objc_release(v45);
v13 = v49;
goto LABEL_51;
}
v21 = (__int64)objc_msgSend(v49, "_currentlyAbuttedContentEdges");
if ( *((_BYTE *)self + 432) < 0 )
{
if ( (unsigned __int8)-[UIScrollViewPanGestureRecognizer _shouldTransferTrackingFromParentScrollViewForCurrentOffset](
self,
"_shouldTransferTrackingFromParentScrollViewForCurrentOffset") )
{
v22 = v45;
objc_msgSend(v45, "contentOffset");
v47 = v3;
v44 = v4;
objc_msgSend(v22, "_pinContentOffsetToClosestPageBoundary");
v23 = objc_msgSend(v22, "panGestureRecognizer");
v24 = (void *)objc_retainAutoreleasedReturnValue(v23);
objc_msgSend(v24, "_cancelRecognition");
v46 = v21;
objc_release(v24);
v25 = objc_msgSend(v22, "pinchGestureRecognizer");
v26 = (void *)objc_retainAutoreleasedReturnValue(v25);
objc_msgSend(v26, "_cancelRecognition");
objc_release(v26);
v21 = v46;
objc_msgSend(v22, "contentOffset");
if ( v21 & 0xA )
{
v27 = 0.0;
v28 = v49;
v3 = v47 - v3;
}
else
{
v28 = v49;
if ( v21 & 5 )
{
v27 = v44 - v4;
v3 = 0.0;
}
else
{
v3 = CGPointZero[0];
v27 = CGPointZero[1];
}
}
-[UIPanGestureRecognizer setTranslation:inView:](self, "setTranslation:inView:", v28, v3, v27);
}
else if ( (unsigned __int8)-[UIScrollViewPanGestureRecognizer _shouldContinueToWaitToTransferTrackingFromParentScrollView](
self,
"_shouldContinueToWaitToTransferTrackingFromParentScrollView")
|| (*((_BYTE *)self + 432) &= 0x7Fu,
v40 = self,
v41.dummy = &OBJC_CLASS___UIScrollViewPanGestureRecognizer,
!(unsigned __int8)objc_msgSendSuper2(&v40, "_shouldTryToBeginWithEvent:", v48)) )
{
LABEL_57:
objc_release(v45);
LABEL_12:
v9 = 0;
event = v48;
v13 = v49;
LABEL_13:
objc_release(v13);
goto LABEL_14;
}
}
v29 = v49;
if ( (unsigned __int8)objc_msgSend(v49, "isPagingEnabled") )
v21 = (__int64)objc_msgSend(v29, "_abuttedPagingEdges");
v46 = v21;
LOBYTE(v44) = (unsigned __int64)objc_msgSend(v29, "_canScrollX");
LOBYTE(v47) = (unsigned __int64)objc_msgSend(v29, "_canScrollY");
v30 = v45;
if ( (unsigned __int8)objc_msgSend(v45, "_canScrollX") )
v31 = (unsigned __int8)objc_msgSend(v30, "isScrollEnabled") == 0;
else
v31 = 1;
if ( (unsigned __int8)objc_msgSend(v30, "_canScrollY") )
v34 = (unsigned __int8)objc_msgSend(v30, "isScrollEnabled") != 0;
else
LODWORD(v34) = 0;
v35 = v46;
LOBYTE(v33) = v31 || v50 != 0 && LOBYTE(v47) != 0;
v36 = (v31 || v50 != 0 && LOBYTE(v47) != 0) == 0;
v37 = LOBYTE(v44);
if ( !v36
|| LOBYTE(v44)
&& ((-[UIScrollViewPanGestureRecognizer _hysteresis](self, "_hysteresis", v32, v33), !(v35 & 2)) || v42 < v3)
&& ((-[UIScrollViewPanGestureRecognizer _hysteresis](self, "_hysteresis"), !(v35 & 8)) || (v3 = -v3, v3 < v42)) )
{
if ( v37 != 0 && v51 != 0 || !(_DWORD)v34 )
goto LABEL_50;
if ( LOBYTE(v47) )
{
-[UIScrollViewPanGestureRecognizer _hysteresis](self, "_hysteresis");
if ( !(v35 & 1) || v43 < v3 )
{
-[UIScrollViewPanGestureRecognizer _hysteresis](self, "_hysteresis");
if ( !(v35 & 4) || -v3 < v43 )
goto LABEL_50;
}
}
}
goto LABEL_57;
}
v9 = 0;
LABEL_14:
objc_release(event);
return v9;
}
void __cdecl -[UIScrollViewPanGestureRecognizer touchesBegan:withEvent:](UIScrollViewPanGestureRecognizer *self, SEL a2, id a3, id a4)
{
id v4; // r14
__int64 v5; // r15
void *v6; // r14
unsigned __int64 v7; // r12
UIScrollViewPanGestureRecognizer *v8; // [rsp+0h] [rbp-30h]
__objc2_class *v9; // [rsp+8h] [rbp-28h]
v4 = a4;
v5 = objc_retain(a3);
v6 = (void *)objc_retain(v4);
v7 = self->super._lastTouchCount;
self->_modifierFlags = (signed __int64)objc_msgSend(v6, "_modifierFlags");
v8 = self;
v9 = &OBJC_CLASS___UIScrollViewPanGestureRecognizer;
objc_msgSendSuper2(&v8, "touchesBegan:withEvent:", v5, v6, self, &OBJC_CLASS___UIScrollViewPanGestureRecognizer);
objc_release(v5);
if ( !v7 )
-[UIScrollViewPanGestureRecognizer _beginScroll](self, "_beginScroll");
*((_BYTE *)self + 432) &= 0xFCu;
if ( !-[UIGestureRecognizer state](self, "state")
&& (unsigned __int8)-[UIScrollViewPanGestureRecognizer _shouldTryToBeginWithEvent:](
self,
"_shouldTryToBeginWithEvent:",
v6) )
{
-[UIPanGestureRecognizer _removeHysteresisFromTranslation](self, "_removeHysteresisFromTranslation");
-[UIGestureRecognizer setState:](self, "setState:", 1LL);
}
objc_release(v6);
}
@madordie
Copy link
Author

madordie commented Sep 9, 2020

疑问

  • A/B/C: 放在UIScrollView
  • B在显示的时候,上下滑动B内容会滚动,左右滑动时B的父视图UIScrollView会滚动
         +----------+
+------+ | +------+ | +------+
|  A   | | |  B   | | |  C   |
|      | | |      | | |      |
|      | | | pan  | | |      |
|      | | +----->+ | |      |
|      | | |      | | |      |
|      | | |      | | |      |
+------+ | |      | | +------+
         +----------+
           |      |
           |      |
           |      |
           |      |
           |      |
           |      |
           +------+

疑问的重点来了: 系统究竟做了什么,这个操作竟然如此流畅?

初步定位

一个猜测: 要判定UIScrollView是否支持左右/上下手势,只需要根据-[UIScrollView contentSize]-[UIScrollView frame].size对比即可, 那么势必要访问-[UIScrollView contentSize], 则断点应该有现场

于是得到如下信息

(lldb) breakpoint set -name '-[UIScrollView contentSize]'
Breakpoint 4: where = UIKitCore`-[UIScrollView contentSize], address = 0x00000001c42ca278
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
    frame #0: 0x00000001c42ca278 UIKitCore`-[UIScrollView contentSize]
  * frame #1: 0x00000001c42d47a4 UIKitCore`-[UIScrollViewPanGestureRecognizer _canTransferTrackingFromParentPagingScrollView] + 428
    frame #2: 0x00000001c42d4dc8 UIKitCore`-[UIScrollViewPanGestureRecognizer _beginScroll] + 212
    frame #3: 0x00000001c42d4ccc UIKitCore`-[UIScrollViewPanGestureRecognizer touchesBegan:withEvent:] + 120
    frame #4: 0x00000001c3ac5128 UIKitCore`-[UIGestureRecognizer _touchesBegan:withEvent:] + 252
    frame #5: 0x00000001c3f652dc UIKitCore`-[UITouchesEvent _sendEventToGestureRecognizer:] + 288
    frame #6: 0x00000001c3abb204 UIKitCore`__47-[UIGestureEnvironment _updateForEvent:window:]_block_invoke + 68
    frame #7: 0x00000001c3abb304 UIKitCore`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 220
    frame #8: 0x00000001c3abb188 UIKitCore`-[UIGestureEnvironment _updateForEvent:window:] + 204
    frame #9: 0x00000001c3ed37d0 UIKitCore`-[UIWindow sendEvent:] + 3112
    frame #10: 0x00000001c3eb385c UIKitCore`-[UIApplication sendEvent:] + 340
    frame #11: 0x00000001c3f799d4 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 1768
    frame #12: 0x00000001c3f7c100 UIKitCore`__handleEventQueueInternal + 4828
    frame #13: 0x00000001c3f75330 UIKitCore`__handleHIDEventFetcherDrain + 152
    frame #14: 0x0000000197520728 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    frame #15: 0x00000001975206a8 CoreFoundation`__CFRunLoopDoSource0 + 88
    frame #16: 0x000000019751ff90 CoreFoundation`__CFRunLoopDoSources0 + 176
    frame #17: 0x000000019751aecc CoreFoundation`__CFRunLoopRun + 1004
    frame #18: 0x000000019751a7c0 CoreFoundation`CFRunLoopRunSpecific + 436
    frame #19: 0x000000019971b79c GraphicsServices`GSEventRunModal + 104
    frame #20: 0x00000001c3e99c38 UIKitCore`UIApplicationMain + 212
    frame #21: 0x00000001009a5494 XXXX`main at AppDelegate.swift:16:7
    frame #22: 0x0000000196fde8e0 libdyld.dylib`start + 4

开始验证

乍一看,-[UIScrollViewPanGestureRecognizer _canTransferTrackingFromParentPagingScrollView]不就是我们需要的么?于是编写代码进行hook测试, 结果对流畅的拖拽并没有什么影响.失望.jpeg

经过漫长的摸索, 定位到-[UIScrollViewPanGestureRecognizer touchesBegan:withEvent:] 中的-[UIScrollViewPanGestureRecognizer _shouldTryToBeginWithEvent:]函数,然后当该函数返回NO的时候,该UIScrollView则无法响应拖拽手势,便会通过-[UIScrollView _parentScrollView]寻找可以响应的UIScrollView. 开心.jpeg

@srv7
Copy link

srv7 commented Sep 10, 2020

真的强!顺便问下大佬的这套流程下来用的哪些工具

@madordie
Copy link
Author

真的强!顺便问下大佬的这套流程下来用的哪些工具

  • 伪代码: IDA
  • 开发&调试: Xcode & lldb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment