RabbitMQ在iOS中的集成應用

文章首次整理自 個人博客:RabbitMQ在iOS中的應用

引言

隨著公司業(yè)務的需要领虹,原來的推送运翼,不能滿足現(xiàn)有的要求,比如壳咕,有的公司需要局域網(wǎng)部署平臺服務席揽,不能訪問外網(wǎng),意味著谓厘,通過自帶的 APNS遠程推送服務幌羞,基本上就掛掉了;再比如,公司的某一個業(yè)務竟稳,需要實時刷新數(shù)據(jù)属桦,如股票價格,報警實時監(jiān)測數(shù)據(jù)等這種場景他爸。這時候聂宾,就需要另辟蹊徑;當然诊笤,我們有很多種選擇系谐,比如,RabbitMQ-實現(xiàn)了高級消息隊列協(xié)議的開源消息代理軟件讨跟,為啥要用這個呢纪他,因為Android已經(jīng)用這個做好了,所以晾匠,iOS 也沒有其他選擇了;

在集成客戶端的時候茶袒,我們有一些概念需要知道,如消息隊列凉馆,生產(chǎn)者薪寓,消費者,交換器澜共,隊列,通道等向叉;

一.RabbitMQ 的相關(guān)概念

1.1 簡介

RabbitMQ 本質(zhì)上是 MB(Message Broker) 消息代理,可以這么認為咳胃,RabbitMQ就像一個 “郵局”植康,負責收發(fā)存儲郵包(消息),唯一與郵局不同的是展懈,它不會處理消息內(nèi)容

AMQP销睁,即Advanced Message Queuing Protocol供璧,高級消息隊列協(xié)議,是應用層協(xié)議的一個開放標準冻记;類似Http,https,STMP這種睡毒,我們只需要知道,他是一個協(xié)議冗栗,實際開發(fā)中會遇到演顾,底層實現(xiàn)我們不用管;

1.2 MQ的特征

RabbitMQ 是一個由 Erlang 語言開發(fā)的 AMQP 的開源實現(xiàn)隅居。

支持多種客戶端钠至,如:Python、Ruby胎源、.NET棉钧、Java、JMS涕蚤、C宪卿、PHP、ActionScript万栅、XMPP佑钾、STOMP,Object-C, Swift等烦粒,支持AJAX休溶。用于在分布式系統(tǒng)中存儲轉(zhuǎn)發(fā)消息,在易用性撒遣、擴展性邮偎、高可用性等方面表現(xiàn)不俗。

  • 可靠性(Reliability)

RabbitMQ 使用一些機制來保證可靠性义黎,如持久化、傳輸確認豁跑、發(fā)布確認廉涕。

  • 靈活的路由(Flexible Routing)

在消息進入隊列之前,通過 Exchange 來路由消息的艇拍。對于典型的路由功能狐蜕,RabbitMQ 已經(jīng)提供了一些內(nèi)置的 Exchange 來實現(xiàn)。針對更復雜的路由功能卸夕,可以將多個 Exchange 綁定在一起层释,也通過插件機制實現(xiàn)自己的 Exchange 。

  • 消息集群(Clustering)

多個 RabbitMQ 服務器可以組成一個集群快集,形成一個邏輯 Broker 贡羔。

  • 高可用(Highly Available Queues)

隊列可以在集群中的機器上進行鏡像廉白,使得在部分節(jié)點出問題的情況下隊列仍然可用。

  • 多種協(xié)議(Multi-protocol)

RabbitMQ 支持多種消息隊列協(xié)議乖寒,比如 STOMP猴蹂、MQTT 等等。

  • 多語言客戶端(Many Clients)

RabbitMQ 幾乎支持所有常用語言楣嘁,比如 Java、.NET逐虚、Ruby, Object-C等等聋溜。

  • 管理界面(Management UI)

RabbitMQ 提供了一個易用的用戶界面,使得用戶可以監(jiān)控和管理消息 Broker 的許多方面叭爱。

  • 跟蹤機制(Tracing)

