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

View File

@@ -0,0 +1,36 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinConnectionResponse.h
// Lookin
//
// Created by Li Kai on 2019/1/15.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#import "LookinConnectionAttachment.h"
@interface LookinConnectionResponseAttachment : LookinConnectionAttachment
+ (instancetype)attachmentWithError:(NSError *)error;
@property(nonatomic, assign) int lookinServerVersion;
@property(nonatomic, strong) NSError *error;
/// 如果为 YES则表示 app 正处于后台模式,默认为 NO
@property(nonatomic, assign) BOOL appIsInBackground;
/**
dataTotalCount 为 0 时表示仅有这一个 response默认为 0
dataTotalCount 大于 0 时表示可能有多个 response当所有 response 的 currentDataCount 的总和大于 dataTotalCount 即表示所有 response 已接收完毕
*/
@property(nonatomic, assign) NSUInteger dataTotalCount;
@property(nonatomic, assign) NSUInteger currentDataCount;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,62 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinConnectionResponse.m
// Lookin
//
// Created by Li Kai on 2019/1/15.
// https://lookin.work
//
#import "LookinConnectionResponseAttachment.h"
#import "LookinDefines.h"
@interface LookinConnectionResponseAttachment ()
@end
@implementation LookinConnectionResponseAttachment
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
[aCoder encodeInt:self.lookinServerVersion forKey:@"lookinServerVersion"];
[aCoder encodeObject:self.error forKey:@"error"];
[aCoder encodeObject:@(self.dataTotalCount) forKey:@"dataTotalCount"];
[aCoder encodeObject:@(self.currentDataCount) forKey:@"currentDataCount"];
[aCoder encodeBool:self.appIsInBackground forKey:@"appIsInBackground"];
}
- (instancetype)init {
if (self = [super init]) {
self.lookinServerVersion = LOOKIN_SERVER_VERSION;
self.dataTotalCount = 0;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
self.lookinServerVersion = [aDecoder decodeIntForKey:@"lookinServerVersion"];
self.error = [aDecoder decodeObjectForKey:@"error"];
self.dataTotalCount = [[aDecoder decodeObjectForKey:@"dataTotalCount"] unsignedIntegerValue];
self.currentDataCount = [[aDecoder decodeObjectForKey:@"currentDataCount"] unsignedIntegerValue];
self.appIsInBackground = [aDecoder decodeBoolForKey:@"appIsInBackground"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
+ (instancetype)attachmentWithError:(NSError *)error {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
attachment.error = error;
return attachment;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,20 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinCustomAttrModification.h
// LookinShared
//
// Created by likaimacbookhome on 2023/11/4.
//
#import <Foundation/Foundation.h>
#import "LookinAttrType.h"
@interface LookinCustomAttrModification : NSObject <NSSecureCoding>
@property(nonatomic, assign) LookinAttrType attrType;
@property(nonatomic, copy) NSString *customSetterID;
@property(nonatomic, strong) id value;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,34 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinCustomAttrModification.m
// LookinShared
//
// Created by likaimacbookhome on 2023/11/4.
//
#import "LookinCustomAttrModification.h"
@implementation LookinCustomAttrModification
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInteger:self.attrType forKey:@"attrType"];
[aCoder encodeObject:self.value forKey:@"value"];
[aCoder encodeObject:self.customSetterID forKey:@"customSetterID"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.attrType = [aDecoder decodeIntegerForKey:@"attrType"];
self.value = [aDecoder decodeObjectForKey:@"value"];
self.customSetterID = [aDecoder decodeObjectForKey:@"customSetterID"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinCustomDisplayItemInfo.h
// LookinServer
//
// Created by likai.123 on 2023/11/1.
//
#import <Foundation/Foundation.h>
@interface LookinCustomDisplayItemInfo : NSObject <NSSecureCoding, NSCopying>
/// 该属性可能有值CGRect也可能是 nilnil 时则表示无图像)
@property(nonatomic, strong) NSValue *frameInWindow;
@property(nonatomic, copy) NSString *title;
@property(nonatomic, copy) NSString *subtitle;
@property(nonatomic, copy) NSString *danceuiSource;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,59 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinCustomDisplayItemInfo.m
// LookinServer
//
// Created by likai.123 on 2023/11/1.
//
#import "LookinCustomDisplayItemInfo.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
@implementation LookinCustomDisplayItemInfo
- (id)copyWithZone:(NSZone *)zone {
LookinCustomDisplayItemInfo *newInstance = [[LookinCustomDisplayItemInfo allocWithZone:zone] init];
if (self.frameInWindow) {
#if TARGET_OS_IPHONE
CGRect rect = [self.frameInWindow CGRectValue];
newInstance.frameInWindow = [NSValue valueWithCGRect:rect];
#elif TARGET_OS_MAC
CGRect rect = [self.frameInWindow rectValue];
newInstance.frameInWindow = [NSValue valueWithRect:rect];
#endif
}
newInstance.title = self.title;
newInstance.subtitle = self.subtitle;
newInstance.danceuiSource = self.danceuiSource;
return newInstance;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.frameInWindow forKey:@"frameInWindow"];
[aCoder encodeObject:self.title forKey:@"title"];
[aCoder encodeObject:self.subtitle forKey:@"subtitle"];
[aCoder encodeObject:self.danceuiSource forKey:@"danceuiSource"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.frameInWindow = [aDecoder decodeObjectForKey:@"frameInWindow"];
self.title = [aDecoder decodeObjectForKey:@"title"];
self.subtitle = [aDecoder decodeObjectForKey:@"subtitle"];
self.danceuiSource = [aDecoder decodeObjectForKey:@"danceuiSource"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,77 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinDashboardBlueprint.h
// Lookin
//
// Created by Li Kai on 2019/6/5.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#import "LookinAttrIdentifiers.h"
#import "LookinAttrType.h"
/**
该对象定义了:
- 每一个 Attr 的信息
- 哪些 GroupID, SectionID, AttrID 是合法的
- 这些 ID 的父子顺序,比如 LookinAttrGroup_Frame 包含哪些 Section
- 这些 ID 展示顺序(比如哪个 Group 在前、哪个 Group 在后)
*/
@interface LookinDashboardBlueprint : NSObject
+ (NSArray<LookinAttrGroupIdentifier> *)groupIDs;
+ (NSArray<LookinAttrSectionIdentifier> *)sectionIDsForGroupID:(LookinAttrGroupIdentifier)groupID;
+ (NSArray<LookinAttrIdentifier> *)attrIDsForSectionID:(LookinAttrSectionIdentifier)sectionID;
/// 返回包含目标 attr 的 groupID 和 sectionID
+ (void)getHostGroupID:(inout LookinAttrGroupIdentifier *)groupID sectionID:(inout LookinAttrSectionIdentifier *)sectionID fromAttrID:(LookinAttrIdentifier)attrID;
/// 返回某个 group 的标题
+ (NSString *)groupTitleWithGroupID:(LookinAttrGroupIdentifier)groupID;
/// 返回某个 section 的标题nil 则表示不显示标题
+ (NSString *)sectionTitleWithSectionID:(LookinAttrSectionIdentifier)secID;
/// 当某个 LookinAttribute 确定是 NSObject 类型时,该方法返回它具体是什么对象,比如 UIColor 等
+ (LookinAttrType)objectAttrTypeWithAttrID:(LookinAttrIdentifier)attrID;
/// 返回某个 LookinAttribute 代表的属性是哪一个类拥有的,比如 LookinAttrSec_UILabel_TextColor 是 UILabel 才有的
+ (NSString *)classNameWithAttrID:(LookinAttrIdentifier)attrID;
/// 一个 attr 要么属于 UIView 要么属于 CALayer如果它属于 UIView 那么该方法返回 YES
+ (BOOL)isUIViewPropertyWithAttrID:(LookinAttrIdentifier)attrID;
/// 如果某个 attribute 是 enum则这里会返回相应的 enum 的名称(如 @"NSTextAlignment"),进而可通过这个名称查询可用的枚举值列表
+ (NSString *)enumListNameWithAttrID:(LookinAttrIdentifier)attrID;
/// 如果返回 YES则说明用户在 Lookin 里修改了该 Attribute 的值后,应该重新拉取和更新相关图层的位置、截图等信息
+ (BOOL)needPatchAfterModificationWithAttrID:(LookinAttrIdentifier)attrID;
/// 完整的名字
+ (NSString *)fullTitleWithAttrID:(LookinAttrIdentifier)attrID;
/// 在某些 textField 和 checkbox 里会显示这里返回的 title
+ (NSString *)briefTitleWithAttrID:(LookinAttrIdentifier)attrID;
/// 获取 getter 方法
+ (SEL)getterWithAttrID:(LookinAttrIdentifier)attrID;
/// 获取 setter 方法
+ (SEL)setterWithAttrID:(LookinAttrIdentifier)attrID;
/// 获取 “hideIfNil” 的值。如果为 YES则当读取 getter 获取的 value 为 nil 时Lookin 不会传输该 attr
/// 如果为 NO则即使 value 为 nil 也会传输(比如 label 的 text 属性,即使它是 nil 我们也要显示,所以它的 hideIfNil 应该为 NO
+ (BOOL)hideIfNilWithAttrID:(LookinAttrIdentifier)attrID;
/// 该属性需要的最低的 iOS 版本,比如 safeAreaInsets 从 iOS 11.0 开始出现,则该方法返回 11如果返回 0 则表示不限制 iOS 版本(注意 Lookin 项目仅支持 iOS 8.0+
+ (NSInteger)minAvailableOSVersionWithAttrID:(LookinAttrIdentifier)attrID;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,172 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinMessageProtocol.h
// Lookin
//
// Created by Li Kai on 2018/8/6.
// https://lookin.work
//
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <Appkit/Appkit.h>
#endif
#include <stdint.h>
#pragma mark - Version
/// current connection protocol version of LookinServer
static const int LOOKIN_SERVER_VERSION = 7;
/// current release version of LookinServer
static NSString * const LOOKIN_SERVER_READABLE_VERSION = @"1.2.8";
/// current connection protocol version of LookinClient
static const int LOOKIN_CLIENT_VERSION = 7;
/// the minimum connection protocol version supported by current LookinClient
static const int LOOKIN_SUPPORTED_SERVER_MIN = 7;
/// the maximum connection protocol version supported by current LookinClient
static const int LOOKIN_SUPPORTED_SERVER_MAX = 7;
#pragma mark - Connection
/// LookinServer 在真机上会依次尝试监听 47175 ~ 47179 这几个端口
static const int LookinUSBDeviceIPv4PortNumberStart = 47175;
static const int LookinUSBDeviceIPv4PortNumberEnd = 47179;
/// LookinServer 在模拟器中会依次尝试监听 47164 ~ 47169 这几个端口
static const int LookinSimulatorIPv4PortNumberStart = 47164;
static const int LookinSimulatorIPv4PortNumberEnd = 47169;
enum {
/// 确认两端是否可以响应通讯
LookinRequestTypePing = 200,
/// 请求 App 的截图、设备型号等信息
LookinRequestTypeApp = 201,
/// 请求 Hierarchy 信息
LookinRequestTypeHierarchy = 202,
/// 请求 screenshots 和 attrGroups 信息
LookinRequestTypeHierarchyDetails = 203,
/// 请求修改某个内置的 Attribute 的值
LookinRequestTypeInbuiltAttrModification = 204,
/// 修改某个 attr 后,请求一系列最新的 Screenshots、属性值等信息
LookinRequestTypeAttrModificationPatch = 205,
/// 执行某个方法
LookinRequestTypeInvokeMethod = 206,
/**
@request: @{@"oid":}
@response: LookinObject *
*/
LookinRequestTypeFetchObject = 207,
LookinRequestTypeFetchImageViewImage = 208,
LookinRequestTypeModifyRecognizerEnable = 209,
/// 请求 attribute group list
LookinRequestTypeAllAttrGroups = 210,
/// 请求 iOS App 里某个 class 的所有 selector 名字列表(包括 superclass
LookinRequestTypeAllSelectorNames = 213,
/// 请求修改某个自定义 Attribute 的值
LookinRequestTypeCustomAttrModification = 214,
/// 从 LookinServer 1.2.7 & Lookin 1.0.7 开始,该属性被废弃、不再使用
LookinPush_BringForwardScreenshotTask = 303,
// 用户在 Lookin 客户端取消了之前 HierarchyDetails 的拉取
LookinPush_CanceHierarchyDetails = 304,
};
static NSString * const LookinParam_ViewLayerTag = @"tag";
static NSString * const LookinParam_SelectorName = @"sn";
static NSString * const LookinParam_MethodType = @"mt";
static NSString * const LookinParam_SelectorClassName = @"scn";
static NSString * const LookinStringFlag_VoidReturn = @"LOOKIN_TAG_RETURN_VALUE_VOID";
#pragma mark - Error
static NSString * const LookinErrorDomain = @"LookinError";
enum {
LookinErrCode_Default = -400,
/// Lookin 内部业务逻辑错误
LookinErrCode_Inner = -401,
/// PeerTalk 内部错误
LookinErrCode_PeerTalk = -402,
/// 连接不存在或已断开
LookinErrCode_NoConnect = -403,
/// ping 失败了,原因是 ping 请求超时
LookinErrCode_PingFailForTimeout = -404,
/// 请求超时未返回
LookinErrCode_Timeout = -405,
/// 有相同 Type 的新请求被发出,因此旧请求被丢弃
LookinErrCode_Discard = -406,
/// ping 失败了,原因是 app 主动报告自身正处于后台模式
LookinErrCode_PingFailForBackgroundState = -407,
/// 没有找到对应的对象,可能已被释放
LookinErrCode_ObjectNotFound = -500,
/// 不支持修改当前类型的 LookinCodingValueType
LookinErrCode_ModifyValueTypeInvalid = -501,
LookinErrCode_Exception = -502,
// LookinServer 版本过高,要升级 client
LookinErrCode_ServerVersionTooHigh = -600,
// LookinServer 版本过低,要升级 server
LookinErrCode_ServerVersionTooLow = -601,
// 不支持的文件类型
LookinErrCode_UnsupportedFileType = -700,
};
#define LookinErr_ObjNotFound [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_ObjectNotFound userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"Failed to get target object in iOS app", nil), NSLocalizedRecoverySuggestionErrorKey:NSLocalizedString(@"Perhaps the related object was deallocated. You can reload Lookin to get newest data.", nil)}]
#define LookinErr_NoConnect [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_NoConnect userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"The operation failed due to disconnection with the iOS app.", nil)}]
#define LookinErr_Inner [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Inner userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"The operation failed due to an inner error.", nil)}]
#define LookinErrorMake(errorTitle, errorDetail) [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Default userInfo:@{NSLocalizedDescriptionKey:errorTitle, NSLocalizedRecoverySuggestionErrorKey:errorDetail}]
#define LookinErrorText_Timeout NSLocalizedString(@"Perhaps your iOS app is paused with breakpoint in Xcode, blocked by other tasks in main thread, or moved to background state.", nil)
#pragma mark - Colors
#if TARGET_OS_IPHONE
#define LookinColor UIColor
#define LookinInsets UIEdgeInsets
#define LookinImage UIImage
#elif TARGET_OS_MAC
#define LookinColor NSColor
#define LookinInsets NSEdgeInsets
#define LookinImage NSImage
#endif
#define LookinColorRGBAMake(r, g, b, a) [LookinColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a]
#define LookinColorMake(r, g, b) [LookinColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]
#pragma mark - Preview
/// SCNNode 所允许的图片的最大的长和宽,单位是 px这个值是 Scenekit 自身指定的
/// Max pixel size of a SCNNode object. It is designated by SceneKit.
static const double LookinNodeImageMaxLengthInPx = 16384;
typedef NS_OPTIONS(NSUInteger, LookinPreviewBitMask) {
LookinPreviewBitMask_None = 0,
LookinPreviewBitMask_Selectable = 1 << 1,
LookinPreviewBitMask_Unselectable = 1 << 2,
LookinPreviewBitMask_HasLight = 1 << 3,
LookinPreviewBitMask_NoLight = 1 << 4
};
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,186 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinDisplayItem.h
// qmuidemo
//
// Created by Li Kai on 2018/11/15.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "TargetConditionals.h"
#import "LookinObject.h"
#import "LookinDefines.h"
#import "LookinCustomDisplayItemInfo.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <Appkit/Appkit.h>
#endif
@class LookinAttributesGroup, LookinIvarTrace, LookinPreviewItemLayer, LookinEventHandler, LKDisplayItemNode, LookinDisplayItem;
typedef NS_ENUM(NSUInteger, LookinDisplayItemImageEncodeType) {
LookinDisplayItemImageEncodeTypeNone, // 不进行 encode
LookinDisplayItemImageEncodeTypeNSData, // 转换为 NSData
LookinDisplayItemImageEncodeTypeImage // 使用 NSImage / UIImage 自身的 encode 方法
};
typedef NS_ENUM(NSUInteger, LookinDoNotFetchScreenshotReason) {
// can sync screenshot
LookinFetchScreenshotPermitted,
// layer is too large
LookinDoNotFetchScreenshotForTooLarge,
// refused by user config in LookinServer
LookinDoNotFetchScreenshotForUserConfig
};
typedef NS_ENUM(NSUInteger, LookinDisplayItemProperty) {
// 当初次设置 delegate 对象时,会立即以该值触发一次 displayItem:propertyDidChange:
LookinDisplayItemProperty_None,
LookinDisplayItemProperty_FrameToRoot,
LookinDisplayItemProperty_DisplayingInHierarchy,
LookinDisplayItemProperty_InHiddenHierarchy,
LookinDisplayItemProperty_IsExpandable,
LookinDisplayItemProperty_IsExpanded,
LookinDisplayItemProperty_SoloScreenshot,
LookinDisplayItemProperty_GroupScreenshot,
LookinDisplayItemProperty_IsSelected,
LookinDisplayItemProperty_IsHovered,
LookinDisplayItemProperty_AvoidSyncScreenshot,
LookinDisplayItemProperty_InNoPreviewHierarchy,
LookinDisplayItemProperty_IsInSearch,
LookinDisplayItemProperty_HighlightedSearchString,
};
@protocol LookinDisplayItemDelegate <NSObject>
- (void)displayItem:(LookinDisplayItem *)displayItem propertyDidChange:(LookinDisplayItemProperty)property;
@end
@interface LookinDisplayItem : NSObject <NSSecureCoding, NSCopying>
/// 当 customInfo 不为 nil 时,意思是该 DisplayItem 为 UserCustom 配置的。此时Encode 属性中仅 subitems 和 customAttrGroupList 属性有意义,其它几乎所有属性都无意义
@property(nonatomic, strong) LookinCustomDisplayItemInfo *customInfo;
@property(nonatomic, copy) NSArray<LookinDisplayItem *> *subitems;
@property(nonatomic, assign) BOOL isHidden;
@property(nonatomic, assign) float alpha;
@property(nonatomic, assign) CGRect frame;
@property(nonatomic, assign) CGRect bounds;
/// 不存在 subitems 时,该属性的值为 nil
@property(nonatomic, strong) LookinImage *soloScreenshot;
/// 无论是否存在 subitems该属性始终存在
@property(nonatomic, strong) LookinImage *groupScreenshot;
@property(nonatomic, strong) LookinObject *viewObject;
@property(nonatomic, strong) LookinObject *layerObject;
@property(nonatomic, strong) LookinObject *hostViewControllerObject;
/// attrGroups 列表
@property(nonatomic, copy) NSArray<LookinAttributesGroup *> *attributesGroupList;
/// 通过 lookin_customDebugInfos 返回的属性列表
@property(nonatomic, copy) NSArray<LookinAttributesGroup *> *customAttrGroupList;
/// attributesGroupList + customAttrGroupList
- (NSArray<LookinAttributesGroup *> *)queryAllAttrGroupList;
@property(nonatomic, copy) NSArray<LookinEventHandler *> *eventHandlers;
// 如果当前 item 代表 UIWindow 且是 keyWindow则该属性为 YES
@property(nonatomic, assign) BOOL representedAsKeyWindow;
/// view 或 layer 的 backgroundColor利用该属性来提前渲染 node 的背景色,使得用户感觉加载的快一点
/// 注意有一个缺点是,理论上应该像 screenshot 一样拆成 soloBackgroundColor 和 groupBackgroundColor这里的 backgroundColor 实际上是 soloBackgroundColor因此某些场景的显示会有瑕疵
@property(nonatomic, strong) LookinColor *backgroundColor;
/// 用户可以在 iOS 项目中添加 Lookin 自定义配置来显式地拒绝传输某些图层的图像,通常是屏蔽一些不重要的 View 以提升刷新速度。如果用户这么配置了,那么这个 shouldCaptureImage 就会被置为 NO
/// 默认为 YES
@property(nonatomic, assign) BOOL shouldCaptureImage;
/// 用户通过重写 lookin_customDebugInfos 而自定义的该实例的名字
/// 可能为 nil
@property(nonatomic, copy) NSString *customDisplayTitle;
/// 为 DanceUI SDK 预留的内部字段,用于文件跳转
/// 可能为 nil
@property(nonatomic, copy) NSString *danceuiSource;
#pragma mark - No Encode/Decode
@property(nonatomic, weak) id<LookinDisplayItemDelegate> previewItemDelegate;
@property(nonatomic, weak) id<LookinDisplayItemDelegate> rowViewDelegate;
/// 父节点
@property(nonatomic, weak) LookinDisplayItem *superItem;
/// 如果存在 viewObject 则返回 viewObject否则返回 layerObject
- (LookinObject *)displayingObject;
/// 在 hierarchy 中的层级,比如顶层的 UIWindow.indentLevel 为 0UIWindow 的 subitem 的 indentLevel 为 1
- (NSInteger)indentLevel;
/**
该项是否被展开
@note 假如自己没有被折叠,但是 superItem 被折叠了,则自己仍然不会被看到,但是 self.isExpanded 值仍然为 NO
@note 如果 item 没有 subitems也就是 isExpandable 为 NO则该值没有意义。换句话说在获取该值之前必须先判断一下 isExpandable
*/
@property(nonatomic, assign) BOOL isExpanded;
/// 如果有 subitems则该属性返回 YES否则返回 NO
@property(nonatomic, assign, readonly) BOOL isExpandable;
/**
是否能在 hierarchy panel 上被看到,假如有任意一层的父级元素的 isExpanded 为 NO则 displayingInHierarchy 为 NO。如果所有父级元素的 isExpanded 均为 YES则 displayingInHierarchy 为 YES
*/
@property(nonatomic, assign, readonly) BOOL displayingInHierarchy;
/**
如果自身或任意一个上层元素的 isHidden 为 YES 或 alpha 为 0则该属性返回 YES
*/
@property(nonatomic, assign, readonly) BOOL inHiddenHierarchy;
@property(nonatomic, assign) LookinDisplayItemImageEncodeType screenshotEncodeType;
/// Whether to fetch screenshot and why. Default to LookinFetchScreenshotPermitted.
@property(nonatomic, assign) LookinDoNotFetchScreenshotReason doNotFetchScreenshotReason;
@property(nonatomic, weak) LookinPreviewItemLayer *previewLayer;
@property(nonatomic, weak) LKDisplayItemNode *previewNode;
/// 如果该值为 YES则该 item 及所有子 item 均不会在 preview 中被显示出来,只能在 hierarchy 中选择。默认为 NO
@property(nonatomic, assign) BOOL noPreview;
/// 如果自身或某个上级元素的 noPreview 值为 YES则该方法返回 YES
/// 注意:当 userCustom 为 YES 时,该属性也可能返回 YES
@property(nonatomic, assign, readonly) BOOL inNoPreviewHierarchy;
/// 当小于 0 时表示未被设置
@property(nonatomic, assign) NSInteger previewZIndex;
@property(nonatomic, assign) BOOL preferToBeCollapsed;
- (void)notifySelectionChangeToDelegates;
- (void)notifyHoverChangeToDelegates;
/// 根据 subItems 属性将 items 打平为一维数组
+ (NSArray<LookinDisplayItem *> *)flatItemsFromHierarchicalItems:(NSArray<LookinDisplayItem *> *)items;
@property(nonatomic, assign) BOOL hasDeterminedExpansion;
/// 设置当前是否处于搜索状态
@property(nonatomic, assign) BOOL isInSearch;
/// 因为搜索而应该被高亮的字符串
@property(nonatomic, copy) NSString *highlightedSearchString;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,450 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinDisplayItem.m
// qmuidemo
//
// Created by Li Kai on 2018/11/15.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "LookinDisplayItem.h"
#import "LookinAttributesGroup.h"
#import "LookinAttributesSection.h"
#import "LookinAttribute.h"
#import "LookinEventHandler.h"
#import "LookinIvarTrace.h"
#import "Color+Lookin.h"
#import "NSArray+Lookin.h"
#import "NSObject+Lookin.h"
#import "LookinDashboardBlueprint.h"
#if TARGET_OS_IPHONE
#import "UIColor+LookinServer.h"
#import "UIImage+LookinServer.h"
#elif TARGET_OS_MAC
#endif
@interface LookinDisplayItem ()
@property(nonatomic, assign, readwrite) CGRect frameToRoot;
@property(nonatomic, assign, readwrite) BOOL inNoPreviewHierarchy;
@property(nonatomic, assign) NSInteger indentLevel;
@property(nonatomic, assign, readwrite) BOOL isExpandable;
@property(nonatomic, assign, readwrite) BOOL inHiddenHierarchy;
@property(nonatomic, assign, readwrite) BOOL displayingInHierarchy;
@end
@implementation LookinDisplayItem
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinDisplayItem *newDisplayItem = [[LookinDisplayItem allocWithZone:zone] init];
newDisplayItem.subitems = [self.subitems lookin_map:^id(NSUInteger idx, LookinDisplayItem *value) {
return value.copy;
}];
newDisplayItem.customInfo = self.customInfo.copy;
newDisplayItem.isHidden = self.isHidden;
newDisplayItem.alpha = self.alpha;
newDisplayItem.frame = self.frame;
newDisplayItem.bounds = self.bounds;
newDisplayItem.soloScreenshot = self.soloScreenshot;
newDisplayItem.groupScreenshot = self.groupScreenshot;
newDisplayItem.viewObject = self.viewObject.copy;
newDisplayItem.layerObject = self.layerObject.copy;
newDisplayItem.hostViewControllerObject = self.hostViewControllerObject.copy;
newDisplayItem.attributesGroupList = [self.attributesGroupList lookin_map:^id(NSUInteger idx, LookinAttributesGroup *value) {
return value.copy;
}];
newDisplayItem.customAttrGroupList = [self.customAttrGroupList lookin_map:^id(NSUInteger idx, LookinAttributesGroup *value) {
return value.copy;
}];
newDisplayItem.eventHandlers = [self.eventHandlers lookin_map:^id(NSUInteger idx, LookinEventHandler *value) {
return value.copy;
}];
newDisplayItem.shouldCaptureImage = self.shouldCaptureImage;
newDisplayItem.representedAsKeyWindow = self.representedAsKeyWindow;
newDisplayItem.customDisplayTitle = self.customDisplayTitle;
newDisplayItem.danceuiSource = self.danceuiSource;
[newDisplayItem _updateDisplayingInHierarchyProperty];
return newDisplayItem;
}
#pragma mark - <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.customInfo forKey:@"customInfo"];
[aCoder encodeObject:self.subitems forKey:@"subitems"];
[aCoder encodeBool:self.isHidden forKey:@"hidden"];
[aCoder encodeFloat:self.alpha forKey:@"alpha"];
[aCoder encodeObject:self.viewObject forKey:@"viewObject"];
[aCoder encodeObject:self.layerObject forKey:@"layerObject"];
[aCoder encodeObject:self.hostViewControllerObject forKey:@"hostViewControllerObject"];
[aCoder encodeObject:self.attributesGroupList forKey:@"attributesGroupList"];
[aCoder encodeObject:self.customAttrGroupList forKey:@"customAttrGroupList"];
[aCoder encodeBool:self.representedAsKeyWindow forKey:@"representedAsKeyWindow"];
[aCoder encodeObject:self.eventHandlers forKey:@"eventHandlers"];
[aCoder encodeBool:self.shouldCaptureImage forKey:@"shouldCaptureImage"];
if (self.screenshotEncodeType == LookinDisplayItemImageEncodeTypeNSData) {
[aCoder encodeObject:[self.soloScreenshot lookin_encodedObjectWithType:LookinCodingValueTypeImage] forKey:@"soloScreenshot"];
[aCoder encodeObject:[self.groupScreenshot lookin_encodedObjectWithType:LookinCodingValueTypeImage] forKey:@"groupScreenshot"];
} else if (self.screenshotEncodeType == LookinDisplayItemImageEncodeTypeImage) {
[aCoder encodeObject:self.soloScreenshot forKey:@"soloScreenshot"];
[aCoder encodeObject:self.groupScreenshot forKey:@"groupScreenshot"];
}
[aCoder encodeObject:self.customDisplayTitle forKey:@"customDisplayTitle"];
[aCoder encodeObject:self.danceuiSource forKey:@"danceuiSource"];
#if TARGET_OS_IPHONE
[aCoder encodeCGRect:self.frame forKey:@"frame"];
[aCoder encodeCGRect:self.bounds forKey:@"bounds"];
[aCoder encodeObject:self.backgroundColor.lks_rgbaComponents forKey:@"backgroundColor"];
#elif TARGET_OS_MAC
[aCoder encodeRect:self.frame forKey:@"frame"];
[aCoder encodeRect:self.bounds forKey:@"bounds"];
[aCoder encodeObject:self.backgroundColor.lookin_rgbaComponents forKey:@"backgroundColor"];
#endif
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.customInfo = [aDecoder decodeObjectForKey:@"customInfo"];
self.subitems = [aDecoder decodeObjectForKey:@"subitems"];
self.isHidden = [aDecoder decodeBoolForKey:@"hidden"];
self.alpha = [aDecoder decodeFloatForKey:@"alpha"];
self.viewObject = [aDecoder decodeObjectForKey:@"viewObject"];
self.layerObject = [aDecoder decodeObjectForKey:@"layerObject"];
self.hostViewControllerObject = [aDecoder decodeObjectForKey:@"hostViewControllerObject"];
self.attributesGroupList = [aDecoder decodeObjectForKey:@"attributesGroupList"];
self.customAttrGroupList = [aDecoder decodeObjectForKey:@"customAttrGroupList"];
self.representedAsKeyWindow = [aDecoder decodeBoolForKey:@"representedAsKeyWindow"];
id soloScreenshotObj = [aDecoder decodeObjectForKey:@"soloScreenshot"];
if (soloScreenshotObj) {
if ([soloScreenshotObj isKindOfClass:[NSData class]]) {
self.soloScreenshot = [soloScreenshotObj lookin_decodedObjectWithType:LookinCodingValueTypeImage];
} else if ([soloScreenshotObj isKindOfClass:[LookinImage class]]) {
self.soloScreenshot = soloScreenshotObj;
} else {
NSAssert(NO, @"");
}
}
id groupScreenshotObj = [aDecoder decodeObjectForKey:@"groupScreenshot"];
if (groupScreenshotObj) {
if ([groupScreenshotObj isKindOfClass:[NSData class]]) {
self.groupScreenshot = [groupScreenshotObj lookin_decodedObjectWithType:LookinCodingValueTypeImage];
} else if ([groupScreenshotObj isKindOfClass:[LookinImage class]]) {
self.groupScreenshot = groupScreenshotObj;
} else {
NSAssert(NO, @"");
}
}
self.eventHandlers = [aDecoder decodeObjectForKey:@"eventHandlers"];
/// this property was added in LookinServer 1.1.3
self.shouldCaptureImage = [aDecoder containsValueForKey:@"shouldCaptureImage"] ? [aDecoder decodeBoolForKey:@"shouldCaptureImage"] : YES;
self.customDisplayTitle = [aDecoder decodeObjectForKey:@"customDisplayTitle"];
self.danceuiSource = [aDecoder decodeObjectForKey:@"danceuiSource"];
#if TARGET_OS_IPHONE
self.frame = [aDecoder decodeCGRectForKey:@"frame"];
self.bounds = [aDecoder decodeCGRectForKey:@"bounds"];
self.backgroundColor = [UIColor lks_colorFromRGBAComponents:[aDecoder decodeObjectForKey:@"backgroundColor"]];
#elif TARGET_OS_MAC
self.frame = [aDecoder decodeRectForKey:@"frame"];
self.bounds = [aDecoder decodeRectForKey:@"bounds"];
self.backgroundColor = [NSColor lookin_colorFromRGBAComponents:[aDecoder decodeObjectForKey:@"backgroundColor"]];
#endif
[self _updateDisplayingInHierarchyProperty];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)init {
if (self = [super init]) {
/// displayItem
[self _updateDisplayingInHierarchyProperty];
}
return self;
}
- (LookinObject *)displayingObject {
return self.viewObject ? : self.layerObject;
}
- (void)setAttributesGroupList:(NSArray<LookinAttributesGroup *> *)attributesGroupList {
_attributesGroupList = attributesGroupList;
[_attributesGroupList enumerateObjectsUsingBlock:^(LookinAttributesGroup * _Nonnull group, NSUInteger idx, BOOL * _Nonnull stop) {
[group.attrSections enumerateObjectsUsingBlock:^(LookinAttributesSection * _Nonnull section, NSUInteger idx, BOOL * _Nonnull stop) {
[section.attributes enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
attr.targetDisplayItem = self;
}];
}];
}];
}
- (void)setCustomAttrGroupList:(NSArray<LookinAttributesGroup *> *)customAttrGroupList {
_customAttrGroupList = customAttrGroupList;
//
[customAttrGroupList enumerateObjectsUsingBlock:^(LookinAttributesGroup * _Nonnull group, NSUInteger idx, BOOL * _Nonnull stop) {
[group.attrSections enumerateObjectsUsingBlock:^(LookinAttributesSection * _Nonnull section, NSUInteger idx, BOOL * _Nonnull stop) {
[section.attributes enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
attr.targetDisplayItem = self;
}];
}];
}];
}
- (void)setSubitems:(NSArray<LookinDisplayItem *> *)subitems {
[_subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.superItem = nil;
}];
_subitems = subitems;
self.isExpandable = (subitems.count > 0);
[subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSAssert(!obj.superItem, @"");
obj.superItem = self;
[obj _updateInHiddenHierarchyProperty];
[obj _updateDisplayingInHierarchyProperty];
}];
}
- (void)setIsExpandable:(BOOL)isExpandable {
if (_isExpandable == isExpandable) {
return;
}
_isExpandable = isExpandable;
[self _notifyDelegatesWith:LookinDisplayItemProperty_IsExpandable];
}
- (void)setIsExpanded:(BOOL)isExpanded {
if (_isExpanded == isExpanded) {
return;
}
_isExpanded = isExpanded;
[self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj _updateDisplayingInHierarchyProperty];
}];
[self _notifyDelegatesWith:LookinDisplayItemProperty_IsExpanded];
}
- (void)setSoloScreenshot:(LookinImage *)soloScreenshot {
if (_soloScreenshot == soloScreenshot) {
return;
}
_soloScreenshot = soloScreenshot;
[self _notifyDelegatesWith:LookinDisplayItemProperty_SoloScreenshot];
}
- (void)notifySelectionChangeToDelegates {
[self _notifyDelegatesWith:LookinDisplayItemProperty_IsSelected];
}
- (void)notifyHoverChangeToDelegates {
[self _notifyDelegatesWith:LookinDisplayItemProperty_IsHovered];
}
- (void)setDoNotFetchScreenshotReason:(LookinDoNotFetchScreenshotReason)doNotFetchScreenshotReason {
if (_doNotFetchScreenshotReason == doNotFetchScreenshotReason) {
return;
}
_doNotFetchScreenshotReason = doNotFetchScreenshotReason;
[self _notifyDelegatesWith:LookinDisplayItemProperty_AvoidSyncScreenshot];
}
- (void)setGroupScreenshot:(LookinImage *)groupScreenshot {
if (_groupScreenshot == groupScreenshot) {
return;
}
_groupScreenshot = groupScreenshot;
[self _notifyDelegatesWith:LookinDisplayItemProperty_GroupScreenshot];
}
- (void)setDisplayingInHierarchy:(BOOL)displayingInHierarchy {
if (_displayingInHierarchy == displayingInHierarchy) {
return;
}
_displayingInHierarchy = displayingInHierarchy;
[self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj _updateDisplayingInHierarchyProperty];
}];
[self _notifyDelegatesWith:LookinDisplayItemProperty_DisplayingInHierarchy];
}
- (void)_updateDisplayingInHierarchyProperty {
if (self.superItem && (!self.superItem.displayingInHierarchy || !self.superItem.isExpanded)) {
self.displayingInHierarchy = NO;
} else {
self.displayingInHierarchy = YES;
}
}
- (void)setIsHidden:(BOOL)isHidden {
_isHidden = isHidden;
[self _updateInHiddenHierarchyProperty];
}
- (void)setAlpha:(float)alpha {
_alpha = alpha;
[self _updateInHiddenHierarchyProperty];
}
- (void)setInHiddenHierarchy:(BOOL)inHiddenHierarchy {
if (_inHiddenHierarchy == inHiddenHierarchy) {
return;
}
_inHiddenHierarchy = inHiddenHierarchy;
[self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj _updateInHiddenHierarchyProperty];
}];
[self _notifyDelegatesWith:LookinDisplayItemProperty_InHiddenHierarchy];
}
- (void)_updateInHiddenHierarchyProperty {
if (self.superItem.inHiddenHierarchy || self.isHidden || self.alpha <= 0) {
self.inHiddenHierarchy = YES;
} else {
self.inHiddenHierarchy = NO;
}
}
+ (NSArray<LookinDisplayItem *> *)flatItemsFromHierarchicalItems:(NSArray<LookinDisplayItem *> *)items {
NSMutableArray *resultArray = [NSMutableArray array];
[items enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.superItem) {
obj.indentLevel = obj.superItem.indentLevel + 1;
}
[resultArray addObject:obj];
if (obj.subitems.count) {
[resultArray addObjectsFromArray:[self flatItemsFromHierarchicalItems:obj.subitems]];
}
}];
return resultArray;
}
- (NSString *)description {
if (self.viewObject) {
return self.viewObject.rawClassName;
} else if (self.layerObject) {
return self.layerObject.rawClassName;
} else {
return [super description];
}
}
- (void)setPreviewItemDelegate:(id<LookinDisplayItemDelegate>)previewItemDelegate {
_previewItemDelegate = previewItemDelegate;
if (![previewItemDelegate respondsToSelector:@selector(displayItem:propertyDidChange:)]) {
NSAssert(NO, @"");
_previewItemDelegate = nil;
return;
}
[self.previewItemDelegate displayItem:self propertyDidChange:LookinDisplayItemProperty_None];
}
- (void)setRowViewDelegate:(id<LookinDisplayItemDelegate>)rowViewDelegate {
if (_rowViewDelegate == rowViewDelegate) {
return;
}
_rowViewDelegate = rowViewDelegate;
if (![rowViewDelegate respondsToSelector:@selector(displayItem:propertyDidChange:)]) {
NSAssert(NO, @"");
_rowViewDelegate = nil;
return;
}
[self.rowViewDelegate displayItem:self propertyDidChange:LookinDisplayItemProperty_None];
}
- (void)setFrame:(CGRect)frame {
_frame = frame;
[self recursivelyNotifyFrameToRootMayChange];
}
- (void)recursivelyNotifyFrameToRootMayChange {
[self _notifyDelegatesWith:LookinDisplayItemProperty_FrameToRoot];
[self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj recursivelyNotifyFrameToRootMayChange];
}];
}
- (void)setBounds:(CGRect)bounds {
_bounds = bounds;
[self recursivelyNotifyFrameToRootMayChange];
}
- (void)setInNoPreviewHierarchy:(BOOL)inNoPreviewHierarchy {
if (_inNoPreviewHierarchy == inNoPreviewHierarchy) {
return;
}
_inNoPreviewHierarchy = inNoPreviewHierarchy;
[self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj _updateInNoPreviewHierarchy];
}];
[self _notifyDelegatesWith:LookinDisplayItemProperty_InNoPreviewHierarchy];
}
- (void)setNoPreview:(BOOL)noPreview {
_noPreview = noPreview;
[self _updateInNoPreviewHierarchy];
}
- (void)_updateInNoPreviewHierarchy {
if (self.superItem.inNoPreviewHierarchy || self.noPreview) {
self.inNoPreviewHierarchy = YES;
} else {
self.inNoPreviewHierarchy = NO;
}
}
- (void)_notifyDelegatesWith:(LookinDisplayItemProperty)property {
[self.previewItemDelegate displayItem:self propertyDidChange:property];
[self.rowViewDelegate displayItem:self propertyDidChange:property];
}
- (void)setIsInSearch:(BOOL)isInSearch {
_isInSearch = isInSearch;
[self _notifyDelegatesWith:LookinDisplayItemProperty_IsInSearch];
}
- (void)setHighlightedSearchString:(NSString *)highlightedSearchString {
_highlightedSearchString = highlightedSearchString;
[self _notifyDelegatesWith:LookinDisplayItemProperty_HighlightedSearchString];
}
- (NSArray<LookinAttributesGroup *> *)queryAllAttrGroupList {
NSMutableArray *array = [NSMutableArray array];
if (self.attributesGroupList) {
[array addObjectsFromArray:self.attributesGroupList];
}
if (self.customAttrGroupList) {
[array addObjectsFromArray:self.customAttrGroupList];
}
return array;
}
//- (void)dealloc
//{
// NSLog(@"moss dealloc -%@", self);
//}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,51 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinDisplayItemDetail.h
// Lookin
//
// Created by Li Kai on 2019/2/19.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinAttributesGroup;
@class LookinDisplayItem;
@interface LookinDisplayItemDetail : NSObject <NSSecureCoding>
@property(nonatomic, assign) unsigned long displayItemOid;
@property(nonatomic, strong) LookinImage *groupScreenshot;
@property(nonatomic, strong) LookinImage *soloScreenshot;
@property(nonatomic, strong) NSValue *frameValue;
@property(nonatomic, strong) NSValue *boundsValue;
@property(nonatomic, strong) NSNumber *hiddenValue;
@property(nonatomic, strong) NSNumber *alphaValue;
@property(nonatomic, copy) NSString *customDisplayTitle;
@property(nonatomic, copy) NSString *danceUISource;
@property(nonatomic, copy) NSArray<LookinAttributesGroup *> *attributesGroupList;
@property(nonatomic, copy) NSArray<LookinAttributesGroup *> *customAttrGroupList;
/// 注意 nil 和空数组的区别nil 表示该属性无意义,空数组表示 subviews 为空
/// Client 1.0.7 & Server 1.2.7 开始支持该属性
/// 默认为 nil
@property(nonatomic, copy) NSArray<LookinDisplayItem *> *subitems;
/// 当 Server 找不到 task 对应的图层时,会返回一个特殊的 LookinDisplayItemDetail 对象,这个对象会被设置 displayItemOid 和 failureCode其中 failureCode 会被置为 -1
/// Client 1.0.7 & Server 1.2.7 开始支持该属性
/// 默认为 0
@property(nonatomic, assign) NSInteger failureCode;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,71 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinDisplayItemDetail.m
// Lookin
//
// Created by Li Kai on 2019/2/19.
// https://lookin.work
//
#import "LookinDisplayItemDetail.h"
#import "Image+Lookin.h"
#if TARGET_OS_IPHONE
#import "UIImage+LookinServer.h"
#endif
@implementation LookinDisplayItemDetail
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.displayItemOid) forKey:@"displayItemOid"];
[aCoder encodeObject:self.groupScreenshot.lookin_data forKey:@"groupScreenshot"];
[aCoder encodeObject:self.soloScreenshot.lookin_data forKey:@"soloScreenshot"];
[aCoder encodeObject:self.frameValue forKey:@"frameValue"];
[aCoder encodeObject:self.boundsValue forKey:@"boundsValue"];
[aCoder encodeObject:self.hiddenValue forKey:@"hiddenValue"];
[aCoder encodeObject:self.alphaValue forKey:@"alphaValue"];
[aCoder encodeObject:self.attributesGroupList forKey:@"attributesGroupList"];
[aCoder encodeObject:self.customAttrGroupList forKey:@"customAttrGroupList"];
[aCoder encodeObject:self.customDisplayTitle forKey:@"customDisplayTitle"];
[aCoder encodeObject:self.danceUISource forKey:@"danceUISource"];
[aCoder encodeInteger:self.failureCode forKey:@"failureCode"];
if (self.subitems) {
[aCoder encodeObject:self.subitems forKey:@"subitems"];
}
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.displayItemOid = [[aDecoder decodeObjectForKey:@"displayItemOid"] unsignedLongValue];
self.groupScreenshot = [[LookinImage alloc] initWithData:[aDecoder decodeObjectForKey:@"groupScreenshot"]];
self.soloScreenshot = [[LookinImage alloc] initWithData:[aDecoder decodeObjectForKey:@"soloScreenshot"]];
self.frameValue = [aDecoder decodeObjectForKey:@"frameValue"];
self.boundsValue = [aDecoder decodeObjectForKey:@"boundsValue"];
self.hiddenValue = [aDecoder decodeObjectForKey:@"hiddenValue"];
self.alphaValue = [aDecoder decodeObjectForKey:@"alphaValue"];
self.attributesGroupList = [aDecoder decodeObjectForKey:@"attributesGroupList"];
self.customAttrGroupList = [aDecoder decodeObjectForKey:@"customAttrGroupList"];
self.customDisplayTitle = [aDecoder decodeObjectForKey:@"customDisplayTitle"];
self.danceUISource = [aDecoder decodeObjectForKey:@"danceUISource"];
if ([aDecoder containsValueForKey:@"failureCode"]) {
self.failureCode = [aDecoder decodeIntegerForKey:@"failureCode"];
} else {
self.failureCode = 0;
}
if ([aDecoder containsValueForKey:@"subitems"]) {
self.subitems = [aDecoder decodeObjectForKey:@"subitems"];
}
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,43 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinEventHandler.h
// Lookin
//
// Created by Li Kai on 2019/8/7.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinObject, LookinIvarTrace, LookinStringTwoTuple;
typedef NS_ENUM(NSInteger, LookinEventHandlerType) {
LookinEventHandlerTypeTargetAction,
LookinEventHandlerTypeGesture
};
@interface LookinEventHandler : NSObject <NSSecureCoding>
@property(nonatomic, assign) LookinEventHandlerType handlerType;
/// 比如 "UIControlEventTouchUpInside", "UITapGestureRecognizer"
@property(nonatomic, copy) NSString *eventName;
/// tuple.first => @"<WRHomeView: 0xff>"tuple.second => @"handleTap"
@property(nonatomic, copy) NSArray<LookinStringTwoTuple *> *targetActions;
/// 返回当前 recognizer 是继承自哪一个基本款 recognizer。
/// 基本款 recognizer 指的是 TapRecognizer, PinchRecognizer 之类的常见 recognizer
/// 如果当前 recognizer 本身就是基本款 recognizer则该属性为 nil
@property(nonatomic, copy) NSString *inheritedRecognizerName;
@property(nonatomic, assign) BOOL gestureRecognizerIsEnabled;
@property(nonatomic, copy) NSString *gestureRecognizerDelegator;
@property(nonatomic, copy) NSArray<NSString *> *recognizerIvarTraces;
/// recognizer 对象
@property(nonatomic, assign) unsigned long long recognizerOid;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,71 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinEventHandler.m
// Lookin
//
// Created by Li Kai on 2019/8/7.
// https://lookin.work
//
#import "LookinEventHandler.h"
#import "LookinObject.h"
#import "LookinTuple.h"
#import "NSArray+Lookin.h"
@implementation LookinEventHandler
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinEventHandler *newHandler = [[LookinEventHandler allocWithZone:zone] init];
newHandler.handlerType = self.handlerType;
newHandler.eventName = self.eventName;
newHandler.targetActions = [self.targetActions lookin_map:^id(NSUInteger idx, LookinStringTwoTuple *value) {
return value.copy;
}];
newHandler.gestureRecognizerIsEnabled = self.gestureRecognizerIsEnabled;
newHandler.gestureRecognizerDelegator = self.gestureRecognizerDelegator;
newHandler.inheritedRecognizerName = self.inheritedRecognizerName;
newHandler.recognizerIvarTraces = self.recognizerIvarTraces.copy;
newHandler.recognizerOid = self.recognizerOid;
return newHandler;
}
#pragma mark - <NSSecureCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInteger:self.handlerType forKey:@"handlerType"];
[aCoder encodeBool:self.gestureRecognizerIsEnabled forKey:@"gestureRecognizerIsEnabled"];
[aCoder encodeObject:self.eventName forKey:@"eventName"];
[aCoder encodeObject:self.gestureRecognizerDelegator forKey:@"gestureRecognizerDelegator"];
[aCoder encodeObject:self.targetActions forKey:@"targetActions"];
[aCoder encodeObject:self.inheritedRecognizerName forKey:@"inheritedRecognizerName"];
[aCoder encodeObject:self.recognizerIvarTraces forKey:@"recognizerIvarTraces"];
[aCoder encodeObject:@(self.recognizerOid) forKey:@"recognizerOid"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.handlerType = [aDecoder decodeIntegerForKey:@"handlerType"];
self.gestureRecognizerIsEnabled = [aDecoder decodeBoolForKey:@"gestureRecognizerIsEnabled"];
self.eventName = [aDecoder decodeObjectForKey:@"eventName"];
self.gestureRecognizerDelegator = [aDecoder decodeObjectForKey:@"gestureRecognizerDelegator"];
self.targetActions = [aDecoder decodeObjectForKey:@"targetActions"];
self.inheritedRecognizerName = [aDecoder decodeObjectForKey:@"inheritedRecognizerName"];
self.recognizerIvarTraces = [aDecoder decodeObjectForKey:@"recognizerIvarTraces"];
self.recognizerOid = ((NSNumber *)[aDecoder decodeObjectForKey:@"recognizerOid"]).unsignedLongValue;
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,32 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinHierarchyFile.h
// Lookin
//
// Created by Li Kai on 2019/5/12.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinHierarchyInfo;
@interface LookinHierarchyFile : NSObject <NSSecureCoding>
/// 记录创建该文件的 LookinServer 的版本
@property(nonatomic, assign) int serverVersion;
@property(nonatomic, strong) LookinHierarchyInfo *hierarchyInfo;
@property(nonatomic, copy) NSDictionary<NSNumber *, NSData *> *soloScreenshots;
@property(nonatomic, copy) NSDictionary<NSNumber *, NSData *> *groupScreenshots;
/// 验证 file 的版本之类的是否和当前 Lookin 客户端匹配,如果没有问题则返回 nil如果有问题则返回 error
+ (NSError *)verifyHierarchyFile:(LookinHierarchyFile *)file;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,64 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinHierarchyFile.m
// Lookin
//
// Created by Li Kai on 2019/5/12.
// https://lookin.work
//
#import "LookinHierarchyFile.h"
#import "NSArray+Lookin.h"
@implementation LookinHierarchyFile
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt:self.serverVersion forKey:@"serverVersion"];
[aCoder encodeObject:self.hierarchyInfo forKey:@"hierarchyInfo"];
[aCoder encodeObject:self.soloScreenshots forKey:@"soloScreenshots"];
[aCoder encodeObject:self.groupScreenshots forKey:@"groupScreenshots"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.serverVersion = [aDecoder decodeIntForKey:@"serverVersion"];
self.hierarchyInfo = [aDecoder decodeObjectForKey:@"hierarchyInfo"];
self.soloScreenshots = [aDecoder decodeObjectForKey:@"soloScreenshots"];
self.groupScreenshots = [aDecoder decodeObjectForKey:@"groupScreenshots"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
+ (NSError *)verifyHierarchyFile:(LookinHierarchyFile *)hierarchyFile {
if (![hierarchyFile isKindOfClass:[LookinHierarchyFile class]]) {
return LookinErr_Inner;
}
if (hierarchyFile.serverVersion < LOOKIN_SUPPORTED_SERVER_MIN) {
//
// serverVersion 6
int fileVersion = hierarchyFile.serverVersion ? : 6;
NSString *detail = [NSString stringWithFormat:NSLocalizedString(@"The document was created by a Lookin app with too old version. Current Lookin app version is %@, but the document version is %@.", nil), @(LOOKIN_CLIENT_VERSION), @(fileVersion)];
return [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_ServerVersionTooLow userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"Failed to open the document.", nil), NSLocalizedRecoverySuggestionErrorKey:detail}];
}
if (hierarchyFile.serverVersion > LOOKIN_SUPPORTED_SERVER_MAX) {
//
NSString *detail = [NSString stringWithFormat:NSLocalizedString(@"Current Lookin app is too old to open this document. Current Lookin app version is %@, but the document version is %@.", nil), @(LOOKIN_CLIENT_VERSION), @(hierarchyFile.serverVersion)];
return [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_ServerVersionTooHigh userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"Failed to open the document.", nil), NSLocalizedRecoverySuggestionErrorKey:detail}];
}
return nil;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,47 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinDisplayInfo.h
// WeRead
//
// Created by Li Kai on 2018/10/22.
// Copyright © 2018年 tencent. All rights reserved.
//
#import "LookinDefines.h"
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <Appkit/Appkit.h>
#endif
@class LookinDisplayItem, LookinAttributesGroup, LookinAppInfo;
@interface LookinHierarchyInfo : NSObject <NSSecureCoding, NSCopying>
#if TARGET_OS_IPHONE
/// version 可能为 nil此时说明 Client 版本号 < 1.0.4
+ (instancetype)staticInfoWithLookinVersion:(NSString *)version;
+ (instancetype)exportedInfo;
#endif
/// 这里其实就是顶端的那几个 UIWindow
@property(nonatomic, copy) NSArray<LookinDisplayItem *> *displayItems;
@property(nonatomic, copy) NSDictionary<NSString *, id> *colorAlias;
@property(nonatomic, copy) NSArray<NSString *> *collapsedClassList;
@property(nonatomic, strong) LookinAppInfo *appInfo;
@property(nonatomic, assign) int serverVersion;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,106 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinDisplayInfo.m
// WeRead
//
// Created by Li Kai on 2018/10/22.
// Copyright © 2018 tencent. All rights reserved.
//
#import <objc/runtime.h>
#import "LookinHierarchyInfo.h"
#import "LookinAttributesGroup.h"
#import "LookinDisplayItem.h"
#import "LookinAppInfo.h"
#import "NSArray+Lookin.h"
#import "NSString+Lookin.h"
#if TARGET_OS_IPHONE
#import "LKS_HierarchyDisplayItemsMaker.h"
#import "LKSConfigManager.h"
#import "LKS_CustomAttrSetterManager.h"
#endif
@implementation LookinHierarchyInfo
#if TARGET_OS_IPHONE
+ (instancetype)staticInfoWithLookinVersion:(NSString *)version {
BOOL readCustomInfo = NO;
// Client 1.0.4 customInfo
if (version && [version lookin_numbericOSVersion] >= 10004) {
readCustomInfo = YES;
}
[[LKS_CustomAttrSetterManager sharedInstance] removeAll];
LookinHierarchyInfo *info = [LookinHierarchyInfo new];
info.serverVersion = LOOKIN_SERVER_VERSION;
info.displayItems = [LKS_HierarchyDisplayItemsMaker itemsWithScreenshots:NO attrList:NO lowImageQuality:NO readCustomInfo:readCustomInfo saveCustomSetter:YES];
info.appInfo = [LookinAppInfo currentInfoWithScreenshot:NO icon:YES localIdentifiers:nil];
info.collapsedClassList = [LKSConfigManager collapsedClassList];
info.colorAlias = [LKSConfigManager colorAlias];
return info;
}
+ (instancetype)exportedInfo {
LookinHierarchyInfo *info = [LookinHierarchyInfo new];
info.serverVersion = LOOKIN_SERVER_VERSION;
info.displayItems = [LKS_HierarchyDisplayItemsMaker itemsWithScreenshots:YES attrList:YES lowImageQuality:YES readCustomInfo:YES saveCustomSetter:NO];
info.appInfo = [LookinAppInfo currentInfoWithScreenshot:NO icon:YES localIdentifiers:nil];
info.collapsedClassList = [LKSConfigManager collapsedClassList];
info.colorAlias = [LKSConfigManager colorAlias];
return info;
}
#endif
#pragma mark - <NSSecureCoding>
static NSString * const LookinHierarchyInfoCodingKey_DisplayItems = @"1";
static NSString * const LookinHierarchyInfoCodingKey_AppInfo = @"2";
static NSString * const LookinHierarchyInfoCodingKey_ColorAlias = @"3";
static NSString * const LookinHierarchyInfoCodingKey_CollapsedClassList = @"4";
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.displayItems forKey:LookinHierarchyInfoCodingKey_DisplayItems];
[aCoder encodeObject:self.colorAlias forKey:LookinHierarchyInfoCodingKey_ColorAlias];
[aCoder encodeObject:self.collapsedClassList forKey:LookinHierarchyInfoCodingKey_CollapsedClassList];
[aCoder encodeObject:self.appInfo forKey:LookinHierarchyInfoCodingKey_AppInfo];
[aCoder encodeInt:self.serverVersion forKey:@"serverVersion"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.displayItems = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_DisplayItems];
self.colorAlias = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_ColorAlias];
self.collapsedClassList = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_CollapsedClassList];
self.appInfo = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_AppInfo];
self.serverVersion = [aDecoder decodeIntForKey:@"serverVersion"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinHierarchyInfo *newAppInfo = [[LookinHierarchyInfo allocWithZone:zone] init];
newAppInfo.serverVersion = self.serverVersion;
newAppInfo.appInfo = self.appInfo.copy;
newAppInfo.collapsedClassList = self.collapsedClassList;
newAppInfo.colorAlias = self.colorAlias;
newAppInfo.displayItems = [self.displayItems lookin_map:^id(NSUInteger idx, LookinDisplayItem *oldItem) {
return oldItem.copy;
}];
return newAppInfo;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,42 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinObject.h
// Lookin
//
// Created by Li Kai on 2019/4/20.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinObjectIvar, LookinIvarTrace;
@interface LookinObject : NSObject <NSSecureCoding, NSCopying>
#if TARGET_OS_IPHONE
+ (instancetype)instanceWithObject:(NSObject *)object;
#endif
@property(nonatomic, assign) unsigned long oid;
@property(nonatomic, copy) NSString *memoryAddress;
/**
比如有一个 UILabel 对象,则它的 classChainList 为 @[@"UILabel", @"UIView", @"UIResponder", @"NSObject"],而它的 ivarList 长度为 4idx 从小到大分别是 UILabel 层级的 ivars, UIView 层级的 ivars.....
*/
@property(nonatomic, copy) NSArray<NSString *> *classChainList;
@property(nonatomic, copy) NSString *specialTrace;
@property(nonatomic, copy) NSArray<LookinIvarTrace *> *ivarTraces;
/// 没有 demangle会包含 Swift Module Name
/// 在 Lookin 的展示中,绝大多数情况下应该使用 lk_demangledSwiftName
- (NSString *)rawClassName;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,82 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinObject.m
// Lookin
//
// Created by Li Kai on 2019/4/20.
// https://lookin.work
//
#import "LookinObject.h"
#import "LookinIvarTrace.h"
#import "NSArray+Lookin.h"
#import "NSString+Lookin.h"
#if TARGET_OS_IPHONE
#import "NSObject+LookinServer.h"
#endif
@implementation LookinObject
#if TARGET_OS_IPHONE
+ (instancetype)instanceWithObject:(NSObject *)object {
LookinObject *lookinObj = [LookinObject new];
lookinObj.oid = [object lks_registerOid];
lookinObj.memoryAddress = [NSString stringWithFormat:@"%p", object];
lookinObj.classChainList = [object lks_classChainList];
lookinObj.specialTrace = object.lks_specialTrace;
lookinObj.ivarTraces = object.lks_ivarTraces;
return lookinObj;
}
#endif
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinObject *newObject = [[LookinObject allocWithZone:zone] init];
newObject.oid = self.oid;
newObject.memoryAddress = self.memoryAddress;
newObject.classChainList = self.classChainList;
newObject.specialTrace = self.specialTrace;
newObject.ivarTraces = [self.ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) {
return value.copy;
}];
return newObject;
}
#pragma mark - <NSSecureCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.oid) forKey:@"oid"];
[aCoder encodeObject:self.memoryAddress forKey:@"memoryAddress"];
[aCoder encodeObject:self.classChainList forKey:@"classChainList"];
[aCoder encodeObject:self.specialTrace forKey:@"specialTrace"];
[aCoder encodeObject:self.ivarTraces forKey:@"ivarTraces"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.oid = [(NSNumber *)[aDecoder decodeObjectForKey:@"oid"] unsignedLongValue];
self.memoryAddress = [aDecoder decodeObjectForKey:@"memoryAddress"];
self.classChainList = [aDecoder decodeObjectForKey:@"classChainList"];
self.specialTrace = [aDecoder decodeObjectForKey:@"specialTrace"];
self.ivarTraces = [aDecoder decodeObjectForKey:@"ivarTraces"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (NSString *)rawClassName {
return self.classChainList.firstObject;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,67 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinStaticAsyncUpdateTask.h
// Lookin
//
// Created by Li Kai on 2019/6/21.
// https://lookin.work
//
#import "LookinDefines.h"
typedef NS_ENUM(NSInteger, LookinStaticAsyncUpdateTaskType) {
LookinStaticAsyncUpdateTaskTypeNoScreenshot,
LookinStaticAsyncUpdateTaskTypeSoloScreenshot,
LookinStaticAsyncUpdateTaskTypeGroupScreenshot
};
typedef NS_ENUM(NSInteger, LookinDetailUpdateTaskAttrRequest) {
/// 由 Server 端自己决定:同一批 task 里server 端会保证同一个 layer 只会构造一次 attr
/// 在 Lookin turbo 模式下,由于同一个 layer 的 task 可能位于不同批的 task 里,因此这会导致冗余的 attr 构造行为、浪费一定时间
LookinDetailUpdateTaskAttrRequest_Automatic,
/// 需要返回 attr
LookinDetailUpdateTaskAttrRequest_Need,
/// 不需要返回 attr
LookinDetailUpdateTaskAttrRequest_NotNeed
};
/// 业务重写了 isEqual
@interface LookinStaticAsyncUpdateTask : NSObject <NSSecureCoding>
@property(nonatomic, assign) unsigned long oid;
@property(nonatomic, assign) LookinStaticAsyncUpdateTaskType taskType;
/// 是否需要返回 attr 数据,默认为 Automatic
/// Client 1.0.7 & Server 1.2.7 开始支持这个参数
@property(nonatomic, assign) LookinDetailUpdateTaskAttrRequest attrRequest;
/// 如果置为 YES则 server 侧会返回这些基础信息frameValue, boundsValue, hiddenValue, alphaValue
/// 默认为 NO
/// Client 1.0.7 & Server 1.2.7 开始支持这个参数
@property(nonatomic, assign) BOOL needBasisVisualInfo;
/// 如果置为 YES则 server 侧会返回 subitems
/// 默认为 NO
/// Client 1.0.7 & Server 1.2.7 开始支持这个参数
@property(nonatomic, assign) BOOL needSubitems;
/// Client 1.0.4 开始加入这个参数
@property(nonatomic, copy) NSString *clientReadableVersion;
#pragma mark - Non Coding
@property(nonatomic, assign) CGSize frameSize;
@end
@interface LookinStaticAsyncUpdateTasksPackage : NSObject <NSSecureCoding>
@property(nonatomic, copy) NSArray<LookinStaticAsyncUpdateTask *> *tasks;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,105 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinStaticAsyncUpdateTask.m
// Lookin
//
// Created by Li Kai on 2019/6/21.
// https://lookin.work
//
#import "LookinStaticAsyncUpdateTask.h"
@implementation LookinStaticAsyncUpdateTask
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.oid) forKey:@"oid"];
[aCoder encodeInteger:self.taskType forKey:@"taskType"];
[aCoder encodeObject:self.clientReadableVersion forKey:@"clientReadableVersion"];
[aCoder encodeInteger:self.attrRequest forKey:@"attrRequest"];
[aCoder encodeBool:self.needBasisVisualInfo forKey:@"needBasisVisualInfo"];
[aCoder encodeBool:self.needSubitems forKey:@"needSubitems"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.oid = [[aDecoder decodeObjectForKey:@"oid"] unsignedLongValue];
self.taskType = [aDecoder decodeIntegerForKey:@"taskType"];
self.clientReadableVersion = [aDecoder decodeObjectForKey:@"clientReadableVersion"];
if ([aDecoder containsValueForKey:@"attrRequest"]) {
NSInteger value = [aDecoder decodeIntegerForKey:@"attrRequest"];
if (value >= LookinDetailUpdateTaskAttrRequest_Automatic && value <= LookinDetailUpdateTaskAttrRequest_NotNeed) {
self.attrRequest = value;
} else {
self.attrRequest = LookinDetailUpdateTaskAttrRequest_Automatic;
}
} else {
self.attrRequest = LookinDetailUpdateTaskAttrRequest_Automatic;
}
if ([aDecoder containsValueForKey:@"needBasisVisualInfo"]) {
self.needBasisVisualInfo = [aDecoder decodeBoolForKey:@"needBasisVisualInfo"];
} else {
self.needBasisVisualInfo = NO;
}
if ([aDecoder containsValueForKey:@"needSubitems"]) {
self.needSubitems = [aDecoder decodeBoolForKey:@"needSubitems"];
} else {
self.needSubitems = NO;
}
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (NSUInteger)hash {
return self.oid ^ self.taskType ^ self.attrRequest ^ self.needBasisVisualInfo ^ self.needSubitems;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinStaticAsyncUpdateTask class]]) {
return NO;
}
LookinStaticAsyncUpdateTask *targetTask = object;
if (self.oid == targetTask.oid
&& self.taskType == targetTask.taskType
&& self.attrRequest == targetTask.attrRequest
&& self.needBasisVisualInfo == targetTask.needBasisVisualInfo
&& self.needSubitems == targetTask.needSubitems) {
return YES;
}
return NO;
}
@end
@implementation LookinStaticAsyncUpdateTasksPackage
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.tasks forKey:@"tasks"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.tasks = [aDecoder decodeObjectForKey:@"tasks"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,31 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinTuples.h
// Lookin
//
// Created by Li Kai on 2019/8/14.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LookinTwoTuple : NSObject <NSSecureCoding>
@property(nonatomic, strong) NSObject *first;
@property(nonatomic, strong) NSObject *second;
@end
@interface LookinStringTwoTuple : NSObject <NSSecureCoding, NSCopying>
+ (instancetype)tupleWithFirst:(NSString *)firstString second:(NSString *)secondString;
@property(nonatomic, copy) NSString *first;
@property(nonatomic, copy) NSString *second;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,93 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinTuples.m
// Lookin
//
// Created by Li Kai on 2019/8/14.
// https://lookin.work
//
#import "LookinTuple.h"
@implementation LookinTwoTuple
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.first forKey:@"first"];
[aCoder encodeObject:self.second forKey:@"second"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.first = [aDecoder decodeObjectForKey:@"first"];
self.second = [aDecoder decodeObjectForKey:@"second"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (NSUInteger)hash {
return self.first.hash ^ self.second.hash;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinTwoTuple class]]) {
return NO;
}
LookinTwoTuple *comparedObj = object;
if ([self.first isEqual:comparedObj.first] && [self.second isEqual:comparedObj.second]) {
return YES;
}
return NO;
}
@end
@implementation LookinStringTwoTuple
+ (instancetype)tupleWithFirst:(NSString *)firstString second:(NSString *)secondString {
LookinStringTwoTuple *tuple = [LookinStringTwoTuple new];
tuple.first = firstString;
tuple.second = secondString;
return tuple;
}
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinStringTwoTuple *newTuple = [[LookinStringTwoTuple allocWithZone:zone] init];
newTuple.first = self.first;
newTuple.second = self.second;
return newTuple;
}
#pragma mark - <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.first forKey:@"first"];
[aCoder encodeObject:self.second forKey:@"second"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.first = [aDecoder decodeObjectForKey:@"first"];
self.second = [aDecoder decodeObjectForKey:@"second"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinWeakContainer.h
// Lookin
//
// Created by Li Kai on 2019/8/14.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LookinWeakContainer : NSObject
+ (instancetype)containerWithObject:(id)object;
@property (nonatomic, weak) id object;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,43 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinWeakContainer.m
// Lookin
//
// Created by Li Kai on 2019/8/14.
// https://lookin.work
//
#import "LookinWeakContainer.h"
@implementation LookinWeakContainer
+ (instancetype)containerWithObject:(id)object {
LookinWeakContainer *container = [LookinWeakContainer new];
container.object = object;
return container;
}
- (NSUInteger)hash {
return [self.object hash];
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinWeakContainer class]]) {
return NO;
}
LookinWeakContainer *comparedObj = object;
if ([self.object isEqual:comparedObj.object]) {
return YES;
}
return NO;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,136 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Represents a communication channel between two endpoints talking the same
// Lookin_PTProtocol.
//
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#import <netinet/in.h>
#import <sys/socket.h>
#import "Lookin_PTProtocol.h"
#import "Lookin_PTUSBHub.h"
@class Lookin_PTData, Lookin_PTAddress;
@protocol Lookin_PTChannelDelegate;
@interface Lookin_PTChannel : NSObject
// Delegate
@property (strong) id<Lookin_PTChannelDelegate> delegate;
// Communication protocol. Must not be nil.
@property Lookin_PTProtocol *protocol;
// YES if this channel is a listening server
@property (readonly) BOOL isListening;
// YES if this channel is a connected peer
@property (readonly) BOOL isConnected;
// Arbitrary attachment. Note that if you set this, the object will grow by
// 8 bytes (64 bits).
@property (strong) id userInfo;
@property(nonatomic, assign) int uniqueID;
@property(nonatomic, assign) NSInteger targetPort;
- (NSString *)debugTag;
// Create a new channel using the shared Lookin_PTProtocol for the current dispatch
// queue, with *delegate*.
+ (Lookin_PTChannel*)channelWithDelegate:(id<Lookin_PTChannelDelegate>)delegate;
// Initialize a new frame channel, configuring it to use the calling queue's
// protocol instance (as returned by [Lookin_PTProtocol sharedProtocolForQueue:
// dispatch_get_current_queue()])
- (id)init;
// Initialize a new frame channel with a specific protocol.
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol;
// Initialize a new frame channel with a specific protocol and delegate.
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id<Lookin_PTChannelDelegate>)delegate;
// Connect to a TCP port on a device connected over USB
- (void)connectToPort:(int)port overUSBHub:(Lookin_PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback;
// Connect to a TCP port at IPv4 address. Provided port must NOT be in network
// byte order. Provided in_addr_t must NOT be in network byte order. A value returned
// from inet_aton() will be in network byte order. You can use a value of inet_aton()
// as the address parameter here, but you must flip the byte order before passing the
// in_addr_t to this function.
- (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, Lookin_PTAddress *address))callback;
// Listen for connections on port and address, effectively starting a socket
// server. Provided port must NOT be in network byte order. Provided in_addr_t
// must NOT be in network byte order.
// For this to make sense, you should provide a onAccept block handler
// or a delegate implementing ioFrameChannel:didAcceptConnection:.
- (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback;
// Send a frame with an optional payload and optional callback.
// If *callback* is not NULL, the block is invoked when either an error occured
// or when the frame (and payload, if any) has been completely sent.
- (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback;
// Lower-level method to assign a connected dispatch IO channel to this channel
- (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error;
// Close the channel, preventing further reading and writing. Any ongoing and
// queued reads and writes will be aborted.
- (void)close;
// "graceful" close -- any ongoing and queued reads and writes will complete
// before the channel ends.
- (void)cancel;
@end
// Wraps a mapped dispatch_data_t object. The memory pointed to by *data* is
// valid until *dispatchData* is deallocated (normally when the receiver is
// deallocated).
@interface Lookin_PTData : NSObject
@property (readonly) dispatch_data_t dispatchData;
@property (readonly) void *data;
@property (readonly) size_t length;
@end
// Represents a peer's address
@interface Lookin_PTAddress : NSObject
// For network addresses, this is the IP address in textual format
@property (readonly) NSString *name;
// For network addresses, this is the port number. Otherwise 0 (zero).
@property (readonly) NSInteger port;
@end
// Protocol for Lookin_PTChannel delegates
@protocol Lookin_PTChannelDelegate <NSObject>
@required
// Invoked when a new frame has arrived on a channel.
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload;
@optional
// Invoked to accept an incoming frame on a channel. Reply NO ignore the
// incoming frame. If not implemented by the delegate, all frames are accepted.
- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize;
// Invoked when the channel closed. If it closed because of an error, *error* is
// a non-nil NSError object.
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error;
// For listening channels, this method is invoked when a new connection has been
// accepted.
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,675 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#import "Lookin_PTChannel.h"
#import "Lookin_PTPrivate.h"
#include <sys/ioctl.h>
#include <sys/un.h>
#include <err.h>
#include <fcntl.h>
#include <arpa/inet.h>
#import <objc/runtime.h>
// Read member of sockaddr_in without knowing the family
#define PT_SOCKADDR_ACCESS(ss, member4, member6) \
(((ss)->ss_family == AF_INET) ? ( \
((const struct sockaddr_in *)(ss))->member4 \
) : ( \
((const struct sockaddr_in6 *)(ss))->member6 \
))
// Connection state (storage: uint8_t)
#define kConnStateNone 0
#define kConnStateConnecting 1
#define kConnStateConnected 2
#define kConnStateListening 3
// Delegate support optimization (storage: uint8_t)
#define kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize 1
#define kDelegateFlagImplements_ioFrameChannel_didEndWithError 2
#define kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress 4
static int ChannelInstanceCount = 0;
static int ChannelUniqueID = 0;
#pragma mark -
// Note: We are careful about the size of this struct as each connected peer
// implies one allocation of this struct.
@interface Lookin_PTChannel () {
dispatch_io_t dispatchObj_channel_;
dispatch_source_t dispatchObj_source_;
NSError *endError_; // 64 bit
@public // here be hacks
id<Lookin_PTChannelDelegate> delegate_; // 64 bit
uint8_t delegateFlags_; // 8 bit
@private
uint8_t connState_; // 8 bit
//char padding_[6]; // 48 bit -- only if allocation speed is important
}
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id<Lookin_PTChannelDelegate>)delegate;
- (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD;
@end
static const uint8_t kUserInfoKey;
#pragma mark -
@interface Lookin_PTData ()
- (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length;
@end
#pragma mark -
@interface Lookin_PTAddress () {
struct sockaddr_storage sockaddr_;
}
- (id)initWithSockaddr:(const struct sockaddr_storage*)addr;
@end
#pragma mark -
@implementation Lookin_PTChannel
@synthesize protocol = protocol_;
+ (Lookin_PTChannel*)channelWithDelegate:(id<Lookin_PTChannelDelegate>)delegate {
return [[Lookin_PTChannel alloc] initWithProtocol:[Lookin_PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()] delegate:delegate];
}
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id<Lookin_PTChannelDelegate>)delegate {
if (!(self = [super init])) return nil;
protocol_ = protocol;
self.delegate = delegate;
[self didInit];
return self;
}
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol {
if (!(self = [super init])) return nil;
protocol_ = protocol;
[self didInit];
return self;
}
- (id)init {
[self didInit];
return [self initWithProtocol:[Lookin_PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()]];
}
- (void)didInit {
ChannelUniqueID++;
ChannelInstanceCount++;
self.uniqueID = ChannelUniqueID;
// NSLog(@"LookinServer - Init channel(ID: %@). Total count: %@", @(self.uniqueID), @(ChannelInstanceCount));
}
- (void)dealloc {
ChannelInstanceCount--;
// NSLog(@"LookinServer - Dealloc channel%@. Still lives count: %@", self.debugTag, @(ChannelInstanceCount));
#if PT_DISPATCH_RETAIN_RELEASE
if (dispatchObj_channel_) dispatch_release(dispatchObj_channel_);
else if (dispatchObj_source_) dispatch_release(dispatchObj_source_);
#endif
}
- (BOOL)isConnected {
return connState_ == kConnStateConnecting || connState_ == kConnStateConnected;
}
- (BOOL)isListening {
return connState_ == kConnStateListening;
}
- (id)userInfo {
return objc_getAssociatedObject(self, (void*)&kUserInfoKey);
}
- (void)setUserInfo:(id)userInfo {
objc_setAssociatedObject(self, (const void*)&kUserInfoKey, userInfo, OBJC_ASSOCIATION_RETAIN);
}
- (void)setConnState:(char)connState {
connState_ = connState;
}
- (void)setDispatchChannel:(dispatch_io_t)channel {
assert(connState_ == kConnStateConnecting || connState_ == kConnStateConnected || connState_ == kConnStateNone);
dispatch_io_t prevChannel = dispatchObj_channel_;
if (prevChannel != channel) {
dispatchObj_channel_ = channel;
#if PT_DISPATCH_RETAIN_RELEASE
if (dispatchObj_channel_) dispatch_retain(dispatchObj_channel_);
if (prevChannel) dispatch_release(prevChannel);
#endif
if (!dispatchObj_channel_ && !dispatchObj_source_) {
connState_ = kConnStateNone;
}
}
}
- (void)setDispatchSource:(dispatch_source_t)source {
assert(connState_ == kConnStateListening || connState_ == kConnStateNone);
dispatch_source_t prevSource = dispatchObj_source_;
if (prevSource != source) {
dispatchObj_source_ = source;
#if PT_DISPATCH_RETAIN_RELEASE
if (dispatchObj_source_) dispatch_retain(dispatchObj_source_);
if (prevSource) dispatch_release(prevSource);
#endif
if (!dispatchObj_channel_ && !dispatchObj_source_) {
connState_ = kConnStateNone;
}
}
}
- (id<Lookin_PTChannelDelegate>)delegate {
return delegate_;
}
- (void)setDelegate:(id<Lookin_PTChannelDelegate>)delegate {
delegate_ = delegate;
delegateFlags_ = 0;
if (!delegate_) {
return;
}
if ([delegate respondsToSelector:@selector(ioFrameChannel:shouldAcceptFrameOfType:tag:payloadSize:)]) {
delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize;
}
if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didEndWithError:)]) {
delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didEndWithError;
}
if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didAcceptConnection:fromAddress:)]) {
delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress;
}
}
- (NSString *)debugTag {
NSString *state = @"";
if (connState_ == kConnStateNone) {
state = @"None";
} else if (connState_ == kConnStateConnecting) {
state = @"Connecting";
} else if (connState_ == kConnStateConnected) {
state = @"Connected";
} else if (connState_ == kConnStateListening) {
state = @"Listening";
} else {
state = @"Undefined";
}
return [NSString stringWithFormat:@"[%@-%@,%@]", @(self.uniqueID), @(self.targetPort), state];
}
//- (void)setFileDescriptor:(dispatch_fd_t)fd {
// [self setDispatchChannel:dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) {
// close(fd);
// })];
//}
#pragma mark - Connecting
- (void)connectToPort:(int)port overUSBHub:(Lookin_PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback {
assert(protocol_ != NULL);
if (connState_ != kConnStateNone) {
if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]);
return;
}
connState_ = kConnStateConnecting;
[usbHub connectToDevice:deviceID port:port onStart:^(NSError *err, dispatch_io_t dispatchChannel) {
NSError *error = err;
if (!error) {
[self startReadingFromConnectedChannel:dispatchChannel error:&error];
} else {
self->connState_ = kConnStateNone;
}
if (callback) callback(error);
} onEnd:^(NSError *error) {
if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) {
[self->delegate_ ioFrameChannel:self didEndWithError:error];
}
self->endError_ = nil;
}];
}
- (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, Lookin_PTAddress *address))callback {
assert(protocol_ != NULL);
if (connState_ != kConnStateNone) {
if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil], nil);
return;
}
connState_ = kConnStateConnecting;
int error = 0;
// Create socket
dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket(AF_INET, SOCK_STREAM, 0) failed");
error = errno;
if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil], nil);
return;
}
// Connect socket
struct sockaddr_in addr;
bzero((char *)&addr, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
//addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
//addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_addr.s_addr = htonl(address);
// prevent SIGPIPE
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
// int socket, const struct sockaddr *address, socklen_t address_len
if (connect(fd, (const struct sockaddr *)&addr, addr.sin_len) == -1) {
//perror("connect");
error = errno;
close(fd);
if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil);
return;
}
// get actual address
//if (getsockname(fd, (struct sockaddr*)&addr, (socklen_t*)&addr.sin_len) == -1) {
// error = errno;
// close(fd);
// if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil);
// return;
//}
dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) {
close(fd);
if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) {
NSError *err = error == 0 ? self->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil];
[self->delegate_ ioFrameChannel:self didEndWithError:err];
self->endError_ = nil;
}
});
if (!dispatchChannel) {
close(fd);
if (callback) callback([[NSError alloc] initWithDomain:@"PTError" code:0 userInfo:nil], nil);
return;
}
// Success
NSError *err = nil;
Lookin_PTAddress *ptAddr = [[Lookin_PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr];
[self startReadingFromConnectedChannel:dispatchChannel error:&err];
if (callback) callback(err, ptAddr);
}
#pragma mark - Listening and serving
- (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback {
assert(dispatchObj_source_ == nil);
// Create socket
dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]);
return;
}
// Connect socket
struct sockaddr_in addr;
bzero((char *)&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
//addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
//addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_addr.s_addr = htonl(address);
socklen_t socklen = sizeof(addr);
int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
close(fd);
if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]);
return;
}
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
close(fd);
if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]);
return;
}
if (bind(fd, (struct sockaddr*)&addr, socklen) != 0) {
close(fd);
if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]);
return;
}
if (listen(fd, 512) != 0) {
close(fd);
if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]);
return;
}
[self setDispatchSource:dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, protocol_.queue)];
dispatch_source_set_event_handler(dispatchObj_source_, ^{
unsigned long nconns = dispatch_source_get_data(self->dispatchObj_source_);
while ([self acceptIncomingConnection:fd] && --nconns);
});
dispatch_source_set_cancel_handler(dispatchObj_source_, ^{
// Captures *self*, effectively holding a reference to *self* until cancelled.
self->dispatchObj_source_ = nil;
close(fd);
if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) {
[self->delegate_ ioFrameChannel:self didEndWithError:self->endError_];
self->endError_ = nil;
}
});
dispatch_resume(dispatchObj_source_);
//NSLog(@"%@ opened on fd #%d", self, fd);
connState_ = kConnStateListening;
if (callback) callback(nil);
}
- (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD {
struct sockaddr_in addr;
socklen_t addrLen = sizeof(addr);
dispatch_fd_t clientSocketFD = accept(serverSocketFD, (struct sockaddr*)&addr, &addrLen);
if (clientSocketFD == -1) {
perror("accept()");
return NO;
}
// prevent SIGPIPE
int on = 1;
setsockopt(clientSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
if (fcntl(clientSocketFD, F_SETFL, O_NONBLOCK) == -1) {
perror("fcntl(.. O_NONBLOCK)");
close(clientSocketFD);
return NO;
}
if (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress) {
Lookin_PTChannel *peerChannel = [[Lookin_PTChannel alloc] initWithProtocol:protocol_ delegate:delegate_];
__block Lookin_PTChannel *localChannelRef = self;
dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, clientSocketFD, protocol_.queue, ^(int error) {
// Important note: This block captures *self*, thus a reference is held to
// *self* until the fd is truly closed.
localChannelRef = nil;
close(clientSocketFD);
if (peerChannel->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) {
NSError *err = error == 0 ? peerChannel->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil];
[peerChannel->delegate_ ioFrameChannel:peerChannel didEndWithError:err];
peerChannel->endError_ = nil;
}
});
[peerChannel setConnState:kConnStateConnected];
[peerChannel setDispatchChannel:dispatchChannel];
assert(((struct sockaddr_storage*)&addr)->ss_len == addrLen);
Lookin_PTAddress *address = [[Lookin_PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr];
[delegate_ ioFrameChannel:self didAcceptConnection:peerChannel fromAddress:address];
NSError *err = nil;
if (![peerChannel startReadingFromConnectedChannel:dispatchChannel error:&err]) {
// NSLog(@"startReadingFromConnectedChannel failed in accept: %@", err);
}
} else {
close(clientSocketFD);
}
return YES;
}
#pragma mark - Closing the channel
- (void)close {
// NSLog(@"LookinServer - Will close chanel: %@", self.debugTag);
if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) {
dispatch_io_close(dispatchObj_channel_, DISPATCH_IO_STOP);
[self setDispatchChannel:NULL];
} else if (connState_ == kConnStateListening && dispatchObj_source_) {
dispatch_source_cancel(dispatchObj_source_);
}
}
/// Client Client Peertalk connect channel
- (void)cancel {
// NSLog(@"LookinServer - Will cancel chanel: %@", self.debugTag);
if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) {
dispatch_io_close(dispatchObj_channel_, 0);
[self setDispatchChannel:NULL];
} else if (connState_ == kConnStateListening && dispatchObj_source_) {
dispatch_source_cancel(dispatchObj_source_);
}
}
#pragma mark - Reading
- (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error {
if (connState_ != kConnStateNone && connState_ != kConnStateConnecting && connState_ != kConnStateConnected) {
if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil];
return NO;
}
if (dispatchObj_channel_ != channel) {
[self close];
[self setDispatchChannel:channel];
}
connState_ = kConnStateConnected;
// helper
BOOL(^handleError)(NSError*,BOOL) = ^BOOL(NSError *error, BOOL isEOS) {
if (error) {
//NSLog(@"Error while communicating: %@", error);
self->endError_ = error;
[self close];
return YES;
} else if (isEOS) {
[self cancel];
return YES;
}
return NO;
};
[protocol_ readFramesOverChannel:channel onFrame:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize, dispatch_block_t resumeReadingFrames) {
if (handleError(error, type == PTFrameTypeEndOfStream)) {
return;
}
BOOL accepted = (channel == self->dispatchObj_channel_);
if (accepted && (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize)) {
accepted = [self->delegate_ ioFrameChannel:self shouldAcceptFrameOfType:type tag:tag payloadSize:payloadSize];
}
if (payloadSize == 0) {
if (accepted && self->delegate_) {
[self->delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:nil];
} else {
// simply ignore the frame
}
resumeReadingFrames();
} else {
// has payload
if (!accepted) {
// Read and discard payload, ignoring frame
[self->protocol_ readAndDiscardDataOfSize:payloadSize overChannel:channel callback:^(NSError *error, BOOL endOfStream) {
if (!handleError(error, endOfStream)) {
resumeReadingFrames();
}
}];
} else {
[self->protocol_ readPayloadOfSize:payloadSize overChannel:channel callback:^(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize) {
if (handleError(error, bufferSize == 0)) {
return;
}
if (self->delegate_) {
Lookin_PTData *payload = [[Lookin_PTData alloc] initWithMappedDispatchData:contiguousData data:(void*)buffer length:bufferSize];
[self->delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:payload];
}
resumeReadingFrames();
}];
}
}
}];
return YES;
}
#pragma mark - Sending
- (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback {
if (connState_ == kConnStateConnecting || connState_ == kConnStateConnected) {
[protocol_ sendFrameOfType:frameType tag:tag withPayload:payload overChannel:dispatchObj_channel_ callback:callback];
} else if (callback) {
callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]);
}
}
#pragma mark - NSObject
- (NSString*)description {
id userInfo = objc_getAssociatedObject(self, (void*)&kUserInfoKey);
return [NSString stringWithFormat:@"<Lookin_PTChannel: %p (%@)%s%@>", self, ( connState_ == kConnStateConnecting ? @"connecting"
: connState_ == kConnStateConnected ? @"connected"
: connState_ == kConnStateListening ? @"listening"
: @"closed"),
userInfo ? " " : "", userInfo ? userInfo : @""];
}
@end
#pragma mark -
@implementation Lookin_PTAddress
- (id)initWithSockaddr:(const struct sockaddr_storage*)addr {
if (!(self = [super init])) return nil;
assert(addr);
memcpy((void*)&sockaddr_, (const void*)addr, addr->ss_len);
return self;
}
- (NSString*)name {
if (sockaddr_.ss_len) {
const void *sin_addr = NULL;
size_t bufsize = 0;
if (sockaddr_.ss_family == AF_INET6) {
bufsize = INET6_ADDRSTRLEN;
sin_addr = (const void *)&((const struct sockaddr_in6*)&sockaddr_)->sin6_addr;
} else {
bufsize = INET_ADDRSTRLEN;
sin_addr = (const void *)&((const struct sockaddr_in*)&sockaddr_)->sin_addr;
}
char *buf = CFAllocatorAllocate(kCFAllocatorDefault, bufsize+1, 0);
if (inet_ntop(sockaddr_.ss_family, sin_addr, buf, (unsigned int)bufsize-1) == NULL) {
CFAllocatorDeallocate(kCFAllocatorDefault, buf);
return nil;
}
return [[NSString alloc] initWithBytesNoCopy:(void*)buf length:strlen(buf) encoding:NSUTF8StringEncoding freeWhenDone:YES];
} else {
return nil;
}
}
- (NSInteger)port {
if (sockaddr_.ss_len) {
return ntohs(PT_SOCKADDR_ACCESS(&sockaddr_, sin_port, sin6_port));
} else {
return 0;
}
}
- (NSString*)description {
if (sockaddr_.ss_len) {
return [NSString stringWithFormat:@"%@:%u", self.name, (unsigned)self.port];
} else {
return @"(?)";
}
}
@end
#pragma mark -
@implementation Lookin_PTData
@synthesize dispatchData = dispatchData_;
@synthesize data = data_;
@synthesize length = length_;
- (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length {
if (!(self = [super init])) return nil;
dispatchData_ = mappedContiguousData;
#if PT_DISPATCH_RETAIN_RELEASE
if (dispatchData_) dispatch_retain(dispatchData_);
#endif
data_ = data;
length_ = length;
return self;
}
- (void)dealloc {
#if PT_DISPATCH_RETAIN_RELEASE
if (dispatchData_) dispatch_release(dispatchData_);
#endif
data_ = NULL;
length_ = 0;
}
#pragma mark - NSObject
- (NSString*)description {
return [NSString stringWithFormat:@"<Lookin_PTData: %p (%zu bytes)>", self, length_];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,20 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (!defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0)) || \
(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && (!defined(__MAC_10_8) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_8))
#define PT_DISPATCH_RETAIN_RELEASE 1
#else
#define PT_DISPATCH_RETAIN_RELEASE 0
#endif
#if PT_DISPATCH_RETAIN_RELEASE
#define PT_PRECISE_LIFETIME
#define PT_PRECISE_LIFETIME_UNUSED
#else
#define PT_PRECISE_LIFETIME __attribute__((objc_precise_lifetime))
#define PT_PRECISE_LIFETIME_UNUSED __attribute__((objc_precise_lifetime, unused))
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,122 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// A universal frame-based communication protocol which can be used to exchange
// arbitrary structured data.
//
// In short:
//
// - Each transmission is comprised by one fixed-size frame.
// - Each frame contains a protocol version number.
// - Each frame contains an application frame type.
// - Each frame can contain an identifying tag.
// - Each frame can have application-specific data of up to UINT32_MAX size.
// - Transactions style messaging can be modeled on top using frame tags.
// - Lightweight API on top of libdispatch (aka GCD) -- close to the metal.
//
#include <dispatch/dispatch.h>
#import <Foundation/Foundation.h>
// Special frame tag that signifies "no tag". Your implementation should never
// create a reply for a frame with this tag.
static const uint32_t PTFrameNoTag = 0;
// Special frame type that signifies that the stream has ended.
static const uint32_t PTFrameTypeEndOfStream = 0;
// NSError domain
FOUNDATION_EXPORT NSString * const Lookin_PTProtocolErrorDomain;
@interface Lookin_PTProtocol : NSObject
// Queue on which to run data processing blocks.
@property dispatch_queue_t queue;
// Get the shared protocol object for *queue*
+ (Lookin_PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue;
// Initialize a new protocol object to use a specific queue.
- (id)initWithDispatchQueue:(dispatch_queue_t)queue;
// Initialize a new protocol object to use the current calling queue.
- (id)init;
#pragma mark Sending frames
// Generate a new tag that is unique within this protocol object.
- (uint32_t)newTag;
// Send a frame over *channel* with an optional payload and optional callback.
// If *callback* is not NULL, the block is invoked when either an error occured
// or when the frame (and payload, if any) has been completely sent.
- (void)sendFrameOfType:(uint32_t)frameType
tag:(uint32_t)tag
withPayload:(dispatch_data_t)payload
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error))callback;
#pragma mark Receiving frames
// Read frames over *channel* as they arrive.
// The onFrame handler is responsible for reading (or discarding) any payload
// and call *resumeReadingFrames* afterwards to resume reading frames.
// To stop reading frames, simply do not invoke *resumeReadingFrames*.
// When the stream ends, a frame of type PTFrameTypeEndOfStream is received.
- (void)readFramesOverChannel:(dispatch_io_t)channel
onFrame:(void(^)(NSError *error,
uint32_t type,
uint32_t tag,
uint32_t payloadSize,
dispatch_block_t resumeReadingFrames))onFrame;
// Read a single frame over *channel*. A frame of type PTFrameTypeEndOfStream
// denotes the stream has ended.
- (void)readFrameOverChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error,
uint32_t frameType,
uint32_t frameTag,
uint32_t payloadSize))callback;
#pragma mark Receiving frame payloads
// Read a complete payload. It's the callers responsibility to make sure
// payloadSize is not too large since memory will be automatically allocated
// where only payloadSize is the limit.
// The returned dispatch_data_t object owns *buffer* and thus you need to call
// dispatch_retain on *contiguousData* if you plan to keep *buffer* around after
// returning from the callback.
- (void)readPayloadOfSize:(size_t)payloadSize
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error,
dispatch_data_t contiguousData,
const uint8_t *buffer,
size_t bufferSize))callback;
// Discard data of *size* waiting on *channel*. *callback* can be NULL.
- (void)readAndDiscardDataOfSize:(size_t)size
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error, BOOL endOfStream))callback;
@end
@interface NSData (Lookin_PTProtocol)
// Creates a new dispatch_data_t object which references the receiver and uses
// the receivers bytes as its backing data. The returned dispatch_data_t object
// holds a reference to the recevier. It's the callers responsibility to call
// dispatch_release on the returned object when done.
- (dispatch_data_t)createReferencingDispatchData;
+ (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data;
@end
@interface NSDictionary (Lookin_PTProtocol)
// See description of -[NSData(Lookin_PTProtocol) createReferencingDispatchData]
- (dispatch_data_t)createReferencingDispatchData;
// Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure.
+ (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,428 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#import "Lookin_PTProtocol.h"
#import "Lookin_PTPrivate.h"
#import <objc/runtime.h>
static const uint32_t PTProtocolVersion1 = 1;
NSString * const Lookin_PTProtocolErrorDomain = @"PTProtocolError";
// This is what we send as the header for each frame.
typedef struct _PTFrame {
// The version of the frame and protocol.
uint32_t version;
// Type of frame
uint32_t type;
// Unless zero, a tag is retained in frames that are responses to previous
// frames. Applications can use this to build transactions or request-response
// logic.
uint32_t tag;
// If payloadSize is larger than zero, *payloadSize* number of bytes are
// following, constituting application-specific data.
uint32_t payloadSize;
} PTFrame;
@interface Lookin_PTProtocol () {
uint32_t nextFrameTag_;
@public
dispatch_queue_t queue_;
}
- (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload;
@end
static void _release_queue_local_protocol(void *objcobj) {
if (objcobj) {
Lookin_PTProtocol *protocol = (__bridge_transfer id)objcobj;
protocol->queue_ = NULL;
}
}
@interface Lookin_RQueueLocalIOFrameProtocol : Lookin_PTProtocol
@end
@implementation Lookin_RQueueLocalIOFrameProtocol
- (void)setQueue:(dispatch_queue_t)queue {
}
@end
@implementation Lookin_PTProtocol
+ (Lookin_PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue {
static const char currentQueueFrameProtocolKey;
//dispatch_queue_t queue = dispatch_get_current_queue();
Lookin_PTProtocol *currentQueueFrameProtocol = (__bridge Lookin_PTProtocol*)dispatch_queue_get_specific(queue, &currentQueueFrameProtocolKey);
if (!currentQueueFrameProtocol) {
currentQueueFrameProtocol = [[Lookin_RQueueLocalIOFrameProtocol alloc] initWithDispatchQueue:NULL];
currentQueueFrameProtocol->queue_ = queue; // reference, no retain, since we would create cyclic references
dispatch_queue_set_specific(queue, &currentQueueFrameProtocolKey, (__bridge_retained void*)currentQueueFrameProtocol, &_release_queue_local_protocol);
return (__bridge Lookin_PTProtocol*)dispatch_queue_get_specific(queue, &currentQueueFrameProtocolKey); // to avoid race conds
} else {
return currentQueueFrameProtocol;
}
}
- (id)initWithDispatchQueue:(dispatch_queue_t)queue {
if (!(self = [super init])) return nil;
queue_ = queue;
#if PT_DISPATCH_RETAIN_RELEASE
if (queue_) dispatch_retain(queue_);
#endif
return self;
}
- (id)init {
return [self initWithDispatchQueue:dispatch_get_main_queue()];
}
- (void)dealloc {
if (queue_) {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(queue_);
#endif
}
}
- (dispatch_queue_t)queue {
return queue_;
}
- (void)setQueue:(dispatch_queue_t)queue {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_queue_t prev_queue = queue_;
queue_ = queue;
if (queue_) dispatch_retain(queue_);
if (prev_queue) dispatch_release(prev_queue);
#else
queue_ = queue;
#endif
}
- (uint32_t)newTag {
return ++nextFrameTag_;
}
#pragma mark -
#pragma mark Creating frames
- (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload {
PTFrame *frame = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(PTFrame), 0);
frame->version = htonl(PTProtocolVersion1);
frame->type = htonl(type);
frame->tag = htonl(frameTag);
if (payload) {
size_t payloadSize = dispatch_data_get_size(payload);
assert(payloadSize <= UINT32_MAX);
frame->payloadSize = htonl((uint32_t)payloadSize);
} else {
frame->payloadSize = 0;
}
dispatch_data_t frameData = dispatch_data_create((const void*)frame, sizeof(PTFrame), queue_, ^{
CFAllocatorDeallocate(kCFAllocatorDefault, (void*)frame);
});
if (payload && frame->payloadSize != 0) {
// chain frame + payload
dispatch_data_t data = dispatch_data_create_concat(frameData, payload);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(frameData);
#endif
frameData = data;
}
return frameData;
}
#pragma mark -
#pragma mark Sending frames
- (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*))callback {
dispatch_data_t frame = [self createDispatchDataWithFrameOfType:frameType frameTag:tag payload:payload];
dispatch_io_write(channel, 0, frame, queue_, ^(bool done, dispatch_data_t data, int _errno) {
if (done && callback) {
callback(_errno == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]);
}
});
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(frame);
#endif
}
#pragma mark -
#pragma mark Receiving frames
- (void)readFrameOverChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, uint32_t frameType, uint32_t frameTag, uint32_t payloadSize))callback {
__block dispatch_data_t allData = NULL;
dispatch_io_read(channel, 0, sizeof(PTFrame), queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error);
size_t dataSize = data ? dispatch_data_get_size(data) : 0;
if (dataSize) {
if (!allData) {
allData = data;
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_retain(allData);
#endif
} else {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_data_t allDataPrev = allData;
allData = dispatch_data_create_concat(allData, data);
dispatch_release(allDataPrev);
#else
allData = dispatch_data_create_concat(allData, data);
#endif
}
}
if (done) {
if (error != 0) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], 0, 0, 0);
return;
}
if (dataSize == 0) {
callback(nil, PTFrameTypeEndOfStream, 0, 0);
return;
}
if (!allData || dispatch_data_get_size(allData) < sizeof(PTFrame)) {
#if PT_DISPATCH_RETAIN_RELEASE
if (allData) dispatch_release(allData);
#endif
callback([[NSError alloc] initWithDomain:Lookin_PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0);
return;
}
PTFrame *frame = NULL;
size_t size = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(allData, (const void **)&frame, &size); // precise lifetime guarantees bytes in frame will stay valid till the end of scope
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(allData);
#endif
if (!contiguousData) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], 0, 0, 0);
return;
}
frame->version = ntohl(frame->version);
if (frame->version != PTProtocolVersion1) {
callback([[NSError alloc] initWithDomain:Lookin_PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0);
} else {
frame->type = ntohl(frame->type);
frame->tag = ntohl(frame->tag);
frame->payloadSize = ntohl(frame->payloadSize);
callback(nil, frame->type, frame->tag, frame->payloadSize);
}
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(contiguousData);
#endif
}
});
}
- (void)readPayloadOfSize:(size_t)payloadSize overChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize))callback {
__block dispatch_data_t allData = NULL;
dispatch_io_read(channel, 0, payloadSize, queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error);
size_t dataSize = dispatch_data_get_size(data);
if (dataSize) {
if (!allData) {
allData = data;
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_retain(allData);
#endif
} else {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_data_t allDataPrev = allData;
allData = dispatch_data_create_concat(allData, data);
dispatch_release(allDataPrev);
#else
allData = dispatch_data_create_concat(allData, data);
#endif
}
}
if (done) {
if (error != 0) {
#if PT_DISPATCH_RETAIN_RELEASE
if (allData) dispatch_release(allData);
#endif
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], NULL, NULL, 0);
return;
}
if (dataSize == 0) {
#if PT_DISPATCH_RETAIN_RELEASE
if (allData) dispatch_release(allData);
#endif
callback(nil, NULL, NULL, 0);
return;
}
uint8_t *buffer = NULL;
size_t bufferSize = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = NULL;
if (allData) {
contiguousData = dispatch_data_create_map(allData, (const void **)&buffer, &bufferSize);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(allData); allData = NULL;
#endif
if (!contiguousData) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], NULL, NULL, 0);
return;
}
}
callback(nil, contiguousData, buffer, bufferSize);
#if PT_DISPATCH_RETAIN_RELEASE
if (contiguousData) dispatch_release(contiguousData);
#endif
}
});
}
- (void)readAndDiscardDataOfSize:(size_t)size overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*, BOOL))callback {
dispatch_io_read(channel, 0, size, queue_, ^(bool done, dispatch_data_t data, int error) {
if (done && callback) {
size_t dataSize = data ? dispatch_data_get_size(data) : 0;
callback(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], dataSize == 0);
}
});
}
- (void)readFramesOverChannel:(dispatch_io_t)channel onFrame:(void(^)(NSError*, uint32_t, uint32_t, uint32_t, dispatch_block_t))onFrame {
[self readFrameOverChannel:channel callback:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize) {
onFrame(error, type, tag, payloadSize, ^{
if (type != PTFrameTypeEndOfStream) {
[self readFramesOverChannel:channel onFrame:onFrame];
}
});
}];
}
@end
@interface Lookin_PTDispatchData : NSObject {
dispatch_data_t dispatchData_;
}
@end
@implementation Lookin_PTDispatchData
- (id)initWithDispatchData:(dispatch_data_t)dispatchData {
if (!(self = [super init])) return nil;
dispatchData_ = dispatchData;
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_retain(dispatchData_);
#endif
return self;
}
- (void)dealloc {
#if PT_DISPATCH_RETAIN_RELEASE
if (dispatchData_) dispatch_release(dispatchData_);
#endif
}
@end
@implementation NSData (Lookin_PTProtocol)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-getter-return-value"
- (dispatch_data_t)createReferencingDispatchData {
// Note: The queue is used to submit the destructor. Since we only perform an
// atomic release of self, it doesn't really matter which queue is used, thus
// we use the current calling queue.
return dispatch_data_create((const void*)self.bytes, self.length, dispatch_get_main_queue(), ^{
// trick to have the block capture the data, thus retain/releasing
[self length];
});
}
#pragma clang diagnostic pop
+ (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data {
if (!data) {
return nil;
}
uint8_t *buffer = NULL;
size_t bufferSize = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize);
if (!contiguousData) {
return nil;
}
Lookin_PTDispatchData *dispatchDataRef = [[Lookin_PTDispatchData alloc] initWithDispatchData:contiguousData];
NSData *newData = [NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO];
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(contiguousData);
#endif
static const bool kDispatchDataRefKey;
objc_setAssociatedObject(newData, (const void*)kDispatchDataRefKey, dispatchDataRef, OBJC_ASSOCIATION_RETAIN);
return newData;
}
@end
@implementation NSDictionary (Lookin_PTProtocol)
- (dispatch_data_t)createReferencingDispatchData {
NSError *error = nil;
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
if (!plistData) {
NSLog(@"Failed to serialize property list: %@", error);
return nil;
} else {
return [plistData createReferencingDispatchData];
}
}
// Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure.
+ (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data {
if (!data) {
return nil;
}
uint8_t *buffer = NULL;
size_t bufferSize = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize);
if (!contiguousData) {
return nil;
}
NSDictionary *dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:nil];
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(contiguousData);
#endif
return dict;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,88 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#include <dispatch/dispatch.h>
#import <Foundation/Foundation.h>
// Lookin_PTUSBDeviceDidAttachNotification
// Posted when a device has been attached. Also posted for each device that is
// already attached when the Lookin_PTUSBHub starts listening.
//
// .userInfo = {
// DeviceID = 3;
// MessageType = Attached;
// Properties = {
// ConnectionSpeed = 480000000;
// ConnectionType = USB;
// DeviceID = 3;
// LocationID = 1234567890;
// ProductID = 1234;
// SerialNumber = 0123456789abcdef0123456789abcdef01234567;
// };
// }
//
FOUNDATION_EXPORT NSString * const Lookin_PTUSBDeviceDidAttachNotification;
// Lookin_PTUSBDeviceDidDetachNotification
// Posted when a device has been detached.
//
// .userInfo = {
// DeviceID = 3;
// MessageType = Detached;
// }
//
FOUNDATION_EXPORT NSString * const Lookin_PTUSBDeviceDidDetachNotification;
// NSError domain
FOUNDATION_EXPORT NSString * const Lookin_PTUSBHubErrorDomain;
// Error codes returned with NSError.code for NSError domain Lookin_PTUSBHubErrorDomain
typedef enum {
PTUSBHubErrorBadDevice = 2,
PTUSBHubErrorConnectionRefused = 3,
} PTUSBHubError;
@interface Lookin_PTUSBHub : NSObject
// Shared, implicitly opened hub.
+ (Lookin_PTUSBHub*)sharedHub;
// Connect to a TCP *port* on a device, while the actual transport is over USB.
// Upon success, *error* is nil and *channel* is a duplex I/O channel.
// You can retrieve the underlying file descriptor using
// dispatch_io_get_descriptor(channel). The dispatch_io_t channel behaves just
// like any stream type dispatch_io_t, making it possible to use the same logic
// for both USB bridged connections and e.g. ethernet-based connections.
//
// *onStart* is called either when a connection failed, in which case the error
// argument is non-nil, or when the connection was successfully established (the
// error argument is nil). Must not be NULL.
//
// *onEnd* is called when a connection was open and just did close. If the error
// argument is non-nil, the channel closed because of an error. Pass NULL for no
// callback.
//
- (void)connectToDevice:(NSNumber*)deviceID
port:(int)port
onStart:(void(^)(NSError *error, dispatch_io_t channel))onStart
onEnd:(void(^)(NSError *error))onEnd;
// Start listening for devices. You only need to invoke this method on custom
// instances to start receiving notifications. The shared instance returned from
// +sharedHub is always in listening mode.
//
// *onStart* is called either when the system failed to start listening, in
// which case the error argument is non-nil, or when the receiver is listening.
// Pass NULL for no callback.
//
// *onEnd* is called when listening stopped. If the error argument is non-nil,
// listening stopped because of an error. Pass NULL for no callback.
//
- (void)listenOnQueue:(dispatch_queue_t)queue
onStart:(void(^)(NSError*))onStart
onEnd:(void(^)(NSError*))onEnd;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,674 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#import "Lookin_PTUSBHub.h"
#import "Lookin_PTPrivate.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <err.h>
NSString * const Lookin_PTUSBHubErrorDomain = @"PTUSBHubError";
typedef uint32_t USBMuxPacketType;
enum {
USBMuxPacketTypeResult = 1,
USBMuxPacketTypeConnect = 2,
USBMuxPacketTypeListen = 3,
USBMuxPacketTypeDeviceAdd = 4,
USBMuxPacketTypeDeviceRemove = 5,
// ? = 6,
// ? = 7,
USBMuxPacketTypePlistPayload = 8,
};
typedef uint32_t USBMuxPacketProtocol;
enum {
USBMuxPacketProtocolBinary = 0,
USBMuxPacketProtocolPlist = 1,
};
typedef uint32_t USBMuxReplyCode;
enum {
USBMuxReplyCodeOK = 0,
USBMuxReplyCodeBadCommand = 1,
USBMuxReplyCodeBadDevice = 2,
USBMuxReplyCodeConnectionRefused = 3,
// ? = 4,
// ? = 5,
USBMuxReplyCodeBadVersion = 6,
};
typedef struct usbmux_packet {
uint32_t size;
USBMuxPacketProtocol protocol;
USBMuxPacketType type;
uint32_t tag;
char data[0];
} __attribute__((__packed__)) usbmux_packet_t;
static const uint32_t kUsbmuxPacketMaxPayloadSize = UINT32_MAX - (uint32_t)sizeof(usbmux_packet_t);
static uint32_t usbmux_packet_payload_size(usbmux_packet_t *upacket) {
return upacket->size - sizeof(usbmux_packet_t);
}
static void *usbmux_packet_payload(usbmux_packet_t *upacket) {
return (void*)upacket->data;
}
static void usbmux_packet_set_payload(usbmux_packet_t *upacket,
const void *payload,
uint32_t payloadLength)
{
memcpy(usbmux_packet_payload(upacket), payload, payloadLength);
}
static usbmux_packet_t *usbmux_packet_alloc(uint32_t payloadSize) {
assert(payloadSize <= kUsbmuxPacketMaxPayloadSize);
uint32_t upacketSize = sizeof(usbmux_packet_t) + payloadSize;
usbmux_packet_t *upacket = CFAllocatorAllocate(kCFAllocatorDefault, upacketSize, 0);
memset(upacket, 0, sizeof(usbmux_packet_t));
upacket->size = upacketSize;
return upacket;
}
static usbmux_packet_t *usbmux_packet_create(USBMuxPacketProtocol protocol,
USBMuxPacketType type,
uint32_t tag,
const void *payload,
uint32_t payloadSize)
{
usbmux_packet_t *upacket = usbmux_packet_alloc(payloadSize);
if (!upacket) {
return NULL;
}
upacket->protocol = protocol;
upacket->type = type;
upacket->tag = tag;
if (payload && payloadSize) {
usbmux_packet_set_payload(upacket, payload, (uint32_t)payloadSize);
}
return upacket;
}
static void usbmux_packet_free(usbmux_packet_t *upacket) {
CFAllocatorDeallocate(kCFAllocatorDefault, upacket);
}
NSString * const Lookin_PTUSBDeviceDidAttachNotification = @"Lookin_PTUSBDeviceDidAttachNotification";
NSString * const Lookin_PTUSBDeviceDidDetachNotification = @"Lookin_PTUSBDeviceDidDetachNotification";
static NSString *kPlistPacketTypeListen = @"Listen";
static NSString *kPlistPacketTypeConnect = @"Connect";
// Represents a channel of communication between the host process and a remote
// (device) system. In practice, a Lookin_PTUSBChannel is connected to a usbmuxd
// endpoint which is configured to either listen for device changes (the
// PTUSBHub's channel is usually configured as a device notification listener) or
// configured as a TCP bridge (e.g. channels returned from PTUSBHub's
// connectToDevice:port:callback:). You should not create channels yourself, but
// let Lookin_PTUSBHub provide you with already configured channels.
@interface Lookin_PTUSBChannel : NSObject {
dispatch_io_t channel_;
dispatch_queue_t queue_;
uint32_t nextPacketTag_;
NSMutableDictionary *responseQueue_;
BOOL autoReadPackets_;
BOOL isReadingPackets_;
}
// The underlying dispatch I/O channel. This is handy if you want to handle your
// own I/O logic without Lookin_PTUSBChannel. Remember to dispatch_retain() the channel
// if you plan on using it as it might be released from the Lookin_PTUSBChannel at any
// point in time.
@property (readonly) dispatch_io_t dispatchChannel;
// The underlying file descriptor.
@property (readonly) dispatch_fd_t fileDescriptor;
// Send data
- (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback;
- (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback;
// Read data
- (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback;
// Close the channel, preventing further reads and writes, but letting currently
// queued reads and writes finish.
- (void)cancel;
// Close the channel, preventing further reads and writes, immediately
// terminating any ongoing reads and writes.
- (void)stop;
@end
@interface Lookin_PTUSBChannel (Private)
+ (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload;
- (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError *error))onEnd;
- (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback;
- (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error;
- (uint32_t)nextPacketTag;
- (void)sendPacketOfType:(USBMuxPacketType)type overProtocol:(USBMuxPacketProtocol)protocol tag:(uint32_t)tag payload:(NSData*)payload callback:(void(^)(NSError*))callback;
- (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError *error))callback;
- (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError *error, NSDictionary *responsePacket))callback;
- (void)scheduleReadPacketWithCallback:(void(^)(NSError *error, NSDictionary *packet, uint32_t packetTag))callback;
- (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler;
- (void)setNeedsReadingPacket;
@end
@interface Lookin_PTUSBHub () {
Lookin_PTUSBChannel *channel_;
}
- (void)handleBroadcastPacket:(NSDictionary*)packet;
@end
@implementation Lookin_PTUSBHub
+ (Lookin_PTUSBHub*)sharedHub {
static Lookin_PTUSBHub *gSharedHub;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gSharedHub = [Lookin_PTUSBHub new];
[gSharedHub listenOnQueue:dispatch_get_main_queue() onStart:^(NSError *error) {
if (error) {
NSLog(@"Lookin_PTUSBHub failed to initialize: %@", error);
}
} onEnd:nil];
});
return gSharedHub;
}
- (id)init {
if (!(self = [super init])) return nil;
return self;
}
- (void)listenOnQueue:(dispatch_queue_t)queue onStart:(void(^)(NSError*))onStart onEnd:(void(^)(NSError*))onEnd {
if (channel_) {
if (onStart) onStart(nil);
return;
}
channel_ = [Lookin_PTUSBChannel new];
NSError *error = nil;
if ([channel_ openOnQueue:queue error:&error onEnd:onEnd]) {
[channel_ listenWithBroadcastHandler:^(NSDictionary *packet) { [self handleBroadcastPacket:packet]; } callback:onStart];
} else if (onStart) {
onStart(error);
}
}
- (void)connectToDevice:(NSNumber*)deviceID port:(int)port onStart:(void(^)(NSError*, dispatch_io_t))onStart onEnd:(void(^)(NSError*))onEnd {
Lookin_PTUSBChannel *channel = [Lookin_PTUSBChannel new];
NSError *error = nil;
if (![channel openOnQueue:dispatch_get_main_queue() error:&error onEnd:onEnd]) {
onStart(error, nil);
return;
}
port = ((port<<8) & 0xFF00) | (port>>8); // limit
NSDictionary *packet = [Lookin_PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeConnect
payload:[NSDictionary dictionaryWithObjectsAndKeys:
deviceID, @"DeviceID",
[NSNumber numberWithInt:port], @"PortNumber",
nil]];
[channel sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) {
NSError *error = error_;
[channel errorFromPlistResponse:responsePacket error:&error];
onStart(error, (error ? nil : channel.dispatchChannel) );
}];
}
- (void)handleBroadcastPacket:(NSDictionary*)packet {
NSString *messageType = [packet objectForKey:@"MessageType"];
if ([@"Attached" isEqualToString:messageType]) {
[[NSNotificationCenter defaultCenter] postNotificationName:Lookin_PTUSBDeviceDidAttachNotification object:self userInfo:packet];
} else if ([@"Detached" isEqualToString:messageType]) {
[[NSNotificationCenter defaultCenter] postNotificationName:Lookin_PTUSBDeviceDidDetachNotification object:self userInfo:packet];
} else {
NSLog(@"Warning: Unhandled broadcast message: %@", packet);
}
}
@end
#pragma mark -
@implementation Lookin_PTUSBChannel
+ (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload {
NSDictionary *packet = nil;
static NSString *bundleName = nil;
static NSString *bundleVersion = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary;
if (infoDict) {
bundleName = [infoDict objectForKey:@"CFBundleName"];
bundleVersion = [[infoDict objectForKey:@"CFBundleVersion"] description];
}
});
if (bundleName) {
packet = [NSDictionary dictionaryWithObjectsAndKeys:
messageType, @"MessageType",
bundleName, @"ProgName",
bundleVersion, @"ClientVersionString",
nil];
} else {
packet = [NSDictionary dictionaryWithObjectsAndKeys:messageType, @"MessageType", nil];
}
if (payload) {
NSMutableDictionary *mpacket = [NSMutableDictionary dictionaryWithDictionary:payload];
[mpacket addEntriesFromDictionary:packet];
packet = mpacket;
}
return packet;
}
- (id)init {
if (!(self = [super init])) return nil;
return self;
}
- (void)dealloc {
//NSLog(@"dealloc %@", self);
if (channel_) {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(channel_);
#endif
channel_ = nil;
}
}
- (BOOL)valid {
return !!channel_;
}
- (dispatch_io_t)dispatchChannel {
return channel_;
}
- (dispatch_fd_t)fileDescriptor {
return dispatch_io_get_descriptor(channel_);
}
- (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError*))onEnd {
assert(queue != nil);
assert(channel_ == nil);
queue_ = queue;
// Create socket
dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
return NO;
}
// prevent SIGPIPE
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
// Connect socket
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/var/run/usbmuxd");
socklen_t socklen = sizeof(addr);
if (connect(fd, (struct sockaddr*)&addr, socklen) == -1) {
if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
return NO;
}
channel_ = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue_, ^(int error) {
close(fd);
if (onEnd) {
onEnd(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]);
}
});
return YES;
}
- (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback {
autoReadPackets_ = YES;
[self scheduleReadPacketWithBroadcastHandler:broadcastHandler];
NSDictionary *packet = [Lookin_PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeListen payload:nil];
[self sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) {
if (!callback)
return;
NSError *error = error_;
[self errorFromPlistResponse:responsePacket error:&error];
callback(error);
}];
}
- (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error {
if (!*error) {
NSNumber *n = [packet objectForKey:@"Number"];
if (!n) {
*error = [NSError errorWithDomain:Lookin_PTUSBHubErrorDomain code:(n ? n.integerValue : 0) userInfo:nil];
return NO;
}
USBMuxReplyCode replyCode = (USBMuxReplyCode)n.integerValue;
if (replyCode != 0) {
NSString *errmessage = @"Unspecified error";
switch (replyCode) {
case USBMuxReplyCodeBadCommand: errmessage = @"illegal command"; break;
case USBMuxReplyCodeBadDevice: errmessage = @"unknown device"; break;
case USBMuxReplyCodeConnectionRefused: errmessage = @"connection refused"; break;
case USBMuxReplyCodeBadVersion: errmessage = @"invalid version"; break;
default: break;
}
*error = [NSError errorWithDomain:Lookin_PTUSBHubErrorDomain code:replyCode userInfo:[NSDictionary dictionaryWithObject:errmessage forKey:NSLocalizedDescriptionKey]];
return NO;
}
}
return YES;
}
- (uint32_t)nextPacketTag {
return ++nextPacketTag_;
}
- (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError*, NSDictionary*))callback {
uint32_t tag = [self nextPacketTag];
[self sendPacket:packet tag:tag callback:^(NSError *error) {
if (error) {
callback(error, nil);
return;
}
// TODO: timeout un-triggered callbacks in responseQueue_
if (!self->responseQueue_) self->responseQueue_ = [NSMutableDictionary new];
[self->responseQueue_ setObject:callback forKey:[NSNumber numberWithUnsignedInt:tag]];
}];
// We are awaiting a response
[self setNeedsReadingPacket];
}
- (void)setNeedsReadingPacket {
if (!isReadingPackets_) {
[self scheduleReadPacketWithBroadcastHandler:nil];
}
}
- (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler {
assert(isReadingPackets_ == NO);
[self scheduleReadPacketWithCallback:^(NSError *error, NSDictionary *packet, uint32_t packetTag) {
// Interpret the package we just received
if (packetTag == 0) {
// Broadcast message
//NSLog(@"Received broadcast: %@", packet);
if (broadcastHandler) broadcastHandler(packet);
} else if (self->responseQueue_) {
// Reply
NSNumber *key = [NSNumber numberWithUnsignedInt:packetTag];
void(^requestCallback)(NSError*,NSDictionary*) = [self->responseQueue_ objectForKey:key];
if (requestCallback) {
[self->responseQueue_ removeObjectForKey:key];
requestCallback(error, packet);
} else {
NSLog(@"Warning: Ignoring reply packet for which there is no registered callback. Packet => %@", packet);
}
}
// Schedule reading another incoming package
if (self->autoReadPackets_) {
[self scheduleReadPacketWithBroadcastHandler:broadcastHandler];
}
}];
}
- (void)scheduleReadPacketWithCallback:(void(^)(NSError*, NSDictionary*, uint32_t))callback {
static usbmux_packet_t ref_upacket;
isReadingPackets_ = YES;
// Read the first `sizeof(ref_upacket.size)` bytes off the channel_
dispatch_io_read(channel_, 0, sizeof(ref_upacket.size), queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read 0,4: done=%d data=%p error=%d", done, data, error);
if (!done)
return;
if (error) {
self->isReadingPackets_ = NO;
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0);
return;
}
// Read size of incoming usbmux_packet_t
uint32_t upacket_len = 0;
char *buffer = NULL;
size_t buffer_size = 0;
PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size); // objc_precise_lifetime guarantees 'map_data' isn't released before memcpy has a chance to do its thing
assert(buffer_size == sizeof(ref_upacket.size));
assert(sizeof(upacket_len) == sizeof(ref_upacket.size));
memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(map_data);
#endif
// Allocate a new usbmux_packet_t for the expected size
uint32_t payloadLength = upacket_len - (uint32_t)sizeof(usbmux_packet_t);
usbmux_packet_t *upacket = usbmux_packet_alloc(payloadLength);
// Read rest of the incoming usbmux_packet_t
off_t offset = sizeof(ref_upacket.size);
dispatch_io_read(self->channel_, offset, (size_t)(upacket->size - offset), self->queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read X,Y: done=%d data=%p error=%d", done, data, error);
if (!done) {
return;
}
self->isReadingPackets_ = NO;
if (error) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0);
usbmux_packet_free(upacket);
return;
}
if (upacket_len > kUsbmuxPacketMaxPayloadSize) {
callback(
[[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:1 userInfo:@{
NSLocalizedDescriptionKey:@"Received a packet that is too large"}],
nil,
0
);
usbmux_packet_free(upacket);
return;
}
// Copy read bytes onto our usbmux_packet_t
char *buffer = NULL;
size_t buffer_size = 0;
PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
assert(buffer_size == upacket->size - offset);
memcpy(((void *)(upacket))+offset, (const void *)buffer, buffer_size);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(map_data);
#endif
// We only support plist protocol
if (upacket->protocol != USBMuxPacketProtocolPlist) {
callback([[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package protocol" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag);
usbmux_packet_free(upacket);
return;
}
// Only one type of packet in the plist protocol
if (upacket->type != USBMuxPacketTypePlistPayload) {
callback([[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package type" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag);
usbmux_packet_free(upacket);
return;
}
// Try to decode any payload as plist
NSError *err = nil;
NSDictionary *dict = nil;
if (usbmux_packet_payload_size(upacket)) {
dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err];
}
// Invoke callback
callback(err, dict, upacket->tag);
usbmux_packet_free(upacket);
});
});
}
- (void)sendPacketOfType:(USBMuxPacketType)type
overProtocol:(USBMuxPacketProtocol)protocol
tag:(uint32_t)tag
payload:(NSData*)payload
callback:(void(^)(NSError*))callback
{
assert(payload.length <= kUsbmuxPacketMaxPayloadSize);
usbmux_packet_t *upacket = usbmux_packet_create(
protocol,
type,
tag,
payload ? payload.bytes : nil,
(uint32_t)(payload ? payload.length : 0)
);
dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, queue_, ^{
// Free packet when data is freed
usbmux_packet_free(upacket);
});
//NSData *data1 = [NSData dataWithBytesNoCopy:(void*)upacket length:upacket->size freeWhenDone:NO];
//[data1 writeToFile:[NSString stringWithFormat:@"/Users/rsms/c-packet-%u.data", tag] atomically:NO];
[self sendDispatchData:data callback:callback];
}
- (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError*))callback {
NSError *error = nil;
// NSPropertyListBinaryFormat_v1_0
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packet format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
if (!plistData) {
callback(error);
} else {
[self sendPacketOfType:USBMuxPacketTypePlistPayload overProtocol:USBMuxPacketProtocolPlist tag:tag payload:plistData callback:callback];
}
}
- (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback {
off_t offset = 0;
dispatch_io_write(channel_, offset, data, queue_, ^(bool done, dispatch_data_t data, int _errno) {
//NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, error);
if (!done)
return;
if (callback) {
NSError *err = nil;
if (_errno) err = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil];
callback(err);
}
});
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write
#endif
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-getter-return-value"
- (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback {
dispatch_data_t ddata = dispatch_data_create((const void*)data.bytes, data.length, queue_, ^{
// trick to have the block capture and retain the data
[data length];
});
[self sendDispatchData:ddata callback:callback];
}
#pragma clang diagnostic pop
- (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback {
dispatch_io_read(channel_, offset, length, queue_, ^(bool done, dispatch_data_t data, int _errno) {
if (!done)
return;
NSError *error = nil;
if (_errno != 0) {
error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil];
}
callback(error, data);
});
}
- (void)cancel {
if (channel_) {
dispatch_io_close(channel_, 0);
}
}
- (void)stop {
if (channel_) {
dispatch_io_close(channel_, DISPATCH_IO_STOP);
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,28 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Peertalk.h
// Peertalk
//
// Created by Marek Cirkos on 12/04/2016.
//
//
#import <Foundation/Foundation.h>
//! Project version number for Peertalk.
FOUNDATION_EXPORT double PeertalkVersionNumber;
//! Project version string for Peertalk.
FOUNDATION_EXPORT const unsigned char PeertalkVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Peertalk/PublicHeader.h>
#import "Lookin_PTChannel.h"
#import "Lookin_PTProtocol.h"
#import "Lookin_PTUSBHub.h"
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */