1
This commit is contained in:
310
Pods/LookinServer/Src/Main/Server/Others/LKS_TraceManager.m
generated
Normal file
310
Pods/LookinServer/Src/Main/Server/Others/LKS_TraceManager.m
generated
Normal file
@@ -0,0 +1,310 @@
|
||||
#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 */
|
||||
Reference in New Issue
Block a user