如果消息異常撮躁,RabbitMQ 提供了消息跟蹤機制,使用者可以找出發(fā)生了什么涤伐。

  • 插件機制(Plugin System)

RabbitMQ 提供了許多插件馒胆,來從多方面進行擴展,也可以編寫自己的插件凝果。

1.3 RabbitMQ的概念模型

所有 MQ 產(chǎn)品從模型抽象上來說都是一樣的過程:
消費者(consumer)訂閱某個隊列祝迂。生產(chǎn)者(producer)創(chuàng)建消息,然后發(fā)布到隊列(queue)中器净,最后將消息發(fā)送到監(jiān)聽的消費者型雳。

消息模型
  • AMQP的內(nèi)部結(jié)構(gòu)

上面介紹RabbitMQ是AMQP的實現(xiàn),所以其內(nèi)部也是AMQP的概念

RabbbitMQ的內(nèi)部結(jié)構(gòu)
  • P(Producer) 生產(chǎn)者山害,有的也稱之為 Publisher,消息的發(fā)布者

發(fā)送消息的程序或者代碼就是生產(chǎn)者纠俭,一般是 服務端

生產(chǎn)者
  • Exchange(交換器)

交換器,用來接收生產(chǎn)者發(fā)送的消息并將這些消息路由給服務器中的隊列.生產(chǎn)者把消息發(fā)布到 Exchange 上浪慌,消息最終到達隊列并被消費者接收冤荆,而 Binding 決定交換器的消息應該發(fā)送到那個隊列。

  • routing key

生產(chǎn)者在將消息發(fā)送給Exchange的時候权纤,一般會指定一個routing key钓简,來指定這個消息的路由規(guī)則,而這個routing key需要與Exchange Type及binding key聯(lián)合使用才能最終生效汹想。
在Exchange Type與binding key固定的情況下(在正常使用時一般這些內(nèi)容都是固定配置好的)外邓,我們的生產(chǎn)者就可以在發(fā)送消息給Exchange時,通過指定routing key來決定消息流向哪里古掏。
RabbitMQ為routing key設(shè)定的長度限制為255 bytes损话。
此外,這個 Key 值槽唾,一般是服務端跟客戶端約定好的丧枪,可以是組織ID光涂,也可以用ID

  • Binding(綁定)

綁定,用于消息隊列和交換器之間的關(guān)聯(lián)豪诲。一個綁定就是基于路由鍵將交換器和消息隊列連接起來的路由規(guī)則顶捷,所以可以將交換器理解成一個由綁定構(gòu)成的路由表。

  • Queue(隊列)

消息隊列屎篱,用來保存消息直到發(fā)送給消費者服赎。它是消息的容器,也是消息的終點交播。一個消息可投入一個或多個隊列重虑。消息一直在隊列里面,等待消費者連接到這個隊列將其取走秦士。

  • Connection(連接)

網(wǎng)絡連接缺厉,比如一個TCP連接,RabbitMQ的OC CLient就是封裝了 開源的TCP實現(xiàn)CocoaAsyncSocket

  • Channel(信道)

信道隧土,多路復用連接中的一條獨立的雙向數(shù)據(jù)流通道提针。信道是建立在真實的TCP連接內(nèi)地虛擬連接,AMQP 命令都是通過信道發(fā)出去的曹傀,不管是發(fā)布消息辐脖、訂閱隊列還是接收消息,這些動作都是通過信道完成皆愉。因為對于操作系統(tǒng)來說建立和銷毀 TCP 都是非常昂貴的開銷嗜价,所以引入了信道的概念,以復用一條 TCP 連接幕庐。

  • Virtual Host(虛擬主機)
    虛擬主機久锥,表示一批交換器、消息隊列和相關(guān)對象异剥。虛擬主機是共享相同的身份認證和加密環(huán)境的獨立服務器域瑟由。每個 vhost 本質(zhì)上就是一個 mini 版的 RabbitMQ 服務器,擁有自己的隊列冤寿、交換器错妖、綁定和權(quán)限機制。vhost 是 AMQP 概念的基礎(chǔ)疚沐,必須在連接時指定,RabbitMQ 默認的 vhost 是 /
  • C(Consumer) 消費者

