#ifdef SHOULD_COMPILE_LOOKIN_SERVER // // LookinServer.m // LookinServer // // Created by Li Kai on 2018/8/5. // https://lookin.work // #import "LKS_ConnectionManager.h" #import "Lookin_PTChannel.h" #import "LKS_RequestHandler.h" #import "LookinConnectionResponseAttachment.h" #import "LKS_ExportManager.h" #import "LookinServerDefines.h" #import "LKS_TraceManager.h" #import "LKS_MultiplatformAdapter.h" NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName"; @interface LKS_ConnectionManager () @property(nonatomic, weak) Lookin_PTChannel *peerChannel_; @property(nonatomic, strong) LKS_RequestHandler *requestHandler; @end @implementation LKS_ConnectionManager + (instancetype)sharedInstance { static LKS_ConnectionManager *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[LKS_ConnectionManager alloc] init]; }); return sharedInstance; } + (void)load { // 触发 init 方法 [LKS_ConnectionManager sharedInstance]; } - (instancetype)init { if (self = [super init]) { NSLog(@"LookinServer - Will launch. Framework version: %@", LOOKIN_SERVER_READABLE_VERSION); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_2D" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_3D" object:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { [[LKS_ExportManager sharedInstance] exportAndShare]; }]; [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_RelationSearch" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { [[LKS_TraceManager sharedInstance] addSearchTarger:note.object]; }]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGetLookinInfo:) name:@"GetLookinInfo" object:nil]; self.requestHandler = [LKS_RequestHandler new]; } return self; } - (void)_handleWillResignActiveNotification { self.applicationIsActive = NO; if (self.peerChannel_ && ![self.peerChannel_ isConnected]) { [self.peerChannel_ close]; self.peerChannel_ = nil; } } - (void)_handleApplicationDidBecomeActive { self.applicationIsActive = YES; [self searchPortToListenIfNoConnection]; } - (void)searchPortToListenIfNoConnection { if ([self.peerChannel_ isConnected]) { NSLog(@"LookinServer - Abort to search ports. Already has connected channel."); return; } NSLog(@"LookinServer - Searching port to listen..."); [self.peerChannel_ close]; self.peerChannel_ = nil; if ([self isiOSAppOnMac]) { [self _tryToListenOnPortFrom:LookinSimulatorIPv4PortNumberStart to:LookinSimulatorIPv4PortNumberEnd current:LookinSimulatorIPv4PortNumberStart]; } else { [self _tryToListenOnPortFrom:LookinUSBDeviceIPv4PortNumberStart to:LookinUSBDeviceIPv4PortNumberEnd current:LookinUSBDeviceIPv4PortNumberStart]; } } - (BOOL)isiOSAppOnMac { #if TARGET_OS_SIMULATOR return YES; #else if (@available(iOS 14.0, *)) { // isiOSAppOnMac 这个 API 看似在 iOS 14.0 上可用,但其实在 iOS 14 beta 上是不存在的、有 unrecognized selector 问题,因此这里要用 respondsToSelector 做一下保护 NSProcessInfo *info = [NSProcessInfo processInfo]; if ([info respondsToSelector:@selector(isiOSAppOnMac)]) { return [info isiOSAppOnMac]; } else if ([info respondsToSelector:@selector(isMacCatalystApp)]) { return [info isMacCatalystApp]; } else { return NO; } } if (@available(iOS 13.0, tvOS 13.0, *)) { return [NSProcessInfo processInfo].isMacCatalystApp; } return NO; #endif } - (void)_tryToListenOnPortFrom:(int)fromPort to:(int)toPort current:(int)currentPort { Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; channel.targetPort = currentPort; [channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { if (error) { if (error.code == 48) { // 该地址已被占用 } else { // 未知失败 } if (currentPort < toPort) { // 尝试下一个端口 NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@). Will try anothor address ...", currentPort, error); [self _tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)]; } else { // 所有端口都尝试完毕,全部失败 NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@).", currentPort, error); NSLog(@"LookinServer - Connect failed in the end."); } } else { // 成功 NSLog(@"LookinServer - Connected successfully on 127.0.0.1:%d", currentPort); // 此时 peerChannel_ 状态为 listening self.peerChannel_ = channel; } }]; } - (void)dealloc { if (self.peerChannel_) { [self.peerChannel_ close]; } [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { [self _sendData:data frameOfType:requestType tag:tag]; } - (void)pushData:(NSObject *)data type:(uint32_t)type { [self _sendData:data frameOfType:type tag:0]; } - (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag { if (self.peerChannel_) { NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; dispatch_data_t payload = [archivedData createReferencingDispatchData]; [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) { if (error) { } }]; } } #pragma mark - Lookin_PTChannelDelegate - (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { if (channel != self.peerChannel_) { return NO; } else if ([self.requestHandler canHandleRequestType:type]) { return YES; } else { [channel close]; return NO; } } - (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload { id object = nil; if (payload) { id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfDispatchData:payload.dispatchData]]; if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) { LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject; object = attachment.data; } else { object = unarchivedObject; } } [self.requestHandler handleRequestType:type tag:tag object:object]; } /// 当 Client 端链接成功时,该方法会被调用,然后 channel 的状态会变成 connected - (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address { NSLog(@"LookinServer - channel:%@, acceptConnection:%@", channel.debugTag, otherChannel.debugTag); Lookin_PTChannel *previousChannel = self.peerChannel_; otherChannel.targetPort = address.port; self.peerChannel_ = otherChannel; [previousChannel cancel]; } /// 当连接过 Lookin 客户端,然后 Lookin 客户端又被关闭时,会走到这里 - (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error { if (self.peerChannel_ != channel) { // Client 端第一次连接上时,之前 listen 的 port 会被 Peertalk 内部 cancel(并在 didAcceptConnection 方法里给业务抛一个新建的 connected 状态的 channel),那个被 cancel 的 channel 会走到这里 NSLog(@"LookinServer - Ignore channel%@ end.", channel.debugTag); return; } // Client 端关闭时,会走到这里 NSLog(@"LookinServer - channel%@ DidEndWithError:%@", channel.debugTag, error); [[NSNotificationCenter defaultCenter] postNotificationName:LKS_ConnectionDidEndNotificationName object:self]; [self searchPortToListenIfNoConnection]; } #pragma mark - Handler - (void)_handleLocalInspect:(NSNotification *)note { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Lookin" message:@"Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality." preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; [alertController addAction:okAction]; UIWindow *keyWindow = [LKS_MultiplatformAdapter keyWindow]; UIViewController *rootViewController = [keyWindow rootViewController]; [rootViewController presentViewController:alertController animated:YES completion:nil]; NSLog(@"LookinServer - Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality."); } - (void)handleGetLookinInfo:(NSNotification *)note { NSDictionary* userInfo = note.userInfo; if (!userInfo) { return; } NSMutableDictionary* infoWrapper = userInfo[@"infos"]; if (![infoWrapper isKindOfClass:[NSMutableDictionary class]]) { NSLog(@"LookinServer - GetLookinInfo failed. Params invalid."); return; } infoWrapper[@"lookinServerVersion"] = LOOKIN_SERVER_READABLE_VERSION; } @end /// 这个类使得用户可以通过 NSClassFromString(@"Lookin") 来判断 LookinServer 是否被编译进了项目里 @interface Lookin : NSObject @end @implementation Lookin @end #endif /* SHOULD_COMPILE_LOOKIN_SERVER */