背景
前段时间做了一些项目解耦重构和一些组件化的工作,推送是很多app种涉及到的应用场景,所以把推送模块做了一些重构的工作,让推送模块能够独立于业务适用于各种的业务场景。
分析
推送消息模块和其他模块从技术角度来看是数据同一级别的模块,推送消息模块为了能够和其他业务组件之间有联系和通信而又不能有耦合,这复合设计中的控制反转原则,依赖的双方依赖于对方的抽象而不依赖于具体的实现,观察者模式就是一种典型的控制反转场景,观察者模式很适合推送模块的独立和解耦。
实现
观察者模式简析
这是一个简单的观察者的类图结构
- 抽象接口
Subject
有一个注册观察者的方法 - 抽象接口
Observer
有一个获取 Subject
数据更新的方法 Subject
和 Observer
具体的子类重写对应的方法,处理数据 - 有新的消息,
ConcreateSubject
会把消息发送给已注册的ConcreateObserver
, ConcreateObserver
负责接收消息进行处理
代码实现
抽象接口
OC接口是使用 protocol 实现的,定义观察者(Observer)和被观察主题(Subject)如下:
@protocol PTNotificationObservable; @protocol PTNotificationObserver <NSObject> - (void)update:(id<PTNotificationObservable>)sender data:(id)data; @end @protocol PTNotificationObservable <NSObject> - (void)addObserver:(id<PTNotificationObserver>)observer; @end
具体事项
推送模块管理类对应的是被观察主题(Subject),定义了如下接口:
- APP启动,初始化推送配置
- 处理注册Token
- 处理接收消息
- 添加观察者
头文件如下:
#import <Foundation/Foundation.h> #import "PTNotificationProtocllDefine.h" #undef AS_SINGLETON #define AS_SINGLETON \ + (instancetype)sharedInstance; #undef DEF_SINGLETON #define DEF_SINGLETON \ + (instancetype)sharedInstance{ \ static dispatch_once_t once; \ static id __singleton__; \ dispatch_once( &once, ^{ __singleton__ = [[self alloc] init]; } ); \ return __singleton__; \ } \ @interface PTNotificationManager : NSObject <PTNotificationObservable> AS_SINGLETON // 处理APP启动,配置推送 - (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; // 处理注册Token - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; // 处理接收消息 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo; // 添加观察者 - (void)addObserver:(id<PTNotificationObserver>)observer; - (void)testSendNotification; @end
集成的是第三方的友盟消息推送,所有里面包含了友盟一些API的使用。
实现文件:
#import "PTNotificationManager.h" #import "UMessage.h" #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 #import <UserNotifications/UserNotifications.h> #endif #define UMengAppKey @"xxxxx" @interface PTNotificationManager ()<UNUserNotificationCenterDelegate> @property (nonatomic, strong) NSPointerArray* observers; @end @implementation PTNotificationManager DEF_SINGLETON - (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //设置 AppKey 及 LaunchOptions [UMessage startWithAppkey:UMengAppKey launchOptions:launchOptions]; //注册通知 [UMessage registerForRemoteNotifications]; //iOS10必须加下面这段代码。 if ([[[UIDevice currentDevice] systemVersion]intValue] >= 10) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate=self; UNAuthorizationOptions types10=UNAuthorizationOptionBadge|UNAuthorizationOptionAlert|UNAuthorizationOptionSound; [center requestAuthorizationWithOptions:types10 completionHandler:^(BOOL granted, NSError * _Nullable error) { if (granted) { //点击允许 } else { //点击不允许 } }]; } //如果你期望使用交互式(只有iOS 8.0及以上有)的通知,请参考下面注释部分的初始化代码 if (([[[UIDevice currentDevice] systemVersion]intValue]>=8)&&([[[UIDevice currentDevice] systemVersion]intValue]<10)) { // UIMutableUserNotificationAction *action1 = [[UIMutableUserNotificationAction alloc] init]; // action1.identifier = @"action1_identifier"; // action1.title=@"打开应用"; // action1.activationMode = UIUserNotificationActivationModeForeground;当点击的时候启动程序 // // UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init]; 第二按钮 // action2.identifier = @"action2_identifier"; // action2.title=@"忽略"; // action2.activationMode = UIUserNotificationActivationModeBackground;当点击的时候不启动程序,在后台处理 // action2.authenticationRequired = YES;需要解锁才能处理,如果action.activationMode = UIUserNotificationActivationModeForeground;则这个属性被忽略; // action2.destructive = YES; // UIMutableUserNotificationCategory *actionCategory1 = [[UIMutableUserNotificationCategory alloc] init]; // actionCategory1.identifier = @"category1";这组动作的唯一标示 // [actionCategory1 setActions:@[action1,action2] forContext:(UIUserNotificationActionContextDefault)]; // NSSet *categories = [NSSet setWithObjects:actionCategory1, nil]; // [UMessage registerForRemoteNotifications:categories]; } //如果要在iOS10显示交互式的通知,必须注意实现以下代码 if ([[[UIDevice currentDevice] systemVersion]intValue]>=10) { // UNNotificationAction *action1_ios10 = [UNNotificationAction actionWithIdentifier:@"action1_ios10_identifier" title:@"打开应用" options:UNNotificationActionOptionForeground]; // UNNotificationAction *action2_ios10 = [UNNotificationAction actionWithIdentifier:@"action2_ios10_identifier" title:@"忽略" options:UNNotificationActionOptionForeground]; //UNNotificationCategoryOptionNone //UNNotificationCategoryOptionCustomDismissAction 清除通知被触发会走通知的代理方法 //UNNotificationCategoryOptionAllowInCarPlay 适用于行车模式 // UNNotificationCategory *category1_ios10 = [UNNotificationCategory categoryWithIdentifier:@"category101" actions:@[action1_ios10,action2_ios10] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]; // NSSet *categories_ios10 = [NSSet setWithObjects:category1_ios10, nil]; // UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; // [center setNotificationCategories:categories_ios10]; } //如果对角标,文字和声音的取舍,请用下面的方法 //UIRemoteNotificationType types7 = UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound; //UIUserNotificationType types8 = UIUserNotificationTypeAlert|UIUserNotificationTypeSound|UIUserNotificationTypeBadge; //[UMessage registerForRemoteNotifications:categories withTypesForIos7:types7 withTypesForIos8:types8]; //for log [UMessage setLogEnabled:YES]; // 应用从通知启动 NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; if (userInfo) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self handleNotificationUserInfo:userInfo]; }); } } - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { //1.2.7版本开始不需要用户再手动注册devicetoken,SDK会自动注册 // [UMessage registerDeviceToken:deviceToken]; NSString* tokenString = [self stringDevicetoken:deviceToken]; NSLog(@"==tokenString = %@", tokenString); printf([[NSString stringWithFormat:@"\n\ntokenString = %@\n\n", tokenString] UTF8String]); } /** - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { //如果注册不成功,打印错误信息,可以在网上找到对应的解决方案 //1.2.7版本开始自动捕获这个方法,log以application:didFailToRegisterForRemoteNotificationsWithError开头 } */ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { [[NSNotificationCenter defaultCenter] postNotificationName:@"userInfoNotification" object:self userInfo:@{@"userinfo":[NSString stringWithFormat:@"%@",userInfo]}]; //关闭友盟自带的弹出框 [UMessage setAutoAlert:NO]; [UMessage didReceiveRemoteNotification:userInfo]; if (application.applicationState != UIApplicationStateActive) { [self handleNotificationUserInfo:userInfo]; } } // 添加观察者 - (void)addObserver:(id<PTNotificationObserver>)observer { [self.observers addPointer:(__bridge void * _Nullable)(observer)]; } - (void)testSendNotification { [self handleNotificationUserInfo:@{@"kkk": @"kkk"}]; } - (void)handleNotificationUserInfo:(NSDictionary *)userInfo { for (id<PTNotificationObserver> observer in self.observers.allObjects) { [observer update:self data:userInfo]; } } #pragma mark - ......::::::: UNUserNotificationCenterDelegate :::::::...... //iOS10新增:处理前台收到通知的代理方法 -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { NSDictionary * userInfo = notification.request.content.userInfo; if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { //应用处于前台时的远程推送接受 //必须加这句代码 [UMessage setAutoAlert:NO]; [UMessage didReceiveRemoteNotification:userInfo]; }else{ //应用处于前台时的本地推送接受 } } //iOS10新增:处理后台点击通知的代理方法 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(nonnull void (^)(void))completionHandler { NSDictionary * userInfo = response.notification.request.content.userInfo; if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { //应用处于后台时的远程推送接受 //必须加这句代码 [UMessage didReceiveRemoteNotification:userInfo]; [self handleNotificationUserInfo:userInfo]; }else{ //应用处于后台时的本地推送接受 } } #pragma mark - ......::::::: Helper :::::::...... -(NSString *)stringDevicetoken:(NSData *)deviceToken { NSString *token = [deviceToken description]; NSString *pushToken = [[[token stringByReplacingOccurrencesOfString:@"<"withString:@""] stringByReplacingOccurrencesOfString:@">"withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""]; return pushToken; } #pragma mark - ......::::::: Lazy Load :::::::...... - (NSPointerArray *)observers { if (nil == _observers) { _observers = [NSPointerArray weakObjectsPointerArray]; } return _observers; } @end
使用方法:
- ViewController 实现了
PTNotificationObserver
Protocol 作为观察者 - 重写
- (void)update:(id<PTNotificationObservable>)sender data:(id)data
方法打印接收到的消息 - 在
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
模拟让消息模块发送消息
@interface PTViewController () <PTNotificationObserver> @end @implementation PTViewController - (void)viewDidLoad { [super viewDidLoad]; [[PTNotificationManager sharedInstance] addObserver:self]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } // 接收到消息 - (void)update:(id<PTNotificationObservable>)sender data:(id)data { NSLog(@"%@", data); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [[PTNotificationManager sharedInstance] testSendNotification]; }
使用cocopods集成
使用 pod lib create
命令创建私有库
➜ DevPods pod lib create PTNotificationManager Cloning `https://github.com/CocoaPods/pod-template.git` into `PTNotificationManager`. Configuring PTNotificationManager template. ------------------------------ To get you started we need to ask a few questions, this should only take a minute. If this is your first time we recommend running through with the guide: - http://guides.cocoapods.org/making/using-pod-lib-create.html ( hold cmd and double click links to open in a browser. ) What language do you want to use?? [ Swift / ObjC ] > Objc Would you like to include a demo application with your library? [ Yes / No ] > yes Which testing frameworks will you use? [ Specta / Kiwi / None ] > None Would you like to do view based testing? [ Yes / No ] > No What is your class prefix? > PT Running pod install on your new library. Analyzing dependencies Fetching podspec for `PTNotificationManager` from `../` Downloading dependencies Installing PTNotificationManager (0.1.0) Generating Pods project Integrating client project [!] Please close any current Xcode sessions and use `PTNotificationManager.xcworkspace` for this project from now on. Sending stats Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed. [!] Automatically assigning platform ios with version 9.3 on target PTNotificationManager_Example because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`. Ace! you're ready to go! We will start you off by opening your project in Xcode open 'PTNotificationManager/Example/PTNotificationManager.xcworkspace' To learn more about the template see `https://github.com/CocoaPods/pod-template.git`. To learn more about creating a new pod, see `http://guides.cocoapods.org/making/making-a-cocoapod`. ➜ DevPods
修改 PTNotificationManager.podspec
文件如下
Pod::Spec.new do |s| s.name = 'PTNotificationManager' s.version = '0.1.0' s.summary = 'A short description of PTNotificationManager.' s.description = <<-DESC Oh PTNotificationManager DESC s.homepage = 'https://github.com/flypigrmvb/PTNotificationManager' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'flypigrmvb' => '862709539@qq.com' } s.source = { :git => 'https://github.com/flypigrmvb/PTNotificationManager.git', :tag => s.version.to_s } s.ios.deployment_target = '8.0' s.source_files = 'PTNotificationManager/Classes/**/*' # s.public_header_files = 'Pod/Classes/**/*.h' # 添加依赖的系统静态库 s.libraries = 'xml2', 'z', 'c++', 'stdc++.6', 'sqlite3' # 添加系统的farme依赖库 s.frameworks = 'UIKit', 'MapKit' # 添加其他Pod依赖库 s.dependency 'UMessage' end
Example项目的 Podfile
添加如下内容
platform :ios, '8.0' inhibit_all_warnings! target 'PTNotificationManager_Example' do pod 'PTNotificationManager', :path => '../' pod 'UMessage', :podspec => 'https://raw.githubusercontent.com/kkme/UMessage/master/UMessage.podspec' end
以上步骤Example项目就可以跑起来了,完成了简单的推送消息模块的组件化和解耦,方便在不同的业主场景中使用。