Files
keyboard/Pods/LookinServer/Src/Main/Server/Others/LKS_TraceManager.m
2025-10-28 10:18:10 +08:00

311 lines
12 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_TraceManager.m
// LookinServer
//
// Created by Li Kai on 2019/5/5.
// https://lookin.work
//
#import "LKS_TraceManager.h"
#import <objc/runtime.h>
#import "LookinIvarTrace.h"
#import "LookinServerDefines.h"
#import "LookinWeakContainer.h"
#import "LKS_MultiplatformAdapter.h"
#ifdef LOOKIN_SERVER_SWIFT_ENABLED
#if __has_include(<LookinServer/LookinServer-Swift.h>)
#import <LookinServer/LookinServer-Swift.h>
#define LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
#elif __has_include("LookinServer-Swift.h")
#import "LookinServer-Swift.h"
#define LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
#endif
#endif
#ifdef SPM_LOOKIN_SERVER_ENABLED
@import LookinServerSwift;
#define LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
#endif
@interface LKS_TraceManager ()
@property(nonatomic, strong) NSMutableArray<LookinWeakContainer *> *searchTargets;
@end
@implementation LKS_TraceManager
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_TraceManager *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
- (void)addSearchTarger:(id)target {
if (!target) {
return;
}
if (!self.searchTargets) {
self.searchTargets = [NSMutableArray array];
}
LookinWeakContainer *container = [LookinWeakContainer containerWithObject:target];
[self.searchTargets addObject:container];
}
- (void)reload {
// 把旧的先都清理掉
[NSObject lks_clearAllObjectsTraces];
[self.searchTargets enumerateObjectsUsingBlock:^(LookinWeakContainer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (!obj.object) {
return;
}
[self _markIVarsInAllClassLevelsOfObject:obj.object];
}];
[[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
[self _addTraceForLayersRootedByLayer:window.layer];
}];
}
- (void)_addTraceForLayersRootedByLayer:(CALayer *)layer {
UIView *view = layer.lks_hostView;
if ([view.superview lks_isChildrenViewOfTabBar]) {
view.lks_isChildrenViewOfTabBar = YES;
} else if ([view isKindOfClass:[UITabBar class]]) {
view.lks_isChildrenViewOfTabBar = YES;
}
if (view) {
[self _markIVarsInAllClassLevelsOfObject:view];
UIViewController* vc = [view lks_findHostViewController];
if (vc) {
[self _markIVarsInAllClassLevelsOfObject:vc];
}
[self _buildSpecialTraceForView:view];
} else {
[self _markIVarsInAllClassLevelsOfObject:layer];
}
[[layer.sublayers copy] enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
[self _addTraceForLayersRootedByLayer:sublayer];
}];
}
- (void)_buildSpecialTraceForView:(UIView *)view {
UIViewController* vc = [view lks_findHostViewController];
if (vc) {
view.lks_specialTrace = [NSString stringWithFormat:@"%@.view", NSStringFromClass(vc.class)];
} else if ([view isKindOfClass:[UIWindow class]]) {
CGFloat currentWindowLevel = ((UIWindow *)view).windowLevel;
if (((UIWindow *)view).isKeyWindow) {
view.lks_specialTrace = [NSString stringWithFormat:@"KeyWindow ( Level: %@ )", @(currentWindowLevel)];
} else {
view.lks_specialTrace = [NSString stringWithFormat:@"WindowLevel: %@", @(currentWindowLevel)];
}
} else if ([view isKindOfClass:[UITableViewCell class]]) {
((UITableViewCell *)view).backgroundView.lks_specialTrace = @"cell.backgroundView";
((UITableViewCell *)view).accessoryView.lks_specialTrace = @"cell.accessoryView";
} else if ([view isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)view;
NSMutableArray<NSNumber *> *relatedSectionIdx = [NSMutableArray array];
[[tableView visibleCells] enumerateObjectsUsingBlock:^(__kindof UITableViewCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
cell.lks_specialTrace = [NSString stringWithFormat:@"{ sec:%@, row:%@ }", @(indexPath.section), @(indexPath.row)];
if (![relatedSectionIdx containsObject:@(indexPath.section)]) {
[relatedSectionIdx addObject:@(indexPath.section)];
}
}];
[relatedSectionIdx enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSUInteger secIdx = [obj unsignedIntegerValue];
UIView *secHeaderView = [tableView headerViewForSection:secIdx];
secHeaderView.lks_specialTrace = [NSString stringWithFormat:@"sectionHeader { sec: %@ }", @(secIdx)];
UIView *secFooterView = [tableView footerViewForSection:secIdx];
secFooterView.lks_specialTrace = [NSString stringWithFormat:@"sectionFooter { sec: %@ }", @(secIdx)];
}];
} else if ([view isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)view;
collectionView.backgroundView.lks_specialTrace = @"collectionView.backgroundView";
if (@available(iOS 9.0, *)) {
[[collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull indexPath, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *headerView = [collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
headerView.lks_specialTrace = [NSString stringWithFormat:@"sectionHeader { sec:%@ }", @(indexPath.section)];
}];
[[collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionFooter] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull indexPath, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *footerView = [collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:indexPath];
footerView.lks_specialTrace = [NSString stringWithFormat:@"sectionFooter { sec:%@ }", @(indexPath.section)];
}];
}
[[collectionView visibleCells] enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) {
NSIndexPath *indexPath = [collectionView indexPathForCell:cell];
cell.lks_specialTrace = [NSString stringWithFormat:@"{ item:%@, sec:%@ }", @(indexPath.item), @(indexPath.section)];
}];
} else if ([view isKindOfClass:[UITableViewHeaderFooterView class]]) {
UITableViewHeaderFooterView *headerFooterView = (UITableViewHeaderFooterView *)view;
headerFooterView.textLabel.lks_specialTrace = @"sectionHeaderFooter.textLabel";
headerFooterView.detailTextLabel.lks_specialTrace = @"sectionHeaderFooter.detailTextLabel";
}
}
- (void)_markIVarsInAllClassLevelsOfObject:(NSObject *)object {
[self _markIVarsOfObject:object class:object.class];
#ifdef LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
[LKS_SwiftTraceManager swiftMarkIVarsOfObject:object];
#endif
}
- (void)_markIVarsOfObject:(NSObject *)hostObject class:(Class)targetClass {
if (!targetClass) {
return;
}
NSArray<NSString *> *prefixesToTerminateRecursion = @[@"NSObject", @"UIResponder", @"UIButton", @"UIButtonLabel"];
BOOL hasPrefix = [prefixesToTerminateRecursion lookin_any:^BOOL(NSString *prefix) {
return [NSStringFromClass(targetClass) hasPrefix:prefix];
}];
if (hasPrefix) {
return;
}
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(targetClass, &outCount);
for (unsigned int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString *ivarType = [[NSString alloc] lookin_safeInitWithUTF8String:ivar_getTypeEncoding(ivar)];
if (![ivarType hasPrefix:@"@"] || ivarType.length <= 3) {
continue;
}
NSString *ivarClassName = [ivarType substringWithRange:NSMakeRange(2, ivarType.length - 3)];
Class ivarClass = NSClassFromString(ivarClassName);
if (![ivarClass isSubclassOfClass:[UIView class]]
&& ![ivarClass isSubclassOfClass:[CALayer class]]
&& ![ivarClass isSubclassOfClass:[UIViewController class]]
&& ![ivarClass isSubclassOfClass:[UIGestureRecognizer class]]) {
continue;
}
const char * ivarNameChar = ivar_getName(ivar);
if (!ivarNameChar) {
continue;
}
// 这个 ivarObject 可能的类型UIView, CALayer, UIViewController, UIGestureRecognizer
NSObject *ivarObject = object_getIvar(hostObject, ivar);
if (!ivarObject || ![ivarObject isKindOfClass:[NSObject class]]) {
continue;
}
LookinIvarTrace *ivarTrace = [LookinIvarTrace new];
ivarTrace.hostObject = hostObject;
ivarTrace.hostClassName = [self makeDisplayClassNameWithSuper:targetClass childClass:hostObject.class];
ivarTrace.ivarName = [[NSString alloc] lookin_safeInitWithUTF8String:ivarNameChar];
if (hostObject == ivarObject) {
ivarTrace.relation = LookinIvarTraceRelationValue_Self;
} else if ([hostObject isKindOfClass:[UIView class]]) {
CALayer *ivarLayer = nil;
if ([ivarObject isKindOfClass:[CALayer class]]) {
ivarLayer = (CALayer *)ivarObject;
} else if ([ivarObject isKindOfClass:[UIView class]]) {
ivarLayer = ((UIView *)ivarObject).layer;
}
if (ivarLayer && (ivarLayer.superlayer == ((UIView *)hostObject).layer)) {
ivarTrace.relation = @"superview";
}
}
if ([LKS_InvalidIvarTraces() containsObject:ivarTrace]) {
continue;
}
if (![ivarObject respondsToSelector:@selector(lks_ivarTraces)] || ![ivarObject respondsToSelector:@selector(setLks_ivarTraces:)]) {
continue;
}
if (!ivarObject.lks_ivarTraces) {
ivarObject.lks_ivarTraces = [NSArray array];
}
if (![ivarObject.lks_ivarTraces containsObject:ivarTrace]) {
ivarObject.lks_ivarTraces = [ivarObject.lks_ivarTraces arrayByAddingObject:ivarTrace];
}
}
free(ivars);
Class superClass = [targetClass superclass];
[self _markIVarsOfObject:hostObject class:superClass];
}
// 比如 superClass 可能是 UIView而 childClass 可能是 UIButton
- (NSString *)makeDisplayClassNameWithSuper:(Class)superClass childClass:(Class)childClass {
NSString *superName = NSStringFromClass(superClass);
if (!childClass) {
return superName;
}
NSString *childName = NSStringFromClass(childClass);
if ([childName isEqualToString:superName]) {
return superName;
}
return [NSString stringWithFormat:@"%@ : %@", childName, superName];
}
static NSSet<LookinIvarTrace *> *LKS_InvalidIvarTraces(void) {
static NSSet *list;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *set = [NSMutableSet set];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIView";
trace.ivarName = @"_window";
trace;
})];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIViewController";
trace.ivarName = @"_view";
trace;
})];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIView";
trace.ivarName = @"_viewDelegate";
trace;
})];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIViewController";
trace.ivarName = @"_parentViewController";
trace;
})];
list = set.copy;
});
return list;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */