#import #import #import 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