This commit is contained in:
2025-10-28 10:18:10 +08:00
parent efb04d134e
commit 1deca2ae5b
166 changed files with 17288 additions and 1427 deletions

View File

@@ -0,0 +1,41 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinMobile.h
// WeRead
//
// Created by Li Kai on 2018/11/30.
// Copyright © 2018 tencent. All rights reserved.
//
#import "LookinDefines.h"
#import "TargetConditionals.h"
#import <UIKit/UIKit.h>
@interface CALayer (LookinServer)
/// 如果 myView.layer == myLayer则 myLayer.lks_hostView 会返回 myView
@property(nonatomic, readonly, weak) UIView *lks_hostView;
- (UIWindow *)lks_window;
- (CGRect)lks_frameInWindow:(UIWindow *)window;
- (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality;
/// 当没有 sublayers 时,该方法返回 nil
- (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality;
/// 获取和该对象有关的对象的 Class 层级树
- (NSArray<NSArray<NSString *> *> *)lks_relatedClassChainList;
- (NSArray<NSString *> *)lks_selfRelation;
@property(nonatomic, strong) UIColor *lks_backgroundColor;
@property(nonatomic, strong) UIColor *lks_borderColor;
@property(nonatomic, strong) UIColor *lks_shadowColor;
@property(nonatomic, assign) CGFloat lks_shadowOffsetWidth;
@property(nonatomic, assign) CGFloat lks_shadowOffsetHeight;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,233 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinMobile.m
// WeRead
//
// Created by Li Kai on 2018/11/30.
// Copyright © 2018 tencent. All rights reserved.
//
#import "CALayer+LookinServer.h"
#import "LKS_HierarchyDisplayItemsMaker.h"
#import "LookinDisplayItem.h"
#import <objc/runtime.h>
#import "LKS_ConnectionManager.h"
#import "LookinIvarTrace.h"
#import "LookinServerDefines.h"
#import "UIColor+LookinServer.h"
#import "LKS_MultiplatformAdapter.h"
@implementation CALayer (LookinServer)
- (UIWindow *)lks_window {
CALayer *layer = self;
while (layer) {
UIView *hostView = layer.lks_hostView;
if (hostView.window) {
return hostView.window;
} else if ([hostView isKindOfClass:[UIWindow class]]) {
return (UIWindow *)hostView;
}
layer = layer.superlayer;
}
return nil;
}
- (CGRect)lks_frameInWindow:(UIWindow *)window {
UIWindow *selfWindow = [self lks_window];
if (!selfWindow) {
return CGRectZero;
}
CGRect rectInSelfWindow = [selfWindow.layer convertRect:self.frame fromLayer:self.superlayer];
CGRect rectInWindow = [window convertRect:rectInSelfWindow fromWindow:selfWindow];
return rectInWindow;
}
#pragma mark - Host View
- (UIView *)lks_hostView {
if (self.delegate && [self.delegate isKindOfClass:UIView.class]) {
UIView *view = (UIView *)self.delegate;
if (view.layer == self) {
return view;
}
}
return nil;
}
#pragma mark - Screenshot
- (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality {
CGFloat screenScale = [LKS_MultiplatformAdapter mainScreenScale];
CGFloat pixelWidth = self.frame.size.width * screenScale;
CGFloat pixelHeight = self.frame.size.height * screenScale;
if (pixelWidth <= 0 || pixelHeight <= 0) {
return nil;
}
CGFloat renderScale = lowQuality ? 1 : 0;
CGFloat maxLength = MAX(pixelWidth, pixelHeight);
if (maxLength > LookinNodeImageMaxLengthInPx) {
// LookinNodeImageMaxLengthInPx
// renderScale 1 1 1
renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1);
}
CGSize contextSize = self.frame.size;
if (contextSize.width <= 0 || contextSize.height <= 0 || contextSize.width > 20000 || contextSize.height > 20000) {
NSLog(@"LookinServer - Failed to capture screenshot. Invalid context size: %@ x %@", @(contextSize.width), @(contextSize.height));
return nil;
}
UIGraphicsBeginImageContextWithOptions(contextSize, NO, renderScale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) {
[self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES];
} else {
[self renderInContext:context];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality {
if (!self.sublayers.count) {
return nil;
}
CGFloat screenScale = [LKS_MultiplatformAdapter mainScreenScale];
CGFloat pixelWidth = self.frame.size.width * screenScale;
CGFloat pixelHeight = self.frame.size.height * screenScale;
if (pixelWidth <= 0 || pixelHeight <= 0) {
return nil;
}
CGFloat renderScale = lowQuality ? 1 : 0;
CGFloat maxLength = MAX(pixelWidth, pixelHeight);
if (maxLength > LookinNodeImageMaxLengthInPx) {
// LookinNodeImageMaxLengthInPx
// renderScale 1 1 1
renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1);
}
if (self.sublayers.count) {
NSArray<CALayer *> *sublayers = [self.sublayers copy];
NSMutableArray<CALayer *> *visibleSublayers = [NSMutableArray arrayWithCapacity:sublayers.count];
[sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
if (!sublayer.hidden) {
sublayer.hidden = YES;
[visibleSublayers addObject:sublayer];
}
}];
CGSize contextSize = self.frame.size;
if (contextSize.width <= 0 || contextSize.height <= 0 || contextSize.width > 20000 || contextSize.height > 20000) {
NSLog(@"LookinServer - Failed to capture screenshot. Invalid context size: %@ x %@", @(contextSize.width), @(contextSize.height));
return nil;
}
UIGraphicsBeginImageContextWithOptions(contextSize, NO, renderScale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) {
[self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES];
} else {
[self renderInContext:context];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[visibleSublayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
sublayer.hidden = NO;
}];
return image;
}
return nil;
}
- (NSArray<NSArray<NSString *> *> *)lks_relatedClassChainList {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:2];
if (self.lks_hostView) {
[array addObject:[CALayer lks_getClassListOfObject:self.lks_hostView endingClass:@"UIView"]];
UIViewController* vc = [self.lks_hostView lks_findHostViewController];
if (vc) {
[array addObject:[CALayer lks_getClassListOfObject:vc endingClass:@"UIViewController"]];
}
} else {
[array addObject:[CALayer lks_getClassListOfObject:self endingClass:@"CALayer"]];
}
return array.copy;
}
+ (NSArray<NSString *> *)lks_getClassListOfObject:(id)object endingClass:(NSString *)endingClass {
NSArray<NSString *> *completedList = [object lks_classChainList];
NSUInteger endingIdx = [completedList indexOfObject:endingClass];
if (endingIdx != NSNotFound) {
completedList = [completedList subarrayWithRange:NSMakeRange(0, endingIdx + 1)];
}
return completedList;
}
- (NSArray<NSString *> *)lks_selfRelation {
NSMutableArray *array = [NSMutableArray array];
NSMutableArray<LookinIvarTrace *> *ivarTraces = [NSMutableArray array];
if (self.lks_hostView) {
UIViewController* vc = [self.lks_hostView lks_findHostViewController];
if (vc) {
[array addObject:[NSString stringWithFormat:@"(%@ *).view", NSStringFromClass(vc.class)]];
[ivarTraces addObjectsFromArray:vc.lks_ivarTraces];
}
[ivarTraces addObjectsFromArray:self.lks_hostView.lks_ivarTraces];
} else {
[ivarTraces addObjectsFromArray:self.lks_ivarTraces];
}
if (ivarTraces.count) {
[array addObjectsFromArray:[ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) {
return [NSString stringWithFormat:@"(%@ *) -> %@", value.hostClassName, value.ivarName];
}]];
}
return array.count ? array.copy : nil;
}
- (UIColor *)lks_backgroundColor {
return [UIColor lks_colorWithCGColor:self.backgroundColor];
}
- (void)setLks_backgroundColor:(UIColor *)lks_backgroundColor {
self.backgroundColor = lks_backgroundColor.CGColor;
}
- (UIColor *)lks_borderColor {
return [UIColor lks_colorWithCGColor:self.borderColor];
}
- (void)setLks_borderColor:(UIColor *)lks_borderColor {
self.borderColor = lks_borderColor.CGColor;
}
- (UIColor *)lks_shadowColor {
return [UIColor lks_colorWithCGColor:self.shadowColor];
}
- (void)setLks_shadowColor:(UIColor *)lks_shadowColor {
self.shadowColor = lks_shadowColor.CGColor;
}
- (CGFloat)lks_shadowOffsetWidth {
return self.shadowOffset.width;
}
- (void)setLks_shadowOffsetWidth:(CGFloat)lks_shadowOffsetWidth {
self.shadowOffset = CGSizeMake(lks_shadowOffsetWidth, self.shadowOffset.height);
}
- (CGFloat)lks_shadowOffsetHeight {
return self.shadowOffset.height;
}
- (void)setLks_shadowOffsetHeight:(CGFloat)lks_shadowOffsetHeight {
self.shadowOffset = CGSizeMake(self.shadowOffset.width, lks_shadowOffsetHeight);
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,41 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
@class LookinIvarTrace;
@interface NSObject (LookinServer)
#pragma mark - oid
/// 如果 oid 不存在则会创建新的 oid
- (unsigned long)lks_registerOid;
/// 0 表示不存在
@property(nonatomic, assign) unsigned long lks_oid;
+ (NSObject *)lks_objectWithOid:(unsigned long)oid;
#pragma mark - trace
@property(nonatomic, copy) NSString *lks_specialTrace;
+ (void)lks_clearAllObjectsTraces;
/**
获取当前对象的 Class 层级树,如 @[@"UIView", @"UIResponder", @"NSObject"]。未 demangle有 Swift Module Name
*/
- (NSArray<NSString *> *)lks_classChainList;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,99 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import "NSObject+Lookin.h"
#import "NSObject+LookinServer.h"
#import "LookinServerDefines.h"
#import "LKS_ObjectRegistry.h"
#import <objc/runtime.h>
@implementation NSObject (LookinServer)
#pragma mark - oid
- (unsigned long)lks_registerOid {
if (!self.lks_oid) {
unsigned long oid = [[LKS_ObjectRegistry sharedInstance] addObject:self];
self.lks_oid = oid;
}
return self.lks_oid;
}
- (void)setLks_oid:(unsigned long)lks_oid {
[self lookin_bindObject:@(lks_oid) forKey:@"lks_oid"];
}
- (unsigned long)lks_oid {
NSNumber *number = [self lookin_getBindObjectForKey:@"lks_oid"];
return [number unsignedLongValue];
}
+ (NSObject *)lks_objectWithOid:(unsigned long)oid {
return [[LKS_ObjectRegistry sharedInstance] objectWithOid:oid];
}
#pragma mark - trace
- (void)setLks_ivarTraces:(NSArray<LookinIvarTrace *> *)lks_ivarTraces {
[self lookin_bindObject:lks_ivarTraces.copy forKey:@"lks_ivarTraces"];
if (lks_ivarTraces) {
[[NSObject lks_allObjectsWithTraces] addPointer:(void *)self];
}
}
- (NSArray<LookinIvarTrace *> *)lks_ivarTraces {
return [self lookin_getBindObjectForKey:@"lks_ivarTraces"];
}
- (void)setLks_specialTrace:(NSString *)lks_specialTrace {
[self lookin_bindObject:lks_specialTrace forKey:@"lks_specialTrace"];
if (lks_specialTrace) {
[[NSObject lks_allObjectsWithTraces] addPointer:(void *)self];
}
}
- (NSString *)lks_specialTrace {
return [self lookin_getBindObjectForKey:@"lks_specialTrace"];
}
+ (void)lks_clearAllObjectsTraces {
[[[NSObject lks_allObjectsWithTraces] allObjects] enumerateObjectsUsingBlock:^(NSObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.lks_ivarTraces = nil;
obj.lks_specialTrace = nil;
}];
[NSObject lks_allObjectsWithTraces].count = 0;
}
+ (NSPointerArray *)lks_allObjectsWithTraces {
static dispatch_once_t onceToken;
static NSPointerArray *lks_allObjectsWithTraces = nil;
dispatch_once(&onceToken,^{
lks_allObjectsWithTraces = [NSPointerArray weakObjectsPointerArray];
});
return lks_allObjectsWithTraces;
}
- (NSArray<NSString *> *)lks_classChainList {
NSMutableArray<NSString *> *classChainList = [NSMutableArray array];
Class currentClass = self.class;
while (currentClass) {
NSString *currentClassName = NSStringFromClass(currentClass);
if (currentClassName) {
[classChainList addObject:currentClassName];
}
currentClass = [currentClass superclass];
}
return classChainList.copy;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIBlurEffect+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIBlurEffect (LookinServer)
/// 该 number 包装的对象是 UIBlurEffectStyle之所以用 NSNumber 是因为想把 0 和 nil 区分开,毕竟这里是在 hook 系统,稳一点好。
/// 该方法的实现需要 Hook因此若定义了 LOOKIN_SERVER_DISABLE_HOOK 宏,则属性会返回 nil
@property(nonatomic, strong) NSNumber *lks_effectStyleNumber;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,57 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIBlurEffect+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import "UIBlurEffect+LookinServer.h"
#import "NSObject+Lookin.h"
#import <objc/runtime.h>
@implementation UIBlurEffect (LookinServer)
#ifdef LOOKIN_SERVER_DISABLE_HOOK
- (void)setLks_effectStyleNumber:(NSNumber *)lks_effectStyleNumber {
}
- (NSNumber *)lks_effectStyleNumber {
return nil;
}
#else
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getClassMethod([self class], @selector(effectWithStyle:));
Method newMethod = class_getClassMethod([self class], @selector(lks_effectWithStyle:));
method_exchangeImplementations(oriMethod, newMethod);
});
}
+ (UIBlurEffect *)lks_effectWithStyle:(UIBlurEffectStyle)style {
id effect = [self lks_effectWithStyle:style];
if ([effect respondsToSelector:@selector(setLks_effectStyleNumber:)]) {
[effect setLks_effectStyleNumber:@(style)];
}
return effect;
}
- (void)setLks_effectStyleNumber:(NSNumber *)lks_effectStyleNumber {
[self lookin_bindObject:lks_effectStyleNumber forKey:@"lks_effectStyleNumber"];
}
- (NSNumber *)lks_effectStyleNumber {
return [self lookin_getBindObjectForKey:@"lks_effectStyleNumber"];
}
#endif /* LOOKIN_SERVER_DISABLE_HOOK */
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIColor+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/6/5.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIColor (LookinServer)
- (NSArray<NSNumber *> *)lks_rgbaComponents;
+ (instancetype)lks_colorFromRGBAComponents:(NSArray<NSNumber *> *)components;
- (NSString *)lks_rgbaString;
- (NSString *)lks_hexString;
/// will check if the argument is a real CGColor
+ (UIColor *)lks_colorWithCGColor:(CGColorRef)cgColor;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,183 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIColor+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/6/5.
// https://lookin.work
//
#import "UIColor+LookinServer.h"
@implementation UIColor (LookinServer)
- (NSArray<NSNumber *> *)lks_rgbaComponents {
CGFloat r, g, b, a;
CGColorRef cgColor = [self CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
if (CGColorGetNumberOfComponents(cgColor) == 4) {
r = components[0];
g = components[1];
b = components[2];
a = components[3];
} else if (CGColorGetNumberOfComponents(cgColor) == 2) {
r = components[0];
g = components[0];
b = components[0];
a = components[1];
} else if (CGColorGetNumberOfComponents(cgColor) == 1) {
r = components[0];
g = components[0];
b = components[0];
a = components[0];
} else {
r = 0;
g = 0;
b = 0;
a = 0;
NSAssert(NO, @"");
}
NSArray<NSNumber *> *rgba = @[@(r), @(g), @(b), @(a)];
return rgba;
}
+ (instancetype)lks_colorFromRGBAComponents:(NSArray<NSNumber *> *)components {
if (!components) {
return nil;
}
if (components.count != 4) {
NSAssert(NO, @"");
return nil;
}
UIColor *color = [UIColor colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue];
return color;
}
- (NSString *)lks_rgbaString {
CGFloat r, g, b, a;
CGColorRef cgColor = [self CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
if (CGColorGetNumberOfComponents(cgColor) == 4) {
r = components[0];
g = components[1];
b = components[2];
a = components[3];
} else if (CGColorGetNumberOfComponents(cgColor) == 2) {
r = components[0];
g = components[0];
b = components[0];
a = components[1];
} else {
r = 0;
g = 0;
b = 0;
a = 0;
NSAssert(NO, @"");
}
if (a >= 1) {
return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f)", r * 255, g * 255, b * 255];
} else {
return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %.2f)", r * 255, g * 255, b * 255, a];
}
}
- (NSString *)lks_hexString {
CGFloat r, g, b, a;
CGColorRef cgColor = [self CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
if (CGColorGetNumberOfComponents(cgColor) == 4) {
r = components[0];
g = components[1];
b = components[2];
a = components[3];
} else if (CGColorGetNumberOfComponents(cgColor) == 2) {
r = components[0];
g = components[0];
b = components[0];
a = components[1];
} else {
r = 0;
g = 0;
b = 0;
a = 0;
NSAssert(NO, @"");
}
NSInteger red = r * 255;
NSInteger green = g * 255;
NSInteger blue = b * 255;
NSInteger alpha = a * 255;
return [[NSString stringWithFormat:@"#%@%@%@%@",
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:alpha]],
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:red]],
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:green]],
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:blue]]] lowercaseString];
}
// 0F0F
+ (NSString *)_alignColorHexStringLength:(NSString *)hexString {
return hexString.length < 2 ? [@"0" stringByAppendingString:hexString] : hexString;
}
+ (NSString *)_hexStringWithInteger:(NSInteger)integer {
NSString *hexString = @"";
NSInteger remainder = 0;
for (NSInteger i = 0; i < 9; i++) {
remainder = integer % 16;
integer = integer / 16;
NSString *letter = [self _hexLetterStringWithInteger:remainder];
hexString = [letter stringByAppendingString:hexString];
if (integer == 0) {
break;
}
}
return hexString;
}
+ (NSString *)_hexLetterStringWithInteger:(NSInteger)integer {
NSAssert(integer < 16, @"要转换的数必须是16进制里的个位数也即小于16但你传给我是%@", @(integer));
NSString *letter = nil;
switch (integer) {
case 10:
letter = @"A";
break;
case 11:
letter = @"B";
break;
case 12:
letter = @"C";
break;
case 13:
letter = @"D";
break;
case 14:
letter = @"E";
break;
case 15:
letter = @"F";
break;
default:
letter = [[NSString alloc]initWithFormat:@"%@", @(integer)];
break;
}
return letter;
}
+ (UIColor *)lks_colorWithCGColor:(CGColorRef)cgColor {
if (!cgColor) {
return nil;
}
if (CFGetTypeID(cgColor) != CGColorGetTypeID()) {
return nil;
}
return [UIColor colorWithCGColor:cgColor];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,22 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImage+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/5/14.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIImage (LookinServer)
/// 该方法的实现需要 Hook因此若定义了 LOOKIN_SERVER_DISABLE_HOOK 宏,则属性会返回 nil
@property(nonatomic, copy) NSString *lks_imageSourceName;
- (NSData *)lookin_data;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,95 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImage+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/5/14.
// https://lookin.work
//
#import <objc/runtime.h>
#import "UIImage+LookinServer.h"
#import "LookinServerDefines.h"
@implementation UIImage (LookinServer)
#ifdef LOOKIN_SERVER_DISABLE_HOOK
- (void)setLks_imageSourceName:(NSString *)lks_imageSourceName {
}
- (NSString *)lks_imageSourceName {
return nil;
}
#else
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getClassMethod([self class], @selector(imageNamed:));
Method newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:));
method_exchangeImplementations(oriMethod, newMethod);
oriMethod = class_getClassMethod([self class], @selector(imageWithContentsOfFile:));
newMethod = class_getClassMethod([self class], @selector(lks_imageWithContentsOfFile:));
method_exchangeImplementations(oriMethod, newMethod);
oriMethod = class_getClassMethod([self class], @selector(imageNamed:inBundle:compatibleWithTraitCollection:));
newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:inBundle:compatibleWithTraitCollection:));
method_exchangeImplementations(oriMethod, newMethod);
if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
oriMethod = class_getClassMethod([self class], @selector(imageNamed:inBundle:withConfiguration:));
newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:inBundle:withConfiguration:));
method_exchangeImplementations(oriMethod, newMethod);
}
});
}
+ (nullable UIImage *)lks_imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle withConfiguration:(nullable UIImageConfiguration *)configuration API_AVAILABLE(ios(13.0),tvos(13.0),watchos(6.0))
{
UIImage *image = [self lks_imageNamed:name inBundle:bundle withConfiguration:configuration];
image.lks_imageSourceName = name;
return image;
}
+ (nullable UIImage *)lks_imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection API_AVAILABLE(ios(8.0))
{
UIImage *image = [self lks_imageNamed:name inBundle:bundle compatibleWithTraitCollection:traitCollection];
image.lks_imageSourceName = name;
return image;
}
+ (UIImage *)lks_imageNamed:(NSString *)name {
UIImage *image = [self lks_imageNamed:name];
image.lks_imageSourceName = name;
return image;
}
+ (UIImage *)lks_imageWithContentsOfFile:(NSString *)path {
UIImage *image = [self lks_imageWithContentsOfFile:path];
NSString *fileName = [[path componentsSeparatedByString:@"/"].lastObject componentsSeparatedByString:@"."].firstObject;
image.lks_imageSourceName = fileName;
return image;
}
- (void)setLks_imageSourceName:(NSString *)lks_imageSourceName {
[self lookin_bindObject:lks_imageSourceName.copy forKey:@"lks_imageSourceName"];
}
- (NSString *)lks_imageSourceName {
return [self lookin_getBindObjectForKey:@"lks_imageSourceName"];
}
#endif /* LOOKIN_SERVER_DISABLE_HOOK */
- (NSData *)lookin_data {
return UIImagePNGRepresentation(self);
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,20 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImageView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIImageView (LookinServer)
- (NSString *)lks_imageSourceName;
- (NSNumber *)lks_imageViewOidIfHasImage;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,31 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImageView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import "UIImageView+LookinServer.h"
#import "UIImage+LookinServer.h"
#import "NSObject+LookinServer.h"
@implementation UIImageView (LookinServer)
- (NSString *)lks_imageSourceName {
return self.image.lks_imageSourceName;
}
- (NSNumber *)lks_imageViewOidIfHasImage {
if (!self.image) {
return nil;
}
unsigned long oid = [self lks_registerOid];
return @(oid);
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UILabel+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UILabel (LookinServer)
@property(nonatomic, assign) CGFloat lks_fontSize;
- (NSString *)lks_fontName;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UILabel+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import "UILabel+LookinServer.h"
@implementation UILabel (LookinServer)
- (CGFloat)lks_fontSize {
return self.font.pointSize;
}
- (void)setLks_fontSize:(CGFloat)lks_fontSize {
UIFont *font = [self.font fontWithSize:lks_fontSize];
self.font = font;
}
- (NSString *)lks_fontName {
return self.font.fontName;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,19 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITableView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/9/5.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UITableView (LookinServer)
- (NSArray<NSNumber *> *)lks_numberOfRows;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITableView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/9/5.
// https://lookin.work
//
#import "UITableView+LookinServer.h"
#import "LookinServerDefines.h"
@implementation UITableView (LookinServer)
- (NSArray<NSNumber *> *)lks_numberOfRows {
NSUInteger sectionsCount = MIN(self.numberOfSections, 10);
NSArray<NSNumber *> *rowsCount = [NSArray lookin_arrayWithCount:sectionsCount block:^id(NSUInteger idx) {
return @([self numberOfRowsInSection:idx]);
}];
if (rowsCount.count == 0) {
return nil;
}
return rowsCount;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextField+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UITextField (LookinServer)
@property(nonatomic, assign) CGFloat lks_fontSize;
- (NSString *)lks_fontName;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextField+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import "UITextField+LookinServer.h"
@implementation UITextField (LookinServer)
- (CGFloat)lks_fontSize {
return self.font.pointSize;
}
- (void)setLks_fontSize:(CGFloat)lks_fontSize {
UIFont *font = [self.font fontWithSize:lks_fontSize];
self.font = font;
}
- (NSString *)lks_fontName {
return self.font.fontName;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UITextView (LookinServer)
@property(nonatomic, assign) CGFloat lks_fontSize;
- (NSString *)lks_fontName;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import "UITextView+LookinServer.h"
@implementation UITextView (LookinServer)
- (CGFloat)lks_fontSize {
return self.font.pointSize;
}
- (void)setLks_fontSize:(CGFloat)lks_fontSize {
UIFont *font = [self.font fontWithSize:lks_fontSize];
self.font = font;
}
- (NSString *)lks_fontName {
return self.font.fontName;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,44 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/3/19.
// https://lookin.work
//
#import "LookinDefines.h"
#import <UIKit/UIKit.h>
@interface UIView (LookinServer)
/// 如果 myViewController.view = myView则可以通过 myView 的 lks_findHostViewController 方法找到 myViewController
- (UIViewController *)lks_findHostViewController;
/// 是否是 UITabBar 的 childrenView如果是的话则截图时需要强制使用 renderInContext: 的方式而非 drawViewHierarchyInRect:afterScreenUpdates: 否则在 iOS 13 上获取到的图像是空的不知道为什么
@property(nonatomic, assign) BOOL lks_isChildrenViewOfTabBar;
/// point 是相对于 receiver 自身的坐标系
- (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray<Class> *)preferredClasses;
- (CGFloat)lks_bestWidth;
- (CGFloat)lks_bestHeight;
- (CGSize)lks_bestSize;
@property(nonatomic, assign) float lks_horizontalContentHuggingPriority;
@property(nonatomic, assign) float lks_verticalContentHuggingPriority;
@property(nonatomic, assign) float lks_horizontalContentCompressionResistancePriority;
@property(nonatomic, assign) float lks_verticalContentCompressionResistancePriority;
/// 遍历全局的 view 并给他们设置 lks_involvedRawConstraints 属性
+ (void)lks_rebuildGlobalInvolvedRawConstraints;
/// 该属性保存了牵扯到当前 view 的所有 constraints包括那些没有生效的
@property(nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *lks_involvedRawConstraints;
- (NSArray<NSDictionary<NSString *, id> *> *)lks_constraints;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,215 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/3/19.
// https://lookin.work
//
#import "UIView+LookinServer.h"
#import <objc/runtime.h>
#import "LookinObject.h"
#import "LookinAutoLayoutConstraint.h"
#import "LookinServerDefines.h"
#import "LKS_MultiplatformAdapter.h"
@implementation UIView (LookinServer)
- (UIViewController *)lks_findHostViewController {
UIResponder *responder = [self nextResponder];
if (!responder) {
return nil;
}
if (![responder isKindOfClass:[UIViewController class]]) {
return nil;
}
UIViewController *viewController = (UIViewController *)responder;
if (viewController.view != self) {
return nil;
}
return viewController;
}
- (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray<Class> *)preferredClasses {
BOOL isPreferredClassForSelf = [preferredClasses lookin_any:^BOOL(Class obj) {
return [self isKindOfClass:obj];
}];
if (isPreferredClassForSelf) {
return self;
}
UIView *targetView = [self.subviews lookin_lastFiltered:^BOOL(__kindof UIView *obj) {
if (obj.hidden || obj.alpha <= 0.01) {
return NO;
}
BOOL contains = CGRectContainsPoint(obj.frame, point);
return contains;
}];
if (!targetView) {
return self;
}
CGPoint newPoint = [targetView convertPoint:point fromView:self];
targetView = [targetView lks_subviewAtPoint:newPoint preferredClasses:preferredClasses];
return targetView;
}
- (CGSize)lks_bestSize {
return [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
}
- (CGFloat)lks_bestWidth {
return self.lks_bestSize.width;
}
- (CGFloat)lks_bestHeight {
return self.lks_bestSize.height;
}
- (void)setLks_isChildrenViewOfTabBar:(BOOL)lks_isChildrenViewOfTabBar {
[self lookin_bindBOOL:lks_isChildrenViewOfTabBar forKey:@"lks_isChildrenViewOfTabBar"];
}
- (BOOL)lks_isChildrenViewOfTabBar {
return [self lookin_getBindBOOLForKey:@"lks_isChildrenViewOfTabBar"];
}
- (void)setLks_verticalContentHuggingPriority:(float)lks_verticalContentHuggingPriority {
[self setContentHuggingPriority:lks_verticalContentHuggingPriority forAxis:UILayoutConstraintAxisVertical];
}
- (float)lks_verticalContentHuggingPriority {
return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisVertical];
}
- (void)setLks_horizontalContentHuggingPriority:(float)lks_horizontalContentHuggingPriority {
[self setContentHuggingPriority:lks_horizontalContentHuggingPriority forAxis:UILayoutConstraintAxisHorizontal];
}
- (float)lks_horizontalContentHuggingPriority {
return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisHorizontal];
}
- (void)setLks_verticalContentCompressionResistancePriority:(float)lks_verticalContentCompressionResistancePriority {
[self setContentCompressionResistancePriority:lks_verticalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisVertical];
}
- (float)lks_verticalContentCompressionResistancePriority {
return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisVertical];
}
- (void)setLks_horizontalContentCompressionResistancePriority:(float)lks_horizontalContentCompressionResistancePriority {
[self setContentCompressionResistancePriority:lks_horizontalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisHorizontal];
}
- (float)lks_horizontalContentCompressionResistancePriority {
return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
}
+ (void)lks_rebuildGlobalInvolvedRawConstraints {
[[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_removeInvolvedRawConstraintsForViewsRootedByView:window];
}];
[[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_addInvolvedRawConstraintsForViewsRootedByView:window];
}];
}
+ (void)lks_addInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView {
[rootView.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull constraint, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *firstView = constraint.firstItem;
if ([firstView isKindOfClass:[UIView class]] && ![firstView.lks_involvedRawConstraints containsObject:constraint]) {
if (!firstView.lks_involvedRawConstraints) {
firstView.lks_involvedRawConstraints = [NSMutableArray array];
}
[firstView.lks_involvedRawConstraints addObject:constraint];
}
UIView *secondView = constraint.secondItem;
if ([secondView isKindOfClass:[UIView class]] && ![secondView.lks_involvedRawConstraints containsObject:constraint]) {
if (!secondView.lks_involvedRawConstraints) {
secondView.lks_involvedRawConstraints = [NSMutableArray array];
}
[secondView.lks_involvedRawConstraints addObject:constraint];
}
}];
[rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_addInvolvedRawConstraintsForViewsRootedByView:subview];
}];
}
+ (void)lks_removeInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView {
[rootView.lks_involvedRawConstraints removeAllObjects];
[rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_removeInvolvedRawConstraintsForViewsRootedByView:subview];
}];
}
- (void)setLks_involvedRawConstraints:(NSMutableArray<NSLayoutConstraint *> *)lks_involvedRawConstraints {
[self lookin_bindObject:lks_involvedRawConstraints forKey:@"lks_involvedRawConstraints"];
}
- (NSMutableArray<NSLayoutConstraint *> *)lks_involvedRawConstraints {
return [self lookin_getBindObjectForKey:@"lks_involvedRawConstraints"];
}
- (NSArray<LookinAutoLayoutConstraint *> *)lks_constraints {
/**
- lks_involvedRawConstraints self constraints inactive inactive constraints
- constraintsAffectingLayoutForAxis self constraints effectiveConstraints
- constraint effectiveConstraints lks_involvedRawConstraints
· UIWindow minX, minY, width, height effectiveConstraints lks_involvedRawConstraints constraints Xcode Inspector Reveal constraints
· View1 center superview center superview width height effectiveConstraints lks_involvedRawConstraints superview width height View1
*/
NSMutableArray<NSLayoutConstraint *> *effectiveConstraints = [NSMutableArray array];
[effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal]];
[effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisVertical]];
NSArray<LookinAutoLayoutConstraint *> *lookinConstraints = [self.lks_involvedRawConstraints lookin_map:^id(NSUInteger idx, __kindof NSLayoutConstraint *constraint) {
BOOL isEffective = [effectiveConstraints containsObject:constraint];
if ([constraint isActive]) {
// trying to get firstItem or secondItem of an inactive constraint may cause dangling-pointer crash
// https://github.com/QMUI/LookinServer/issues/86
LookinConstraintItemType firstItemType = [self _lks_constraintItemTypeForItem:constraint.firstItem];
LookinConstraintItemType secondItemType = [self _lks_constraintItemTypeForItem:constraint.secondItem];
LookinAutoLayoutConstraint *lookinConstraint = [LookinAutoLayoutConstraint instanceFromNSConstraint:constraint isEffective:isEffective firstItemType:firstItemType secondItemType:secondItemType];
return lookinConstraint;
}
return nil;
}];
return lookinConstraints.count ? lookinConstraints : nil;
}
- (LookinConstraintItemType)_lks_constraintItemTypeForItem:(id)item {
if (!item) {
return LookinConstraintItemTypeNil;
}
if (item == self) {
return LookinConstraintItemTypeSelf;
}
if (item == self.superview) {
return LookinConstraintItemTypeSuper;
}
// runtime UILayoutGuide _UILayoutGuide UIView UIView
if (@available(iOS 9.0, *)) {
if ([item isKindOfClass:[UILayoutGuide class]]) {
return LookinConstraintItemTypeLayoutGuide;
}
}
NSString *className = NSStringFromClass([item class]);
if ([className hasSuffix:@"_UILayoutGuide"]) {
return LookinConstraintItemTypeLayoutGuide;
}
if ([item isKindOfClass:[UIView class]]) {
return LookinConstraintItemTypeView;
}
NSAssert(NO, @"");
return LookinConstraintItemTypeUnknown;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,19 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIViewController+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/4/22.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIViewController (LookinServer)
+ (UIViewController *)lks_visibleViewController;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,48 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIViewController+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/4/22.
// https://lookin.work
//
#import "UIViewController+LookinServer.h"
#import "UIView+LookinServer.h"
#import <objc/runtime.h>
#import "LKS_MultiplatformAdapter.h"
@implementation UIViewController (LookinServer)
+ (nullable UIViewController *)lks_visibleViewController {
UIViewController *rootViewController = [LKS_MultiplatformAdapter keyWindow].rootViewController;
UIViewController *visibleViewController = [rootViewController lks_visibleViewControllerIfExist];
return visibleViewController;
}
- (UIViewController *)lks_visibleViewControllerIfExist {
if (self.presentedViewController) {
return [self.presentedViewController lks_visibleViewControllerIfExist];
}
if ([self isKindOfClass:[UINavigationController class]]) {
return [((UINavigationController *)self).visibleViewController lks_visibleViewControllerIfExist];
}
if ([self isKindOfClass:[UITabBarController class]]) {
return [((UITabBarController *)self).selectedViewController lks_visibleViewControllerIfExist];
}
if (self.isViewLoaded && !self.view.hidden && self.view.alpha > 0.01) {
return self;
} else {
return nil;
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIVisualEffectView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIVisualEffectView (LookinServer)
- (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber;
- (NSNumber *)lks_blurEffectStyleNumber;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,33 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIVisualEffectView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import "UIVisualEffectView+LookinServer.h"
#import "UIBlurEffect+LookinServer.h"
@implementation UIVisualEffectView (LookinServer)
- (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber {
UIBlurEffectStyle style = [lks_blurEffectStyleNumber integerValue];
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:style];
self.effect = effect;
}
- (NSNumber *)lks_blurEffectStyleNumber {
UIVisualEffect *effect = self.effect;
if (![effect isKindOfClass:[UIBlurEffect class]]) {
return nil;
}
UIBlurEffect *blurEffect = (UIBlurEffect *)effect;
return blurEffect.lks_effectStyleNumber;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Lookin.h
// Lookin
//
// Created by Li Kai on 2018/8/5.
// https://lookin.work
//
#import <UIKit/UIKit.h>
extern NSString *const LKS_ConnectionDidEndNotificationName;
@class LookinConnectionResponseAttachment;
@interface LKS_ConnectionManager : NSObject
+ (instancetype)sharedInstance;
@property(nonatomic, assign) BOOL applicationIsActive;
- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag;
- (void)pushData:(NSObject *)data type:(uint32_t)type;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,268 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinServer.m
// LookinServer
//
// Created by Li Kai on 2018/8/5.
// https://lookin.work
//
#import "LKS_ConnectionManager.h"
#import "Lookin_PTChannel.h"
#import "LKS_RequestHandler.h"
#import "LookinConnectionResponseAttachment.h"
#import "LKS_ExportManager.h"
#import "LookinServerDefines.h"
#import "LKS_TraceManager.h"
#import "LKS_MultiplatformAdapter.h"
NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName";
@interface LKS_ConnectionManager () <Lookin_PTChannelDelegate>
@property(nonatomic, weak) Lookin_PTChannel *peerChannel_;
@property(nonatomic, strong) LKS_RequestHandler *requestHandler;
@end
@implementation LKS_ConnectionManager
+ (instancetype)sharedInstance {
static LKS_ConnectionManager *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[LKS_ConnectionManager alloc] init];
});
return sharedInstance;
}
+ (void)load {
// init
[LKS_ConnectionManager sharedInstance];
}
- (instancetype)init {
if (self = [super init]) {
NSLog(@"LookinServer - Will launch. Framework version: %@", LOOKIN_SERVER_READABLE_VERSION);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_2D" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_3D" object:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
[[LKS_ExportManager sharedInstance] exportAndShare];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_RelationSearch" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
[[LKS_TraceManager sharedInstance] addSearchTarger:note.object];
}];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGetLookinInfo:) name:@"GetLookinInfo" object:nil];
self.requestHandler = [LKS_RequestHandler new];
}
return self;
}
- (void)_handleWillResignActiveNotification {
self.applicationIsActive = NO;
if (self.peerChannel_ && ![self.peerChannel_ isConnected]) {
[self.peerChannel_ close];
self.peerChannel_ = nil;
}
}
- (void)_handleApplicationDidBecomeActive {
self.applicationIsActive = YES;
[self searchPortToListenIfNoConnection];
}
- (void)searchPortToListenIfNoConnection {
if ([self.peerChannel_ isConnected]) {
NSLog(@"LookinServer - Abort to search ports. Already has connected channel.");
return;
}
NSLog(@"LookinServer - Searching port to listen...");
[self.peerChannel_ close];
self.peerChannel_ = nil;
if ([self isiOSAppOnMac]) {
[self _tryToListenOnPortFrom:LookinSimulatorIPv4PortNumberStart to:LookinSimulatorIPv4PortNumberEnd current:LookinSimulatorIPv4PortNumberStart];
} else {
[self _tryToListenOnPortFrom:LookinUSBDeviceIPv4PortNumberStart to:LookinUSBDeviceIPv4PortNumberEnd current:LookinUSBDeviceIPv4PortNumberStart];
}
}
- (BOOL)isiOSAppOnMac {
#if TARGET_OS_SIMULATOR
return YES;
#else
if (@available(iOS 14.0, *)) {
// isiOSAppOnMac API iOS 14.0 iOS 14 beta unrecognized selector respondsToSelector
NSProcessInfo *info = [NSProcessInfo processInfo];
if ([info respondsToSelector:@selector(isiOSAppOnMac)]) {
return [info isiOSAppOnMac];
} else if ([info respondsToSelector:@selector(isMacCatalystApp)]) {
return [info isMacCatalystApp];
} else {
return NO;
}
}
if (@available(iOS 13.0, tvOS 13.0, *)) {
return [NSProcessInfo processInfo].isMacCatalystApp;
}
return NO;
#endif
}
- (void)_tryToListenOnPortFrom:(int)fromPort to:(int)toPort current:(int)currentPort {
Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self];
channel.targetPort = currentPort;
[channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
if (error) {
if (error.code == 48) {
//
} else {
//
}
if (currentPort < toPort) {
//
NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@). Will try anothor address ...", currentPort, error);
[self _tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)];
} else {
//
NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@).", currentPort, error);
NSLog(@"LookinServer - Connect failed in the end.");
}
} else {
//
NSLog(@"LookinServer - Connected successfully on 127.0.0.1:%d", currentPort);
// peerChannel_ listening
self.peerChannel_ = channel;
}
}];
}
- (void)dealloc {
if (self.peerChannel_) {
[self.peerChannel_ close];
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
[self _sendData:data frameOfType:requestType tag:tag];
}
- (void)pushData:(NSObject *)data type:(uint32_t)type {
[self _sendData:data frameOfType:type tag:0];
}
- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag {
if (self.peerChannel_) {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data];
dispatch_data_t payload = [archivedData createReferencingDispatchData];
[self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) {
if (error) {
}
}];
}
}
#pragma mark - Lookin_PTChannelDelegate
- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize {
if (channel != self.peerChannel_) {
return NO;
} else if ([self.requestHandler canHandleRequestType:type]) {
return YES;
} else {
[channel close];
return NO;
}
}
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload {
id object = nil;
if (payload) {
id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfDispatchData:payload.dispatchData]];
if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) {
LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject;
object = attachment.data;
} else {
object = unarchivedObject;
}
}
[self.requestHandler handleRequestType:type tag:tag object:object];
}
/// Client channel connected
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address {
NSLog(@"LookinServer - channel:%@, acceptConnection:%@", channel.debugTag, otherChannel.debugTag);
Lookin_PTChannel *previousChannel = self.peerChannel_;
otherChannel.targetPort = address.port;
self.peerChannel_ = otherChannel;
[previousChannel cancel];
}
/// Lookin Lookin
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error {
if (self.peerChannel_ != channel) {
// Client listen port Peertalk cancel didAcceptConnection connected channel cancel channel
NSLog(@"LookinServer - Ignore channel%@ end.", channel.debugTag);
return;
}
// Client
NSLog(@"LookinServer - channel%@ DidEndWithError:%@", channel.debugTag, error);
[[NSNotificationCenter defaultCenter] postNotificationName:LKS_ConnectionDidEndNotificationName object:self];
[self searchPortToListenIfNoConnection];
}
#pragma mark - Handler
- (void)_handleLocalInspect:(NSNotification *)note {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Lookin" message:@"Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
UIWindow *keyWindow = [LKS_MultiplatformAdapter keyWindow];
UIViewController *rootViewController = [keyWindow rootViewController];
[rootViewController presentViewController:alertController animated:YES completion:nil];
NSLog(@"LookinServer - Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality.");
}
- (void)handleGetLookinInfo:(NSNotification *)note {
NSDictionary* userInfo = note.userInfo;
if (!userInfo) {
return;
}
NSMutableDictionary* infoWrapper = userInfo[@"infos"];
if (![infoWrapper isKindOfClass:[NSMutableDictionary class]]) {
NSLog(@"LookinServer - GetLookinInfo failed. Params invalid.");
return;
}
infoWrapper[@"lookinServerVersion"] = LOOKIN_SERVER_READABLE_VERSION;
}
@end
/// 使 NSClassFromString(@"Lookin") LookinServer
@interface Lookin : NSObject
@end
@implementation Lookin
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_RequestHandler.h
// LookinServer
//
// Created by Li Kai on 2019/1/15.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LKS_RequestHandler : NSObject
- (BOOL)canHandleRequestType:(uint32_t)requestType;
- (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,558 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_RequestHandler.m
// LookinServer
//
// Created by Li Kai on 2019/1/15.
// https://lookin.work
//
#import "LKS_RequestHandler.h"
#import "NSObject+LookinServer.h"
#import "UIImage+LookinServer.h"
#import "LKS_ConnectionManager.h"
#import "LookinConnectionResponseAttachment.h"
#import "LookinAttributeModification.h"
#import "LookinDisplayItemDetail.h"
#import "LookinHierarchyInfo.h"
#import "LookinServerDefines.h"
#import <objc/runtime.h>
#import "LookinObject.h"
#import "LookinAppInfo.h"
#import "LKS_AttrGroupsMaker.h"
#import "LKS_InbuiltAttrModificationHandler.h"
#import "LKS_CustomAttrModificationHandler.h"
#import "LKS_AttrModificationPatchHandler.h"
#import "LKS_HierarchyDetailsHandler.h"
#import "LookinStaticAsyncUpdateTask.h"
@interface LKS_RequestHandler ()
@property(nonatomic, strong) NSMutableSet<LKS_HierarchyDetailsHandler *> *activeDetailHandlers;
@end
@implementation LKS_RequestHandler {
NSSet *_validRequestTypes;
}
- (instancetype)init {
if (self = [super init]) {
_validRequestTypes = [NSSet setWithObjects:@(LookinRequestTypePing),
@(LookinRequestTypeApp),
@(LookinRequestTypeHierarchy),
@(LookinRequestTypeInbuiltAttrModification),
@(LookinRequestTypeCustomAttrModification),
@(LookinRequestTypeAttrModificationPatch),
@(LookinRequestTypeHierarchyDetails),
@(LookinRequestTypeFetchObject),
@(LookinRequestTypeAllAttrGroups),
@(LookinRequestTypeAllSelectorNames),
@(LookinRequestTypeInvokeMethod),
@(LookinRequestTypeFetchImageViewImage),
@(LookinRequestTypeModifyRecognizerEnable),
@(LookinPush_CanceHierarchyDetails),
nil];
self.activeDetailHandlers = [NSMutableSet set];
}
return self;
}
- (BOOL)canHandleRequestType:(uint32_t)requestType {
if ([_validRequestTypes containsObject:@(requestType)]) {
return YES;
}
return NO;
}
- (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object {
if (requestType == LookinRequestTypePing) {
LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
// app 使 appIsInBackground app Lookin
if (![LKS_ConnectionManager sharedInstance].applicationIsActive) {
responseAttachment.appIsInBackground = YES;
}
[[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeApp) {
//
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary<NSString *, id> *params = object;
BOOL needImages = ((NSNumber *)params[@"needImages"]).boolValue;
NSArray<NSNumber *> *localIdentifiers = params[@"local"];
LookinAppInfo *appInfo = [LookinAppInfo currentInfoWithScreenshot:needImages icon:needImages localIdentifiers:localIdentifiers];
LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
responseAttachment.data = appInfo;
[[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeHierarchy) {
// LookinClient 1.0.4 nil
NSString *clientVersion = nil;
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary<NSString *, id> *params = object;
NSString *version = params[@"clientVersion"];
if ([version isKindOfClass:[NSString class]]) {
clientVersion = version;
}
}
LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
responseAttachment.data = [LookinHierarchyInfo staticInfoWithLookinVersion:clientVersion];
[[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeInbuiltAttrModification) {
//
[LKS_InbuiltAttrModificationHandler handleModification:object completion:^(LookinDisplayItemDetail *data, NSError *error) {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
if (error) {
attachment.error = error;
} else {
attachment.data = data;
}
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}];
} else if (requestType == LookinRequestTypeCustomAttrModification) {
BOOL succ = [LKS_CustomAttrModificationHandler handleModification:object];
if (succ) {
[self _submitResponseWithData:nil requestType:requestType tag:tag];
} else {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
}
} else if (requestType == LookinRequestTypeAttrModificationPatch) {
NSArray<LookinStaticAsyncUpdateTask *> *tasks = object;
NSUInteger dataTotalCount = tasks.count;
[LKS_InbuiltAttrModificationHandler handlePatchWithTasks:tasks block:^(LookinDisplayItemDetail *data) {
LookinConnectionResponseAttachment *attrAttachment = [LookinConnectionResponseAttachment new];
attrAttachment.data = data;
attrAttachment.dataTotalCount = dataTotalCount;
attrAttachment.currentDataCount = 1;
[[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag];
}];
} else if (requestType == LookinRequestTypeHierarchyDetails) {
NSArray<LookinStaticAsyncUpdateTasksPackage *> *packages = object;
NSUInteger responsesDataTotalCount = [packages lookin_reduceInteger:^NSInteger(NSInteger accumulator, NSUInteger idx, LookinStaticAsyncUpdateTasksPackage *package) {
accumulator += package.tasks.count;
return accumulator;
} initialAccumlator:0];
LKS_HierarchyDetailsHandler *handler = [LKS_HierarchyDetailsHandler new];
[self.activeDetailHandlers addObject:handler];
[handler startWithPackages:packages block:^(NSArray<LookinDisplayItemDetail *> *details) {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
attachment.data = details;
attachment.dataTotalCount = responsesDataTotalCount;
attachment.currentDataCount = details.count;
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag];
} finishedBlock:^{
[self.activeDetailHandlers removeObject:handler];
}];
} else if (requestType == LookinRequestTypeFetchObject) {
unsigned long oid = ((NSNumber *)object).unsignedLongValue;
NSObject *object = [NSObject lks_objectWithOid:oid];
LookinObject *lookinObj = [LookinObject instanceWithObject:object];
LookinConnectionResponseAttachment *attach = [LookinConnectionResponseAttachment new];
attach.data = lookinObj;
[[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeAllAttrGroups) {
unsigned long oid = ((NSNumber *)object).unsignedLongValue;
CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid];
if (![layer isKindOfClass:[CALayer class]]) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:LookinRequestTypeAllAttrGroups tag:tag];
return;
}
NSArray<LookinAttributesGroup *> *list = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
[self _submitResponseWithData:list requestType:LookinRequestTypeAllAttrGroups tag:tag];
} else if (requestType == LookinRequestTypeAllSelectorNames) {
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary *params = object;
Class targetClass = NSClassFromString(params[@"className"]);
BOOL hasArg = [(NSNumber *)params[@"hasArg"] boolValue];
if (!targetClass) {
NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"Didn't find the class named \"%@\". Please input another class and try again."), object];
[self _submitResponseWithError:LookinErrorMake(errorMsg, @"") requestType:requestType tag:tag];
return;
}
NSArray<NSString *> *selNames = [self _methodNameListForClass:targetClass hasArg:hasArg];
[self _submitResponseWithData:selNames requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeInvokeMethod) {
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary *param = object;
unsigned long oid = [param[@"oid"] unsignedLongValue];
NSString *text = param[@"text"];
if (!text.length) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSObject *targerObj = [NSObject lks_objectWithOid:oid];
if (!targerObj) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
return;
}
SEL targetSelector = NSSelectorFromString(text);
if (targetSelector && [targerObj respondsToSelector:targetSelector]) {
NSString *resultDescription;
NSObject *resultObject;
NSError *error;
[self _handleInvokeWithObject:targerObj selector:targetSelector resultDescription:&resultDescription resultObject:&resultObject error:&error];
if (error) {
[self _submitResponseWithError:error requestType:requestType tag:tag];
return;
}
NSMutableDictionary *responseData = [NSMutableDictionary dictionaryWithCapacity:2];
if (resultDescription) {
responseData[@"description"] = resultDescription;
}
if (resultObject) {
responseData[@"object"] = resultObject;
}
[self _submitResponseWithData:responseData requestType:requestType tag:tag];
} else {
NSString *errMsg = [NSString stringWithFormat:LKS_Localized(@"%@ doesn't have an instance method called \"%@\"."), NSStringFromClass(targerObj.class), text];
[self _submitResponseWithError:LookinErrorMake(errMsg, @"") requestType:requestType tag:tag];
}
} else if (requestType == LookinPush_CanceHierarchyDetails) {
[self.activeDetailHandlers enumerateObjectsUsingBlock:^(LKS_HierarchyDetailsHandler * _Nonnull handler, BOOL * _Nonnull stop) {
[handler cancel];
}];
[self.activeDetailHandlers removeAllObjects];
} else if (requestType == LookinRequestTypeFetchImageViewImage) {
if (![object isKindOfClass:[NSNumber class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
unsigned long imageViewOid = [(NSNumber *)object unsignedLongValue];
UIImageView *imageView = (UIImageView *)[NSObject lks_objectWithOid:imageViewOid];
if (!imageView) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
return;
}
if (![imageView isKindOfClass:[UIImageView class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
UIImage *image = imageView.image;
NSData *imageData = [image lookin_data];
[self _submitResponseWithData:imageData requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeModifyRecognizerEnable) {
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary<NSString *, NSNumber *> *params = object;
unsigned long recognizerOid = ((NSNumber *)params[@"oid"]).unsignedLongValue;
BOOL shouldBeEnabled = ((NSNumber *)params[@"enable"]).boolValue;
UIGestureRecognizer *recognizer = (UIGestureRecognizer *)[NSObject lks_objectWithOid:recognizerOid];
if (!recognizer) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
return;
}
if (![recognizer isKindOfClass:[UIGestureRecognizer class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
recognizer.enabled = shouldBeEnabled;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// dispatch enabled
[self _submitResponseWithData:@(recognizer.enabled) requestType:requestType tag:tag];
});
}
}
- (NSArray<NSString *> *)_methodNameListForClass:(Class)aClass hasArg:(BOOL)hasArg {
NSSet<NSString *> *prefixesToVoid = [NSSet setWithObjects:@"_", @"CA_", @"cpl", @"mf_", @"vs_", @"pep_", @"isNS", @"avkit_", @"PG_", @"px_", @"pl_", @"nsli_", @"pu_", @"pxg_", nil];
NSMutableArray<NSString *> *array = [NSMutableArray array];
Class currentClass = aClass;
while (currentClass) {
NSString *className = NSStringFromClass(currentClass);
BOOL isSystemClass = ([className hasPrefix:@"UI"] || [className hasPrefix:@"CA"] || [className hasPrefix:@"NS"]);
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(currentClass, &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
NSString *selName = NSStringFromSelector(method_getName(methods[i]));
if (!hasArg && [selName containsString:@":"]) {
continue;
}
if (isSystemClass) {
BOOL invalid = [prefixesToVoid lookin_any:^BOOL(NSString *prefix) {
return [selName hasPrefix:prefix];
}];
if (invalid) {
continue;
}
}
if (selName.length && ![array containsObject:selName]) {
[array addObject:selName];
}
}
if (methods) free(methods);
currentClass = [currentClass superclass];
}
return [array lookin_sortedArrayByStringLength];
}
- (void)_handleInvokeWithObject:(NSObject *)obj selector:(SEL)selector resultDescription:(NSString **)description resultObject:(LookinObject **)resultObject error:(NSError **)error {
NSMethodSignature *signature = [obj methodSignatureForSelector:selector];
if (signature.numberOfArguments > 2) {
*error = LookinErrorMake(LKS_Localized(@"Lookin doesn't support invoking methods with arguments yet."), @"");
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:obj];
[invocation setSelector:selector];
[invocation invoke];
const char *returnType = [signature methodReturnType];
if (strcmp(returnType, @encode(void)) == 0) {
//void, do nothing
*description = LookinStringFlag_VoidReturn;
} else if (strcmp(returnType, @encode(char)) == 0) {
char charValue;
[invocation getReturnValue:&charValue];
*description = [NSString stringWithFormat:@"%@", @(charValue)];
} else if (strcmp(returnType, @encode(int)) == 0) {
int intValue;
[invocation getReturnValue:&intValue];
if (intValue == INT_MAX) {
*description = @"INT_MAX";
} else if (intValue == INT_MIN) {
*description = @"INT_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(intValue)];
}
} else if (strcmp(returnType, @encode(short)) == 0) {
short shortValue;
[invocation getReturnValue:&shortValue];
if (shortValue == SHRT_MAX) {
*description = @"SHRT_MAX";
} else if (shortValue == SHRT_MIN) {
*description = @"SHRT_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(shortValue)];
}
} else if (strcmp(returnType, @encode(long)) == 0) {
long longValue;
[invocation getReturnValue:&longValue];
if (longValue == NSNotFound) {
*description = @"NSNotFound";
} else if (longValue == LONG_MAX) {
*description = @"LONG_MAX";
} else if (longValue == LONG_MIN) {
*description = @"LONG_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(longValue)];
}
} else if (strcmp(returnType, @encode(long long)) == 0) {
long long longLongValue;
[invocation getReturnValue:&longLongValue];
if (longLongValue == LLONG_MAX) {
*description = @"LLONG_MAX";
} else if (longLongValue == LLONG_MIN) {
*description = @"LLONG_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(longLongValue)];
}
} else if (strcmp(returnType, @encode(unsigned char)) == 0) {
unsigned char ucharValue;
[invocation getReturnValue:&ucharValue];
if (ucharValue == UCHAR_MAX) {
*description = @"UCHAR_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ucharValue)];
}
} else if (strcmp(returnType, @encode(unsigned int)) == 0) {
unsigned int uintValue;
[invocation getReturnValue:&uintValue];
if (uintValue == UINT_MAX) {
*description = @"UINT_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(uintValue)];
}
} else if (strcmp(returnType, @encode(unsigned short)) == 0) {
unsigned short ushortValue;
[invocation getReturnValue:&ushortValue];
if (ushortValue == USHRT_MAX) {
*description = @"USHRT_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ushortValue)];
}
} else if (strcmp(returnType, @encode(unsigned long)) == 0) {
unsigned long ulongValue;
[invocation getReturnValue:&ulongValue];
if (ulongValue == ULONG_MAX) {
*description = @"ULONG_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ulongValue)];
}
} else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
unsigned long long ulongLongValue;
[invocation getReturnValue:&ulongLongValue];
if (ulongLongValue == ULONG_LONG_MAX) {
*description = @"ULONG_LONG_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ulongLongValue)];
}
} else if (strcmp(returnType, @encode(float)) == 0) {
float floatValue;
[invocation getReturnValue:&floatValue];
if (floatValue == FLT_MAX) {
*description = @"FLT_MAX";
} else if (floatValue == FLT_MIN) {
*description = @"FLT_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(floatValue)];
}
} else if (strcmp(returnType, @encode(double)) == 0) {
double doubleValue;
[invocation getReturnValue:&doubleValue];
if (doubleValue == DBL_MAX) {
*description = @"DBL_MAX";
} else if (doubleValue == DBL_MIN) {
*description = @"DBL_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(doubleValue)];
}
} else if (strcmp(returnType, @encode(BOOL)) == 0) {
BOOL boolValue;
[invocation getReturnValue:&boolValue];
*description = boolValue ? @"YES" : @"NO";
} else if (strcmp(returnType, @encode(SEL)) == 0) {
SEL selValue;
[invocation getReturnValue:&selValue];
*description = [NSString stringWithFormat:@"SEL(%@)", NSStringFromSelector(selValue)];
} else if (strcmp(returnType, @encode(Class)) == 0) {
Class classValue;
[invocation getReturnValue:&classValue];
*description = [NSString stringWithFormat:@"<%@>", NSStringFromClass(classValue)];
} else if (strcmp(returnType, @encode(CGPoint)) == 0) {
CGPoint targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromCGPoint(targetValue);
} else if (strcmp(returnType, @encode(CGVector)) == 0) {
CGVector targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromCGVector(targetValue);
} else if (strcmp(returnType, @encode(CGSize)) == 0) {
CGSize targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromCGSize(targetValue);
} else if (strcmp(returnType, @encode(CGRect)) == 0) {
CGRect rectValue;
[invocation getReturnValue:&rectValue];
*description = NSStringFromCGRect(rectValue);
} else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) {
CGAffineTransform rectValue;
[invocation getReturnValue:&rectValue];
*description = NSStringFromCGAffineTransform(rectValue);
} else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) {
UIEdgeInsets targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromUIEdgeInsets(targetValue);
} else if (strcmp(returnType, @encode(UIOffset)) == 0) {
UIOffset targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromUIOffset(targetValue);
} else {
if (@available(iOS 11.0, tvOS 11.0, *)) {
if (strcmp(returnType, @encode(NSDirectionalEdgeInsets)) == 0) {
NSDirectionalEdgeInsets targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromDirectionalEdgeInsets(targetValue);
return;
}
}
NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType];
if ([argType_string hasPrefix:@"@"] || [argType_string hasPrefix:@"^{"]) {
__unsafe_unretained id returnObjValue;
[invocation getReturnValue:&returnObjValue];
if (returnObjValue) {
*description = [NSString stringWithFormat:@"%@", returnObjValue];
LookinObject *parsedLookinObj = [LookinObject instanceWithObject:returnObjValue];
*resultObject = parsedLookinObj;
} else {
*description = @"nil";
}
} else {
*description = [NSString stringWithFormat:LKS_Localized(@"%@ was invoked successfully, but Lookin can't parse the return value:%@"), NSStringFromSelector(selector), argType_string];
}
}
}
- (void)_submitResponseWithError:(NSError *)error requestType:(uint32_t)requestType tag:(uint32_t)tag {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
attachment.error = error;
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}
- (void)_submitResponseWithData:(NSObject *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
attachment.data = data;
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrModificationPatchHandler.h
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinDisplayItemDetail;
@interface LKS_AttrModificationPatchHandler : NSObject
/**
@param oids 数组内 idx 较小的应该为 displayItems 里的 subItemidx 较大的应该为 superItem
@param lowImageQuality 是否采用低图像质量
@param block 该 block 会被多次调用,其中 tasksTotalCount 是总的调用次数(即可被用来作为 TotalResponseCount
*/
+ (void)handleLayerOids:(NSArray<NSNumber *> *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,51 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrModificationPatchHandler.m
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import "LKS_AttrModificationPatchHandler.h"
#import "LookinDisplayItemDetail.h"
#import "LookinServerDefines.h"
@implementation LKS_AttrModificationPatchHandler
+ (void)handleLayerOids:(NSArray<NSNumber *> *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block {
if (!block) {
NSAssert(NO, @"");
return;
}
if (![oids isKindOfClass:[NSArray class]]) {
block(nil, 1, LookinErr_Inner);
return;
}
[oids enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
unsigned long oid = [obj unsignedLongValue];
LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new];
detail.displayItemOid = oid;
CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid];
if (![layer isKindOfClass:[CALayer class]]) {
block(nil, idx + 1, LookinErr_ObjNotFound);
*stop = YES;
return;
}
if (idx == 0) {
detail.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowImageQuality];
detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality];
} else {
detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality];
}
block(detail, oids.count, nil);
}];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,19 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrModificationHandler.h
// LookinServer
//
// Created by likaimacbookhome on 2023/11/4.
//
#import <Foundation/Foundation.h>
#import "LookinCustomAttrModification.h"
@interface LKS_CustomAttrModificationHandler : NSObject
/// 返回值表示是否修改成功(有成功调用 setter block 就算成功)
+ (BOOL)handleModification:(LookinCustomAttrModification *)modification;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,155 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrModificationHandler.m
// LookinServer
//
// Created by likaimacbookhome on 2023/11/4.
//
#import "LKS_CustomAttrModificationHandler.h"
#import "LKS_CustomAttrSetterManager.h"
#import "UIColor+LookinServer.h"
@implementation LKS_CustomAttrModificationHandler
+ (BOOL)handleModification:(LookinCustomAttrModification *)modification {
if (!modification || modification.customSetterID.length == 0) {
return NO;
}
switch (modification.attrType) {
case LookinAttrTypeNSString: {
NSString *newValue = modification.value;
if (newValue != nil && ![newValue isKindOfClass:[NSString class]]) {
// nil
return NO;
}
LKS_StringSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getStringSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue);
return YES;
}
case LookinAttrTypeDouble: {
NSNumber *newValue = modification.value;
if (![newValue isKindOfClass:[NSNumber class]]) {
return NO;
}
LKS_NumberSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getNumberSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue);
return YES;
}
case LookinAttrTypeBOOL: {
NSNumber *newValue = modification.value;
if (![newValue isKindOfClass:[NSNumber class]]) {
return NO;
}
LKS_BoolSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getBoolSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.boolValue);
return YES;
}
case LookinAttrTypeUIColor: {
LKS_ColorSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getColorSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
NSArray<NSNumber *> *newValue = modification.value;
if (newValue == nil) {
// nil
setter(nil);
return YES;
}
if (![newValue isKindOfClass:[NSArray class]]) {
return NO;
}
UIColor *color = [UIColor lks_colorFromRGBAComponents:newValue];
if (!color) {
return NO;
}
setter(color);
return YES;
}
case LookinAttrTypeEnumString: {
NSString *newValue = modification.value;
if (![newValue isKindOfClass:[NSString class]]) {
return NO;
}
LKS_EnumSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getEnumSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue);
return YES;
}
case LookinAttrTypeCGRect: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_RectSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getRectSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.CGRectValue);
return YES;
}
case LookinAttrTypeCGSize: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_SizeSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getSizeSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.CGSizeValue);
return YES;
}
case LookinAttrTypeCGPoint: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_PointSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getPointSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.CGPointValue);
return YES;
}
case LookinAttrTypeUIEdgeInsets: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_InsetsSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getInsetsSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.UIEdgeInsetsValue);
return YES;
}
default:
return NO;
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,30 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDetailsHandler.h
// LookinServer
//
// Created by Li Kai on 2019/6/20.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinDisplayItemDetail, LookinStaticAsyncUpdateTasksPackage;
typedef void (^LKS_HierarchyDetailsHandler_ProgressBlock)(NSArray<LookinDisplayItemDetail *> *details);
typedef void (^LKS_HierarchyDetailsHandler_FinishBlock)(void);
@interface LKS_HierarchyDetailsHandler : NSObject
/// packages 会按照 idx 从小到大的顺序被执行
/// 全部任务完成时finishBlock 会被调用
/// 如果调用了 cancel则 finishBlock 不会被执行
- (void)startWithPackages:(NSArray<LookinStaticAsyncUpdateTasksPackage *> *)packages block:(LKS_HierarchyDetailsHandler_ProgressBlock)progressBlock finishedBlock:(LKS_HierarchyDetailsHandler_FinishBlock)finishBlock;
/// 取消所有任务
- (void)cancel;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,148 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDetailsHandler.m
// LookinServer
//
// Created by Li Kai on 2019/6/20.
// https://lookin.work
//
#import "LKS_HierarchyDetailsHandler.h"
#import "LookinDisplayItemDetail.h"
#import "LKS_AttrGroupsMaker.h"
#import "LookinStaticAsyncUpdateTask.h"
#import "LKS_ConnectionManager.h"
#import "LookinServerDefines.h"
#import "LKS_CustomAttrGroupsMaker.h"
#import "LKS_HierarchyDisplayItemsMaker.h"
@interface LKS_HierarchyDetailsHandler ()
@property(nonatomic, strong) NSMutableArray<LookinStaticAsyncUpdateTasksPackage *> *taskPackages;
/// oid attrGroups
@property(nonatomic, strong) NSMutableSet<NSNumber *> *attrGroupsSyncedOids;
@property(nonatomic, copy) LKS_HierarchyDetailsHandler_ProgressBlock progressBlock;
@property(nonatomic, copy) LKS_HierarchyDetailsHandler_FinishBlock finishBlock;
@end
@implementation LKS_HierarchyDetailsHandler
- (instancetype)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleConnectionDidEnd:) name:LKS_ConnectionDidEndNotificationName object:nil];
self.attrGroupsSyncedOids = [NSMutableSet set];
}
return self;
}
- (void)startWithPackages:(NSArray<LookinStaticAsyncUpdateTasksPackage *> *)packages block:(LKS_HierarchyDetailsHandler_ProgressBlock)progressBlock finishedBlock:(LKS_HierarchyDetailsHandler_FinishBlock)finishBlock {
if (!progressBlock || !finishBlock) {
NSAssert(NO, @"");
return;
}
if (!packages.count) {
finishBlock();
return;
}
self.taskPackages = [packages mutableCopy];
self.progressBlock = progressBlock;
self.finishBlock = finishBlock;
[UIView lks_rebuildGlobalInvolvedRawConstraints];
[self _dequeueAndHandlePackage];
}
- (void)cancel {
[self.taskPackages removeAllObjects];
}
- (void)_dequeueAndHandlePackage {
dispatch_async(dispatch_get_main_queue(), ^{
LookinStaticAsyncUpdateTasksPackage *package = self.taskPackages.firstObject;
if (!package) {
self.finishBlock();
return;
}
// NSLog(@"LookinServer - will handle tasks, count: %@", @(tasks.count));
NSArray<LookinDisplayItemDetail *> *details = [package.tasks lookin_map:^id(NSUInteger idx, LookinStaticAsyncUpdateTask *task) {
LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new];
itemDetail.displayItemOid = task.oid;
id object = [NSObject lks_objectWithOid:task.oid];
if (!object || ![object isKindOfClass:[CALayer class]]) {
itemDetail.failureCode = -1;
return itemDetail;
}
CALayer *layer = object;
if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) {
UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO];
itemDetail.soloScreenshot = image;
} else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) {
UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO];
itemDetail.groupScreenshot = image;
}
BOOL shouldMakeAttr = [self queryIfShouldMakeAttrsFromTask:task];
if (shouldMakeAttr) {
itemDetail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
NSString *version = task.clientReadableVersion;
if (version.length > 0 && [version lookin_numbericOSVersion] >= 10004) {
LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
[maker execute];
itemDetail.customAttrGroupList = [maker getGroups];
itemDetail.customDisplayTitle = [maker getCustomDisplayTitle];
itemDetail.danceUISource = [maker getDanceUISource];
}
[self.attrGroupsSyncedOids addObject:@(task.oid)];
}
if (task.needBasisVisualInfo) {
itemDetail.frameValue = [NSValue valueWithCGRect:layer.frame];
itemDetail.boundsValue = [NSValue valueWithCGRect:layer.bounds];
itemDetail.hiddenValue = [NSNumber numberWithBool:layer.isHidden];
itemDetail.alphaValue = @(layer.opacity);
}
if (task.needSubitems) {
itemDetail.subitems = [LKS_HierarchyDisplayItemsMaker subitemsOfLayer:layer];
}
return itemDetail;
}];
self.progressBlock(details);
[self.taskPackages removeObjectAtIndex:0];
[self _dequeueAndHandlePackage];
});
}
- (BOOL)queryIfShouldMakeAttrsFromTask:(LookinStaticAsyncUpdateTask *)task {
switch (task.attrRequest) {
case LookinDetailUpdateTaskAttrRequest_Automatic: {
BOOL alreadyMadeBefore = [self.attrGroupsSyncedOids containsObject:@(task.oid)];
return !alreadyMadeBefore;
}
case LookinDetailUpdateTaskAttrRequest_Need:
return YES;
case LookinDetailUpdateTaskAttrRequest_NotNeed:
return NO;
}
NSAssert(NO, @"");
return YES;
}
- (void)_handleConnectionDidEnd:(id)obj {
[self cancel];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_InbuiltAttrModificationHandler.h
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinAttributeModification, LookinDisplayItemDetail, LookinStaticAsyncUpdateTask;
@interface LKS_InbuiltAttrModificationHandler : NSObject
+ (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion;
+ (void)handlePatchWithTasks:(NSArray<LookinStaticAsyncUpdateTask *> *)tasks block:(void (^)(LookinDisplayItemDetail *data))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,255 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_InbuiltAttrModificationHandler.m
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import "LKS_InbuiltAttrModificationHandler.h"
#import "UIColor+LookinServer.h"
#import "LookinAttributeModification.h"
#import "LKS_AttrGroupsMaker.h"
#import "LookinDisplayItemDetail.h"
#import "LookinStaticAsyncUpdateTask.h"
#import "LookinServerDefines.h"
#import "LKS_CustomAttrGroupsMaker.h"
@implementation LKS_InbuiltAttrModificationHandler
+ (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion {
if (!completion) {
NSAssert(NO, @"");
return;
}
if (!modification || ![modification isKindOfClass:[LookinAttributeModification class]]) {
completion(nil, LookinErr_Inner);
return;
}
NSObject *receiver = [NSObject lks_objectWithOid:modification.targetOid];
if (!receiver) {
completion(nil, LookinErr_ObjNotFound);
return;
}
NSMethodSignature *setterSignature = [receiver methodSignatureForSelector:modification.setterSelector];
NSInvocation *setterInvocation = [NSInvocation invocationWithMethodSignature:setterSignature];
setterInvocation.target = receiver;
setterInvocation.selector = modification.setterSelector;
if (setterSignature.numberOfArguments != 3 || ![receiver respondsToSelector:modification.setterSelector]) {
completion(nil, LookinErr_Inner);
return;
}
switch (modification.attrType) {
case LookinAttrTypeNone:
case LookinAttrTypeVoid: {
completion(nil, LookinErr_Inner);
return;
}
case LookinAttrTypeChar: {
char expectedValue = [(NSNumber *)modification.value charValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeInt:
case LookinAttrTypeEnumInt: {
int expectedValue = [(NSNumber *)modification.value intValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeShort: {
short expectedValue = [(NSNumber *)modification.value shortValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeLong:
case LookinAttrTypeEnumLong: {
long expectedValue = [(NSNumber *)modification.value longValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeLongLong: {
long long expectedValue = [(NSNumber *)modification.value longLongValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedChar: {
unsigned char expectedValue = [(NSNumber *)modification.value unsignedCharValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedInt: {
unsigned int expectedValue = [(NSNumber *)modification.value unsignedIntValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedShort: {
unsigned short expectedValue = [(NSNumber *)modification.value unsignedShortValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedLong: {
unsigned long expectedValue = [(NSNumber *)modification.value unsignedLongValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedLongLong: {
unsigned long long expectedValue = [(NSNumber *)modification.value unsignedLongLongValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeFloat: {
float expectedValue = [(NSNumber *)modification.value floatValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeDouble: {
double expectedValue = [(NSNumber *)modification.value doubleValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeBOOL: {
BOOL expectedValue = [(NSNumber *)modification.value boolValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeSel: {
SEL expectedValue = NSSelectorFromString(modification.value);
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeClass: {
Class expectedValue = NSClassFromString(modification.value);
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGPoint: {
CGPoint expectedValue = [(NSValue *)modification.value CGPointValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGVector: {
CGVector expectedValue = [(NSValue *)modification.value CGVectorValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGSize: {
CGSize expectedValue = [(NSValue *)modification.value CGSizeValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGRect: {
CGRect expectedValue = [(NSValue *)modification.value CGRectValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGAffineTransform: {
CGAffineTransform expectedValue = [(NSValue *)modification.value CGAffineTransformValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUIEdgeInsets: {
UIEdgeInsets expectedValue = [(NSValue *)modification.value UIEdgeInsetsValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUIOffset: {
UIOffset expectedValue = [(NSValue *)modification.value UIOffsetValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCustomObj:
case LookinAttrTypeNSString: {
NSObject *expectedValue = modification.value;
[setterInvocation setArgument:&expectedValue atIndex:2];
[setterInvocation retainArguments];
break;
}
case LookinAttrTypeUIColor: {
NSArray<NSNumber *> *rgba = modification.value;
UIColor *expectedValue = [UIColor lks_colorFromRGBAComponents:rgba];
[setterInvocation setArgument:&expectedValue atIndex:2];
[setterInvocation retainArguments];
break;
}
default: {
completion(nil, LookinErr_Inner);
return;
}
}
NSError *error = nil;
@try {
[setterInvocation invoke];
} @catch (NSException *exception) {
NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"<%@: %p>: an exception was raised when invoking %@. (%@)"), NSStringFromClass(receiver.class), receiver, NSStringFromSelector(modification.setterSelector), exception.reason];
error = [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Exception userInfo:@{NSLocalizedDescriptionKey:LKS_Localized(@"The modification may failed."), NSLocalizedRecoverySuggestionErrorKey:errorMsg}];
} @finally {
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CALayer *layer = nil;
if ([receiver isKindOfClass:[CALayer class]]) {
layer = (CALayer *)receiver;
} else if ([receiver isKindOfClass:[UIView class]]) {
layer = ((UIView *)receiver).layer;
} else {
completion(nil, LookinErr_ObjNotFound);
return;
}
// frame relayout dispatch attrGroups
LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new];
detail.displayItemOid = modification.targetOid;
detail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
NSString *version = modification.clientReadableVersion;
if (version.length > 0 && [version lookin_numbericOSVersion] >= 10004) {
LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
[maker execute];
detail.customAttrGroupList = [maker getGroups];
}
detail.frameValue = [NSValue valueWithCGRect:layer.frame];
detail.boundsValue = [NSValue valueWithCGRect:layer.bounds];
detail.hiddenValue = [NSNumber numberWithBool:layer.isHidden];
detail.alphaValue = @(layer.opacity);
completion(detail, error);
});
}
+ (void)handlePatchWithTasks:(NSArray<LookinStaticAsyncUpdateTask *> *)tasks block:(void (^)(LookinDisplayItemDetail *data))block {
if (!block) {
NSAssert(NO, @"");
return;
}
[tasks enumerateObjectsUsingBlock:^(LookinStaticAsyncUpdateTask * _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new];
itemDetail.displayItemOid = task.oid;
id object = [NSObject lks_objectWithOid:task.oid];
if (!object || ![object isKindOfClass:[CALayer class]]) {
block(itemDetail);
return;
}
CALayer *layer = object;
if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) {
UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO];
itemDetail.soloScreenshot = image;
} else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) {
UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO];
itemDetail.groupScreenshot = image;
}
block(itemDetail);
}];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,17 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/7/20.
// https://lookin.work
//
#ifndef LookinServer_h
#define LookinServer_h
#endif /* LookinServer_h */
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKSConfigManager.h
// LookinServer
//
// Created by likai.123 on 2023/1/10.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LKSConfigManager : NSObject
+ (NSArray<NSString *> *)collapsedClassList;
+ (NSDictionary<NSString *, UIColor *> *)colorAlias;
+ (BOOL)shouldCaptureScreenshotOfLayer:(CALayer *)layer;
@end
NS_ASSUME_NONNULL_END
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,195 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKSConfigManager.m
// LookinServer
//
// Created by likai.123 on 2023/1/10.
//
#import "LKSConfigManager.h"
#import "NSArray+Lookin.h"
#import "CALayer+LookinServer.h"
@implementation LKSConfigManager
+ (NSArray<NSString *> *)collapsedClassList {
NSArray<NSString *> *result = [self queryCollapsedClassListWithClass:[NSObject class] selector:@"lookin_collapsedClassList"];
if (result) {
return result;
}
// Legacy logic. Deprecated.
Class configClass = NSClassFromString(@"LookinConfig");
if (!configClass) {
return nil;
}
NSArray<NSString *> *legacyCodeResult = [self queryCollapsedClassListWithClass:configClass selector:@"collapsedClasses"];
return legacyCodeResult;
}
+ (NSArray<NSString *> *)queryCollapsedClassListWithClass:(Class)class selector:(NSString *)selectorName {
SEL selector = NSSelectorFromString(selectorName);
if (![class respondsToSelector:selector]) {
return nil;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[class methodSignatureForSelector:selector]];
[invocation setTarget:class];
[invocation setSelector:selector];
[invocation invoke];
void *arrayValue;
[invocation getReturnValue:&arrayValue];
id classList = (__bridge id)(arrayValue);
if ([classList isKindOfClass:[NSArray class]]) {
NSArray *validClassList = [((NSArray *)classList) lookin_filter:^BOOL(id obj) {
return [obj isKindOfClass:[NSString class]];
}];
return [validClassList copy];
}
return nil;
}
+ (NSDictionary<NSString *, UIColor *> *)colorAlias {
NSDictionary<NSString *, UIColor *> *result = [self queryColorAliasWithClass:[NSObject class] selector:@"lookin_colorAlias"];
if (result) {
return result;
}
// Legacy logic. Deprecated.
Class configClass = NSClassFromString(@"LookinConfig");
if (!configClass) {
return nil;
}
NSDictionary<NSString *, UIColor *> *legacyCodeResult = [self queryColorAliasWithClass:configClass selector:@"colors"];
return legacyCodeResult;
}
+ (NSDictionary<NSString *, UIColor *> *)queryColorAliasWithClass:(Class)class selector:(NSString *)selectorName {
SEL selector = NSSelectorFromString(selectorName);
if (![class respondsToSelector:selector]) {
return nil;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[class methodSignatureForSelector:selector]];
[invocation setTarget:class];
[invocation setSelector:selector];
[invocation invoke];
void *dictValue;
[invocation getReturnValue:&dictValue];
id colorAlias = (__bridge id)(dictValue);
if ([colorAlias isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *validDictionary = [NSMutableDictionary dictionary];
[(NSDictionary *)colorAlias enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([key isKindOfClass:[NSString class]]) {
if ([obj isKindOfClass:[UIColor class]]) {
[validDictionary setObject:obj forKey:key];
} else if ([obj isKindOfClass:[NSDictionary class]]) {
__block BOOL isValidSubDict = YES;
[((NSDictionary *)obj) enumerateKeysAndObjectsUsingBlock:^(id _Nonnull subKey, id _Nonnull subObj, BOOL * _Nonnull stop) {
if (![subKey isKindOfClass:[NSString class]] || ![subObj isKindOfClass:[UIColor class]]) {
isValidSubDict = NO;
*stop = YES;
}
}];
if (isValidSubDict) {
[validDictionary setObject:obj forKey:key];
}
}
}
}];
return [validDictionary copy];
}
return nil;
}
+ (BOOL)shouldCaptureScreenshotOfLayer:(CALayer *)layer {
if (!layer) {
return YES;
}
if (![self shouldCaptureImageOfLayer:layer]) {
return NO;
}
UIView *view = layer.lks_hostView;
if (!view) {
return YES;
}
if (![self shouldCaptureImageOfView:view]) {
return NO;
}
return YES;
}
+ (BOOL)shouldCaptureImageOfLayer:(CALayer *)layer {
if (!layer) {
return YES;
}
SEL selector = NSSelectorFromString(@"lookin_shouldCaptureImageOfLayer:");
if ([NSObject respondsToSelector:selector]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSObject methodSignatureForSelector:selector]];
[invocation setTarget:[NSObject class]];
[invocation setSelector:selector];
[invocation setArgument:&layer atIndex:2];
[invocation invoke];
BOOL resultValue = YES;
[invocation getReturnValue:&resultValue];
if (!resultValue) {
return NO;
}
}
SEL selector2 = NSSelectorFromString(@"lookin_shouldCaptureImage");
if ([layer respondsToSelector:selector2]) {
NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:[layer methodSignatureForSelector:selector2]];
[invocation2 setTarget:layer];
[invocation2 setSelector:selector2];
[invocation2 invoke];
BOOL resultValue2 = YES;
[invocation2 getReturnValue:&resultValue2];
if (!resultValue2) {
return NO;
}
}
return YES;
}
+ (BOOL)shouldCaptureImageOfView:(UIView *)view {
if (!view) {
return YES;
}
SEL selector = NSSelectorFromString(@"lookin_shouldCaptureImageOfView:");
if ([NSObject respondsToSelector:selector]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSObject methodSignatureForSelector:selector]];
[invocation setTarget:[NSObject class]];
[invocation setSelector:selector];
[invocation setArgument:&view atIndex:2];
[invocation invoke];
BOOL resultValue = YES;
[invocation getReturnValue:&resultValue];
if (!resultValue) {
return NO;
}
}
SEL selector2 = NSSelectorFromString(@"lookin_shouldCaptureImage");
if ([view respondsToSelector:selector2]) {
NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:[view methodSignatureForSelector:selector2]];
[invocation2 setTarget:view];
[invocation2 setSelector:selector2];
[invocation2 invoke];
BOOL resultValue2 = YES;
[invocation2 getReturnValue:&resultValue2];
if (!resultValue2) {
return NO;
}
}
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrGroupsMaker.h
// LookinServer
//
// Created by Li Kai on 2019/6/6.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinAttributesGroup;
@interface LKS_AttrGroupsMaker : NSObject
+ (NSArray<LookinAttributesGroup *> *)attrGroupsForLayer:(CALayer *)layer;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,302 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrGroupsMaker.m
// LookinServer
//
// Created by Li Kai on 2019/6/6.
// https://lookin.work
//
#import "LKS_AttrGroupsMaker.h"
#import "LookinAttributesGroup.h"
#import "LookinAttributesSection.h"
#import "LookinAttribute.h"
#import "LookinDashboardBlueprint.h"
#import "LookinIvarTrace.h"
#import "UIColor+LookinServer.h"
#import "LookinServerDefines.h"
@implementation LKS_AttrGroupsMaker
+ (NSArray<LookinAttributesGroup *> *)attrGroupsForLayer:(CALayer *)layer {
if (!layer) {
NSAssert(NO, @"");
return nil;
}
NSArray<LookinAttributesGroup *> *groups = [[LookinDashboardBlueprint groupIDs] lookin_map:^id(NSUInteger idx, LookinAttrGroupIdentifier groupID) {
LookinAttributesGroup *group = [LookinAttributesGroup new];
group.identifier = groupID;
NSArray<LookinAttrSectionIdentifier> *secIDs = [LookinDashboardBlueprint sectionIDsForGroupID:groupID];
group.attrSections = [secIDs lookin_map:^id(NSUInteger idx, LookinAttrSectionIdentifier secID) {
LookinAttributesSection *sec = [LookinAttributesSection new];
sec.identifier = secID;
NSArray<LookinAttrIdentifier> *attrIDs = [LookinDashboardBlueprint attrIDsForSectionID:secID];
sec.attributes = [attrIDs lookin_map:^id(NSUInteger idx, LookinAttrIdentifier attrID) {
NSInteger minAvailableVersion = [LookinDashboardBlueprint minAvailableOSVersionWithAttrID:attrID];
if (minAvailableVersion > 0 && (NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < minAvailableVersion)) {
// iOS
return nil;
}
id targetObj = nil;
if ([LookinDashboardBlueprint isUIViewPropertyWithAttrID:attrID]) {
targetObj = layer.lks_hostView;
} else {
targetObj = layer;
}
if (targetObj) {
Class targetClass = NSClassFromString([LookinDashboardBlueprint classNameWithAttrID:attrID]);
if (![targetObj isKindOfClass:targetClass]) {
return nil;
}
LookinAttribute *attr = [self _attributeWithIdentifer:attrID targetObject:targetObj];
return attr;
} else {
return nil;
}
}];
if (sec.attributes.count) {
return sec;
} else {
return nil;
}
}];
if ([groupID isEqualToString:LookinAttrGroup_AutoLayout]) {
// AutoLayout Constraints Hugging Resistance AutoLayout
BOOL hasConstraits = [group.attrSections lookin_any:^BOOL(LookinAttributesSection *obj) {
return [obj.identifier isEqualToString:LookinAttrSec_AutoLayout_Constraints];
}];
if (!hasConstraits) {
return nil;
}
}
if (group.attrSections.count) {
return group;
} else {
return nil;
}
}];
return groups;
}
+ (LookinAttribute *)_attributeWithIdentifer:(LookinAttrIdentifier)identifier targetObject:(id)target {
if (!target) {
NSAssert(NO, @"");
return nil;
}
LookinAttribute *attribute = [LookinAttribute new];
attribute.identifier = identifier;
SEL getter = [LookinDashboardBlueprint getterWithAttrID:identifier];
if (!getter) {
NSAssert(NO, @"");
return nil;
}
if (![target respondsToSelector:getter]) {
// QMUI QMUI
return nil;
}
NSMethodSignature *signature = [target methodSignatureForSelector:getter];
if (signature.numberOfArguments > 2) {
NSAssert(NO, @"getter 不可以有参数");
return nil;
}
if (strcmp([signature methodReturnType], @encode(void)) == 0) {
NSAssert(NO, @"getter 返回值不能为 void");
return nil;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = target;
invocation.selector = getter;
[invocation invoke];
const char *returnType = [signature methodReturnType];
if (strcmp(returnType, @encode(char)) == 0) {
char targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeChar;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(int)) == 0) {
int targetValue;
[invocation getReturnValue:&targetValue];
attribute.value = @(targetValue);
if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) {
attribute.attrType = LookinAttrTypeEnumInt;
} else {
attribute.attrType = LookinAttrTypeInt;
}
} else if (strcmp(returnType, @encode(short)) == 0) {
short targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeShort;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(long)) == 0) {
long targetValue;
[invocation getReturnValue:&targetValue];
attribute.value = @(targetValue);
if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) {
attribute.attrType = LookinAttrTypeEnumLong;
} else {
attribute.attrType = LookinAttrTypeLong;
}
} else if (strcmp(returnType, @encode(long long)) == 0) {
long long targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeLongLong;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned char)) == 0) {
unsigned char targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedChar;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned int)) == 0) {
unsigned int targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedInt;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned short)) == 0) {
unsigned short targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedShort;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned long)) == 0) {
unsigned long targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedLong;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
unsigned long long targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedLongLong;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(float)) == 0) {
float targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeFloat;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(double)) == 0) {
double targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeDouble;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(BOOL)) == 0) {
BOOL targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeBOOL;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(SEL)) == 0) {
SEL targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeSel;
attribute.value = NSStringFromSelector(targetValue);
} else if (strcmp(returnType, @encode(Class)) == 0) {
Class targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeClass;
attribute.value = NSStringFromClass(targetValue);
} else if (strcmp(returnType, @encode(CGPoint)) == 0) {
CGPoint targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGPoint;
attribute.value = [NSValue valueWithCGPoint:targetValue];
} else if (strcmp(returnType, @encode(CGVector)) == 0) {
CGVector targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGVector;
attribute.value = [NSValue valueWithCGVector:targetValue];
} else if (strcmp(returnType, @encode(CGSize)) == 0) {
CGSize targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGSize;
attribute.value = [NSValue valueWithCGSize:targetValue];
} else if (strcmp(returnType, @encode(CGRect)) == 0) {
CGRect targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGRect;
attribute.value = [NSValue valueWithCGRect:targetValue];
} else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) {
CGAffineTransform targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGAffineTransform;
attribute.value = [NSValue valueWithCGAffineTransform:targetValue];
} else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) {
UIEdgeInsets targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUIEdgeInsets;
attribute.value = [NSValue valueWithUIEdgeInsets:targetValue];
} else if (strcmp(returnType, @encode(UIOffset)) == 0) {
UIOffset targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUIOffset;
attribute.value = [NSValue valueWithUIOffset:targetValue];
} else {
NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType];
if ([argType_string hasPrefix:@"@"]) {
__unsafe_unretained id returnObjValue;
[invocation getReturnValue:&returnObjValue];
if (!returnObjValue && [LookinDashboardBlueprint hideIfNilWithAttrID:identifier]) {
// value nil
return nil;
}
attribute.attrType = [LookinDashboardBlueprint objectAttrTypeWithAttrID:identifier];
if (attribute.attrType == LookinAttrTypeUIColor) {
if (returnObjValue == nil) {
attribute.value = nil;
} else if ([returnObjValue isKindOfClass:[UIColor class]] && [returnObjValue respondsToSelector:@selector(lks_rgbaComponents)]) {
attribute.value = [returnObjValue lks_rgbaComponents];
} else {
// https://github.com/QMUI/LookinServer/issues/124
return nil;
}
} else {
attribute.value = returnObjValue;
}
} else {
NSAssert(NO, @"不支持解析该类型的返回值");
return nil;
}
}
return attribute;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,27 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrGroupsMaker.h
// LookinServer
//
// Created by LikaiMacStudioWork on 2023/10/31.
//
#import "LookinDefines.h"
@class LookinAttributesGroup;
@interface LKS_CustomAttrGroupsMaker : NSObject
- (instancetype)initWithLayer:(CALayer *)layer;
- (void)execute;
- (NSArray<LookinAttributesGroup *> *)getGroups;
- (NSString *)getCustomDisplayTitle;
- (NSString *)getDanceUISource;
+ (NSArray<LookinAttributesGroup *> *)makeGroupsFromRawProperties:(NSArray *)rawProperties saveCustomSetter:(BOOL)saveCustomSetter;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,486 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrGroupsMaker.m
// LookinServer
//
// Created by LikaiMacStudioWork on 2023/10/31.
//
#import "LKS_CustomAttrGroupsMaker.h"
#import "LKS_AttrGroupsMaker.h"
#import "LookinAttributesGroup.h"
#import "LookinAttributesSection.h"
#import "LookinAttribute.h"
#import "LookinDashboardBlueprint.h"
#import "LookinIvarTrace.h"
#import "UIColor+LookinServer.h"
#import "LookinServerDefines.h"
#import "LKS_CustomAttrSetterManager.h"
@interface LKS_CustomAttrGroupsMaker ()
/// key section title
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray<LookinAttribute *> *> *sectionAndAttrs;
@property(nonatomic, copy) NSString *resolvedCustomDisplayTitle;
@property(nonatomic, copy) NSString *resolvedDanceUISource;
@property(nonatomic, strong) NSMutableArray *resolvedGroups;
@property(nonatomic, weak) CALayer *layer;
@end
@implementation LKS_CustomAttrGroupsMaker
- (instancetype)initWithLayer:(CALayer *)layer {
if (self = [super init]) {
self.sectionAndAttrs = [NSMutableDictionary dictionary];
self.layer = layer;
}
return self;
}
- (void)execute {
if (!self.layer) {
NSAssert(NO, @"");
return;
}
NSMutableArray<NSString *> *selectors = [NSMutableArray array];
[selectors addObject:@"lookin_customDebugInfos"];
for (int i = 0; i < 5; i++) {
[selectors addObject:[NSString stringWithFormat:@"lookin_customDebugInfos_%@", @(i)]];
}
for (NSString *name in selectors) {
[self makeAttrsForViewOrLayer:self.layer selectorName:name];
UIView *view = self.layer.lks_hostView;
if (view) {
[self makeAttrsForViewOrLayer:view selectorName:name];
}
}
if ([self.sectionAndAttrs count] == 0) {
return;
}
NSMutableArray<LookinAttributesGroup *> *groups = [NSMutableArray array];
[self.sectionAndAttrs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull groupTitle, NSMutableArray<LookinAttribute *> * _Nonnull attrs, BOOL * _Nonnull stop) {
LookinAttributesGroup *group = [LookinAttributesGroup new];
group.userCustomTitle = groupTitle;
group.identifier = LookinAttrGroup_UserCustom;
NSMutableArray<LookinAttributesSection *> *sections = [NSMutableArray array];
[attrs enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
LookinAttributesSection *sec = [LookinAttributesSection new];
sec.identifier = LookinAttrSec_UserCustom;
sec.attributes = @[attr];
[sections addObject:sec];
}];
group.attrSections = sections;
[groups addObject:group];
}];
[groups sortedArrayUsingComparator:^NSComparisonResult(LookinAttributesGroup *obj1, LookinAttributesGroup *obj2) {
return [obj1.userCustomTitle compare:obj2.userCustomTitle];
}];
self.resolvedGroups = groups;
}
- (void)makeAttrsForViewOrLayer:(id)viewOrLayer selectorName:(NSString *)selectorName {
if (!viewOrLayer || !selectorName.length) {
return;
}
if (![viewOrLayer isKindOfClass:[UIView class]] && ![viewOrLayer isKindOfClass:[CALayer class]]) {
return;
}
SEL selector = NSSelectorFromString(selectorName);
if (![viewOrLayer respondsToSelector:selector]) {
return;
}
NSMethodSignature *signature = [viewOrLayer methodSignatureForSelector:selector];
if (signature.numberOfArguments > 2) {
NSAssert(NO, @"LookinServer - There should be no explicit parameters.");
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:viewOrLayer];
[invocation setSelector:selector];
[invocation invoke];
//
NSDictionary<NSString *, id> * __unsafe_unretained tempRawData;
[invocation getReturnValue:&tempRawData];
if (!tempRawData || ![tempRawData isKindOfClass:[NSDictionary class]]) {
return;
}
NSDictionary<NSString *, id> *rawData = tempRawData;
NSArray *rawProperties = rawData[@"properties"];
NSString *customTitle = rawData[@"title"];
if (customTitle && [customTitle isKindOfClass:[NSString class]] && customTitle.length > 0) {
self.resolvedCustomDisplayTitle = customTitle;
}
NSString *danceSource = rawData[@"lookin_source"];
if (danceSource && [danceSource isKindOfClass:[NSString class]] && danceSource.length > 0) {
self.resolvedDanceUISource = danceSource;
}
[self makeAttrsFromRawProperties:rawProperties];
}
- (void)makeAttrsFromRawProperties:(NSArray *)rawProperties {
if (!rawProperties || ![rawProperties isKindOfClass:[NSArray class]]) {
return;
}
for (NSDictionary<NSString *, id> *dict in rawProperties) {
NSString *groupTitle;
LookinAttribute *attr = [LKS_CustomAttrGroupsMaker attrFromRawDict:dict saveCustomSetter:YES groupTitle:&groupTitle];
if (!attr) {
continue;
}
if (!self.sectionAndAttrs[groupTitle]) {
self.sectionAndAttrs[groupTitle] = [NSMutableArray array];
}
[self.sectionAndAttrs[groupTitle] addObject:attr];
}
}
+ (LookinAttribute *)attrFromRawDict:(NSDictionary *)dict saveCustomSetter:(BOOL)saveCustomSetter groupTitle:(inout NSString **)inoutGroupTitle {
LookinAttribute *attr = [LookinAttribute new];
attr.identifier = LookinAttr_UserCustom;
NSString *title = dict[@"title"];
NSString *type = dict[@"valueType"];
NSString *section = dict[@"section"];
id value = dict[@"value"];
if (!title || ![title isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong title");
return nil;
}
if (!type || ![type isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong valueType");
return nil;
}
if (!section || ![section isKindOfClass:[NSString class]] || section.length == 0) {
*inoutGroupTitle = @"Custom";
} else {
*inoutGroupTitle = section;
}
attr.displayTitle = title;
NSString *fixedType = type.lowercaseString;
if ([fixedType isEqualToString:@"string"]) {
if (value != nil && ![value isKindOfClass:[NSString class]]) {
// nil
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeNSString;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_StringSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveStringSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"number"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeDouble;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_NumberSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveNumberSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"bool"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeBOOL;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_BoolSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveBoolSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"color"]) {
if (value != nil && ![value isKindOfClass:[UIColor class]]) {
// nil
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeUIColor;
attr.value = [(UIColor *)value lks_rgbaComponents];
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_ColorSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveColorSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"rect"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeCGRect;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_RectSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveRectSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"size"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeCGSize;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_SizeSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveSizeSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"point"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeCGPoint;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_PointSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] savePointSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"insets"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeUIEdgeInsets;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_InsetsSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveInsetsSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"shadow"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSDictionary class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
NSDictionary *shadowInfo = value;
if (![shadowInfo[@"offset"] isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value. No offset.");
return nil;
}
if (![shadowInfo[@"opacity"] isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value. No opacity.");
return nil;
}
if (![shadowInfo[@"radius"] isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value. No radius.");
return nil;
}
NSMutableDictionary *checkedShadowInfo = [@{
@"offset": shadowInfo[@"offset"],
@"opacity": shadowInfo[@"opacity"],
@"radius": shadowInfo[@"radius"]
} mutableCopy];
if ([shadowInfo[@"color"] isKindOfClass:[UIColor class]]) {
checkedShadowInfo[@"color"] = [(UIColor *)shadowInfo[@"color"] lks_rgbaComponents];
}
attr.attrType = LookinAttrTypeShadow;
attr.value = checkedShadowInfo;
return attr;
}
if ([fixedType isEqualToString:@"enum"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeEnumString;
attr.value = value;
NSArray<NSString *> *allEnumCases = dict[@"allEnumCases"];
if ([allEnumCases isKindOfClass:[NSArray class]]) {
attr.extraValue = allEnumCases;
}
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_EnumSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveEnumSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"json"]) {
if (![value isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeJson;
attr.value = value;
return attr;
}
NSLog(@"LookinServer - Unsupported value type.");
return nil;
}
- (NSArray<LookinAttributesGroup *> *)getGroups {
return self.resolvedGroups;
}
- (NSString *)getCustomDisplayTitle {
return self.resolvedCustomDisplayTitle;
}
- (NSString *)getDanceUISource {
return self.resolvedDanceUISource;
}
+ (NSArray<LookinAttributesGroup *> *)makeGroupsFromRawProperties:(NSArray *)rawProperties saveCustomSetter:(BOOL)saveCustomSetter {
if (!rawProperties || ![rawProperties isKindOfClass:[NSArray class]]) {
return nil;
}
// key group title
NSMutableDictionary<NSString *, NSMutableArray<LookinAttribute *> *> *groupTitleAndAttrs = [NSMutableDictionary dictionary];
for (NSDictionary<NSString *, id> *dict in rawProperties) {
NSString *groupTitle;
LookinAttribute *attr = [LKS_CustomAttrGroupsMaker attrFromRawDict:dict saveCustomSetter:saveCustomSetter groupTitle:&groupTitle];
if (!attr) {
continue;
}
if (!groupTitleAndAttrs[groupTitle]) {
groupTitleAndAttrs[groupTitle] = [NSMutableArray array];
}
[groupTitleAndAttrs[groupTitle] addObject:attr];
}
if ([groupTitleAndAttrs count] == 0) {
return nil;
}
NSMutableArray<LookinAttributesGroup *> *groups = [NSMutableArray array];
[groupTitleAndAttrs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull groupTitle, NSMutableArray<LookinAttribute *> * _Nonnull attrs, BOOL * _Nonnull stop) {
LookinAttributesGroup *group = [LookinAttributesGroup new];
group.userCustomTitle = groupTitle;
group.identifier = LookinAttrGroup_UserCustom;
NSMutableArray<LookinAttributesSection *> *sections = [NSMutableArray array];
[attrs enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
LookinAttributesSection *sec = [LookinAttributesSection new];
sec.identifier = LookinAttrSec_UserCustom;
sec.attributes = @[attr];
[sections addObject:sec];
}];
group.attrSections = sections;
[groups addObject:group];
}];
[groups sortedArrayUsingComparator:^NSComparisonResult(LookinAttributesGroup *obj1, LookinAttributesGroup *obj2) {
return [obj1.userCustomTitle compare:obj2.userCustomTitle];
}];
return [groups copy];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,56 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrSetterManager.h
// LookinServer
//
// Created by likai.123 on 2023/11/4.
//
#import <UIKit/UIKit.h>
typedef void(^LKS_StringSetter)(NSString *);
typedef void(^LKS_NumberSetter)(NSNumber *);
typedef void(^LKS_BoolSetter)(BOOL);
typedef void(^LKS_ColorSetter)(UIColor *);
typedef void(^LKS_EnumSetter)(NSString *);
typedef void(^LKS_RectSetter)(CGRect);
typedef void(^LKS_SizeSetter)(CGSize);
typedef void(^LKS_PointSetter)(CGPoint);
typedef void(^LKS_InsetsSetter)(UIEdgeInsets);
@interface LKS_CustomAttrSetterManager : NSObject
+ (instancetype)sharedInstance;
- (void)removeAll;
- (void)saveStringSetter:(LKS_StringSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_StringSetter)getStringSetterWithID:(NSString *)uniqueID;
- (void)saveNumberSetter:(LKS_NumberSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_NumberSetter)getNumberSetterWithID:(NSString *)uniqueID;
- (void)saveBoolSetter:(LKS_BoolSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_BoolSetter)getBoolSetterWithID:(NSString *)uniqueID;
- (void)saveColorSetter:(LKS_ColorSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_ColorSetter)getColorSetterWithID:(NSString *)uniqueID;
- (void)saveEnumSetter:(LKS_EnumSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_EnumSetter)getEnumSetterWithID:(NSString *)uniqueID;
- (void)saveRectSetter:(LKS_RectSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_RectSetter)getRectSetterWithID:(NSString *)uniqueID;
- (void)saveSizeSetter:(LKS_SizeSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_SizeSetter)getSizeSetterWithID:(NSString *)uniqueID;
- (void)savePointSetter:(LKS_PointSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_PointSetter)getPointSetterWithID:(NSString *)uniqueID;
- (void)saveInsetsSetter:(LKS_InsetsSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_InsetsSetter)getInsetsSetterWithID:(NSString *)uniqueID;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,117 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrSetterManager.m
// LookinServer
//
// Created by likai.123 on 2023/11/4.
//
#import "LKS_CustomAttrSetterManager.h"
@interface LKS_CustomAttrSetterManager ()
@property(nonatomic, strong) NSMutableDictionary *settersMap;
@end
@implementation LKS_CustomAttrSetterManager
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_CustomAttrSetterManager *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
- (instancetype)init {
self = [super init];
if (self) {
self.settersMap = [NSMutableDictionary new];
}
return self;
}
- (void)removeAll {
[self.settersMap removeAllObjects];
}
- (void)saveStringSetter:(nonnull LKS_StringSetter)setter uniqueID:(nonnull NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (nullable LKS_StringSetter)getStringSetterWithID:(nonnull NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveNumberSetter:(LKS_NumberSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (nullable LKS_NumberSetter)getNumberSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveBoolSetter:(LKS_BoolSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_BoolSetter)getBoolSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveColorSetter:(LKS_ColorSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_ColorSetter)getColorSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveEnumSetter:(LKS_EnumSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_EnumSetter)getEnumSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveRectSetter:(LKS_RectSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_RectSetter)getRectSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveSizeSetter:(LKS_SizeSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_SizeSetter)getSizeSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)savePointSetter:(LKS_PointSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_PointSetter)getPointSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveInsetsSetter:(LKS_InsetsSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_InsetsSetter)getInsetsSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,22 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomDisplayItemsMaker.h
// LookinServer
//
// Created by likai.123 on 2023/11/1.
//
#import <UIKit/UIKit.h>
@class LookinDisplayItem;
@interface LKS_CustomDisplayItemsMaker : NSObject
- (instancetype)initWithLayer:(CALayer *)layer saveAttrSetter:(BOOL)saveAttrSetter;
- (NSArray<LookinDisplayItem *> *)make;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,144 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomDisplayItemsMaker.m
// LookinServer
//
// Created by likai.123 on 2023/11/1.
//
#import "LKS_CustomDisplayItemsMaker.h"
#import "CALayer+LookinServer.h"
#import "LookinDisplayItem.h"
#import "NSArray+Lookin.h"
#import "LKS_CustomAttrGroupsMaker.h"
@interface LKS_CustomDisplayItemsMaker ()
@property(nonatomic, weak) CALayer *layer;
@property(nonatomic, assign) BOOL saveAttrSetter;
@property(nonatomic, strong) NSMutableArray *allSubitems;
@end
@implementation LKS_CustomDisplayItemsMaker
- (instancetype)initWithLayer:(CALayer *)layer saveAttrSetter:(BOOL)saveAttrSetter {
if (self = [super init]) {
self.layer = layer;
self.saveAttrSetter = saveAttrSetter;
self.allSubitems = [NSMutableArray array];
}
return self;
}
- (NSArray<LookinDisplayItem *> *)make {
if (!self.layer) {
NSAssert(NO, @"");
return nil;
}
NSMutableArray<NSString *> *selectors = [NSMutableArray array];
[selectors addObject:@"lookin_customDebugInfos"];
for (int i = 0; i < 5; i++) {
[selectors addObject:[NSString stringWithFormat:@"lookin_customDebugInfos_%@", @(i)]];
}
for (NSString *name in selectors) {
[self makeSubitemsForViewOrLayer:self.layer selectorName:name];
UIView *view = self.layer.lks_hostView;
if (view) {
[self makeSubitemsForViewOrLayer:view selectorName:name];
}
}
if (self.allSubitems.count) {
return self.allSubitems;
} else {
return nil;
}
}
- (void)makeSubitemsForViewOrLayer:(id)viewOrLayer selectorName:(NSString *)selectorName {
if (!viewOrLayer || !selectorName.length) {
return;
}
if (![viewOrLayer isKindOfClass:[UIView class]] && ![viewOrLayer isKindOfClass:[CALayer class]]) {
return;
}
SEL selector = NSSelectorFromString(selectorName);
if (![viewOrLayer respondsToSelector:selector]) {
return;
}
NSMethodSignature *signature = [viewOrLayer methodSignatureForSelector:selector];
if (signature.numberOfArguments > 2) {
NSAssert(NO, @"LookinServer - There should be no explicit parameters.");
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:viewOrLayer];
[invocation setSelector:selector];
[invocation invoke];
//
NSDictionary<NSString *, id> * __unsafe_unretained tempRawData;
[invocation getReturnValue:&tempRawData];
NSDictionary<NSString *, id> *rawData = tempRawData;
[self makeSubitemsFromRawData:rawData];
}
- (void)makeSubitemsFromRawData:(NSDictionary<NSString *, id> *)data {
if (!data || ![data isKindOfClass:[NSDictionary class]]) {
return;
}
NSArray *rawSubviews = data[@"subviews"];
NSArray<LookinDisplayItem *> *newSubitems = [self displayItemsFromRawArray:rawSubviews];
if (newSubitems) {
[self.allSubitems addObjectsFromArray:newSubitems];
}
}
- (NSArray<LookinDisplayItem *> *)displayItemsFromRawArray:(NSArray<NSDictionary *> *)rawArray {
if (!rawArray || ![rawArray isKindOfClass:[NSArray class]]) {
return nil;
}
NSArray *items = [rawArray lookin_map:^id(NSUInteger idx, NSDictionary *rawDict) {
if (![rawDict isKindOfClass:[NSDictionary class]]) {
return nil;
}
return [self displayItemFromRawDict:rawDict];
}];
return items;
}
- (LookinDisplayItem *)displayItemFromRawDict:(NSDictionary<NSString *, id> *)dict {
NSString *title = dict[@"title"];
NSString *subtitle = dict[@"subtitle"];
NSValue *frameValue = dict[@"frameInWindow"];
NSArray *properties = dict[@"properties"];
NSArray *subviews = dict[@"subviews"];
NSString *danceSource = dict[@"lookin_source"];
if (![title isKindOfClass:[NSString class]]) {
return nil;
}
LookinDisplayItem *newItem = [LookinDisplayItem new];
if (subviews && [subviews isKindOfClass:[NSArray class]]) {
newItem.subitems = [self displayItemsFromRawArray:subviews];
}
newItem.isHidden = NO;
newItem.alpha = 1.0;
newItem.customInfo = [LookinCustomDisplayItemInfo new];
newItem.customInfo.title = title;
newItem.customInfo.subtitle = subtitle;
newItem.customInfo.frameInWindow = frameValue;
newItem.customInfo.danceuiSource = danceSource;
newItem.customAttrGroupList = [LKS_CustomAttrGroupsMaker makeGroupsFromRawProperties:properties saveCustomSetter:self.saveAttrSetter];
return newItem;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_EventHandlerMaker.h
// LookinServer
//
// Created by Li Kai on 2019/8/7.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinEventHandler;
@interface LKS_EventHandlerMaker : NSObject
+ (NSArray<LookinEventHandler *> *)makeForView:(UIView *)view;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,215 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_EventHandlerMaker.m
// LookinServer
//
// Created by Li Kai on 2019/8/7.
// https://lookin.work
//
#import "LKS_EventHandlerMaker.h"
#import "LookinTuple.h"
#import "LookinEventHandler.h"
#import "LookinObject.h"
#import "LookinWeakContainer.h"
#import "LookinIvarTrace.h"
#import "LookinServerDefines.h"
#import "LKS_GestureTargetActionsSearcher.h"
#import "LKS_MultiplatformAdapter.h"
@implementation LKS_EventHandlerMaker
+ (NSArray<LookinEventHandler *> *)makeForView:(UIView *)view {
if (!view) {
return nil;
}
NSMutableArray<LookinEventHandler *> *allHandlers = nil;
if ([view isKindOfClass:[UIControl class]]) {
NSArray<LookinEventHandler *> *targetActionHandlers = [self _targetActionHandlersForControl:(UIControl *)view];
if (targetActionHandlers.count) {
if (!allHandlers) {
allHandlers = [NSMutableArray array];
}
[allHandlers addObjectsFromArray:targetActionHandlers];
}
}
NSArray<LookinEventHandler *> *gestureHandlers = [self _gestureHandlersForView:view];
if (gestureHandlers.count) {
if (!allHandlers) {
allHandlers = [NSMutableArray array];
}
[allHandlers addObjectsFromArray:gestureHandlers];
}
return allHandlers.copy;
}
+ (NSArray<LookinEventHandler *> *)_gestureHandlersForView:(UIView *)view {
if (view.gestureRecognizers.count == 0) {
return nil;
}
NSArray<LookinEventHandler *> *handlers = [view.gestureRecognizers lookin_map:^id(NSUInteger idx, __kindof UIGestureRecognizer *recognizer) {
LookinEventHandler *handler = [LookinEventHandler new];
handler.handlerType = LookinEventHandlerTypeGesture;
handler.eventName = NSStringFromClass([recognizer class]);
NSArray<LookinTwoTuple *> *targetActionInfos = [LKS_GestureTargetActionsSearcher getTargetActionsFromRecognizer:recognizer];
handler.targetActions = [targetActionInfos lookin_map:^id(NSUInteger idx, LookinTwoTuple *rawTuple) {
NSObject *target = ((LookinWeakContainer *)rawTuple.first).object;
if (!target) {
// target
return nil;
}
LookinStringTwoTuple *newTuple = [LookinStringTwoTuple new];
newTuple.first = [LKS_Helper descriptionOfObject:target];
newTuple.second = (NSString *)rawTuple.second;
return newTuple;
}];
handler.inheritedRecognizerName = [self _inheritedRecognizerNameForRecognizer:recognizer];
handler.gestureRecognizerIsEnabled = recognizer.enabled;
if (recognizer.delegate) {
handler.gestureRecognizerDelegator = [LKS_Helper descriptionOfObject:recognizer.delegate];
}
handler.recognizerIvarTraces = [recognizer.lks_ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *trace) {
return [NSString stringWithFormat:@"(%@ *) -> %@", trace.hostClassName, trace.ivarName];
}];
handler.recognizerOid = [recognizer lks_registerOid];
return handler;
}];
return handlers;
}
+ (NSString *)_inheritedRecognizerNameForRecognizer:(UIGestureRecognizer *)recognizer {
if (!recognizer) {
NSAssert(NO, @"");
return nil;
}
static NSArray<Class> *baseRecognizers;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// UIScreenEdgePanGestureRecognizer UIPanGestureRecognizer UIScreenEdgePanGestureRecognizer UIPanGestureRecognizer
#if TARGET_OS_TV
baseRecognizers = @[[UILongPressGestureRecognizer class],
[UIPanGestureRecognizer class],
[UISwipeGestureRecognizer class],
[UITapGestureRecognizer class]];
#elif TARGET_OS_VISION
baseRecognizers = @[[UILongPressGestureRecognizer class],
[UIPanGestureRecognizer class],
[UISwipeGestureRecognizer class],
[UIRotationGestureRecognizer class],
[UIPinchGestureRecognizer class],
[UITapGestureRecognizer class]];
#else
baseRecognizers = @[[UILongPressGestureRecognizer class],
[UIScreenEdgePanGestureRecognizer class],
[UIPanGestureRecognizer class],
[UISwipeGestureRecognizer class],
[UIRotationGestureRecognizer class],
[UIPinchGestureRecognizer class],
[UITapGestureRecognizer class]];
#endif
});
__block NSString *result = @"UIGestureRecognizer";
[baseRecognizers enumerateObjectsUsingBlock:^(Class _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([recognizer isMemberOfClass:obj]) {
// nil
result = nil;
*stop = YES;
return;
}
if ([recognizer isKindOfClass:obj]) {
result = NSStringFromClass(obj);
*stop = YES;
return;
}
}];
return result;
}
+ (NSArray<LookinEventHandler *> *)_targetActionHandlersForControl:(UIControl *)control {
static dispatch_once_t onceToken;
static NSArray<NSNumber *> *allEvents = nil;
dispatch_once(&onceToken,^{
allEvents = @[@(UIControlEventTouchDown), @(UIControlEventTouchDownRepeat), @(UIControlEventTouchDragInside), @(UIControlEventTouchDragOutside), @(UIControlEventTouchDragEnter), @(UIControlEventTouchDragExit), @(UIControlEventTouchUpInside), @(UIControlEventTouchUpOutside), @(UIControlEventTouchCancel), @(UIControlEventValueChanged), @(UIControlEventEditingDidBegin), @(UIControlEventEditingChanged), @(UIControlEventEditingDidEnd), @(UIControlEventEditingDidEndOnExit)];
if (@available(iOS 9.0, *)) {
allEvents = [allEvents arrayByAddingObject:@(UIControlEventPrimaryActionTriggered)];
}
});
NSSet *allTargets = control.allTargets;
if (!allTargets.count) {
return nil;
}
NSMutableArray<LookinEventHandler *> *handlers = [NSMutableArray array];
[allEvents enumerateObjectsUsingBlock:^(NSNumber * _Nonnull eventNum, NSUInteger idx, BOOL * _Nonnull stop) {
UIControlEvents event = [eventNum unsignedIntegerValue];
NSMutableArray<LookinStringTwoTuple *> *targetActions = [NSMutableArray array];
[allTargets enumerateObjectsUsingBlock:^(id _Nonnull target, BOOL * _Nonnull stop) {
NSArray<NSString *> *actions = [control actionsForTarget:target forControlEvent:event];
[actions enumerateObjectsUsingBlock:^(NSString * _Nonnull action, NSUInteger idx, BOOL * _Nonnull stop) {
LookinStringTwoTuple *tuple = [LookinStringTwoTuple new];
tuple.first = [LKS_Helper descriptionOfObject:target];
tuple.second = action;
[targetActions addObject:tuple];
}];
}];
if (targetActions.count) {
LookinEventHandler *handler = [LookinEventHandler new];
handler.handlerType = LookinEventHandlerTypeTargetAction;
handler.eventName = [self _nameFromControlEvent:event];
handler.targetActions = targetActions.copy;
[handlers addObject:handler];
}
}];
return handlers;
}
+ (NSString *)_nameFromControlEvent:(UIControlEvents)event {
static dispatch_once_t onceToken;
static NSDictionary<NSNumber *, NSString *> *eventsAndNames = nil;
dispatch_once(&onceToken,^{
NSMutableDictionary<NSNumber *, NSString *> *eventsAndNames_m = @{
@(UIControlEventTouchDown): @"UIControlEventTouchDown",
@(UIControlEventTouchDownRepeat): @"UIControlEventTouchDownRepeat",
@(UIControlEventTouchDragInside): @"UIControlEventTouchDragInside",
@(UIControlEventTouchDragOutside): @"UIControlEventTouchDragOutside",
@(UIControlEventTouchDragEnter): @"UIControlEventTouchDragEnter",
@(UIControlEventTouchDragExit): @"UIControlEventTouchDragExit",
@(UIControlEventTouchUpInside): @"UIControlEventTouchUpInside",
@(UIControlEventTouchUpOutside): @"UIControlEventTouchUpOutside",
@(UIControlEventTouchCancel): @"UIControlEventTouchCancel",
@(UIControlEventValueChanged): @"UIControlEventValueChanged",
@(UIControlEventEditingDidBegin): @"UIControlEventEditingDidBegin",
@(UIControlEventEditingChanged): @"UIControlEventEditingChanged",
@(UIControlEventEditingDidEnd): @"UIControlEventEditingDidEnd",
@(UIControlEventEditingDidEndOnExit): @"UIControlEventEditingDidEndOnExit",
}.mutableCopy;
if (@available(iOS 9.0, *)) {
eventsAndNames_m[@(UIControlEventPrimaryActionTriggered)] = @"UIControlEventPrimaryActionTriggered";
}
eventsAndNames = eventsAndNames_m.copy;
});
NSString *name = eventsAndNames[@(event)];
return name;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ExportManager.h
// LookinServer
//
// Created by Li Kai on 2019/5/13.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LKS_ExportManager : NSObject
+ (instancetype)sharedInstance;
- (void)exportAndShare;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,193 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ExportManager.m
// LookinServer
//
// Created by Li Kai on 2019/5/13.
// https://lookin.work
//
#import "LKS_ExportManager.h"
#import "UIViewController+LookinServer.h"
#import "LookinHierarchyInfo.h"
#import "LookinHierarchyFile.h"
#import "LookinAppInfo.h"
#import "LookinServerDefines.h"
#import "LKS_MultiplatformAdapter.h"
@interface LKS_ExportManagerMaskView : UIView
@property(nonatomic, strong) UIView *tipsView;
@property(nonatomic, strong) UILabel *firstLabel;
@property(nonatomic, strong) UILabel *secondLabel;
@property(nonatomic, strong) UILabel *thirdLabel;
@end
@implementation LKS_ExportManagerMaskView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.35];
self.tipsView = [UIView new];
self.tipsView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.88];
self.tipsView.layer.cornerRadius = 6;
self.tipsView.layer.masksToBounds = YES;
[self addSubview:self.tipsView];
self.firstLabel = [UILabel new];
self.firstLabel.text = LKS_Localized(@"Creating File…");
self.firstLabel.textColor = [UIColor whiteColor];
self.firstLabel.font = [UIFont boldSystemFontOfSize:14];
self.firstLabel.textAlignment = NSTextAlignmentCenter;
self.firstLabel.numberOfLines = 0;
[self.tipsView addSubview:self.firstLabel];
self.secondLabel = [UILabel new];
self.secondLabel.text = LKS_Localized(@"May take 8 or more seconds according to the UI complexity.");
self.secondLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1];
self.secondLabel.font = [UIFont systemFontOfSize:12];
self.secondLabel.textAlignment = NSTextAlignmentLeft;
self.secondLabel.numberOfLines = 0;
[self.tipsView addSubview:self.secondLabel];
self.thirdLabel = [UILabel new];
self.thirdLabel.text = LKS_Localized(@"The file can be opend by Lookin.app in macOS.");
self.thirdLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1];
self.thirdLabel.font = [UIFont systemFontOfSize:12];
self.thirdLabel.textAlignment = NSTextAlignmentCenter;
self.thirdLabel.numberOfLines = 0;
[self.tipsView addSubview:self.thirdLabel];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
UIEdgeInsets insets = UIEdgeInsetsMake(8, 10, 8, 10);
CGFloat maxLabelWidth = self.bounds.size.width * .8 - insets.left - insets.right;
CGSize firstSize = [self.firstLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
CGSize secondSize = [self.secondLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
CGSize thirdSize = [self.thirdLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
CGFloat tipsWidth = MAX(MAX(firstSize.width, secondSize.width), thirdSize.width) + insets.left + insets.right;
self.firstLabel.frame = CGRectMake(tipsWidth / 2.0 - firstSize.width / 2.0, insets.top, firstSize.width, firstSize.height);
self.secondLabel.frame = CGRectMake(tipsWidth / 2.0 - secondSize.width / 2.0, CGRectGetMaxY(self.firstLabel.frame) + 10, secondSize.width, secondSize.height);
self.thirdLabel.frame = CGRectMake(tipsWidth / 2.0 - thirdSize.width / 2.0, CGRectGetMaxY(self.secondLabel.frame) + 5, thirdSize.width, thirdSize.height);
self.tipsView.frame = ({
CGFloat height = CGRectGetMaxY(self.thirdLabel.frame) + insets.bottom;
CGRectMake(self.bounds.size.width / 2.0 - tipsWidth / 2.0, self.bounds.size.height / 2.0 - height / 2.0, tipsWidth, height);
});
}
@end
@interface LKS_ExportManager ()
#if TARGET_OS_TV
#else
@property(nonatomic, strong) UIDocumentInteractionController *documentController;
#endif
@property(nonatomic, strong) LKS_ExportManagerMaskView *maskView;
@end
@implementation LKS_ExportManager
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_ExportManager *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
}
#if TARGET_OS_TV
- (void)exportAndShare {
NSAssert(NO, @"not supported");
}
#else
- (void)exportAndShare {
UIViewController *visibleVc = [UIViewController lks_visibleViewController];
if (!visibleVc) {
NSLog(@"LookinServer - Failed to export because we didn't find any visible view controller.");
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_WillExport" object:nil];
if (!self.maskView) {
self.maskView = [LKS_ExportManagerMaskView new];
}
[visibleVc.view.window addSubview:self.maskView];
self.maskView.frame = visibleVc.view.window.bounds;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
LookinHierarchyInfo *info = [LookinHierarchyInfo exportedInfo];
LookinHierarchyFile *file = [LookinHierarchyFile new];
file.serverVersion = info.serverVersion;
file.hierarchyInfo = info;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:file];
if (!data) {
return;
}
NSString *fileName = ({
NSString *timeString = ({
NSDate *date = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"MMddHHmm"];
[formatter stringFromDate:date];
});
NSString *iOSVersion = ({
NSString *str = info.appInfo.osDescription;
NSUInteger dotIdx = [str rangeOfString:@"."].location;
if (dotIdx != NSNotFound) {
str = [str substringToIndex:dotIdx];
}
str;
});
[NSString stringWithFormat:@"%@_ios%@_%@.lookin", info.appInfo.appName, iOSVersion, timeString];
});
NSString *path = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), fileName];
[data writeToFile:path atomically:YES];
[self.maskView removeFromSuperview];
if (!self.documentController) {
self.documentController = [UIDocumentInteractionController new];
}
self.documentController.URL = [NSURL fileURLWithPath:path];
if ([LKS_MultiplatformAdapter isiPad]) {
[self.documentController presentOpenInMenuFromRect:CGRectMake(0, 0, 1, 1) inView:visibleVc.view animated:YES];
} else {
[self.documentController presentOpenInMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES];
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_DidFinishExport" object:nil];
// [self.documentController presentOptionsMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES];
// CFTimeInterval endTime = CACurrentMediaTime();
// CFTimeInterval consumingTime = endTime - startTime;
// NSLog(@"LookinServer - 导出 UI 结构耗时:%@", @(consumingTime));
});
}
#endif
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_GestureTargetActionsSearcher.h
// LookinServer
//
// Created by likai.123 on 2023/9/11.
//
#import <UIKit/UIKit.h>
@class LookinTwoTuple;
NS_ASSUME_NONNULL_BEGIN
@interface LKS_GestureTargetActionsSearcher : NSObject
/// 返回一个 UIGestureRecognizer 实例身上绑定的 target & action 信息
/// tuple.first => LookinWeakContainer(包裹着 target)tuple.second => action(方法名字符串)
+ (NSArray<LookinTwoTuple *> *)getTargetActionsFromRecognizer:(UIGestureRecognizer *)recognizer;
@end
NS_ASSUME_NONNULL_END
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,52 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_GestureTargetActionsSearcher.m
// LookinServer
//
// Created by likai.123 on 2023/9/11.
//
#import "LKS_GestureTargetActionsSearcher.h"
#import <objc/runtime.h>
#import "NSArray+Lookin.h"
#import "LookinTuple.h"
#import "LookinWeakContainer.h"
@implementation LKS_GestureTargetActionsSearcher
+ (NSArray<LookinTwoTuple *> *)getTargetActionsFromRecognizer:(UIGestureRecognizer *)recognizer {
if (!recognizer) {
return @[];
}
// KVC try catch Crash
@try {
NSArray* targetsList = [recognizer valueForKey:@"_targets"];
if (!targetsList || targetsList.count == 0) {
return @[];
}
// UIGestureRecognizerTarget*
// _target _action SEL
NSArray<LookinTwoTuple *>* ret = [targetsList lookin_map:^id(NSUInteger idx, id targetBox) {
id targetObj = [targetBox valueForKey:@"_target"];
if (!targetObj) {
return nil;
}
SEL action = ((SEL (*)(id, Ivar))object_getIvar)(targetBox, class_getInstanceVariable([targetBox class], "_action"));
LookinTwoTuple* tuple = [LookinTwoTuple new];
tuple.first = [LookinWeakContainer containerWithObject:targetObj];
tuple.second = (action == NULL ? @"NULL" : NSStringFromSelector(action));
return tuple;
}];
return ret;
}
@catch (NSException * e) {
NSLog(@"LookinServer - %@", e);
return @[];
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_Helper.h
// LookinServer
//
// Created by Li Kai on 2019/7/20.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
#define LKS_Localized(stringKey) NSLocalizedStringFromTableInBundle(stringKey, nil, [NSBundle bundleForClass:self.class], nil)
@interface LKS_Helper : NSObject
/// 如果 object 为 nil 则返回字符串 “nil”否则返回字符串格式类似于 (UIView *)
+ (NSString *)descriptionOfObject:(id)object;
/// 返回当前的bundle
+ (NSBundle *)bundle;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,38 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_Helper.m
// LookinServer
//
// Created by Li Kai on 2019/7/20.
// https://lookin.work
//
#import "LKS_Helper.h"
#import "NSObject+LookinServer.h"
@implementation LKS_Helper
+ (NSString *)descriptionOfObject:(id)object {
if (!object) {
return @"nil";
}
NSString *className = NSStringFromClass([object class]);
return [NSString stringWithFormat:@"(%@ *)", className];
}
+ (NSBundle *)bundle {
static id bundle = nil;
if (bundle != nil) {
#ifdef SPM_RESOURCE_BUNDLE_IDENTIFITER
bundle = [NSBundle bundleWithIdentifier:SPM_RESOURCE_BUNDLE_IDENTIFITER];
#else
bundle = [NSBundle bundleForClass:self.class];
#endif
}
return bundle;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,31 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDisplayItemsMaker.h
// LookinServer
//
// Created by Li Kai on 2019/2/19.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinDisplayItem;
@interface LKS_HierarchyDisplayItemsMaker : NSObject
/// @param hasScreenshots 是否包含 soloScreenshots 和 groupScreenshot 属性
/// @param hasAttrList 是否包含 attributesGroupList 属性
/// @param lowQuality screenshots 是否为低质量(当 hasScreenshots 为 NO 时,该属性无意义)
/// @param readCustomInfo 是否读取 lookin_customDebugInfos比如低版本的 Lookin 发请求时,就无需读取(因为 Lookin 解析不了、还可能出 Bug
/// @param saveCustomSetter 是否要读取并保存用户给 attribute 配置的 custom setter
+ (NSArray<LookinDisplayItem *> *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter;
/// 把 layer 的 sublayers 转换为 displayItem 数组并返回
+ (NSArray<LookinDisplayItem *> *)subitemsOfLayer:(CALayer *)layer;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,162 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDisplayItemsMaker.m
// LookinServer
//
// Created by Li Kai on 2019/2/19.
// https://lookin.work
//
#import "LKS_HierarchyDisplayItemsMaker.h"
#import "LookinDisplayItem.h"
#import "LKS_TraceManager.h"
#import "LKS_AttrGroupsMaker.h"
#import "LKS_EventHandlerMaker.h"
#import "LookinServerDefines.h"
#import "UIColor+LookinServer.h"
#import "LKSConfigManager.h"
#import "LKS_CustomAttrGroupsMaker.h"
#import "LKS_CustomDisplayItemsMaker.h"
#import "LKS_CustomAttrSetterManager.h"
#import "LKS_MultiplatformAdapter.h"
@implementation LKS_HierarchyDisplayItemsMaker
+ (NSArray<LookinDisplayItem *> *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter {
[[LKS_TraceManager sharedInstance] reload];
NSArray<UIWindow *> *windows = [LKS_MultiplatformAdapter allWindows];
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:windows.count];
[windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItem *item = [self _displayItemWithLayer:window.layer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality readCustomInfo:readCustomInfo saveCustomSetter:saveCustomSetter];
item.representedAsKeyWindow = window.isKeyWindow;
if (item) {
[resultArray addObject:item];
}
}];
return [resultArray copy];
}
+ (LookinDisplayItem *)_displayItemWithLayer:(CALayer *)layer screenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter {
if (!layer) {
return nil;
}
LookinDisplayItem *item = [LookinDisplayItem new];
CGRect layerFrame = layer.frame;
UIView *hostView = layer.lks_hostView;
if (hostView && hostView.superview) {
layerFrame = [hostView.superview convertRect:layerFrame toView:nil];
}
if ([self validateFrame:layerFrame]) {
item.frame = layer.frame;
} else {
NSLog(@"LookinServer - The layer frame(%@) seems really weird. Lookin will ignore it to avoid potential render error in Lookin.", NSStringFromCGRect(layer.frame));
item.frame = CGRectZero;
}
item.bounds = layer.bounds;
if (hasScreenshots) {
item.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowQuality];
item.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowQuality];
item.screenshotEncodeType = LookinDisplayItemImageEncodeTypeNSData;
}
if (hasAttrList) {
item.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
[maker execute];
item.customAttrGroupList = [maker getGroups];
item.customDisplayTitle = [maker getCustomDisplayTitle];
item.danceuiSource = [maker getDanceUISource];
}
item.isHidden = layer.isHidden;
item.alpha = layer.opacity;
item.layerObject = [LookinObject instanceWithObject:layer];
item.shouldCaptureImage = [LKSConfigManager shouldCaptureScreenshotOfLayer:layer];
if (layer.lks_hostView) {
UIView *view = layer.lks_hostView;
item.viewObject = [LookinObject instanceWithObject:view];
item.eventHandlers = [LKS_EventHandlerMaker makeForView:view];
item.backgroundColor = view.backgroundColor;
UIViewController* vc = [view lks_findHostViewController];
if (vc) {
item.hostViewControllerObject = [LookinObject instanceWithObject:vc];
}
} else {
item.backgroundColor = [UIColor lks_colorWithCGColor:layer.backgroundColor];
}
if (layer.sublayers.count) {
NSArray<CALayer *> *sublayers = [layer.sublayers copy];
NSMutableArray<LookinDisplayItem *> *allSubitems = [NSMutableArray arrayWithCapacity:sublayers.count];
[sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItem *sublayer_item = [self _displayItemWithLayer:sublayer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality readCustomInfo:readCustomInfo saveCustomSetter:saveCustomSetter];
if (sublayer_item) {
[allSubitems addObject:sublayer_item];
}
}];
item.subitems = [allSubitems copy];
}
if (readCustomInfo) {
NSArray<LookinDisplayItem *> *customSubitems = [[[LKS_CustomDisplayItemsMaker alloc] initWithLayer:layer saveAttrSetter:saveCustomSetter] make];
if (customSubitems.count > 0) {
if (item.subitems) {
item.subitems = [item.subitems arrayByAddingObjectsFromArray:customSubitems];
} else {
item.subitems = customSubitems;
}
}
}
return item;
}
+ (NSArray<LookinDisplayItem *> *)subitemsOfLayer:(CALayer *)layer {
if (!layer || layer.sublayers.count == 0) {
return @[];
}
[[LKS_TraceManager sharedInstance] reload];
NSMutableArray<LookinDisplayItem *> *resultSubitems = [NSMutableArray array];
NSArray<CALayer *> *sublayers = [layer.sublayers copy];
[sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItem *sublayer_item = [self _displayItemWithLayer:sublayer screenshots:NO attrList:NO lowImageQuality:NO readCustomInfo:YES saveCustomSetter:YES];
if (sublayer_item) {
[resultSubitems addObject:sublayer_item];
}
}];
NSArray<LookinDisplayItem *> *customSubitems = [[[LKS_CustomDisplayItemsMaker alloc] initWithLayer:layer saveAttrSetter:YES] make];
if (customSubitems.count > 0) {
[resultSubitems addObjectsFromArray:customSubitems];
}
return resultSubitems;
}
+ (BOOL)validateFrame:(CGRect)frame {
return !CGRectIsNull(frame) && !CGRectIsInfinite(frame) && ![self cgRectIsNaN:frame] && ![self cgRectIsInf:frame] && ![self cgRectIsUnreasonable:frame];
}
+ (BOOL)cgRectIsNaN:(CGRect)rect {
return isnan(rect.origin.x) || isnan(rect.origin.y) || isnan(rect.size.width) || isnan(rect.size.height);
}
+ (BOOL)cgRectIsInf:(CGRect)rect {
return isinf(rect.origin.x) || isinf(rect.origin.y) || isinf(rect.size.width) || isinf(rect.size.height);
}
+ (BOOL)cgRectIsUnreasonable:(CGRect)rect {
return ABS(rect.origin.x) > 100000 || ABS(rect.origin.y) > 100000 || rect.size.width < 0 || rect.size.height < 0 || rect.size.width > 100000 || rect.size.height > 100000;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,30 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_MultiplatformAdapter.h
//
//
// Created by nixjiang on 2024/3/12.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LKS_MultiplatformAdapter : NSObject
+ (UIWindow *)keyWindow;
+ (NSArray<UIWindow *> *)allWindows;
+ (CGRect)mainScreenBounds;
+ (CGFloat)mainScreenScale;
+ (BOOL)isiPad;
@end
NS_ASSUME_NONNULL_END
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,92 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_MultiplatformAdapter.m
//
//
// Created by nixjiang on 2024/3/12.
//
#import "LKS_MultiplatformAdapter.h"
#import <UIKit/UIKit.h>
@implementation LKS_MultiplatformAdapter
+ (BOOL)isiPad {
static BOOL s_isiPad = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *nsModel = [UIDevice currentDevice].model;
s_isiPad = [nsModel hasPrefix:@"iPad"];
});
return s_isiPad;
}
+ (CGRect)mainScreenBounds {
#if TARGET_OS_VISION
return [LKS_MultiplatformAdapter getFirstActiveWindowScene].coordinateSpace.bounds;
#else
return [UIScreen mainScreen].bounds;
#endif
}
+ (CGFloat)mainScreenScale {
#if TARGET_OS_VISION
return 2.f;
#else
return [UIScreen mainScreen].scale;
#endif
}
#if TARGET_OS_VISION
+ (UIWindowScene *)getFirstActiveWindowScene {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:UIWindowScene.class]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (windowScene.activationState == UISceneActivationStateForegroundActive) {
return windowScene;
}
}
return nil;
}
#endif
+ (UIWindow *)keyWindow {
#if TARGET_OS_VISION
return [self getFirstActiveWindowScene].keyWindow;
#else
return [UIApplication sharedApplication].keyWindow;
#endif
}
+ (NSArray<UIWindow *> *)allWindows {
#if TARGET_OS_VISION
NSMutableArray<UIWindow *> *windows = [NSMutableArray new];
for (UIScene *scene in
UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:UIWindowScene.class]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
[windows addObjectsFromArray:windowScene.windows];
// UIModalPresentationFormSheetwindowscene.windowsscene.keyWindow
if (![windows containsObject:windowScene.keyWindow]) {
if (![NSStringFromClass(windowScene.keyWindow.class) containsString:@"HUD"]) {
[windows addObject:windowScene.keyWindow];
}
}
}
return [windows copy];
#else
return [[UIApplication sharedApplication].windows copy];
#endif
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ObjectRegistry.h
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LKS_ObjectRegistry : NSObject
+ (instancetype)sharedInstance;
- (unsigned long)addObject:(NSObject *)object;
- (NSObject *)objectWithOid:(unsigned long)oid;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,62 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ObjectRegistry.m
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import "LKS_ObjectRegistry.h"
#import <objc/runtime.h>
@interface LKS_ObjectRegistry ()
@property(nonatomic, strong) NSPointerArray *data;
@end
@implementation LKS_ObjectRegistry
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_ObjectRegistry *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
}
- (instancetype)init {
if (self = [super init]) {
self.data = [NSPointerArray weakObjectsPointerArray];
// index 0 Null
self.data.count = 1;
}
return self;
}
- (unsigned long)addObject:(NSObject *)object {
if (!object) {
return 0;
}
[self.data addPointer:(void *)object];
return self.data.count - 1;
}
- (NSObject *)objectWithOid:(unsigned long)oid {
if (self.data.count <= oid) {
return nil;
}
id object = [self.data pointerAtIndex:oid];
return object;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,27 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_TraceManager.h
// LookinServer
//
// Created by Li Kai on 2019/5/5.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinIvarTrace;
@interface LKS_TraceManager : NSObject
+ (instancetype)sharedInstance;
- (void)reload;
- (void)addSearchTarger:(id)target;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View 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 */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinServer_PrefixHeader.pch
// LookinServer
//
// Created by Li Kai on 2018/12/21.
// https://lookin.work
//
#import "TargetConditionals.h"
#import "LookinDefines.h"
#import "LKS_Helper.h"
#import "NSObject+LookinServer.h"
#import "NSArray+Lookin.h"
#import "NSSet+Lookin.h"
#import "CALayer+Lookin.h"
#import "UIView+LookinServer.h"
#import "CALayer+LookinServer.h"
#import "NSObject+Lookin.h"
#import "NSString+Lookin.h"
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */