201 lines
7.8 KiB
Plaintext
201 lines
7.8 KiB
Plaintext
#import <UIKit/UIKit.h>
|
|||
|
|
#import <objc/runtime.h>
|
||
|
|
#import <QuartzCore/QuartzCore.h>
|
||
|
|
|
||
|
|
extern UIView *UnityGetGLView(void);
|
||
|
|
|
||
|
|
@interface UIApplication (UnityTouchHook)
|
||
|
|
- (void)ut_sendEvent:(UIEvent *)event;
|
||
|
|
@end
|
||
|
|
|
||
|
|
@implementation UIApplication (UnityTouchHook)
|
||
|
|
|
||
|
|
// ==========================================
|
||
|
|
// 【实装优化】:将 Ivar、Class 提取为静态全局变量,在 +load 中一次性缓存
|
||
|
|
static CFTimeInterval g_lastMovedTime = 0;
|
||
|
|
static Class g_touchClass = Nil;
|
||
|
|
static Ivar g_ivarView = NULL;
|
||
|
|
static Ivar g_ivarLocation = NULL;
|
||
|
|
static Ivar g_ivarPrevLocation = NULL;
|
||
|
|
// ==========================================
|
||
|
|
|
||
|
|
+ (void)load
|
||
|
|
{
|
||
|
|
static dispatch_once_t onceToken;
|
||
|
|
dispatch_once(&onceToken, ^{
|
||
|
|
Class cls = [UIApplication class];
|
||
|
|
Method original = class_getInstanceMethod(cls, @selector(sendEvent:));
|
||
|
|
Method swizzled = class_getInstanceMethod(cls, @selector(ut_sendEvent:));
|
||
|
|
|
||
|
|
if (original && swizzled) {
|
||
|
|
method_exchangeImplementations(original, swizzled);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==========================================
|
||
|
|
// 【实装优化】:在 +load 时集中初始化,彻底消灭每帧的 Runtime 检索开销
|
||
|
|
g_touchClass = [UITouch class];
|
||
|
|
g_ivarView = class_getInstanceVariable(g_touchClass, "_view");
|
||
|
|
g_ivarLocation = class_getInstanceVariable(g_touchClass, "_locationInWindow");
|
||
|
|
g_ivarPrevLocation = class_getInstanceVariable(g_touchClass, "_previousLocationInWindow");
|
||
|
|
// ==========================================
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)ut_sendEvent:(UIEvent *)event
|
||
|
|
{
|
||
|
|
// 1. 让系统和 WebView 先走正常响应
|
||
|
|
[self ut_sendEvent:event];
|
||
|
|
|
||
|
|
if (event.type != UIEventTypeTouches)
|
||
|
|
return;
|
||
|
|
|
||
|
|
UIView *unityView = UnityGetGLView();
|
||
|
|
if (!unityView)
|
||
|
|
return;
|
||
|
|
|
||
|
|
NSSet *touches = event.allTouches;
|
||
|
|
UITouch *touch = [touches anyObject];
|
||
|
|
if (!touch)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// 2. 拦截所有来自 WebView 或非 UnityView 的点击
|
||
|
|
if (touch.view != unityView && ![touch.view isDescendantOfView:unityView]) {
|
||
|
|
|
||
|
|
UITouchPhase phase = touch.phase;
|
||
|
|
|
||
|
|
if (phase == UITouchPhaseMoved) {
|
||
|
|
CFTimeInterval currentTime = CACurrentMediaTime();
|
||
|
|
if (currentTime - g_lastMovedTime < 0.016) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
g_lastMovedTime = currentTime;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 【优化成果】:直接使用 self 获取 keyWindow
|
||
|
|
UIWindow *window = self.keyWindow;
|
||
|
|
if (!window && unityView.window) {
|
||
|
|
window = unityView.window;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==========================================
|
||
|
|
// 【实装优化】:window == nil 时提前返回,防止后续无意义计算与 KVC 崩溃风险
|
||
|
|
if (!window) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// ==========================================
|
||
|
|
|
||
|
|
// 【优化成果】:提取 locationInView 只计算一次
|
||
|
|
CGPoint pointInWindow = [touch locationInView:window];
|
||
|
|
CGPoint pointPrevWindow = [touch previousLocationInView:window];
|
||
|
|
|
||
|
|
CGPoint originalLocationStruct = pointInWindow;
|
||
|
|
CGPoint originalPrevLocationStruct = pointPrevWindow;
|
||
|
|
|
||
|
|
// 【优化成果】:砍掉 convertPoint:fromView: 的矩阵计算
|
||
|
|
CGPoint pointInUnity = pointInWindow;
|
||
|
|
CGRect unityFrame = unityView.frame;
|
||
|
|
if (unityFrame.origin.x != 0 || unityFrame.origin.y != 0) {
|
||
|
|
pointInUnity.x -= unityFrame.origin.x;
|
||
|
|
pointInUnity.y -= unityFrame.origin.y;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 【优化安全加固】:保留最后一道防线,防御极端情况下的 NaN / Inf
|
||
|
|
if (isnan(pointInUnity.x) || isinf(pointInUnity.x)) pointInUnity.x = 0;
|
||
|
|
if (isnan(pointInUnity.y) || isinf(pointInUnity.y)) pointInUnity.y = 0;
|
||
|
|
|
||
|
|
// 【优化成果】:在栈上缓存一次 bounds 结构体
|
||
|
|
CGRect bounds = unityView.bounds;
|
||
|
|
CGFloat viewWidth = bounds.size.width <= 0 ? 375 : bounds.size.width;
|
||
|
|
CGFloat viewHeight = bounds.size.height <= 0 ? 812 : bounds.size.height;
|
||
|
|
|
||
|
|
// 【优化成果】:改用利于 CPU 分支预测的 if-else 约束范围
|
||
|
|
if (pointInUnity.x < 0) {
|
||
|
|
pointInUnity.x = 0;
|
||
|
|
} else if (pointInUnity.x > viewWidth) {
|
||
|
|
pointInUnity.x = viewWidth;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pointInUnity.y < 0) {
|
||
|
|
pointInUnity.y = 0;
|
||
|
|
} else if (pointInUnity.y > viewHeight) {
|
||
|
|
pointInUnity.y = viewHeight;
|
||
|
|
}
|
||
|
|
|
||
|
|
CGPoint finalInjectPoint = pointInUnity;
|
||
|
|
|
||
|
|
// 3. 备份原始值(后续无需重复获取 Ivar,直接使用静态全局变量)
|
||
|
|
UIView *originalView = touch.view;
|
||
|
|
NSValue *originalLocation = nil;
|
||
|
|
NSValue *originalPrevLocation = nil;
|
||
|
|
|
||
|
|
if (g_ivarLocation) {
|
||
|
|
originalLocation = [touch valueForKey:@"locationInWindow"];
|
||
|
|
}
|
||
|
|
if (g_ivarPrevLocation) {
|
||
|
|
originalPrevLocation = [touch valueForKey:@"previousLocationInWindow"];
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4. 【强制临时修改】重写原生 touch
|
||
|
|
if (g_ivarView) {
|
||
|
|
object_setIvar(touch, g_ivarView, unityView);
|
||
|
|
} else {
|
||
|
|
[touch setValue:unityView forKey:@"view"];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (g_ivarLocation) object_setIvar(touch, g_ivarLocation, [NSValue valueWithCGPoint:finalInjectPoint]);
|
||
|
|
|
||
|
|
// 计算 Moved 阶段的伪造历史坐标
|
||
|
|
if (phase == UITouchPhaseMoved && originalLocation && originalPrevLocation) {
|
||
|
|
CGPoint offset = CGPointMake(originalLocationStruct.x - originalPrevLocationStruct.x, originalLocationStruct.y - originalPrevLocationStruct.y);
|
||
|
|
CGPoint fakePrevPoint = CGPointMake(finalInjectPoint.x - offset.x, finalInjectPoint.y - offset.y);
|
||
|
|
|
||
|
|
if (isnan(fakePrevPoint.x) || isinf(fakePrevPoint.x)) fakePrevPoint.x = finalInjectPoint.x;
|
||
|
|
if (isnan(fakePrevPoint.y) || isinf(fakePrevPoint.y)) fakePrevPoint.y = finalInjectPoint.y;
|
||
|
|
|
||
|
|
if (fakePrevPoint.x < 0) {
|
||
|
|
fakePrevPoint.x = 0;
|
||
|
|
} else if (fakePrevPoint.x > viewWidth) {
|
||
|
|
fakePrevPoint.x = viewWidth;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (fakePrevPoint.y < 0) {
|
||
|
|
fakePrevPoint.y = 0;
|
||
|
|
} else if (fakePrevPoint.y > viewHeight) {
|
||
|
|
fakePrevPoint.y = viewHeight;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (g_ivarPrevLocation) object_setIvar(touch, g_ivarPrevLocation, [NSValue valueWithCGPoint:fakePrevPoint]);
|
||
|
|
} else {
|
||
|
|
if (g_ivarPrevLocation) object_setIvar(touch, g_ivarPrevLocation, [NSValue valueWithCGPoint:finalInjectPoint]);
|
||
|
|
}
|
||
|
|
|
||
|
|
[touch setValue:[NSValue valueWithCGPoint:finalInjectPoint] forKey:@"locationInWindow"];
|
||
|
|
|
||
|
|
// 5. 将清洗干净的事件丢给 UnityView 通道
|
||
|
|
switch (phase) {
|
||
|
|
case UITouchPhaseBegan: [unityView touchesBegan:touches withEvent:event]; break;
|
||
|
|
case UITouchPhaseMoved: [unityView touchesMoved:touches withEvent:event]; break;
|
||
|
|
case UITouchPhaseEnded: [unityView touchesEnded:touches withEvent:event]; break;
|
||
|
|
case UITouchPhaseCancelled: [unityView touchesCancelled:touches withEvent:event]; break;
|
||
|
|
default: break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 6. 立刻恢复现场
|
||
|
|
if (g_ivarView) {
|
||
|
|
object_setIvar(touch, g_ivarView, originalView);
|
||
|
|
} else {
|
||
|
|
[touch setValue:originalView forKey:@"view"];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (g_ivarLocation && originalLocation) {
|
||
|
|
object_setIvar(touch, g_ivarLocation, originalLocation);
|
||
|
|
[touch setValue:originalLocation forKey:@"locationInWindow"];
|
||
|
|
}
|
||
|
|
if (g_ivarPrevLocation && originalPrevLocation) {
|
||
|
|
object_setIvar(touch, g_ivarPrevLocation, originalPrevLocation);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@end
|