訂閱某個隊列潮模,消息的接受者

消費者

1.4 RabbitMQ 中的消息路由

生產(chǎn)者把消息發(fā)布到 Exchange 上亮蛔,消息最終到達隊列并被消費者接收,而 Binding 決定交換器的消息應該發(fā)送到那個隊列擎厢。

在綁定(Binding)Exchange與Queue的同時究流,一般會指定一個binding key辣吃;生產(chǎn)者將消息發(fā)送給Exchange時,一般會指定一個routing key芬探;當binding key與routing key相匹配時神得,消息將會被路由到對應的Queue中。
在綁定多個Queue到同一個Exchange的時候偷仿,這些Binding允許使用相同的binding key哩簿。
binding key 并不是在所有情況下都生效,它依賴于Exchange Type酝静,比如fanout類型的Exchange就會無視binding key节榜,而是將消息路由到所有綁定到該Exchange的Queue。

AMOP的消息路由

1.5 RabbitMQ 的Exchange Types

RabbitMQ常用的Exchange Type有==fanout==别智、==direct==宗苍、==topic==、==headers==這四種(AMQP規(guī)范里還提到兩種Exchange Type薄榛,分別為system與自定義讳窟,這里不予以描述),headers 匹配 AMQP 消息的 header 而不是路由鍵敞恋,此外 headers 交換器和 direct 交換器完全一致丽啡,但性能差很多,目前幾乎用不到了耳舅,所以直接看另外三種類型:

  • direct

direct類型的Exchange路由規(guī)則也很簡單碌上,它會把消息路由到那些binding key與routing key完全匹配的Queue中。

相關(guān)代碼如下:

    /** 創(chuàng)建信道 */
    id<RMQChannel> ch = [_conn createChannel];
    /** 創(chuàng)建交換器浦徊,direct方法馏予,這個需要跟后臺保持一致,包括交換器的名稱盔性,也要跟后臺約定好霞丧,還有配置選項,是否持久化等 */
    RMQExchange *x = [ch  direct:@"Mobile_Alarm" options:(RMQExchangeDeclareNoOptions)];
    /** 創(chuàng)建隊列冕香,如果不指定隊列名稱蛹尝,MQ會默認自動創(chuàng)建一個隊列,前綴以 `rmq-objc-client.gen-` 開頭 */
    RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
    /** 隊列綁定交換器悉尾,并指定 rountKey突那,需要跟后臺指定相同的規(guī)則,可以是用戶ID等 */
    [q bind:x routingKey:@"5"];

Exchange Direct類型
  • fanout
    這種類型的Exchange,就是群發(fā)构眯,此exchange的路由規(guī)則很簡單直接將消息路由到所有綁定的隊列中愕难,無須對消息的routingkey進行匹配操作。


    MQ fanout.png
    /**  需要注意:<RMQConnectionDelegate> 如果創(chuàng)建連接,指定代理是 當前class,那么當前class需要遵守連接的代理協(xié)議猫缭,并實現(xiàn)相關(guān)代理方法*/

    /** 創(chuàng)建連接 */
    RMQConnection *conn = [[RMQConnection alloc] initWithUri:url5 delegate:self];
    [conn start];
    /** 創(chuàng)建信道 */
    id<RMQChannel> ch = [conn createChannel];
    /** 創(chuàng)建交換器 */
    RMQExchange *x = [ch fanout:@"Alarm"];
    RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive];
    /** 綁定交換器 */
    [q bind:x];
    /** 訂閱消息 */
    [q subscribe:^(RMQMessage * _Nonnull message) {
        NSLog(@"Received %@", [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding]);
    }];
    
