// // AppDelegate.m // LekangGuard // // Created by ecell on 2022/9/21. // #import "AppDelegate.h" #import "TabBarViewController.h" #import "LoginViewController.h" #import "PPGetAddressBook.h" #import "ChatViewController.h" #import "MessageViewController.h" #define kMusicTime 20 #define videoTimerCount 3 @interface AppDelegate () @property (nonatomic, strong) CLLocationManager *locationManager; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. [UIApplication sharedApplication].applicationIconBadgeNumber = 0; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.backgroundColor = UIColor.whiteColor; [self showRootViewController]; [self initIQKeyBorard]; [self.window makeKeyAndVisible]; if(CurrentSystemVersion.doubleValue < 13.0){ //ios 13.0 以下的系统注册 VOIP [self initPushKit]; } [self registerForRemoteNotification]; [PPGetAddressBook requestAddressBookAuthorization]; //高德 poi 搜索 [AMapServices sharedServices].apiKey = AMapKey; [self upding]; return YES; } #pragma mark 键盘自动调整位置 - (void)initIQKeyBorard{ IQKeyboardManager *manager = [IQKeyboardManager sharedManager]; manager.enable = YES; manager.shouldResignOnTouchOutside = YES; // manager.shouldToolbarUsesTextFieldTintColor = NO; manager.enableAutoToolbar = NO; } - (void)closeIQKeyBorard{ IQKeyboardManager *manager = [IQKeyboardManager sharedManager]; manager.enable = NO; manager.shouldResignOnTouchOutside = NO; // manager.shouldToolbarUsesTextFieldTintColor = YES; } /** MARK: 推送注册 PushKit */ - (void)initPushKit { if (CurrentSystemVersion.floatValue >= 8.0) { UIUserNotificationSettings *userNotifiSetting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:userNotifiSetting]; PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:nil]; pushRegistry.delegate = self; pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } } /** MARK: -----注册获取设备 PushKit 唯一 token 这个代理方法是获取了设备的唯一tokenStr,是要给服务器的 */ - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type{ _deviceVoIPTokenData = pushCredentials.token; uint8_t* tokenBytes = (uint8_t*)pushCredentials.token.bytes; NSMutableString* output = [NSMutableString stringWithCapacity:pushCredentials.token.length * 2]; for(int i = 0; i < pushCredentials.token.length; i++) { [output appendFormat:@"%02x", tokenBytes[i]]; } _deviceVoIPTokenStr = [output copy]; NSLog(@"PushKit 注册的 VoIP token: %@",_deviceVoIPTokenStr); // if(_deviceTokenStr && _deviceVoIPTokenStr){ // [User setDeviceToken:_deviceTokenStr AndVoIPToken:_deviceVoIPTokenStr]; // } } /// MARK:程序杀掉进程,重启,退到后台,服务器推送过来的消息都会走 这里 - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { //提示的文字 alert NSString * alertStr = [[payload.dictionaryPayload objectForKey:@"aps"] objectForKey:@"alert"]; NSDictionary *dataDic =[payload.dictionaryPayload objectForKey:@"data"]; NSString *NotificationType = dataDic[@"message"][@"type"]; int video_type = [dataDic[@"message"][@"video_type"] intValue]; if ([NotificationType isEqualToString:@"video"]) { NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithDictionary:payload.dictionaryPayload]; [mDic setValue:alertStr forKey:@"alertStr"]; [self setNotificationData:mDic]; // //MARK:收到视频消息,震动一下 // AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);//执行震动 // if([UIApplication sharedApplication].applicationState == UIApplicationStateActive){ // //判断如果是前台,推送通知 // [[NSNotificationCenter defaultCenter] postNotificationName:AccountMessageNotification object:dataDic userInfo:dataDic]; // if(self.backgroudMsg){ // [self cleanVideoNotification]; // self.backgroudMsg = nil; // } // // }else{ // if (video_type == 4) { //如果是菊风视频 // //在后台,先不显示本地通知,发通知,登录SDK先,收到设备的拨打,判断还在后台再显示本地通知 // [self setNotificationData:payload.dictionaryPayload]; // }else{ // [self generateLocalNotificationsWithAlertStr:alertStr AndDataInfo:payload.dictionaryPayload]; // } // // // } } else if([NotificationType isEqualToString:@"unbind"]) { [self setNotificationData:payload.dictionaryPayload]; } else { NSLog(@"其他VOIP推送的消息: %@",payload.dictionaryPayload); // [SVProgressHUD showWithStatus:[NSString stringWithFormat:@"其他VOIP推送的消息: %@", payload.dictionaryPayload]]; AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);//执行震动 } } /// 登录 - (void)showRootViewController { NSLog(@"登录信息==%@",[APIManager sharedManager].loginModel); if ([APIManager sharedManager].loginModel && [APIManager sharedManager].loginModel.isLogin == YES) { self.window.rootViewController = [TabBarViewController new]; } else { ZXNavigationBarNavigationController *nav = [[ZXNavigationBarNavigationController alloc]initWithRootViewController:[LoginViewController new]]; self.window.rootViewController = nav; } } /** 收到推送的时候触发的 **/ -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [self setNotificationData:userInfo]; completionHandler(UIBackgroundFetchResultNewData); } ///** //在前台时推送消息才会调用的接口 ios 10 以上的新接口 // **/ //- (void)userNotificationCenter:(UNUserNotificationCenter *)center // willPresentNotification:(UNNotification *)notification // withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler //{ // [self setNotificationData:notification.request.content.userInfo]; //} /** MARK: APP 挂后台的情况下 点击 通知栏跳转到响应页面 @param response 收到的 推送消息内容 response.notification.request.content.userInfo */ - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler { kWeakSelf(self) //MARK: 点击推送跳转对应的页面 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakself handlePushMsg:response.notification.request.content.userInfo]; }); } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error { NSLog(@"Registfail%@",error); } - (void)application:(UIApplication *)applicationdidRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken { NSLog(@"regisger success:%@",pToken); //注册成功,将deviceToken保存到应用服务器[数据库](http://lib.csdn.net/base/14)中 } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { // 处理推送消息 NSLog(@"userinfo:%@",userInfo); [self setNotificationData:userInfo]; } - (void)setNotificationData:(NSDictionary*)userInfo { if (![APIManager sharedManager].loginModel.isLogin) return; if(!userInfo){return;} NSDictionary *dict = userInfo[@"data"]; NSLog(@"消息推送:%@",userInfo); if (userInfo[@"aps"]) { if([dict[@"type"] isEqualToString: @"chatGroupMessage"]) { //群聊消息推送的问题 if(APIManager.sharedManager.loginModel.openid != nil ){ if ([APIManager.sharedManager.loginModel.openid isEqualToString:dict[@"message"][@"senderID"]]) { //如果是自己的消息静默处理 } else { //不是自己的消息 //首先先判断不在聊天界面 // UIViewController *vcc = [self currentViewController]; // if (![vcc isKindOfClass:[ChatViewController class]]) // { // return; // } // else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); } } } [[NSNotificationCenter defaultCenter] postNotificationName:PUSH_type_chatGroupMessage object:dict]; } else if([dict[@"type"] isEqualToString: @"accountMessage"]) { NSLog(@"收到消息中心的消息推送"); //消息中心的数据 [[NSNotificationCenter defaultCenter] postNotificationName:AccountMessageNotification object:dict userInfo:dict]; } else { //其他的消息 , 比如 主动定位推送的消息,群解散 等 的f [[NSNotificationCenter defaultCenter] postNotificationName:dict[@"type"] object:dict]; } } } - (void)handlePushMsg:(NSDictionary *)infoData { if (![APIManager sharedManager].loginModel.isLogin) return; NSLog(@"Userinfo %@",infoData); if(infoData.count == 0){ return; } //判断消息类型 跳转到对应的页面 NSDictionary *dict = infoData[@"data"]; if(!dict){ return; } UITabBarController *tabBarVC = (UITabBarController*)self.window.rootViewController; UINavigationController *navVC = (UINavigationController*)tabBarVC.selectedViewController; if([dict[@"type"] isEqualToString:@"chatGroupMessage"]) { //聊天消息 //判断imei 设置 主 device 并且 跳转到 对应的聊天界面 if(APIManager.sharedManager.loginModel.openid != nil ) { //senderID 不同 震动 if (![APIManager.sharedManager.loginModel.openid isEqualToString:dict[@"message"][@"senderID"]]) { //不同账号的微聊消息,且不在聊天页面,手机震动 if(![navVC.visibleViewController isKindOfClass:[ChatViewController class]]) { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); } AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); // 跳转 聊天页面 if(navVC) { ChatViewController *vc = [ChatViewController new]; [[UICommon currentVC].navigationController pushViewController:vc animated:YES]; } } } } else { //其他消息类型 NSDictionary *msgDic = dict[@"message"]; NSString * msgType = msgDic[@"type"]; NSInteger selectIndex = 0; //是否是申请的消息 if(!msgType || [msgType isEqualToString:@"unbind"]){ //被管理员取消关注不跳转页面 return ; } if(!msgType || [msgType isEqualToString:@"video"]){ //视频 return ; } if (!msgType || [msgType isEqualToString:@"battery"]) { return; } if(!msgType || [msgType isEqualToString:@"LoginOffLine"]) { //被异地推送挤下线 NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; [[[APIManager sharedManager] APGET:Logout_URL parameters:parameters resultClass:nil] subscribeNext:^(id _Nullable x) { [UICommon HidenLoading]; EasyAlertView *alertV = [EasyAlertView alertViewWithTitle:GJText(@"提示") subtitle:GJText(@"您的账号已在其他设备登录!") AlertViewType:AlertViewTypeSystemAlert config:nil]; [alertV addAlertItem:^EasyAlertItem *{ return [EasyAlertItem itemWithTitle:GJText(@"确定") type:AlertItemTypeSystemDefault callback:^(EasyAlertView *showview, long index) { [[NSNotificationCenter defaultCenter] postNotificationName:sRelogin object:nil]; }]; }]; [alertV showAlertView]; } error:^(NSError * _Nullable error) { NSDictionary *dic = error.userInfo; [UICommon MessageErrorText:dic[NSLocalizedDescriptionKey]]; }]; return ; } //lowBattery rail sms sos photograph 提醒消息 //checkResults checkedResults 关注消息 //identityTransfer identityTransfered systemUpdate 系统消息 if([msgType isEqualToString:@"lowBattery"] || [msgType isEqualToString:@"rail"] || [msgType isEqualToString:@"sms"] || [msgType isEqualToString:@"sos"] || [msgType isEqualToString:@"photograph"]){ selectIndex = 0; } if([msgType isEqualToString:@"checkResults"] || [msgType isEqualToString:@"checkedResults"] || [msgType isEqualToString:@"applicationRrecord"] || [msgType isEqualToString:@"applicationOvertime"]){ selectIndex = 1; } if([msgType isEqualToString:@"identityTransfer"] || [msgType isEqualToString:@"identityTransfered"] || [msgType isEqualToString:@"systemUpdate"]){ selectIndex = 2; } if([msgType isEqualToString:@"CostFlow"]){ selectIndex = 3; } // 跳转 消息中心页面 if(![msgType isEqualToString:@"photograph"]) { //前往查看 if ([navVC.visibleViewController isKindOfClass:[MessageViewController class]]) { UIViewController *viewController = navVC.viewControllers.lastObject; MessageViewController *tempVC = (MessageViewController*)viewController; if(selectIndex == 0) [tempVC updataRemindMessage]; else if (selectIndex == 1) [tempVC applicationRrecord]; else if (selectIndex == 2) [tempVC updateAllMessage]; else [tempVC updataCallMessage]; } else { MessageViewController *vc = [MessageViewController new]; vc.selectedIndex = selectIndex; [[UICommon currentVC].navigationController pushViewController:vc animated:YES]; } } } } /// 注册消息推送 - (void)registerForRemoteNotification { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center setDelegate:self]; [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) { if (!error) { NSLog(@"request authorization succeeded!"); } }]; [[UIApplication sharedApplication] registerForRemoteNotifications]; } #pragma mark ---注册 获取到 APNS 的 DeviceToken - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { uint8_t* tokenBytes = (uint8_t*)deviceToken.bytes; NSMutableString* output = [NSMutableString stringWithCapacity:deviceToken.length * 2]; for(int i = 0; i < deviceToken.length; i++) { [output appendFormat:@"%02x", tokenBytes[i]]; } NSString *deviceTokens = [UserDefaults objectForKey:DeviceTokenKey]; if(![deviceTokens isEqualToString:output]) [UserDefaults setObject:[output copy] forKey:DeviceTokenKey]; NSLog(@"_deviceTokenStr=%@", deviceTokens); } -(void)cleanVideoNotification { if(self.backgroudMsg){ // 使用 UNUserNotificationCenter 来管理通知 UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; NSString * videoIdentifier = @"VideoLocationNotificationRequest"; [center removePendingNotificationRequestsWithIdentifiers:@[videoIdentifier]]; } } -(void)setAlertStr:(NSString *)alertStr WithNotificationDate:(NSDictionary*)dataDic { @try { NSString * videoIdentifier = @"VideoLocationNotificationRequest"; //使用 UNNotification 本地通知 // 使用 UNUserNotificationCenter 来管理通知 UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; if(self.backgroudMsg){ [center removePendingNotificationRequestsWithIdentifiers:@[videoIdentifier]]; self.backgroudMsg = nil; } //需创建一个包含待通知内容的 UNMutableNotificationContent 对象,注意不是 UNNotificationContent ,此对象为不可变对象。 self.backgroudMsg = [[UNMutableNotificationContent alloc] init]; // self.backgroudMsg.title = alertStr; self.backgroudMsg.body = alertStr; self.backgroudMsg.userInfo = dataDic; self.backgroudMsg.sound = [UNNotificationSound soundNamed:@"videoring.caf"]; self.backgroudMsg.categoryIdentifier = videoIdentifier; // 在 1s 后推送本地推送 , nil 是立刻的意思 // UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:YES]; UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:videoIdentifier content:self.backgroudMsg trigger:nil]; //添加查看按钮 UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:@"enterApp" title:@"查看" options:UNNotificationActionOptionForeground]; UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"VideoLocationNotificationCategory" actions:@[action] intentIdentifiers:@[videoIdentifier] options:UNNotificationCategoryOptionNone]; [center setNotificationCategories:[NSSet setWithObject:category]]; //添加推送成功后的处理! [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { if(error){ NSLog(@"====视频通话本地推送添加成功===="); } }]; } @catch (NSException *exception) { NSLog(@"exception: %@",exception.description); } @finally { } } /** MARK: 创建一个平常的本地通知 */ -(void)setNormallyAlertStr:(NSString *)alertStr UserInfo:(NSDictionary*)userinfo { NSString * locNot = @"NormallyLocationNotificationRequest"; //使用 UNNotification 本地通知 // 使用 UNUserNotificationCenter 来管理通知 UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; if(self.backgroudMsg){ [center removePendingNotificationRequestsWithIdentifiers:@[locNot]]; self.backgroudMsg = nil; } //需创建一个包含待通知内容的 UNMutableNotificationContent 对象,注意不是 UNNotificationContent ,此对象为不可变对象。 self.backgroudMsg = [[UNMutableNotificationContent alloc] init]; self.backgroudMsg.body = alertStr; self.backgroudMsg.userInfo = userinfo; self.backgroudMsg.sound = [UNNotificationSound defaultSound]; UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:locNot content:self.backgroudMsg trigger:nil]; //添加查看按钮 UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:@"NOEnterApp" title:@"好的" options:UNNotificationActionOptionDestructive]; UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"NormallyLocationNotificationCategory" actions:@[action] intentIdentifiers:@[locNot] options:UNNotificationCategoryOptionNone]; [center setNotificationCategories:[NSSet setWithObject:category]]; //添加推送成功后的处理! [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { if(error){ NSLog(@"====\"普通的\"本地推送添加成功===="); } }]; } - (void)generateLocalNotificationsWithAlertStr:(NSString*)alertStr AndDataInfo:(NSDictionary*)dataInfo { self.alertNotificationCount = 0; [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:VideoTimerIdentify]; [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:PlayVibrationIdentify]; [[JX_GCDTimerManager sharedInstance] scheduledDispatchTimerWithName:VideoTimerIdentify timeInterval:kMusicTime queue:dispatch_get_main_queue() repeats:YES fireInstantly:YES action:^ { self.alertNotificationCount += 1; if(self.alertNotificationCount < videoTimerCount){ //取消掉之前的本地通知,然后重新弄一个 [self setAlertStr:alertStr WithNotificationDate:dataInfo]; }else{ [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:VideoTimerIdentify]; [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:PlayVibrationIdentify]; self.stopVibration = YES; [self cleanVideoNotification]; } }]; [self playVibration]; } -(void)playVibration { [[JX_GCDTimerManager sharedInstance] scheduledDispatchTimerWithName:PlayVibrationIdentify timeInterval:1.5 queue:dispatch_get_main_queue() repeats:YES fireInstantly:YES action:^{ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);//执行震动 }]; } //MARK: APP将要从后台返回 - (void)applicationWillEnterForeground:(UIApplication *)application { [UIApplication sharedApplication].applicationIconBadgeNumber = 0; // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. if(self.backgroudMsg){ //MARK: 取消本地推送 [self cleanVideoNotification]; // [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:VideoTimerIdentify]; // [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:PlayVibrationIdentify]; // self.stopVibration = YES; //APP 运行之后发送推送 [self setNotificationData:self.backgroudMsg.userInfo]; self.backgroudMsg = nil; } } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. //MARK:重新启动在应用程序处于非活动状态时暂停(或尚未启动)的任何任务。如果应用程序以前在后台,可以选择刷新用户界面。 if(self.backgroudMsg){ //MARK: 取消本地推送 [self cleanVideoNotification]; // [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:VideoTimerIdentify]; // [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:PlayVibrationIdentify]; // self.stopVibration = YES; //APP 运行之后发送推送 [self setNotificationData:self.backgroudMsg.userInfo]; self.backgroudMsg = nil; } } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - (void)upding { //定位一次自身的位置 //初始化管理器 self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; self.locationManager.delegate = self; [self.locationManager startUpdatingLocation]; //检查是否授权 if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { [self.locationManager requestWhenInUseAuthorization]; } } -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { CLLocation *userLocation = [locations lastObject]; self.curLocation = [userLocation locationMarsFromEarth]; [manager stopUpdatingLocation]; } -(UIViewController *) currentViewController { UIViewController *viewController = [UIApplication sharedApplication].keyWindow.rootViewController; return [self atPersentViewController:viewController]; } -(UIViewController *)atPersentViewController:(UIViewController *)vc { if (vc.presentedViewController) { return [self atPersentViewController:vc.presentedViewController]; } else if ([vc isKindOfClass:[UISplitViewController class]]) { UISplitViewController* svc = (UISplitViewController *) vc; if (svc.viewControllers.count > 0) return [self atPersentViewController:svc.viewControllers.lastObject]; else return vc; } else if ([vc isKindOfClass:[UINavigationController class]]) { UINavigationController *svc = (UINavigationController *) vc; if (svc.viewControllers.count > 0) return [self atPersentViewController:svc.topViewController]; else return vc; } else if ([vc isKindOfClass:[UITabBarController class]]) { UITabBarController *svc = (UITabBarController *) vc; if (svc.viewControllers.count > 0) return [self atPersentViewController:svc.selectedViewController]; else return vc; } else { return vc; } } @end