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