///socket 連接失敗回調(diào)葱弟,超時或者地址有誤
- (void)connection:(RMQConnection *)connection failedToConnectWithError:(NSError *)error;
/// 沒有連接成功回調(diào)
- (void)connection:(RMQConnection *)connection disconnectedWithError:(NSError *)error;
/// 自動恢復連接調(diào)用
- (void)willStartRecoveryWithConnection:(RMQConnection *)connection;
/// 正在開始恢復連接
- (void)startingRecoveryWithConnection:(RMQConnection *)connection;
/// 已經(jīng)恢復連接的時候,調(diào)用
- (void)recoveredConnection:(RMQConnection *)connection;
/// 連接過程中猜丹,信道異常調(diào)用
- (void)channel:(id<RMQChannel>)channel error:(NSError *)error;
  • Topic

前面講到direct類型的Exchange路由規(guī)則是完全匹配binding key與routing key芝加,但這種嚴格的匹配方式在很多情況下不能滿足實際業(yè)務需求。topic類型的Exchange在匹配規(guī)則上進行了擴展射窒,它與direct類型的Exchage相似藏杖,也是將消息路由到binding key與routing key相匹配的Queue中,但這里的匹配規(guī)則有些不同轮洋,它約定:

routing key為一個句點號“. ”分隔的字符串(我們將被句點號“. ”分隔開的每一段獨立的字符串稱為一個單詞)制市,如“device.userId”、“alarm.type”弊予,
binding key與routing key一樣也是句點號“. ”分隔的字符串
binding key中可以存在兩種特殊字符“”與“#”祥楣,用于做模糊匹配,其中“”用于匹配一個單詞汉柒,“#”用于匹配多個單詞(可以是零個)

    RMQConnection * connection = [[RMQConnection alloc] initWithUri:url delegate:[RMQConnectionDelegateLogger new]];
    [connection start];
    id<RMQChannel>channel = [connection createChannel];
    RMQExchange * exchange = [channel topic:@"topic_logs" options:RMQExchangeDeclarePassive];
    [exchange publish:finalData routingKey:[NSString stringWithFormat:@"device.%@",didStr]];
    [connection close];

MQ topic Type.png

以上圖中的配置為例误褪,routingKey=”dev.alarm.device”的消息會同時路由到QA與QB,routingKey=”dev.alarm.type”的消息會路由到QA碾褂,routingKey=”lazy.brown.fox”的消息會路由到Q2兽间,routingKey=”water.type.ID”的消息會路由到QB;routingKey=”device.user.water”正塌、routingKey=”alarmtype”的消息將會被丟棄嘀略,因為它們沒有匹配任何bindingKey。

二.RabbitMQ 的集成

2.1 客戶端集成

RabbitMQ官方Git倉庫

  • 我用的是CocoaPods集成,在 podfile 中添加:
pod 'RMQClient'

注意:

RabbitMQ pod 內(nèi)部有 CocoaAsyncSocket,如果項目中乓诽,已經(jīng)集成了帜羊,需要刪除多余的

2.2 源代碼文件

考慮到客戶端架構(gòu)的特殊性,需要將MQ消息訂閱封裝成一個工具,并配置為 單例 模式鸠天。

導入頭文件 #import <RMQClient.h>

LXCustomRabbitMQManger.h

//開始訂閱消息
- (void)startScribeMessage;
//關(guān)閉連接
- (void)closeConnection;

LXCustomRabbitMQManger.m

@interface LXCustomRabbitMQManger()<RMQConnectionDelegate>

@property (nonatomic,strong) RMQConnection *conn;
@property (nonatomic,strong) LXCustomAlarmModel *alarmModel;

@end


- (void)startScribeMessage {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}

- (void)closeConnection {
    if (_conn) {
        [self.conn close];
        self.conn = nil;
    }
}

