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,40 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinIvarTrace.h
// Lookin
//
// Created by Li Kai on 2019/4/30.
// https://lookin.work
//
#import <Foundation/Foundation.h>
extern NSString *const LookinIvarTraceRelationValue_Self;
/// 如果 hostClassName 和 ivarName 均 equal则认为两个 LookinIvarTrace 对象彼此 equal
/// 比如 A 是 B 的 superview且 A 的 "_stageView" 指向 B则 B 会有一个 LookinIvarTracehostType 为 “superview”hostClassName 为 A 的 classivarName 为 “_stageView”
@interface LookinIvarTrace : NSObject <NSSecureCoding, NSCopying>
/// 该值可能是 "superview"、"superlayer"、“self” 或 nil
@property(nonatomic, copy) NSString *relation;
@property(nonatomic, copy) NSString *hostClassName;
@property(nonatomic, copy) NSString *ivarName;
#pragma mark - No Coding
#if TARGET_OS_IPHONE
@property(nonatomic, weak) id hostObject;
#endif
@end
@interface NSObject (LookinServerTrace)
@property(nonatomic, copy) NSArray<LookinIvarTrace *> *lks_ivarTraces;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,70 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinIvarTrace.m
// Lookin
//
// Created by Li Kai on 2019/4/30.
// https://lookin.work
//
#import "LookinIvarTrace.h"
NSString *const LookinIvarTraceRelationValue_Self = @"self";
@implementation LookinIvarTrace
#pragma mark - Equal
- (NSUInteger)hash {
return self.hostClassName.hash ^ self.ivarName.hash;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinIvarTrace class]]) {
return NO;
}
LookinIvarTrace *comparedObj = object;
if ([self.hostClassName isEqualToString:comparedObj.hostClassName] && [self.ivarName isEqualToString:comparedObj.ivarName]) {
return YES;
}
return NO;
}
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinIvarTrace *newTrace = [[LookinIvarTrace allocWithZone:zone] init];
newTrace.relation = self.relation;
newTrace.hostClassName = self.hostClassName;
newTrace.ivarName = self.ivarName;
return newTrace;
}
#pragma mark - <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.relation forKey:@"relation"];
[aCoder encodeObject:self.hostClassName forKey:@"hostClassName"];
[aCoder encodeObject:self.ivarName forKey:@"ivarName"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.relation = [aDecoder decodeObjectForKey:@"relation"];
self.hostClassName = [aDecoder decodeObjectForKey:@"hostClassName"];
self.ivarName = [aDecoder decodeObjectForKey:@"ivarName"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

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

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// CALayer+Lookin.h
// Lookin
//
// Created by Li Kai on 2018/8/4.
// https://lookin.work
//
#import "LookinDefines.h"
#import <QuartzCore/QuartzCore.h>
@interface CALayer (Lookin)
- (void)lookin_removeImplicitAnimations;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,70 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// CALayer+Lookin.m
// Lookin
//
// Created by Li Kai on 2018/8/4.
// https://lookin.work
//
#import "CALayer+Lookin.h"
@implementation CALayer (Lookin)
- (void)lookin_removeImplicitAnimations {
NSMutableDictionary<NSString *, id<CAAction>> *actions = @{NSStringFromSelector(@selector(bounds)): [NSNull null],
NSStringFromSelector(@selector(position)): [NSNull null],
NSStringFromSelector(@selector(zPosition)): [NSNull null],
NSStringFromSelector(@selector(anchorPoint)): [NSNull null],
NSStringFromSelector(@selector(anchorPointZ)): [NSNull null],
NSStringFromSelector(@selector(transform)): [NSNull null],
NSStringFromSelector(@selector(sublayerTransform)): [NSNull null],
NSStringFromSelector(@selector(masksToBounds)): [NSNull null],
NSStringFromSelector(@selector(contents)): [NSNull null],
NSStringFromSelector(@selector(contentsRect)): [NSNull null],
NSStringFromSelector(@selector(contentsScale)): [NSNull null],
NSStringFromSelector(@selector(contentsCenter)): [NSNull null],
NSStringFromSelector(@selector(minificationFilterBias)): [NSNull null],
NSStringFromSelector(@selector(backgroundColor)): [NSNull null],
NSStringFromSelector(@selector(cornerRadius)): [NSNull null],
NSStringFromSelector(@selector(borderWidth)): [NSNull null],
NSStringFromSelector(@selector(borderColor)): [NSNull null],
NSStringFromSelector(@selector(opacity)): [NSNull null],
NSStringFromSelector(@selector(compositingFilter)): [NSNull null],
NSStringFromSelector(@selector(filters)): [NSNull null],
NSStringFromSelector(@selector(backgroundFilters)): [NSNull null],
NSStringFromSelector(@selector(shouldRasterize)): [NSNull null],
NSStringFromSelector(@selector(rasterizationScale)): [NSNull null],
NSStringFromSelector(@selector(shadowColor)): [NSNull null],
NSStringFromSelector(@selector(shadowOpacity)): [NSNull null],
NSStringFromSelector(@selector(shadowOffset)): [NSNull null],
NSStringFromSelector(@selector(shadowRadius)): [NSNull null],
NSStringFromSelector(@selector(shadowPath)): [NSNull null]}.mutableCopy;
if ([self isKindOfClass:[CAShapeLayer class]]) {
[actions addEntriesFromDictionary:@{NSStringFromSelector(@selector(path)): [NSNull null],
NSStringFromSelector(@selector(fillColor)): [NSNull null],
NSStringFromSelector(@selector(strokeColor)): [NSNull null],
NSStringFromSelector(@selector(strokeStart)): [NSNull null],
NSStringFromSelector(@selector(strokeEnd)): [NSNull null],
NSStringFromSelector(@selector(lineWidth)): [NSNull null],
NSStringFromSelector(@selector(miterLimit)): [NSNull null],
NSStringFromSelector(@selector(lineDashPhase)): [NSNull null]}];
}
if ([self isKindOfClass:[CAGradientLayer class]]) {
[actions addEntriesFromDictionary:@{NSStringFromSelector(@selector(colors)): [NSNull null],
NSStringFromSelector(@selector(locations)): [NSNull null],
NSStringFromSelector(@selector(startPoint)): [NSNull null],
NSStringFromSelector(@selector(endPoint)): [NSNull null]}];
}
self.actions = actions;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,27 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Color+Lookin.h
// LookinShared
//
// Created by 李凯 on 2022/4/2.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@interface NSColor (Lookin)
+ (instancetype)lookin_colorFromRGBAComponents:(NSArray<NSNumber *> *)components;
- (NSArray<NSNumber *> *)lookin_rgbaComponents;
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,42 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Color+Lookin.m
// LookinShared
//
// Created by on 2022/4/2.
//
#import "Image+Lookin.h"
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@implementation NSColor (Lookin)
+ (instancetype)lookin_colorFromRGBAComponents:(NSArray<NSNumber *> *)components {
if (!components) {
return nil;
}
if (components.count != 4) {
NSAssert(NO, @"");
return nil;
}
NSColor *color = [NSColor colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue];
return color;
}
- (NSArray<NSNumber *> *)lookin_rgbaComponents {
NSColor *rgbColor = [self colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
CGFloat r, g, b, a;
[rgbColor getRed:&r green:&g blue:&b alpha:&a];
NSArray<NSNumber *> *rgba = @[@(r), @(g), @(b), @(a)];
return rgba;
}
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,25 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Image+Lookin.h
// LookinShared
//
// Created by 李凯 on 2022/4/2.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@interface NSImage (LookinClient)
- (NSData *)lookin_data;
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Image+Lookin.m
// LookinShared
//
// Created by on 2022/4/2.
//
#import "Image+Lookin.h"
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@implementation NSImage (LookinClient)
- (NSData *)lookin_data {
return [self TIFFRepresentation];
}
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,72 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSArray+Lookin.h
// Lookin
//
// Created by Li Kai on 2018/9/3.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@interface NSArray<__covariant ValueType> (Lookin)
/**
初始化一个新的 NSArray 并返回,新数组的长度为 count如果当前数组长度比 count 小则会补充新元素(被补充的元素由 addBlock 返回),如果当前数组长度比 count 大则会舍弃多余的元素,被舍弃的元素会作为参数传入 removeBlock。最终新数组的所有元素均会作为参数被传入 doBlock。
*/
- (NSArray<ValueType> *)lookin_resizeWithCount:(NSUInteger)count add:(ValueType (^)(NSUInteger idx))addBlock remove:(void (^)(NSUInteger idx, ValueType obj))removeBlock doNext:(void (^)(NSUInteger idx, ValueType obj))doBlock __attribute__((warn_unused_result));
+ (NSArray *)lookin_arrayWithCount:(NSUInteger)count block:(id (^)(NSUInteger idx))block;
/**
检查 index 位置是否有元素存在
*/
- (BOOL)lookin_hasIndex:(NSInteger)index;
- (NSArray *)lookin_map:(id (^)(NSUInteger idx, ValueType value))block;
- (NSArray<ValueType> *)lookin_filter:(BOOL (^)( ValueType obj))block;
- (ValueType)lookin_firstFiltered:(BOOL (^)(ValueType obj))block;
/// 返回最后一个 block 返回 YES 的元素
- (ValueType)lookin_lastFiltered:(BOOL (^)(ValueType obj))block;
- (id)lookin_reduce:(id (^)(id accumulator, NSUInteger idx, ValueType obj))block;
- (CGFloat)lookin_reduceCGFloat:(CGFloat (^)(CGFloat accumulator, NSUInteger idx, ValueType obj))block initialAccumlator:(CGFloat)initialAccumlator;
- (NSInteger)lookin_reduceInteger:(NSInteger (^)(NSInteger accumulator, NSUInteger idx, ValueType obj))block initialAccumlator:(NSInteger)initialAccumlator;
- (BOOL)lookin_all:(BOOL (^)(ValueType obj))block;
- (BOOL)lookin_any:(BOOL (^)(ValueType obj))block;
- (NSArray<ValueType> *)lookin_arrayByRemovingObject:(ValueType)obj;
- (NSArray<ValueType> *)lookin_nonredundantArray;
- (ValueType)lookin_safeObjectAtIndex:(NSInteger)idx;
/// 字符串长度从短到长,即 length 小的字符串的 idx 更小
- (NSArray<ValueType> *)lookin_sortedArrayByStringLength;
@end
@interface NSMutableArray<ValueType> (Lookin)
/**
如果当前数组长度比 count 小则会补充新元素(被补充的元素由 addBlock 返回),如果当前数组长度比 count 大则多余的元素会被作为参数传入 notDequeued。然后从 idx 为 0 算起,前 count 个元素会被作为参数传入 doBlock
*/
- (void)lookin_dequeueWithCount:(NSUInteger)count add:(ValueType (^)(NSUInteger idx))addBlock notDequeued:(void (^)(NSUInteger idx, ValueType obj))notDequeuedBlock doNext:(void (^)(NSUInteger idx, ValueType obj))doBlock;
- (void)lookin_removeObjectsPassingTest:(BOOL (^)(NSUInteger idx, ValueType obj))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,300 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSArray+Lookin.m
// Lookin
//
// Created by Li Kai on 2018/9/3.
// https://lookin.work
//
#import "NSArray+Lookin.h"
@implementation NSArray (Lookin)
- (NSArray *)lookin_resizeWithCount:(NSUInteger)count add:(id (^)(NSUInteger idx))addBlock remove:(void (^)(NSUInteger idx, id obj))removeBlock doNext:(void (^)(NSUInteger idx, id obj))doBlock {
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; i++) {
if (self.count > i) {
id obj = [self objectAtIndex:i];
[resultArray addObject:obj];
if (doBlock) {
doBlock(i, obj);
}
} else {
if (addBlock) {
id newObj = addBlock(i);
if (newObj) {
[resultArray addObject:newObj];
if (doBlock) {
doBlock(i, newObj);
}
} else {
NSAssert(NO, @"");
}
} else {
NSAssert(NO, @"");
}
}
}
if (removeBlock) {
if (self.count > count) {
for (NSUInteger i = count; i < self.count; i++) {
id obj = [self objectAtIndex:i];
removeBlock(i, obj);
}
}
}
return [resultArray copy];
}
+ (NSArray *)lookin_arrayWithCount:(NSUInteger)count block:(id (^)(NSUInteger idx))block {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; i++) {
id obj = block(i);
if (obj) {
[array addObject:obj];
}
}
return [array copy];
}
- (BOOL)lookin_hasIndex:(NSInteger)index {
if (index == NSNotFound || index < 0) {
return NO;
}
return self.count > index;
}
- (NSArray *)lookin_map:(id (^)(NSUInteger , id))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:self.count];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id newObj = block(idx, obj);
if (newObj) {
[array addObject:newObj];
}
}];
return [array copy];
}
- (NSArray *)lookin_filter:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableArray *mArray = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (block(obj)) {
[mArray addObject:obj];
}
}];
return [mArray copy];
}
- (id)lookin_firstFiltered:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id targetObj = nil;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (block(obj)) {
targetObj = obj;
*stop = YES;
}
}];
return targetObj;
}
- (id)lookin_lastFiltered:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id targetObj = nil;
[self enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (block(obj)) {
targetObj = obj;
*stop = YES;
}
}];
return targetObj;
}
- (id)lookin_reduce:(id (^)(id accumulator, NSUInteger idx, id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id accumulator = nil;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
accumulator = block(accumulator, idx, obj);
}];
return accumulator;
}
- (CGFloat)lookin_reduceCGFloat:(CGFloat (^)(CGFloat accumulator, NSUInteger idx, id obj))block initialAccumlator:(CGFloat)initialAccumlator {
if (!block) {
NSAssert(NO, @"");
return initialAccumlator;
}
__block CGFloat accumulator = initialAccumlator;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
accumulator = block(accumulator, idx, obj);
}];
return accumulator;
}
- (NSInteger)lookin_reduceInteger:(NSInteger (^)(NSInteger, NSUInteger, id))block initialAccumlator:(NSInteger)initialAccumlator {
if (!block) {
NSAssert(NO, @"");
return initialAccumlator;
}
__block NSInteger accumulator = initialAccumlator;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
accumulator = block(accumulator, idx, obj);
}];
return accumulator;
}
- (BOOL)lookin_all:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return NO;
}
__block BOOL allPass = YES;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL boolValue = block(obj);
if (!boolValue) {
allPass = NO;
*stop = YES;
}
}];
return allPass;
}
- (BOOL)lookin_any:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return NO;
}
__block BOOL anyPass = NO;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL boolValue = block(obj);
if (boolValue) {
anyPass = YES;
*stop = YES;
}
}];
return anyPass;
}
- (NSArray *)lookin_arrayByRemovingObject:(id)obj {
if (!obj || ![self containsObject:obj]) {
return self;
}
NSMutableArray *mutableArray = self.mutableCopy;
[mutableArray removeObject:obj];
return mutableArray.copy;
}
- (NSArray *)lookin_nonredundantArray {
NSSet *set = [NSSet setWithArray:self];
NSArray *newArray = [set allObjects];
return newArray;
}
- (id)lookin_safeObjectAtIndex:(NSInteger)idx {
if (idx == NSNotFound || idx < 0) {
return nil;
}
if (self.count <= idx) {
return nil;
}
return [self objectAtIndex:idx];
}
- (NSArray *)lookin_sortedArrayByStringLength {
NSArray<NSString *> *sortedArray = [self sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
if (obj1.length > obj2.length) {
return NSOrderedDescending;
} else if (obj1.length == obj2.length) {
return NSOrderedSame;
} else {
return NSOrderedAscending;
}
}];
return sortedArray;
}
@end
@implementation NSMutableArray (Lookin)
- (void)lookin_dequeueWithCount:(NSUInteger)count add:(id (^)(NSUInteger idx))addBlock notDequeued:(void (^)(NSUInteger idx, id obj))notDequeuedBlock doNext:(void (^)(NSUInteger idx, id obj))doBlock {
for (NSUInteger i = 0; i < count; i++) {
if ([self lookin_hasIndex:i]) {
id obj = [self objectAtIndex:i];
if (doBlock) {
doBlock(i, obj);
}
} else {
if (addBlock) {
id newObj = addBlock(i);
if (newObj) {
[self addObject:newObj];
if (doBlock) {
doBlock(i, newObj);
}
} else {
NSAssert(NO, @"");
}
} else {
NSAssert(NO, @"");
}
}
}
if (notDequeuedBlock) {
if (self.count > count) {
for (NSUInteger i = count; i < self.count; i++) {
id obj = [self objectAtIndex:i];
notDequeuedBlock(i, obj);
}
}
}
}
- (void)lookin_removeObjectsPassingTest:(BOOL (^)(NSUInteger idx, id obj))block {
if (!block) {
return;
}
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
[self enumerateObjectsUsingBlock:^(id _Nonnull currentObj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL boolValue = block(idx, currentObj);
if (boolValue) {
[indexSet addIndex:idx];
}
}];
[self removeObjectsAtIndexes:indexSet];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,108 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+Lookin.h
// Lookin
//
// Created by Li Kai on 2018/12/22.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
#import "LookinCodingValueType.h"
@interface NSObject (Lookin)
#pragma mark - Data Bind
/**
给对象绑定上另一个对象以供后续取出使用,如果 object 传入 nil 则会清除该 key 之前绑定的对象
@attention 被绑定的对象会被 strong 强引用
@note 内部是使用 objc_setAssociatedObject / objc_getAssociatedObject 来实现
@code
- (UITableViewCell *)cellForIndexPath:(NSIndexPath *)indexPath {
// 1在这里给 button 绑定上 indexPath 对象
[cell lookin_bindObject:indexPath forKey:@"indexPath"];
}
- (void)didTapButton:(UIButton *)button {
// 2在这里取出被点击的 button 的 indexPath 对象
NSIndexPath *indexPathTapped = [button lookin_getBindObjectForKey:@"indexPath"];
}
@endcode
*/
- (void)lookin_bindObject:(id)object forKey:(NSString *)key;
/**
给对象绑定上另一个对象以供后续取出使用,但相比于 lookin_bindObject:forKey:,该方法不会 strong 强引用传入的 object
*/
- (void)lookin_bindObjectWeakly:(id)object forKey:(NSString *)key;
/**
取出之前使用 bind 方法绑定的对象
*/
- (id)lookin_getBindObjectForKey:(NSString *)key;
/**
给对象绑定上一个 double 值以供后续取出使用
*/
- (void)lookin_bindDouble:(double)doubleValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindDouble:forKey: 绑定的值
*/
- (double)lookin_getBindDoubleForKey:(NSString *)key;
/**
给对象绑定上一个 BOOL 值以供后续取出使用
*/
- (void)lookin_bindBOOL:(BOOL)boolValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindBOOL:forKey: 绑定的值
*/
- (BOOL)lookin_getBindBOOLForKey:(NSString *)key;
/**
给对象绑定上一个 long 值以供后续取出使用
*/
- (void)lookin_bindLong:(long)longValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindLong:forKey: 绑定的值
*/
- (long)lookin_getBindLongForKey:(NSString *)key;
/**
给对象绑定上一个 CGPoint 值以供后续取出使用
*/
- (void)lookin_bindPoint:(CGPoint)pointValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindPoint:forKey: 绑定的值
*/
- (CGPoint)lookin_getBindPointForKey:(NSString *)key;
/**
移除之前使用 bind 方法绑定的对象
*/
- (void)lookin_clearBindForKey:(NSString *)key;
@end
@interface NSObject (Lookin_Coding)
/// 会把 NSImage/UIImage 转换为 NSData把 NSColor/UIColor 转换回 NSNumber 数组(rgba)
- (id)lookin_encodedObjectWithType:(LookinCodingValueType)type;
/// 会把 NSData 转换回 NSImage/UIImage把 NSNumber 数组(rgba) 转换为 NSColor/UIColor
- (id)lookin_decodedObjectWithType:(LookinCodingValueType)type;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,238 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+Lookin.m
// Lookin
//
// Created by Li Kai on 2018/12/22.
// https://lookin.work
//
#import "NSObject+Lookin.h"
#import <objc/runtime.h>
#import "TargetConditionals.h"
#import "LookinWeakContainer.h"
@implementation NSObject (Lookin)
#pragma mark - Data Bind
static char kAssociatedObjectKey_LookinAllBindObjects;
- (NSMutableDictionary<id, id> *)lookin_allBindObjects {
NSMutableDictionary<id, id> *dict = objc_getAssociatedObject(self, &kAssociatedObjectKey_LookinAllBindObjects);
if (!dict) {
dict = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &kAssociatedObjectKey_LookinAllBindObjects, dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return dict;
}
- (void)lookin_bindObject:(id)object forKey:(NSString *)key {
if (!key.length) {
NSAssert(NO, @"");
return;
}
@synchronized (self) {
if (object) {
[[self lookin_allBindObjects] setObject:object forKey:key];
} else {
[[self lookin_allBindObjects] removeObjectForKey:key];
}
}
}
- (id)lookin_getBindObjectForKey:(NSString *)key {
if (!key.length) {
NSAssert(NO, @"");
return nil;
}
@synchronized (self) {
id storedObj = [[self lookin_allBindObjects] objectForKey:key];
if ([storedObj isKindOfClass:[LookinWeakContainer class]]) {
storedObj = [(LookinWeakContainer *)storedObj object];
}
return storedObj;
}
}
- (void)lookin_bindObjectWeakly:(id)object forKey:(NSString *)key {
if (!key.length) {
NSAssert(NO, @"");
return;
}
if (object) {
LookinWeakContainer *container = [[LookinWeakContainer alloc] init];
container.object = object;
[self lookin_bindObject:container forKey:key];
} else {
[self lookin_bindObject:nil forKey:key];
}
}
- (void)lookin_bindDouble:(double)doubleValue forKey:(NSString *)key {
[self lookin_bindObject:@(doubleValue) forKey:key];
}
- (double)lookin_getBindDoubleForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSNumber class]]) {
double doubleValue = [(NSNumber *)object doubleValue];
return doubleValue;
} else {
return 0.0;
}
}
- (void)lookin_bindBOOL:(BOOL)boolValue forKey:(NSString *)key {
[self lookin_bindObject:@(boolValue) forKey:key];
}
- (BOOL)lookin_getBindBOOLForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSNumber class]]) {
BOOL boolValue = [(NSNumber *)object boolValue];
return boolValue;
} else {
return NO;
}
}
- (void)lookin_bindLong:(long)longValue forKey:(NSString *)key {
[self lookin_bindObject:@(longValue) forKey:key];
}
- (long)lookin_getBindLongForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSNumber class]]) {
long longValue = [(NSNumber *)object longValue];
return longValue;
} else {
return 0;
}
}
- (void)lookin_bindPoint:(CGPoint)pointValue forKey:(NSString *)key {
#if TARGET_OS_IPHONE
[self lookin_bindObject:[NSValue valueWithCGPoint:pointValue] forKey:key];
#elif TARGET_OS_MAC
NSPoint nsPoint = NSMakePoint(pointValue.x, pointValue.y);
[self lookin_bindObject:[NSValue valueWithPoint:nsPoint] forKey:key];
#endif
}
- (CGPoint)lookin_getBindPointForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSValue class]]) {
#if TARGET_OS_IPHONE
CGPoint pointValue = [(NSValue *)object CGPointValue];
#elif TARGET_OS_MAC
NSPoint nsPointValue = [(NSValue *)object pointValue];
CGPoint pointValue = CGPointMake(nsPointValue.x, nsPointValue.y);
#endif
return pointValue;
} else {
return CGPointZero;
}
}
- (void)lookin_clearBindForKey:(NSString *)key {
[self lookin_bindObject:nil forKey:key];
}
@end
@implementation NSObject (Lookin_Coding)
- (id)lookin_encodedObjectWithType:(LookinCodingValueType)type {
if (type == LookinCodingValueTypeColor) {
if ([self isKindOfClass:[LookinColor class]]) {
CGFloat r, g, b, a;
#if TARGET_OS_IPHONE
CGFloat white;
if ([(UIColor *)self getRed:&r green:&g blue:&b alpha:&a]) {
// valid
} else if ([(UIColor *)self getWhite:&white alpha:&a]) {
r = white;
g = white;
b = white;
} else {
NSAssert(NO, @"");
r = 0;
g = 0;
b = 0;
a = 0;
}
#elif TARGET_OS_MAC
NSColor *color = [((NSColor *)self) colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
[color getRed:&r green:&g blue:&b alpha:&a];
#endif
NSArray<NSNumber *> *rgba = @[@(r), @(g), @(b), @(a)];
return rgba;
} else {
NSAssert(NO, @"");
return nil;
}
} else if (type == LookinCodingValueTypeImage) {
#if TARGET_OS_IPHONE
if ([self isKindOfClass:[UIImage class]]) {
UIImage *image = (UIImage *)self;
return UIImagePNGRepresentation(image);
} else {
NSAssert(NO, @"");
return nil;
}
#elif TARGET_OS_MAC
if ([self isKindOfClass:[NSImage class]]) {
NSImage *image = (NSImage *)self;
return [image TIFFRepresentation];
} else {
NSAssert(NO, @"");
return nil;
}
#endif
} else {
return self;
}
}
- (id)lookin_decodedObjectWithType:(LookinCodingValueType)type {
if (type == LookinCodingValueTypeColor) {
if ([self isKindOfClass:[NSArray class]]) {
NSArray<NSNumber *> *rgba = (NSArray *)self;
CGFloat r = [rgba[0] doubleValue];
CGFloat g = [rgba[1] doubleValue];
CGFloat b = [rgba[2] doubleValue];
CGFloat a = [rgba[3] doubleValue];
LookinColor *color = [LookinColor colorWithRed:r green:g blue:b alpha:a];
return color;
} else {
NSAssert(NO, @"");
return nil;
}
} else if (type == LookinCodingValueTypeImage) {
if ([self isKindOfClass:[NSData class]]) {
LookinImage *image = [[LookinImage alloc] initWithData:(NSData *)self];
return image;
} else {
NSAssert(NO, @"");
return nil;
}
} else {
return self;
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,39 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSSet+Lookin.h
// Lookin
//
// Created by Li Kai on 2019/1/13.
// https://lookin.work
//
#import "LookinDefines.h"
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <Appkit/Appkit.h>
#endif
@interface NSSet<__covariant ValueType> (Lookin)
- (NSSet *)lookin_map:(id (^)(ValueType obj))block;
- (ValueType)lookin_firstFiltered:(BOOL (^)(ValueType obj))block;
- (NSSet<ValueType> *)lookin_filter:(BOOL (^)(ValueType obj))block;
/**
是否有任何一个元素满足某条件
@note 元素将被依次传入 block 里,如果任何一个 block 返回 YES则该方法返回 YES。如果所有 block 均返回 NO则该方法返回 NO。
*/
- (BOOL)lookin_any:(BOOL (^)(ValueType obj))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,81 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSSet+Lookin.m
// Lookin
//
// Created by Li Kai on 2019/1/13.
// https://lookin.work
//
#import "NSSet+Lookin.h"
@implementation NSSet (Lookin)
- (NSSet *)lookin_map:(id (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableSet *newSet = [NSMutableSet setWithCapacity:self.count];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
id newObj = block(obj);
if (newObj) {
[newSet addObject:newObj];
}
}];
return [newSet copy];
}
- (id)lookin_firstFiltered:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id targetObj = nil;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if (block(obj)) {
targetObj = obj;
*stop = YES;
}
}];
return targetObj;
}
- (NSSet *)lookin_filter:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableSet *mSet = [NSMutableSet set];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if (block(obj)) {
[mSet addObject:obj];
}
}];
return [mSet copy];
}
- (BOOL)lookin_any:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return NO;
}
__block BOOL boolValue = NO;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if (block(obj)) {
boolValue = YES;
*stop = YES;
}
}];
return boolValue;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,42 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSString+Lookin.h
// Lookin
//
// Created by Li Kai on 2019/5/11.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
@interface NSString (Lookin)
/**
把 CGFloat 转成字符串,最多保留 3 位小数,转换后末尾的 0 会被删除
1.2341 => @"1.234", 2.1002 => @"2.1", 3.000 => @"3"
*/
+ (NSString *)lookin_stringFromDouble:(double)doubleValue decimal:(NSUInteger)decimal;
+ (NSString *)lookin_stringFromRect:(CGRect)rect;
+ (NSString *)lookin_stringFromInset:(LookinInsets)insets;
+ (NSString *)lookin_stringFromSize:(CGSize)size;
+ (NSString *)lookin_stringFromPoint:(CGPoint)point;
+ (NSString *)lookin_rgbaStringFromColor:(LookinColor *)color;
- (NSString *)lookin_safeInitWithUTF8String:(const char *)string;
/// 把 1.2.3 这种 String 版本号转换成数字,可用于大小比较,如 110205 代表 11.2.5 版本
- (NSInteger)lookin_numbericOSVersion;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,117 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSString+Lookin.m
// Lookin
//
// Created by Li Kai on 2019/5/11.
// https://lookin.work
//
#import "NSString+Lookin.h"
@implementation NSString (Lookin)
+ (NSString *)lookin_stringFromDouble:(double)doubleValue decimal:(NSUInteger)decimal {
NSString *formatString = [NSString stringWithFormat:@"%%.%@f", @(decimal)];
NSString *string = [NSString stringWithFormat:formatString, doubleValue];
for (int i = 0; i < decimal; i++) {
if ([[string substringFromIndex:string.length - 1] isEqualToString:@"0"]) {
string = [string substringToIndex:string.length - 1];
}
}
if ([[string substringFromIndex:string.length - 1] isEqualToString:@"."]) {
string = [string substringToIndex:string.length - 1];
}
return string;
}
+ (NSString *)lookin_stringFromInset:(LookinInsets)insets {
return [NSString stringWithFormat:@"{%@, %@, %@, %@}",
[NSString lookin_stringFromDouble:insets.top decimal:2],
[NSString lookin_stringFromDouble:insets.left decimal:2],
[NSString lookin_stringFromDouble:insets.bottom decimal:2],
[NSString lookin_stringFromDouble:insets.right decimal:2]];
}
+ (NSString *)lookin_stringFromSize:(CGSize)size {
return [NSString stringWithFormat:@"{%@, %@}",
[NSString lookin_stringFromDouble:size.width decimal:2],
[NSString lookin_stringFromDouble:size.height decimal:2]];
}
+ (NSString *)lookin_stringFromPoint:(CGPoint)point {
return [NSString stringWithFormat:@"{%@, %@}",
[NSString lookin_stringFromDouble:point.x decimal:2],
[NSString lookin_stringFromDouble:point.y decimal:2]];
}
+ (NSString *)lookin_stringFromRect:(CGRect)rect {
return [NSString stringWithFormat:@"{%@, %@, %@, %@}",
[NSString lookin_stringFromDouble:rect.origin.x decimal:2],
[NSString lookin_stringFromDouble:rect.origin.y decimal:2],
[NSString lookin_stringFromDouble:rect.size.width decimal:2],
[NSString lookin_stringFromDouble:rect.size.height decimal:2]];
}
+ (NSString *)lookin_rgbaStringFromColor:(LookinColor *)color {
if (!color) {
return @"nil";
}
#if TARGET_OS_IPHONE
UIColor *rgbColor = color;
#elif TARGET_OS_MAC
NSColor *rgbColor = [color colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
#endif
CGFloat r, g, b, a;
[rgbColor getRed:&r green:&g blue:&b alpha:&a];
NSString *colorDesc;
if (a >= 1) {
colorDesc = [NSString stringWithFormat:@"(%.0f, %.0f, %.0f)", r * 255, g * 255, b * 255];
} else {
colorDesc = [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %@)", r * 255, g * 255, b * 255, [NSString lookin_stringFromDouble:a decimal:2]];
}
return colorDesc;
}
- (NSString *)lookin_safeInitWithUTF8String:(const char *)string {
if (NULL != string) {
return [self initWithUTF8String:string];
}
return nil;
}
- (NSInteger)lookin_numbericOSVersion {
if (self.length == 0) {
NSAssert(NO, @"");
return 0;
}
NSArray *versionArr = [self componentsSeparatedByString:@"."];
if (versionArr.count != 3) {
NSAssert(NO, @"");
return 0;
}
NSInteger numbericOSVersion = 0;
NSInteger pos = 0;
while ([versionArr count] > pos && pos < 3) {
numbericOSVersion += ([[versionArr objectAtIndex:pos] integerValue] * pow(10, (4 - pos * 2)));
pos++;
}
return numbericOSVersion;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,72 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAppInfo.h
// qmuidemo
//
// Created by Li Kai on 2018/11/3.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "LookinDefines.h"
typedef NS_ENUM(NSInteger, LookinAppInfoDevice) {
LookinAppInfoDeviceSimulator, // 模拟器
LookinAppInfoDeviceIPad, // iPad 真机
LookinAppInfoDeviceOthers // 应该视为 iPhone 真机
};
@interface LookinAppInfo : NSObject <NSSecureCoding, NSCopying>
/// 每次启动 app 时都会随机生成一个 appInfoIdentifier 直到 app 被 kill 掉
@property(nonatomic, assign) NSUInteger appInfoIdentifier;
/// mac 端应该先读取该属性,如果为 YES 则表示应该使用之前保存的旧 appInfo 对象即可
@property(nonatomic, assign) BOOL shouldUseCache;
/// LookinServer 的版本
@property(nonatomic, assign) int serverVersion;
/// 类似 "1.1.9",只在 1.2.3 以及之后的 LookinServer 版本里有值
@property(nonatomic, assign) NSString *serverReadableVersion;
/// 如果 iOS 侧使用了 SPM 或引入了 Swift Subspec则该属性为 1
/// 如果 iOS 侧没使用,则该属性为 -1
/// 如果不知道,则该属性为 0
@property(nonatomic, assign) int swiftEnabledInLookinServer;
/// app 的当前截图
@property(nonatomic, strong) LookinImage *screenshot;
/// 可能为 nil比如新建的 iOS 空项目
@property(nonatomic, strong) LookinImage *appIcon;
/// @"微信读书"
@property(nonatomic, copy) NSString *appName;
/// hughkli.lookin
@property(nonatomic, copy) NSString *appBundleIdentifier;
/// @"iPhone X"
@property(nonatomic, copy) NSString *deviceDescription;
/// @"12.1"
@property(nonatomic, copy) NSString *osDescription;
/// 返回 os 的主版本号,比如 iOS 12.1 的设备将返回 12iOS 13.2.1 的设备将返回 13
@property(nonatomic, assign) NSUInteger osMainVersion;
/// 设备类型
@property(nonatomic, assign) LookinAppInfoDevice deviceType;
/// 屏幕的宽度
@property(nonatomic, assign) double screenWidth;
/// 屏幕的高度
@property(nonatomic, assign) double screenHeight;
/// 是几倍的屏幕
@property(nonatomic, assign) double screenScale;
- (BOOL)isEqualToAppInfo:(LookinAppInfo *)info;
#if TARGET_OS_IPHONE
+ (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasIcon localIdentifiers:(NSArray<NSNumber *> *)localIdentifiers;
#else
@property(nonatomic, assign) NSTimeInterval cachedTimestamp;
#endif
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,242 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAppInfo.m
// qmuidemo
//
// Created by Li Kai on 2018/11/3.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "LookinAppInfo.h"
#import "LKS_MultiplatformAdapter.h"
static NSString * const CodingKey_AppIcon = @"1";
static NSString * const CodingKey_Screenshot = @"2";
static NSString * const CodingKey_DeviceDescription = @"3";
static NSString * const CodingKey_OsDescription = @"4";
static NSString * const CodingKey_AppName = @"5";
static NSString * const CodingKey_ScreenWidth = @"6";
static NSString * const CodingKey_ScreenHeight = @"7";
static NSString * const CodingKey_DeviceType = @"8";
@implementation LookinAppInfo
- (id)copyWithZone:(NSZone *)zone {
LookinAppInfo *newAppInfo = [[LookinAppInfo allocWithZone:zone] init];
newAppInfo.appIcon = self.appIcon;
newAppInfo.appName = self.appName;
newAppInfo.deviceDescription = self.deviceDescription;
newAppInfo.osDescription = self.osDescription;
newAppInfo.osMainVersion = self.osMainVersion;
newAppInfo.deviceType = self.deviceType;
newAppInfo.screenWidth = self.screenWidth;
newAppInfo.screenHeight = self.screenHeight;
newAppInfo.screenScale = self.screenScale;
newAppInfo.appInfoIdentifier = self.appInfoIdentifier;
return newAppInfo;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.serverVersion = [aDecoder decodeIntForKey:@"serverVersion"];
self.serverReadableVersion = [aDecoder decodeObjectForKey:@"serverReadableVersion"];
self.swiftEnabledInLookinServer = [aDecoder decodeIntForKey:@"swiftEnabledInLookinServer"];
NSData *screenshotData = [aDecoder decodeObjectForKey:CodingKey_Screenshot];
self.screenshot = [[LookinImage alloc] initWithData:screenshotData];
NSData *appIconData = [aDecoder decodeObjectForKey:CodingKey_AppIcon];
self.appIcon = [[LookinImage alloc] initWithData:appIconData];
self.appName = [aDecoder decodeObjectForKey:CodingKey_AppName];
self.appBundleIdentifier = [aDecoder decodeObjectForKey:@"appBundleIdentifier"];
self.deviceDescription = [aDecoder decodeObjectForKey:CodingKey_DeviceDescription];
self.osDescription = [aDecoder decodeObjectForKey:CodingKey_OsDescription];
self.osMainVersion = [aDecoder decodeIntegerForKey:@"osMainVersion"];
self.deviceType = [aDecoder decodeIntegerForKey:CodingKey_DeviceType];
self.screenWidth = [aDecoder decodeDoubleForKey:CodingKey_ScreenWidth];
self.screenHeight = [aDecoder decodeDoubleForKey:CodingKey_ScreenHeight];
self.screenScale = [aDecoder decodeDoubleForKey:@"screenScale"];
self.appInfoIdentifier = [aDecoder decodeIntegerForKey:@"appInfoIdentifier"];
self.shouldUseCache = [aDecoder decodeBoolForKey:@"shouldUseCache"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt:self.serverVersion forKey:@"serverVersion"];
[aCoder encodeObject:self.serverReadableVersion forKey:@"serverReadableVersion"];
[aCoder encodeInt:self.swiftEnabledInLookinServer forKey:@"swiftEnabledInLookinServer"];
#if TARGET_OS_IPHONE
NSData *screenshotData = UIImagePNGRepresentation(self.screenshot);
[aCoder encodeObject:screenshotData forKey:CodingKey_Screenshot];
NSData *appIconData = UIImagePNGRepresentation(self.appIcon);
[aCoder encodeObject:appIconData forKey:CodingKey_AppIcon];
#elif TARGET_OS_MAC
NSData *screenshotData = [self.screenshot TIFFRepresentation];
[aCoder encodeObject:screenshotData forKey:CodingKey_Screenshot];
NSData *appIconData = [self.appIcon TIFFRepresentation];
[aCoder encodeObject:appIconData forKey:CodingKey_AppIcon];
#endif
[aCoder encodeObject:self.appName forKey:CodingKey_AppName];
[aCoder encodeObject:self.appBundleIdentifier forKey:@"appBundleIdentifier"];
[aCoder encodeObject:self.deviceDescription forKey:CodingKey_DeviceDescription];
[aCoder encodeObject:self.osDescription forKey:CodingKey_OsDescription];
[aCoder encodeInteger:self.osMainVersion forKey:@"osMainVersion"];
[aCoder encodeInteger:self.deviceType forKey:CodingKey_DeviceType];
[aCoder encodeDouble:self.screenWidth forKey:CodingKey_ScreenWidth];
[aCoder encodeDouble:self.screenHeight forKey:CodingKey_ScreenHeight];
[aCoder encodeDouble:self.screenScale forKey:@"screenScale"];
[aCoder encodeInteger:self.appInfoIdentifier forKey:@"appInfoIdentifier"];
[aCoder encodeBool:self.shouldUseCache forKey:@"shouldUseCache"];
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinAppInfo class]]) {
return NO;
}
if ([self isEqualToAppInfo:object]) {
return YES;
}
return NO;
}
- (NSUInteger)hash {
return self.appName.hash ^ self.deviceDescription.hash ^ self.osDescription.hash ^ self.deviceType;
}
- (BOOL)isEqualToAppInfo:(LookinAppInfo *)info {
if (!info) {
return NO;
}
if ([self.appName isEqualToString:info.appName] && [self.deviceDescription isEqualToString:info.deviceDescription] && [self.osDescription isEqualToString:info.osDescription] && self.deviceType == info.deviceType) {
return YES;
}
return NO;
}
#if TARGET_OS_IPHONE
+ (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasIcon localIdentifiers:(NSArray<NSNumber *> *)localIdentifiers {
NSInteger selfIdentifier = [self getAppInfoIdentifier];
if ([localIdentifiers containsObject:@(selfIdentifier)]) {
LookinAppInfo *info = [LookinAppInfo new];
info.appInfoIdentifier = selfIdentifier;
info.shouldUseCache = YES;
return info;
}
LookinAppInfo *info = [[LookinAppInfo alloc] init];
info.serverReadableVersion = LOOKIN_SERVER_READABLE_VERSION;
#ifdef LOOKIN_SERVER_SWIFT_ENABLED
info.swiftEnabledInLookinServer = 1;
#else
info.swiftEnabledInLookinServer = -1;
#endif
info.appInfoIdentifier = selfIdentifier;
info.appName = [self appName];
info.deviceDescription = [UIDevice currentDevice].name;
info.appBundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
if ([self isSimulator]) {
info.deviceType = LookinAppInfoDeviceSimulator;
} else if ([LKS_MultiplatformAdapter isiPad]) {
info.deviceType = LookinAppInfoDeviceIPad;
} else {
info.deviceType = LookinAppInfoDeviceOthers;
}
info.osDescription = [UIDevice currentDevice].systemVersion;
NSString *mainVersionStr = [[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."].firstObject;
info.osMainVersion = [mainVersionStr integerValue];
CGSize screenSize = [LKS_MultiplatformAdapter mainScreenBounds].size;
info.screenWidth = screenSize.width;
info.screenHeight = screenSize.height;
info.screenScale = [LKS_MultiplatformAdapter mainScreenScale];
if (hasScreenshot) {
info.screenshot = [self screenshotImage];
}
if (hasIcon) {
info.appIcon = [self appIcon];
}
return info;
}
+ (NSString *)appName {
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSString *displayName = [info objectForKey:@"CFBundleDisplayName"];
NSString *name = [info objectForKey:@"CFBundleName"];
return displayName.length ? displayName : name;
}
+ (UIImage *)appIcon {
#if TARGET_OS_TV
return nil;
#else
NSString *imageName = [[[[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIcons"] objectForKey:@"CFBundlePrimaryIcon"] objectForKey:@"CFBundleIconFiles"] lastObject];
if (!imageName.length) {
// name AppIcon60x60 nil return [UIImage imageNamed:nil] console "CUICatalog: Invalid asset name supplied: '(null)'"
return nil;
}
return [UIImage imageNamed:imageName];
#endif
}
+ (UIImage *)screenshotImage {
UIWindow *window = [LKS_MultiplatformAdapter keyWindow];
if (!window) {
return nil;
}
CGSize size = window.bounds.size;
if (size.width <= 0 || size.height <= 0) {
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UIGraphicsBeginImageContext() failed to allocate CGBitampContext: size={0, 0}, scale=3.000000, bitmapInfo=0x2002. Use UIGraphicsImageRenderer to avoid this assert.'
// https://github.com/hughkli/Lookin/issues/21
return nil;
}
UIGraphicsBeginImageContextWithOptions(size, YES, 0.4);
[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (BOOL)isSimulator {
if (TARGET_OS_SIMULATOR) {
return YES;
}
return NO;
}
#endif
+ (NSInteger)getAppInfoIdentifier {
static dispatch_once_t onceToken;
static NSInteger identifier = 0;
dispatch_once(&onceToken,^{
identifier = [[NSDate date] timeIntervalSince1970];
});
return identifier;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,257 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttrIdentifiers.h
// Lookin
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#pragma mark - Group
typedef NSString * LookinAttrGroupIdentifier;
extern LookinAttrGroupIdentifier const LookinAttrGroup_None;
extern LookinAttrGroupIdentifier const LookinAttrGroup_Class;
extern LookinAttrGroupIdentifier const LookinAttrGroup_Relation;
extern LookinAttrGroupIdentifier const LookinAttrGroup_Layout;
extern LookinAttrGroupIdentifier const LookinAttrGroup_AutoLayout;
extern LookinAttrGroupIdentifier const LookinAttrGroup_ViewLayer;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIImageView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UILabel;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIControl;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIButton;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIScrollView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UITableView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UITextView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UITextField;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIVisualEffectView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIStackView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UserCustom;
#pragma mark - Section
typedef NSString * LookinAttrSectionIdentifier;
extern LookinAttrSectionIdentifier const LookinAttrSec_None;
extern LookinAttrSectionIdentifier const LookinAttrSec_UserCustom;
extern LookinAttrSectionIdentifier const LookinAttrSec_Class_Class;
extern LookinAttrSectionIdentifier const LookinAttrSec_Relation_Relation;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Frame;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Bounds;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_SafeArea;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Position;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_AnchorPoint;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Hugging;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Resistance;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Constraints;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_IntrinsicSize;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Visibility;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_InterationAndMasks;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Corner;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_BgColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Border;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Shadow;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_ContentMode;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_TintColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Tag;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Name;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Open;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Text;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Font;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_NumberOfLines;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_TextColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_BreakMode;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_CanAdjustFont;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_EnabledSelected;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_VerAlignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_HorAlignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_QMUIOutsideEdge;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ContentInsets;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_TitleInsets;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ImageInsets;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_AdjustedInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_IndicatorInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Offset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentSize;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Behavior;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ShowsIndicator;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Bounce;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ScrollPaging;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentTouches;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Zoom;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_QMUIInitialInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_Style;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SectionsNumber;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_RowsNumber;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorStyle;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Basic;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Text;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Font;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_TextColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_ContainerInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Text;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Placeholder;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Font;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_TextColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Clears;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_CanAdjustFont;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_ClearButtonMode;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_Style;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_QMUIForegroundColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Axis;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Distribution;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Spacing;
#pragma mark - Attr
typedef NSString * LookinAttrIdentifier;
extern LookinAttrIdentifier const LookinAttr_None;
/// 用户自定义的
extern LookinAttrIdentifier const LookinAttr_UserCustom;
extern LookinAttrIdentifier const LookinAttr_Class_Class_Class;
extern LookinAttrIdentifier const LookinAttr_Relation_Relation_Relation;
extern LookinAttrIdentifier const LookinAttr_Layout_Frame_Frame;
extern LookinAttrIdentifier const LookinAttr_Layout_Bounds_Bounds;
extern LookinAttrIdentifier const LookinAttr_Layout_SafeArea_SafeArea;
extern LookinAttrIdentifier const LookinAttr_Layout_Position_Position;
extern LookinAttrIdentifier const LookinAttr_Layout_AnchorPoint_AnchorPoint;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Hor;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Ver;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Hor;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Ver;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Constraints_Constraints;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_IntrinsicSize_Size;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Hidden;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Opacity;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_Interaction;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Corner_Radius;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_BgColor_BgColor;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Color;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Width;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Color;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Opacity;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Radius;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetW;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetH;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_ContentMode_Mode;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Color;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Mode;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Tag_Tag;
extern LookinAttrIdentifier const LookinAttr_UIImageView_Name_Name;
extern LookinAttrIdentifier const LookinAttr_UIImageView_Open_Open;
extern LookinAttrIdentifier const LookinAttr_UILabel_Text_Text;
extern LookinAttrIdentifier const LookinAttr_UILabel_Font_Name;
extern LookinAttrIdentifier const LookinAttr_UILabel_Font_Size;
extern LookinAttrIdentifier const LookinAttr_UILabel_NumberOfLines_NumberOfLines;
extern LookinAttrIdentifier const LookinAttr_UILabel_TextColor_Color;
extern LookinAttrIdentifier const LookinAttr_UILabel_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UILabel_BreakMode_Mode;
extern LookinAttrIdentifier const LookinAttr_UILabel_CanAdjustFont_CanAdjustFont;
extern LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Enabled;
extern LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Selected;
extern LookinAttrIdentifier const LookinAttr_UIControl_VerAlignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UIControl_HorAlignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UIControl_QMUIOutsideEdge_Edge;
extern LookinAttrIdentifier const LookinAttr_UIButton_ContentInsets_Insets;
extern LookinAttrIdentifier const LookinAttr_UIButton_TitleInsets_Insets;
extern LookinAttrIdentifier const LookinAttr_UIButton_ImageInsets_Insets;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Offset_Offset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentSize_Size;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_AdjustedInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Behavior_Behavior;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_IndicatorInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_PagingEnabled;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Ver;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Hor;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Hor;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Ver;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_Delay;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_CanCancel;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MinScale;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MaxScale;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Scale;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Bounce;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_QMUIInitialInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UITableView_Style_Style;
extern LookinAttrIdentifier const LookinAttr_UITableView_SectionsNumber_Number;
extern LookinAttrIdentifier const LookinAttr_UITableView_RowsNumber_Number;
extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorColor_Color;
extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorStyle_Style;
extern LookinAttrIdentifier const LookinAttr_UITextView_Font_Name;
extern LookinAttrIdentifier const LookinAttr_UITextView_Font_Size;
extern LookinAttrIdentifier const LookinAttr_UITextView_Basic_Editable;
extern LookinAttrIdentifier const LookinAttr_UITextView_Basic_Selectable;
extern LookinAttrIdentifier const LookinAttr_UITextView_Text_Text;
extern LookinAttrIdentifier const LookinAttr_UITextView_TextColor_Color;
extern LookinAttrIdentifier const LookinAttr_UITextView_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UITextView_ContainerInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UITextField_Text_Text;
extern LookinAttrIdentifier const LookinAttr_UITextField_Placeholder_Placeholder;
extern LookinAttrIdentifier const LookinAttr_UITextField_Font_Name;
extern LookinAttrIdentifier const LookinAttr_UITextField_Font_Size;
extern LookinAttrIdentifier const LookinAttr_UITextField_TextColor_Color;
extern LookinAttrIdentifier const LookinAttr_UITextField_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnBeginEditing;
extern LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnInsertion;
extern LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_CanAdjustFont;
extern LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_MinSize;
extern LookinAttrIdentifier const LookinAttr_UITextField_ClearButtonMode_Mode;
extern LookinAttrIdentifier const LookinAttr_UIVisualEffectView_Style_Style;
extern LookinAttrIdentifier const LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Axis_Axis;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Distribution_Distribution;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Spacing_Spacing;
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,253 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttrIdentifiers.m
// Lookin
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import "LookinAttrIdentifiers.h"
// value AppDelegate runTests test
// value preference value userDefaults
#pragma mark - Group
LookinAttrGroupIdentifier const LookinAttrGroup_None = @"n";
LookinAttrGroupIdentifier const LookinAttrGroup_Class = @"c";
LookinAttrGroupIdentifier const LookinAttrGroup_Relation = @"r";
LookinAttrGroupIdentifier const LookinAttrGroup_Layout = @"l";
LookinAttrGroupIdentifier const LookinAttrGroup_AutoLayout = @"a";
LookinAttrGroupIdentifier const LookinAttrGroup_ViewLayer = @"vl";
LookinAttrGroupIdentifier const LookinAttrGroup_UIImageView = @"i";
LookinAttrGroupIdentifier const LookinAttrGroup_UILabel = @"la";
LookinAttrGroupIdentifier const LookinAttrGroup_UIControl = @"co";
LookinAttrGroupIdentifier const LookinAttrGroup_UIButton = @"b";
LookinAttrGroupIdentifier const LookinAttrGroup_UIScrollView = @"s";
LookinAttrGroupIdentifier const LookinAttrGroup_UITableView = @"ta";
LookinAttrGroupIdentifier const LookinAttrGroup_UITextView = @"te";
LookinAttrGroupIdentifier const LookinAttrGroup_UITextField = @"tf";
LookinAttrGroupIdentifier const LookinAttrGroup_UIVisualEffectView = @"ve";
LookinAttrGroupIdentifier const LookinAttrGroup_UIStackView = @"UIStackView";
LookinAttrGroupIdentifier const LookinAttrGroup_UserCustom = @"guc"; // user custom
#pragma mark - Section
LookinAttrSectionIdentifier const LookinAttrSec_None = @"n";
LookinAttrSectionIdentifier const LookinAttrSec_UserCustom = @"sec_ctm";
LookinAttrSectionIdentifier const LookinAttrSec_Class_Class = @"cl_c";
LookinAttrSectionIdentifier const LookinAttrSec_Relation_Relation = @"r_r";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_Frame = @"l_f";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_Bounds = @"l_b";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_SafeArea = @"l_s";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_Position = @"l_p";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_AnchorPoint = @"l_a";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Hugging = @"a_h";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Resistance = @"a_r";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Constraints = @"a_c";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_IntrinsicSize = @"a_i";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Visibility = @"v_v";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_InterationAndMasks = @"v_i";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Corner = @"v_c";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_BgColor = @"v_b";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Border = @"v_bo";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Shadow = @"v_s";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_ContentMode = @"v_co";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_TintColor = @"v_t";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Tag = @"v_ta";
LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Name = @"i_n";
LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Open = @"i_o";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Text = @"lb_t";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Font = @"lb_f";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_NumberOfLines = @"lb_n";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_TextColor = @"lb_tc";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_BreakMode = @"lb_b";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Alignment = @"lb_a";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_CanAdjustFont = @"lb_c";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_EnabledSelected = @"c_e";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_VerAlignment = @"c_v";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_HorAlignment = @"c_h";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_QMUIOutsideEdge = @"c_o";
LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ContentInsets = @"b_c";
LookinAttrSectionIdentifier const LookinAttrSec_UIButton_TitleInsets = @"b_t";
LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ImageInsets = @"b_i";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentInset = @"s_c";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_AdjustedInset = @"s_a";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_IndicatorInset = @"s_i";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Offset = @"s_o";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentSize = @"s_cs";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Behavior = @"s_b";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ShowsIndicator = @"s_si";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Bounce = @"s_bo";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ScrollPaging = @"s_s";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentTouches = @"s_ct";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Zoom = @"s_z";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_QMUIInitialInset = @"s_ii";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_Style = @"t_s";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SectionsNumber = @"t_sn";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_RowsNumber = @"t_r";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorStyle = @"t_ss";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorColor = @"t_sc";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorInset = @"t_si";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Basic = @"tv_b";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Text = @"tv_t";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Font = @"tv_f";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_TextColor = @"tv_tc";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Alignment = @"tv_a";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_ContainerInset = @"tv_c";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Text = @"tf_t";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Placeholder = @"tf_p";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Font = @"tf_f";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_TextColor = @"tf_tc";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Alignment = @"tf_a";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Clears = @"tf_c";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_CanAdjustFont = @"tf_ca";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_ClearButtonMode = @"tf_cb";
LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_Style = @"ve_s";
LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_QMUIForegroundColor = @"ve_f";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Axis = @"usv_axis";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Distribution = @"usv_dis";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Alignment = @"usv_align";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Spacing = @"usv_spa";
#pragma mark - Attr
LookinAttrIdentifier const LookinAttr_None = @"n";
LookinAttrIdentifier const LookinAttr_UserCustom = @"ctm";
LookinAttrIdentifier const LookinAttr_Class_Class_Class = @"c_c_c";
LookinAttrIdentifier const LookinAttr_Relation_Relation_Relation = @"r_r_r";
LookinAttrIdentifier const LookinAttr_Layout_Frame_Frame = @"l_f_f";
LookinAttrIdentifier const LookinAttr_Layout_Bounds_Bounds = @"l_b_b";
LookinAttrIdentifier const LookinAttr_Layout_SafeArea_SafeArea = @"l_s_s";
LookinAttrIdentifier const LookinAttr_Layout_Position_Position = @"l_p_p";
LookinAttrIdentifier const LookinAttr_Layout_AnchorPoint_AnchorPoint = @"l_a_a";
LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Hor = @"al_h_h";
LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Ver = @"al_h_v";
LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Hor = @"al_r_h";
LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Ver = @"al_r_v";
LookinAttrIdentifier const LookinAttr_AutoLayout_Constraints_Constraints = @"al_c_c";
LookinAttrIdentifier const LookinAttr_AutoLayout_IntrinsicSize_Size = @"cl_i_s";
LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Hidden = @"vl_v_h";
LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Opacity = @"vl_v_o";
LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_Interaction = @"vl_i_i";
LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds = @"vl_i_m";
LookinAttrIdentifier const LookinAttr_ViewLayer_Corner_Radius = @"vl_c_r";
LookinAttrIdentifier const LookinAttr_ViewLayer_BgColor_BgColor = @"vl_b_b";
LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Color = @"vl_b_c";
LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Width = @"vl_b_w";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Color = @"vl_s_c";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Opacity = @"vl_s_o";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Radius = @"vl_s_r";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetW = @"vl_s_ow";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetH = @"vl_s_oh";
LookinAttrIdentifier const LookinAttr_ViewLayer_ContentMode_Mode = @"vl_c_m";
LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Color = @"vl_t_c";
LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Mode = @"vl_t_m";
LookinAttrIdentifier const LookinAttr_ViewLayer_Tag_Tag = @"vl_t_t";
LookinAttrIdentifier const LookinAttr_UIImageView_Name_Name = @"iv_n_n";
LookinAttrIdentifier const LookinAttr_UIImageView_Open_Open = @"iv_o_o";
LookinAttrIdentifier const LookinAttr_UILabel_Text_Text = @"lb_t_t";
LookinAttrIdentifier const LookinAttr_UILabel_Font_Name = @"lb_f_n";
LookinAttrIdentifier const LookinAttr_UILabel_Font_Size = @"lb_f_s";
LookinAttrIdentifier const LookinAttr_UILabel_NumberOfLines_NumberOfLines = @"lb_n_n";
LookinAttrIdentifier const LookinAttr_UILabel_TextColor_Color = @"lb_t_c";
LookinAttrIdentifier const LookinAttr_UILabel_Alignment_Alignment = @"lb_a_a";
LookinAttrIdentifier const LookinAttr_UILabel_BreakMode_Mode = @"lb_b_m";
LookinAttrIdentifier const LookinAttr_UILabel_CanAdjustFont_CanAdjustFont = @"lb_c_c";
LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Enabled = @"ct_e_e";
LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Selected = @"ct_e_s";
LookinAttrIdentifier const LookinAttr_UIControl_VerAlignment_Alignment = @"ct_v_a";
LookinAttrIdentifier const LookinAttr_UIControl_HorAlignment_Alignment = @"ct_h_a";
LookinAttrIdentifier const LookinAttr_UIControl_QMUIOutsideEdge_Edge = @"ct_o_e";
LookinAttrIdentifier const LookinAttr_UIButton_ContentInsets_Insets = @"bt_c_i";
LookinAttrIdentifier const LookinAttr_UIButton_TitleInsets_Insets = @"bt_t_i";
LookinAttrIdentifier const LookinAttr_UIButton_ImageInsets_Insets = @"bt_i_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_Offset_Offset = @"sv_o_o";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentSize_Size = @"sv_c_s";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentInset_Inset = @"sv_c_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_AdjustedInset_Inset = @"sv_a_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_Behavior_Behavior = @"sv_b_b";
LookinAttrIdentifier const LookinAttr_UIScrollView_IndicatorInset_Inset = @"sv_i_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled = @"sv_s_s";
LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_PagingEnabled = @"sv_s_p";
LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Ver = @"sv_b_v";
LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Hor = @"sv_b_h";
LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Hor = @"sv_h_h";
LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Ver = @"sv_s_v";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_Delay = @"sv_c_d";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_CanCancel = @"sv_c_c";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MinScale = @"sv_z_mi";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MaxScale = @"sv_z_ma";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Scale = @"sv_z_s";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Bounce = @"sv_z_b";
LookinAttrIdentifier const LookinAttr_UIScrollView_QMUIInitialInset_Inset = @"sv_qi_i";
LookinAttrIdentifier const LookinAttr_UITableView_Style_Style = @"tv_s_s";
LookinAttrIdentifier const LookinAttr_UITableView_SectionsNumber_Number = @"tv_s_n";
LookinAttrIdentifier const LookinAttr_UITableView_RowsNumber_Number = @"tv_r_n";
LookinAttrIdentifier const LookinAttr_UITableView_SeparatorInset_Inset = @"tv_s_i";
LookinAttrIdentifier const LookinAttr_UITableView_SeparatorColor_Color = @"tv_s_c";
LookinAttrIdentifier const LookinAttr_UITableView_SeparatorStyle_Style = @"tv_ss_s";
LookinAttrIdentifier const LookinAttr_UITextView_Font_Name = @"te_f_n";
LookinAttrIdentifier const LookinAttr_UITextView_Font_Size = @"te_f_s";
LookinAttrIdentifier const LookinAttr_UITextView_Basic_Editable = @"te_b_e";
LookinAttrIdentifier const LookinAttr_UITextView_Basic_Selectable = @"te_b_s";
LookinAttrIdentifier const LookinAttr_UITextView_Text_Text = @"te_t_t";
LookinAttrIdentifier const LookinAttr_UITextView_TextColor_Color = @"te_t_c";
LookinAttrIdentifier const LookinAttr_UITextView_Alignment_Alignment = @"te_a_a";
LookinAttrIdentifier const LookinAttr_UITextView_ContainerInset_Inset = @"te_c_i";
LookinAttrIdentifier const LookinAttr_UITextField_Text_Text = @"tf_t_t";
LookinAttrIdentifier const LookinAttr_UITextField_Placeholder_Placeholder = @"tf_p_p";
LookinAttrIdentifier const LookinAttr_UITextField_Font_Name = @"tf_f_n";
LookinAttrIdentifier const LookinAttr_UITextField_Font_Size = @"tf_f_s";
LookinAttrIdentifier const LookinAttr_UITextField_TextColor_Color = @"tf_t_c";
LookinAttrIdentifier const LookinAttr_UITextField_Alignment_Alignment = @"tf_a_a";
LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnBeginEditing = @"tf_c_c";
LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnInsertion = @"tf_c_co";
LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_CanAdjustFont = @"tf_c_ca";
LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_MinSize = @"tf_c_m";
LookinAttrIdentifier const LookinAttr_UITextField_ClearButtonMode_Mode = @"tf_cb_m";
LookinAttrIdentifier const LookinAttr_UIVisualEffectView_Style_Style = @"ve_s_s";
LookinAttrIdentifier const LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color = @"ve_f_c";
LookinAttrIdentifier const LookinAttr_UIStackView_Axis_Axis = @"usv_axis_axis";
LookinAttrIdentifier const LookinAttr_UIStackView_Distribution_Distribution = @"usv_dis_dis";
LookinAttrIdentifier const LookinAttr_UIStackView_Alignment_Alignment = @"usv_ali_ali";
LookinAttrIdentifier const LookinAttr_UIStackView_Spacing_Spacing = @"usv_spa_spa";
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,50 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttrIdentifiers.h
// Lookin
//
// Created by Li Kai on 2018/12/1.
// https://lookin.work
//
/// 注意:新属性只能加到末尾,否则新旧版本搭配时可能有兼容问题
typedef NS_ENUM(NSInteger, LookinAttrType) {
LookinAttrTypeNone,
LookinAttrTypeVoid,
LookinAttrTypeChar,
LookinAttrTypeInt,
LookinAttrTypeShort,
LookinAttrTypeLong,
LookinAttrTypeLongLong,
LookinAttrTypeUnsignedChar,
LookinAttrTypeUnsignedInt,
LookinAttrTypeUnsignedShort,
LookinAttrTypeUnsignedLong,
LookinAttrTypeUnsignedLongLong,
LookinAttrTypeFloat,
LookinAttrTypeDouble,
LookinAttrTypeBOOL,
LookinAttrTypeSel,
LookinAttrTypeClass,
LookinAttrTypeCGPoint,
LookinAttrTypeCGVector,
LookinAttrTypeCGSize,
LookinAttrTypeCGRect,
LookinAttrTypeCGAffineTransform,
LookinAttrTypeUIEdgeInsets,
LookinAttrTypeUIOffset,
LookinAttrTypeNSString,
LookinAttrTypeEnumInt,
LookinAttrTypeEnumLong,
/// value 实际为 RGBA 数组,即 @[NSNumber, NSNumber, NSNumber, NSNumber]NSNumber 范围是 0 ~ 1
LookinAttrTypeUIColor,
/// 业务需要根据具体的 AttrIdentifier 来解析
LookinAttrTypeCustomObj,
LookinAttrTypeEnumString,
LookinAttrTypeShadow,
LookinAttrTypeJson
};
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,48 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttribute.h
// qmuidemo
//
// Created by Li Kai on 2018/11/17.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "LookinAttrIdentifiers.h"
#import "LookinCodingValueType.h"
#import "LookinAttrType.h"
@class LookinDisplayItem;
@interface LookinAttribute : NSObject <NSSecureCoding, NSCopying>
@property(nonatomic, copy) LookinAttrIdentifier identifier;
/// 只有 Custom Attr 才有该属性
@property(nonatomic, copy) NSString *displayTitle;
/// 标识 value 的具体类型(如 double / NSString /...
@property(nonatomic, assign) LookinAttrType attrType;
/// 具体的值,需配合 attrType 属性来解析它
/// 对于 String、Color 等 attyType该属性可能为 nil
@property(nonatomic, strong) id value;
/// 额外信息,大部分情况下它是 nil
/// 当 attyType 为 LookinAttrTypeEnumString 时extraValue 是一个 [String] 且保存了 allEnumCases
@property(nonatomic, strong) id extraValue;
/// 仅 Custom Attr 可能有该属性
/// 对于有 retainedSetter 的 Custom Attr它的 setter 会以 customSetterID 作为 key 被保存到 LKS_CustomAttrSetterManager 里,后续可以通过这个 uniqueID 重新把 setter 从 LKS_CustomAttrSetterManager 里取出来并调用
@property(nonatomic, copy) NSString *customSetterID;
#pragma mark - 以下属性不会参与 encode/decode
/// 标识该 LookinAttribute 对象隶属于哪一个 LookinDisplayItem
@property(nonatomic, weak) LookinDisplayItem *targetDisplayItem;
- (BOOL)isUserCustom;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,64 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttribute.m
// qmuidemo
//
// Created by Li Kai on 2018/11/17.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "LookinAttribute.h"
#import "LookinDisplayItem.h"
@implementation LookinAttribute
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinAttribute *newAttr = [[LookinAttribute allocWithZone:zone] init];
newAttr.identifier = self.identifier;
newAttr.displayTitle = self.displayTitle;
newAttr.value = self.value;
newAttr.attrType = self.attrType;
newAttr.extraValue = self.extraValue;
newAttr.customSetterID = self.customSetterID;
return newAttr;
}
#pragma mark - <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.displayTitle forKey:@"displayTitle"];
[aCoder encodeObject:self.identifier forKey:@"identifier"];
[aCoder encodeInteger:self.attrType forKey:@"attrType"];
[aCoder encodeObject:self.value forKey:@"value"];
[aCoder encodeObject:self.extraValue forKey:@"extraValue"];
[aCoder encodeObject:self.customSetterID forKey:@"customSetterID"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.displayTitle = [aDecoder decodeObjectForKey:@"displayTitle"];
self.identifier = [aDecoder decodeObjectForKey:@"identifier"];
self.attrType = [aDecoder decodeIntegerForKey:@"attrType"];
self.value = [aDecoder decodeObjectForKey:@"value"];
self.extraValue = [aDecoder decodeObjectForKey:@"extraValue"];
self.customSetterID = [aDecoder decodeObjectForKey:@"customSetterID"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (BOOL)isUserCustom {
return [self.identifier isEqualToString:LookinAttr_UserCustom];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,31 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttributeModification.h
// Lookin
//
// Created by Li Kai on 2018/11/20.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#import "LookinAttrType.h"
@interface LookinAttributeModification : NSObject <NSSecureCoding>
@property(nonatomic, assign) unsigned long targetOid;
@property(nonatomic, assign) SEL setterSelector;
@property(nonatomic, assign) SEL getterSelector;
@property(nonatomic, assign) LookinAttrType attrType;
@property(nonatomic, strong) id value;
/// 1.0.4 开始加入这个参数
@property(nonatomic, copy) NSString *clientReadableVersion;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,40 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttributeModification.m
// Lookin
//
// Created by Li Kai on 2018/11/20.
// https://lookin.work
//
#import "LookinAttributeModification.h"
@implementation LookinAttributeModification
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.targetOid) forKey:@"targetOid"];
[aCoder encodeObject:NSStringFromSelector(self.setterSelector) forKey:@"setterSelector"];
[aCoder encodeInteger:self.attrType forKey:@"attrType"];
[aCoder encodeObject:self.value forKey:@"value"];
[aCoder encodeObject:self.clientReadableVersion forKey:@"clientReadableVersion"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.targetOid = [[aDecoder decodeObjectForKey:@"targetOid"] unsignedLongValue];
self.setterSelector = NSSelectorFromString([aDecoder decodeObjectForKey:@"setterSelector"]);
self.attrType = [aDecoder decodeIntegerForKey:@"attrType"];
self.value = [aDecoder decodeObjectForKey:@"value"];
self.clientReadableVersion = [aDecoder decodeObjectForKey:@"clientReadableVersion"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,41 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttributesGroup.h
// Lookin
//
// Created by Li Kai on 2018/11/19.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#import "LookinAttrIdentifiers.h"
@class LookinAttributesSection;
/**
In Lookin, a LookinAttributesGroup instance will be rendered as a property card.
When isUserCustom is false: two LookinAttributesGroup instances will be regard as equal when they has the same LookinAttrGroupIdentifier.
When isUserCustom is true: two LookinAttributesGroup instances will be regard as equal when they has the same title.
当 isUserCustom 为 false 时:若两个 attrGroup 有相同的 LookinAttrGroupIdentifier则 isEqual: 返回 YES
*/
@interface LookinAttributesGroup : NSObject <NSSecureCoding, NSCopying>
/// 只有在 identifier 为 custom 时,才存在该值
@property(nonatomic, copy) NSString *userCustomTitle;
@property(nonatomic, copy) LookinAttrGroupIdentifier identifier;
@property(nonatomic, copy) NSArray<LookinAttributesSection *> *attrSections;
/// 如果是 custom 则返回 userCustomTitle如果不是 custom 则返回 identifier
- (NSString *)uniqueKey;
- (BOOL)isUserCustom;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,92 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttributesGroup.m
// Lookin
//
// Created by Li Kai on 2018/11/19.
// https://lookin.work
//
#import "LookinAttributesGroup.h"
#import "LookinAttribute.h"
#import "LookinAttributesSection.h"
#import "LookinDashboardBlueprint.h"
#import "NSArray+Lookin.h"
@implementation LookinAttributesGroup
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinAttributesGroup *newGroup = [[LookinAttributesGroup allocWithZone:zone] init];
newGroup.userCustomTitle = self.userCustomTitle;
newGroup.identifier = self.identifier;
newGroup.attrSections = [self.attrSections lookin_map:^id(NSUInteger idx, LookinAttributesSection *value) {
return value.copy;
}];
return newGroup;
}
#pragma mark - <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.userCustomTitle forKey:@"userCustomTitle"];
[aCoder encodeObject:self.identifier forKey:@"identifier"];
[aCoder encodeObject:self.attrSections forKey:@"attrSections"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.userCustomTitle = [aDecoder decodeObjectForKey:@"userCustomTitle"];
self.identifier = [aDecoder decodeObjectForKey:@"identifier"];
self.attrSections = [aDecoder decodeObjectForKey:@"attrSections"];
}
return self;
}
- (NSUInteger)hash {
return self.uniqueKey.hash;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinAttributesGroup class]]) {
return NO;
}
LookinAttributesGroup *targetObject = object;
if (![self.identifier isEqualToString:targetObject.identifier]) {
return false;
}
if ([self.identifier isEqualToString:LookinAttrGroup_UserCustom]) {
BOOL ret = [self.userCustomTitle isEqualToString:targetObject.userCustomTitle];
return ret;
} else {
return true;
}
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (NSString *)uniqueKey {
if ([self.identifier isEqualToString:LookinAttrGroup_UserCustom]) {
return self.userCustomTitle;
} else {
return self.identifier;
}
}
- (BOOL)isUserCustom {
return [self.identifier isEqualToString:LookinAttrSec_UserCustom];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,35 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttributesSection.h
// Lookin
//
// Created by Li Kai on 2019/3/2.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#import "LookinAttrIdentifiers.h"
@class LookinAttribute;
typedef NS_ENUM (NSInteger, LookinAttributesSectionStyle) {
LookinAttributesSectionStyleDefault, // 每个 attr 独占一行
LookinAttributesSectionStyle0, // frame 等卡片使用,前 4 个 attr 每行两个,之后每个 attr 在同一排,每个宽度为 1/4
LookinAttributesSectionStyle1, // 第一个 attr 在第一排靠左,第二个 attr 在第一排靠右,之后的 attr 每个独占一行
LookinAttributesSectionStyle2 // 第一排独占一行,剩下的在同一行且均分宽度
};
@interface LookinAttributesSection : NSObject <NSSecureCoding, NSCopying>
@property(nonatomic, copy) LookinAttrSectionIdentifier identifier;
@property(nonatomic, copy) NSArray<LookinAttribute *> *attributes;
- (BOOL)isUserCustom;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,56 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttributesSection.m
// Lookin
//
// Created by Li Kai on 2019/3/2.
// https://lookin.work
//
#import "LookinAttributesSection.h"
#import "LookinAttribute.h"
#import "NSArray+Lookin.h"
@implementation LookinAttributesSection
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinAttributesSection *newSection = [[LookinAttributesSection allocWithZone:zone] init];
newSection.identifier = self.identifier;
newSection.attributes = [self.attributes lookin_map:^id(NSUInteger idx, LookinAttribute *value) {
return value.copy;
}];
return newSection;
}
#pragma mark - <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.identifier forKey:@"identifier"];
[aCoder encodeObject:self.attributes forKey:@"attributes"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.identifier = [aDecoder decodeObjectForKey:@"identifier"];
self.attributes = [aDecoder decodeObjectForKey:@"attributes"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (BOOL)isUserCustom {
return [self.identifier isEqualToString:LookinAttrSec_UserCustom];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,51 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAutoLayoutConstraint.h
// Lookin
//
// Created by Li Kai on 2019/9/28.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinObject;
typedef NS_ENUM(NSInteger, LookinConstraintItemType) {
LookinConstraintItemTypeUnknown,
LookinConstraintItemTypeNil,
LookinConstraintItemTypeView,
LookinConstraintItemTypeSelf,
LookinConstraintItemTypeSuper,
LookinConstraintItemTypeLayoutGuide
};
@interface LookinAutoLayoutConstraint : NSObject <NSSecureCoding>
#if TARGET_OS_IPHONE
+ (instancetype)instanceFromNSConstraint:(NSLayoutConstraint *)constraint isEffective:(BOOL)isEffective firstItemType:(LookinConstraintItemType)firstItemType secondItemType:(LookinConstraintItemType)secondItemType;
#endif
@property(nonatomic, assign) BOOL effective;
@property(nonatomic, assign) BOOL active;
@property(nonatomic, assign) BOOL shouldBeArchived;
@property(nonatomic, strong) LookinObject *firstItem;
@property(nonatomic, assign) LookinConstraintItemType firstItemType;
/// iOS 里的 NSLayoutAttribute注意 iOS 和 macOS 虽然都有 NSLayoutAttribute 但是 value 非常不同,因此这里使用 NSInteger 避免混淆
@property(nonatomic, assign) NSInteger firstAttribute;
@property(nonatomic, assign) NSLayoutRelation relation;
@property(nonatomic, strong) LookinObject *secondItem;
@property(nonatomic, assign) LookinConstraintItemType secondItemType;
/// iOS 里的 NSLayoutAttribute注意 iOS 和 macOS 虽然都有 NSLayoutAttribute 但是 value 非常不同,因此这里使用 NSInteger 避免混淆
@property(nonatomic, assign) NSInteger secondAttribute;
@property(nonatomic, assign) CGFloat multiplier;
@property(nonatomic, assign) CGFloat constant;
@property(nonatomic, assign) CGFloat priority;
@property(nonatomic, copy) NSString *identifier;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,107 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAutoLayoutConstraint.m
// Lookin
//
// Created by Li Kai on 2019/9/28.
// https://lookin.work
//
#import "LookinAutoLayoutConstraint.h"
#import "LookinObject.h"
@implementation LookinAutoLayoutConstraint
#if TARGET_OS_IPHONE
+ (instancetype)instanceFromNSConstraint:(NSLayoutConstraint *)constraint isEffective:(BOOL)isEffective firstItemType:(LookinConstraintItemType)firstItemType secondItemType:(LookinConstraintItemType)secondItemType {
LookinAutoLayoutConstraint *instance = [LookinAutoLayoutConstraint new];
instance.effective = isEffective;
instance.active = constraint.active;
instance.shouldBeArchived = constraint.shouldBeArchived;
instance.firstItem = [LookinObject instanceWithObject:constraint.firstItem];
instance.firstItemType = firstItemType;
instance.firstAttribute = constraint.firstAttribute;
instance.relation = constraint.relation;
instance.secondItem = [LookinObject instanceWithObject:constraint.secondItem];
instance.secondItemType = secondItemType;
instance.secondAttribute = constraint.secondAttribute;
instance.multiplier = constraint.multiplier;
instance.constant = constraint.constant;
instance.priority = constraint.priority;
instance.identifier = constraint.identifier;
return instance;
}
- (void)setFirstAttribute:(NSInteger)firstAttribute {
_firstAttribute = firstAttribute;
[self _assertUnknownAttribute:firstAttribute];
}
- (void)setSecondAttribute:(NSInteger)secondAttribute {
_secondAttribute = secondAttribute;
[self _assertUnknownAttribute:secondAttribute];
}
- (void)_assertUnknownAttribute:(NSInteger)attribute {
// assert assert
if (attribute > 20 && attribute < 32) {
NSAssert(NO, nil);
}
if (attribute > 37) {
NSAssert(NO, nil);
}
}
#endif
#pragma mark - <NSSecureCoding>
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeBool:self.effective forKey:@"effective"];
[aCoder encodeBool:self.active forKey:@"active"];
[aCoder encodeBool:self.shouldBeArchived forKey:@"shouldBeArchived"];
[aCoder encodeObject:self.firstItem forKey:@"firstItem"];
[aCoder encodeInteger:self.firstItemType forKey:@"firstItemType"];
[aCoder encodeInteger:self.firstAttribute forKey:@"firstAttribute"];
[aCoder encodeInteger:self.relation forKey:@"relation"];
[aCoder encodeObject:self.secondItem forKey:@"secondItem"];
[aCoder encodeInteger:self.secondItemType forKey:@"secondItemType"];
[aCoder encodeInteger:self.secondAttribute forKey:@"secondAttribute"];
[aCoder encodeDouble:self.multiplier forKey:@"multiplier"];
[aCoder encodeDouble:self.constant forKey:@"constant"];
[aCoder encodeDouble:self.priority forKey:@"priority"];
[aCoder encodeObject:self.identifier forKey:@"identifier"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.effective = [aDecoder decodeBoolForKey:@"effective"];
self.active = [aDecoder decodeBoolForKey:@"active"];
self.shouldBeArchived = [aDecoder decodeBoolForKey:@"shouldBeArchived"];
self.firstItem = [aDecoder decodeObjectForKey:@"firstItem"];
self.firstItemType = [aDecoder decodeIntegerForKey:@"firstItemType"];
self.firstAttribute = [aDecoder decodeIntegerForKey:@"firstAttribute"];
self.relation = [aDecoder decodeIntegerForKey:@"relation"];
self.secondItem = [aDecoder decodeObjectForKey:@"secondItem"];
self.secondItemType = [aDecoder decodeIntegerForKey:@"secondItemType"];
self.secondAttribute = [aDecoder decodeIntegerForKey:@"secondAttribute"];
self.multiplier = [aDecoder decodeDoubleForKey:@"multiplier"];
self.constant = [aDecoder decodeDoubleForKey:@"constant"];
self.priority = [aDecoder decodeDoubleForKey:@"priority"];
self.identifier = [aDecoder decodeObjectForKey:@"identifier"];
}
return self;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,30 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinCodingValueType.h
// Lookin
//
// Created by Li Kai on 2019/2/13.
// https://lookin.work
//
typedef NS_ENUM(NSInteger, LookinCodingValueType) {
LookinCodingValueTypeUnknown,
LookinCodingValueTypeChar,
LookinCodingValueTypeDouble,
LookinCodingValueTypeFloat,
LookinCodingValueTypeLongLong,
// LookinCodingValueTypePoint,
// LookinCodingValueTypeString,
// LookinCodingValueTypeStringArray,
// LookinCodingValueTypeEdgeInsets,
// LookinCodingValueTypeRect,
LookinCodingValueTypeBOOL,
// LookinCodingValueTypeSize,
LookinCodingValueTypeColor,
LookinCodingValueTypeEnum,
// LookinCodingValueTypeRange,
LookinCodingValueTypeImage
};
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,24 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinConnectionAttachment.h
// Lookin
//
// Created by Li Kai on 2019/2/15.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#import "LookinCodingValueType.h"
@interface LookinConnectionAttachment : NSObject <NSSecureCoding>
@property(nonatomic, assign) LookinCodingValueType dataType;
@property(nonatomic, strong) id data;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,52 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinConnectionAttachment.m
// Lookin
//
// Created by Li Kai on 2019/2/15.
// https://lookin.work
//
#import "LookinConnectionAttachment.h"
#import "LookinDefines.h"
#import "NSObject+Lookin.h"
static NSString * const Key_Data = @"0";
static NSString * const Key_DataType = @"1";
@interface LookinConnectionAttachment ()
@end
@implementation LookinConnectionAttachment
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:[self.data lookin_encodedObjectWithType:self.dataType] forKey:Key_Data];
[aCoder encodeInteger:self.dataType forKey:Key_DataType];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.dataType = [aDecoder decodeIntegerForKey:Key_DataType];
self.data = [[aDecoder decodeObjectForKey:Key_Data] lookin_decodedObjectWithType:self.dataType];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

Some files were not shown because too many files have changed in this diff Show More