//創(chuàng)建本地推送
- (void)registerNotification:(NSInteger )alerTime {
    kweakSelf;
    kStrongSelf;
    // 使用 UNUserNotificationCenter 來管理通知
    if (@available(iOS 10.0, *)) {
        // 使用 UNUserNotificationCenter 來管理通知
        UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
        //需創(chuàng)建一個包含待通知內(nèi)容的 UNMutableNotificationContent 對象讼育,注意不是 UNNotificationContent ,此對象為不可變對象。
        UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
        content.title = [NSString localizedUserNotificationStringForKey:self.alarmModel.alarm_type arguments:nil];
        content.body = [NSString localizedUserNotificationStringForKey:self.alarmModel.geography
                                                             arguments:nil];
        content.sound = [UNNotificationSound defaultSound];
        
        // 在 alertTime 后推送本地推送
        UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger
                                                      triggerWithTimeInterval:alerTime repeats:NO];
        UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"FiveSecond"
                                                                              content:content trigger:trigger];
        //添加推送成功后的處理稠集!
        [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
           /*
            DQAlertView *reservationAlert = [[DQAlertView alloc] initWithTitle:self.alarmModel.alarm_type
                                                                       message:self.alarmModel.geography
                                                                      delegate:self cancelButtonTitle:@"取消"
                                                             otherButtonTitles:@"確定"];
            
            reservationAlert.shouldDimBackgroundWhenShowInView = YES;
            reservationAlert.shouldDismissOnOutsideTapped = YES;
            [reservationAlert show];
            [reservationAlert actionWithBlocksCancelButtonHandler:^{
                
                
            } otherButtonHandler:^{
                //跳轉(zhuǎn)
                LXDeviceAlarmModel *alarmIDModel = [[LXDeviceAlarmModel alloc] init];
                alarmIDModel.ID = strongSelf.alarmModel.ID;
                LXDeviceDetailController *deviceDetailVC = [[LXDeviceDetailController alloc] init];
                deviceDetailVC.deviceDealType = KearlyNoDealType;
                deviceDetailVC.deviceAlarm = alarmIDModel;
               [[self getCurrentVC].navigationController pushViewController:deviceDetailVC animated:YES];
                
            }];
            */
            UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.alarmModel.alarm_type message:self.alarmModel.geography preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
            UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                LXDeviceAlarmModel *alarmIDModel = [[LXDeviceAlarmModel alloc] init];
                alarmIDModel.ID = strongSelf.alarmModel.ID;
                LXDeviceDetailController *deviceDetailVC = [[LXDeviceDetailController alloc] init];
                deviceDetailVC.deviceDealType = KearlyNoDealType;
                deviceDetailVC.deviceAlarm = alarmIDModel;
                [[self getCurrentVC].navigationController pushViewController:deviceDetailVC animated:YES];
            }];
            [alert addAction:cancelAction];
            [alert addAction:confirmAction];
            [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
            
        }];
    }
    else {
        UILocalNotification *notification = [[UILocalNotification alloc] init];
        // 設(shè)置觸發(fā)通知的時間
        NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:1];
        NSLog(@"fireDate=%@",fireDate);
        
        notification.fireDate = fireDate;
        // 時區(qū)
        notification.timeZone = [NSTimeZone defaultTimeZone];
        // 設(shè)置重復的間隔
        notification.repeatInterval = kCFCalendarUnitSecond;
        
        // 通知內(nèi)容
        notification.alertBody =  self.alarmModel.alarm_type;
        notification.applicationIconBadgeNumber = 1;
        // 通知被觸發(fā)時播放的聲音
        notification.soundName = UILocalNotificationDefaultSoundName;
        // 通知參數(shù)
        NSDictionary *userDict = [NSDictionary dictionaryWithObject:self.alarmModel.geography forKey:@"key"];
        notification.userInfo = userDict;
        
        // ios8后奶段,需要添加這個注冊,才能得到授權(quán)
        if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
            UIUserNotificationType type =  UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
            UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type
                                                                                     categories:nil];
            [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
            // 通知重復提示的單位剥纷,可以是天痹籍、周、月
            notification.repeatInterval = NSCalendarUnitDay;
        } else {
            // 通知重復提示的單位晦鞋,可以是天词裤、周刺洒、月
            notification.repeatInterval = NSDayCalendarUnit;
        }
        // 執(zhí)行通知注冊
        [[UIApplication sharedApplication] scheduleLocalNotification:notification];
        
    }
    
}

- (void)receiveRabbitServicerMessage {
    
   //    NSString *url5 = @"amqp://web_user:123@192.178.0.2:5672/device_log_2";
    DebugLog(@"MQ服務器地址:%@",url5);
    if (_conn == nil) {
        _conn = [[RMQConnection alloc] initWithUri:url5 delegate:self];
    }
    [_conn start];
    id<RMQChannel> ch = [_conn createChannel];
    RMQExchange *x = [ch  direct:@"Mobile_Electric_Alarm" options:(RMQExchangeDeclareNoOptions)];
    RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
    [q bind:x routingKey:kUserInfo.company_id];
    kweakSelf;
    [q subscribe:^(RMQMessage * _Nonnull message) {
        id result = [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding];
        NSDictionary *dict = [self dictionaryWithJsonString:result];
        if ([dict isKindOfClass:[NSDictionary class]]) {
            weakSelf.alarmModel = [LXCustomAlarmModel mj_objectWithKeyValues:dict];
            [weakSelf registerNotification:1];
        }
    }];
}

- (void)emitLogDirect:(NSString *)msg severity:(NSString *)severity {
//    NSString *url5 = @"amqp://web_user:123@192.178.0.2:5672/device_log_2";
    DebugLog(@"MQ發(fā)送服務器地址:%@",url5);
    RMQConnection *conn = [[RMQConnection alloc] initWithUri:url5 delegate:self];
    self.conn = conn;
    [conn start];
    id<RMQChannel> ch = [conn createChannel];
    RMQExchange *x = [ch  direct:@"Mobile_Electric_Alarm" options:(RMQExchangeDeclareNoOptions)];
    RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
    [q bind:x routingKey:kUserInfo.company_id];
    [x publish:[msg dataUsingEncoding:NSUTF8StringEncoding] routingKey:severity];
    NSLog(@"Sent '%@'", msg);
    [conn close];
}

#pragma mark - 系統(tǒng)的通知監(jiān)聽
- (void)activeNotification:(NSNotification *)notification{
    if (_conn == nil) {
        //登錄成功
        if ([[LXUserInfoManger shareLXUserInfoManger].currentUserInfo.is_cloud isEqualToString:@"0"]) {
            //MQ推送
            [self receiveRabbitServicerMessage];
        }
    }
}
- (void)backgroundNotification:(NSNotification *)notification{
    [self closeConnection];
}

- (void)connection:(RMQConnection *)connection failedToConnectWithError:(NSError *)error {
    if (error) {
        NSLog(@"%@",error);
        NSLog(@"連接超時");
        [self closeConnection];

    }else{
        
    }
}
- (void)connection:(RMQConnection *)connection disconnectedWithError:(NSError *)error {
    if (error) {
        NSLog(@"%@",error);
    }
    else{
        NSLog(@"連接成功");
    }
}
- (void)willStartRecoveryWithConnection:(RMQConnection *)connection {
    DebugLog(@"將要開始恢復鏈接");
}
- (void)startingRecoveryWithConnection:(RMQConnection *)connection {
    DebugLog(@"開始恢復鏈接");

}

- (void)recoveredConnection:(RMQConnection *)connection {
    DebugLog(@"恢復鏈接");

}
- (void)channel:(id<RMQChannel>)channel error:(NSError *)error {
    if (error) {
        NSLog(@"%@",error);
        [self closeConnection];
    }
}

//獲取當前屏幕顯示的viewcontroller,當接收到推送需要跳轉(zhuǎn)VC
- (UIViewController *)getCurrentVC {
    UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    UIViewController *currentVC = [self getCurrentVCFrom:rootViewController];
    return currentVC;
}

- (UIViewController *)getCurrentVCFrom:(UIViewController *)rootVC {
    UIViewController *currentVC;
    if ([rootVC presentedViewController]) {
        // 視圖是被presented出來的
        rootVC = [rootVC presentedViewController];
    }
    if ([rootVC isKindOfClass:[UITabBarController class]]) {
        // 根視圖為UITabBarController
        currentVC = [self getCurrentVCFrom:[(UITabBarController *)rootVC selectedViewController]];
        
    } else if ([rootVC isKindOfClass:[UINavigationController class]]){
        // 根視圖為UINavigationController
        currentVC = [self getCurrentVCFrom:[(UINavigationController *)rootVC visibleViewController]];
        
    } else {
        // 根視圖為非導航類
        currentVC = rootVC;
    }
    return currentVC;
}
//接收到推送吼砂,json格式字符串轉(zhuǎn)字典:因為MQ推過來的消息是 String 類型的
- (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {
    
    if (jsonString == nil) {
        return nil;
    }
    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    NSError *err;
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
                                                        options:NSJSONReadingMutableContainers
                                                          error:&err];
    if(err) {
        NSLog(@"json解析失敗:%@",err);
        return nil;
    }
    return dic;
    
}

2.3 本地安裝啟動MQ服務

如果電腦安裝 brew 軟件包工具的話鼎文,執(zhí)行并啟動

brew install rabbitmq

sudo ./rabbitmq-server

瀏覽器訪問:localhost:15672,默認賬號為:guest 密碼: guest

三.RabbitMQ 遇到的問題

3.1 地址不對渔肩,connect fail

Received connection: <RMQConnection: 0x103fa1fc0> disconnectedWithError: Error Domain=GCDAsyncSocketErrorDomain Code=7 "Socket closed by remote peer" UserInfo={NSLocalizedDescription=Socket closed by remote peer}

解決辦法:MQ的鑒權(quán),O-C跟Java的格式不太一樣拇惋,Java是通過Name,Host,Ip等周偎,iOS的是直接設(shè)置地址

amqps://user:pass@hostname:1234/myvhost

說明:

  • 協(xié)議頭:如果是Https的話,協(xié)議用:amqps撑帖,如果是Http蓉坎,用amqp
  • 用戶名:userName
  • 用戶密碼:password
  • ip地址:182.142.123等
  • port端口:5672
  • 虛擬主機:由后臺定義,可有可無胡嘿,根據(jù)需求蛉艾,myloghost

3.2 服務端與客戶端參數(shù)配置有誤,消息隊列持久化

Error Domain=com.rabbitmq.rabbitmq-objc-client Code=406 "PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'Mobile_Electric_Alarm' in vhost 'device_log_2': received 'false' but current is 'true'" UserInfo={NSLocalizedDescription=PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'Mobile_Electric_Alarm' in vhost 'device_log_2': received 'false' but current is 'true'}

看3.3解決辦法

3.3 MQ 找不到對應的隊列

Error Domain=com.rabbitmq.rabbitmq-objc-client Code=404 "NOT_FOUND - no queue 'rmq-objc-client.gen-3B6E0E14-06E6-4AFC-B6E1-42A7F8FCD218-46927-00002C8AE36AB350' in vhost 'device_log_2'" UserInfo={NSLocalizedDescription=NOT_FOUND - no queue 'rmq-objc-client.gen-3B6E0E14-06E6-4AFC-B6E1-42A7F8FCD218-46927-00002C8AE36AB350' in vhost 'device_log_2'}

解決辦法:

Exchange 交換器的配置參數(shù),跟后臺約定的不一致衷敌,有時候后臺也不知道自己配置的啥勿侯,就需要嘗試了;

    /**
    
    typedef NS_OPTIONS(NSUInteger, RMQExchangeDeclareOptions) {
    RMQExchangeDeclareNoOptions  = 0,
    /// 被動聲明
    RMQExchangeDeclarePassive    = 1 << 0,
    ///交換器持久化
    RMQExchangeDeclareDurable    = 1 << 1,
    /// 自動銷毀交換器缴罗,當所有的消息隊列都被使用
    RMQExchangeDeclareAutoDelete = 1 << 2,
    /// 配置的交換器內(nèi)部構(gòu)造對應用發(fā)布者不可見
    RMQExchangeDeclareInternal   = 1 << 3,
    /// @brief
    RMQExchangeDeclareNoWait     = 1 << 4,
     };*/
    RMQExchange *x = [ch  direct:@"Mobile_Electric_Alarm" options:(RMQExchangeDeclareNoOptions)];
    /** 
     * 隊列的配置可選參數(shù)
     * 
     typedef NS_OPTIONS(NSUInteger, RMQQueueDeclareOptions) {
    RMQQueueDeclareNoOptions  = 0,
    /// 被動聲明
    RMQQueueDeclarePassive    = 1 << 0,
    /// 隊列持久化
    RMQQueueDeclareDurable    = 1 << 1,
    /// 只能被當前的連接授權(quán)訪問
    RMQQueueDeclareExclusive  = 1 << 2,
    /// 自動刪除
    RMQQueueDeclareAutoDelete = 1 << 3,
    ///
    RMQQueueDeclareNoWait     = 1 << 4,
    };
     */
    RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];


如果連接成功后助琐,那么在MQ的管理臺,就可以看到當前連接的消費者了

[圖片上傳失敗...(image-97aaad-1534667228188)]

[參考文章]

1> RabbbitMQ官網(wǎng)

2> RabbitMQ基礎(chǔ)概念詳細介紹

3> 消息隊列之 RabbitMQ

4> RabbitMQ——第一篇:RabbitMQ介紹

交流討論

RabbitMQ在iOS中的使用資料很少面氓,如果有問題的話兵钮,歡迎留言探討。

另外舌界,如果你覺得我的文章對你有一定的幫助掘譬,非常感謝能夠點贊,謝謝禀横!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屁药,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子柏锄,更是在濱河造成了極大的恐慌酿箭,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趾娃,死亡現(xiàn)場離奇詭異缭嫡,居然都是意外死亡,警方通過查閱死者的電腦和手機抬闷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門妇蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耕突,“玉大人,你說我怎么就攤上這事评架【熳拢” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵纵诞,是天一觀的道長上祈。 經(jīng)常有香客問我,道長浙芙,這世上最難降的妖魔是什么登刺? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮嗡呼,結(jié)果婚禮上纸俭,老公的妹妹穿的比我還像新娘。我一直安慰自己南窗,他們只是感情好揍很,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著矾瘾,像睡著了一般女轿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壕翩,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天蛉迹,我揣著相機與錄音,去河邊找鬼放妈。 笑死北救,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的芜抒。 我是一名探鬼主播珍策,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宅倒!你這毒婦竟也來了攘宙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拐迁,失蹤者是張志新(化名)和其女友劉穎蹭劈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體线召,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡铺韧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缓淹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哈打。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡塔逃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出料仗,到底是詐尸還是另有隱情湾盗,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布罢维,位于F島的核電站淹仑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏肺孵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一颜阐、第九天 我趴在偏房一處隱蔽的房頂上張望平窘。 院中可真熱鬧,春花似錦凳怨、人聲如沸瑰艘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽紫新。三九已至,卻和暖如春李剖,著一層夾襖步出監(jiān)牢的瞬間芒率,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工篙顺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留偶芍,地道東北人筑公。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓掌桩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闻蛀。 傳聞我的和親對象是個殘疾皇子宰僧,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容