Статьи

Изучение структуры многопользовательского подключения: настройка проекта

Как и в каждом основном выпуске, iOS 7 включает в себя множество новых API, которые разработчики могут использовать в своих приложениях. В этом уроке мы рассмотрим совершенно новый фреймворк, представленный в iOS 7, фреймворк Multipeer Connectivity. Эта структура добавляет поддержку для обнаружения, подключения и связи с близлежащими службами, такими как устройства iOS. В этом уроке я покажу вам, как создать простую многопользовательскую игру с использованием этого нового фреймворка.

Широкий спектр новых API был представлен в iOS 7, предоставляя разработчикам новые возможности и возможности. Фреймворк, который привлек мое внимание, это фреймворк Multipeer Connectivity. Он предлагает ряд полезных функций, которые я хотел бы продемонстрировать в этом уроке.

Прежде чем мы начнем работать с платформой Multipeer Connectivity, важно сначала ознакомиться с этой платформой. Цель платформы — позволить соседним устройствам связываться друг с другом, что включает в себя обмен данными, такими как изображения и видео.

Важно понимать значение термина поблизости в контексте структуры многопользовательского подключения. Инфраструктура определяет близлежащие устройства как устройства, которые находятся в одной сети Wi-Fi или могут общаться через Bluetooth. Платформа многопользовательского подключения, например, не может использоваться для связи между устройствами через Интернет. Это не то, для чего была разработана платформа Multipeer Connectivity.

Прежде чем два устройства смогут начать обмен данными, им сначала необходимо подключиться друг к другу. Это включает в себя две фазы: 1 обнаружение соседних устройств и 2 установление сеанса между устройствами. На этапе обнаружения одно устройство выполняет поиск или ищет устройства в непосредственной близости. Если устройство хочет быть обнаруживаемым, оно должно рекламировать себя. Это позволяет устройству просмотра находить рекламное устройство.

Когда первое устройство находит рекламное устройство, оно отправляет на устройство запрос на установление соединения, которое рекламодатель может принять или отклонить. Принимая приглашение, сеанс устанавливается, что означает, что устройства могут начать обмен данными. Соединение между двумя устройствами имеет одно из трех возможных состояний: 1 не подключено, 2 подключено или 2 подключено. В любое время устройство может завершить сеанс, который закрывает соединение между устройствами.

Структура Multipeer Connectivity предоставляет два пути для просмотра других устройств. Первый вариант — использовать специализированный подкласс контроллера представления, который сделает все за вас. Это подход, который мы будем использовать в этом уроке. Отправка приглашения и организация сеанса — это все, что вам нужно. Конечно, это не подходит для каждого варианта использования. Другой вариант — вручную реализовать логику для поиска ближайших устройств. Это, однако, тема, которую мы не будем освещать в этом руководстве.

В контексте структуры многопользовательского подключения устройство называется одноранговым peerID и каждый одноранговый узел имеет уникальный идентификатор или идентификатор peerID и отображаемое имя. Последнее — это имя, которое пользователь дал своему устройству, например, «iPhone Габриэля». Отображаемое имя — важная часть информации, так как это единственный способ, которым пользователи узнают, какое устройство кому принадлежит.

Данные, которые могут быть переданы между одноранговыми узлами, ограничены тремя типами, 1 экземплярами NSData , 2 ресурсами, такими как файлы и документы, и 3 потоковыми данными. В этом уроке я покажу вам, как отправлять и получать объекты данных, то есть экземпляры NSData . Мы не будем рассматривать ресурсы и потоки в этом руководстве.

Данные могут быть отправлены с использованием одного из двух режимов: надежного режима, который медленнее, но гарантирует, что переданные данные получены получателем, и ненадежен, что намного более производительно, но не гарантирует, что данные, отправленные один одноранговый узел получен другим или получен в том порядке, в котором он был отправлен. Какой режим вы выберете, зависит от потребностей вашего приложения.

Это все, что вам нужно знать о среде многопользовательского подключения, чтобы начать работу. Позвольте мне закончить это введение упоминанием четырех классов, с которыми мы будем работать в этом уроке.

  • MCPeerID : экземпляр этого класса представляет одноранговый узел в многопользовательском сеансе.
  • MCSession : этот класс используется для управления связью между пирами в сеансе.
  • MCAdvertiserAssistant : этот класс используется для помощи в рекламе одноранговых MCAdvertiserAssistant на соседних устройствах.
  • MCBrowserViewController : этот подкласс UIViewController заботится о представлении соседних устройств пользователю и помогает установить многопользовательский сеанс.

Давайте кратко рассмотрим пример приложения, которое мы собираемся создать. Цель этого руководства — показать, как установить соединение между соседними устройствами и обмениваться данными, например NSData , между подключенными устройствами.

Я уверен, что вы можете подумать о многих случаях использования инфраструктуры Multipeer Connectivity. Однако в этом уроке я выбрал очень простую игру под названием « Угадай секретный номер» . В игре один человек выбирает число в пределах заранее определенного диапазона, который другие игроки в игре должны угадать. После каждого предположения хозяин игры, человек, который выбрал число, сообщает игроку, правильно ли она сделала предположение, и является ли секретное число большим или меньшим. Это достаточно просто, верно?

Давайте начнем с создания нового проекта в XCode, как показано ниже. Выберите шаблон приложения « Один вид» из списка шаблонов в категории « Приложение iOS » и нажмите « Далее» .


Дайте название вашему проекту, введите идентификатор компании и выберите iPhone в меню « Устройства» . Нажмите кнопку Next , чтобы продолжить.


Сообщите Xcode, где вы хотите сохранить файлы проекта, и нажмите кнопку « Создать» , чтобы продолжить.


Существует довольно много стандартного кода, который нам нужно написать, чтобы установить и управлять многопользовательским сеансом. Чтобы не повторяться, мы централизуем эту логику в пользовательском классе, который мы можем использовать в нашем проекте. Вот что мы будем делать в этом разделе.

Создайте новый класс в XCode, выбрав New> File … в меню File . Выберите класс Objective-C из категории iOS Cocoa Touch и нажмите « Далее» .


Назовите новый класс MPCHandler , установите для его суперкласса значение NSObject и нажмите кнопку « Далее» . Скажите Xcode, где вы хотите хранить файлы классов, и нажмите « Create .


Откройте файл MPCHandler класса MPCHandler и начните с добавления оператора импорта для платформы Multipeer Connectivity, как показано ниже.

1
2
3
4
5
6
#import <Foundation/Foundation.h>
#import <MultipeerConnectivity/MultipeerConnectivity.h>
 
@interface MPCHandler : NSObject
 
@end
До Xcode 5 нам сначала нужно было бы связать проект со структурой Multipeer Connectivity. В Xcode 5, однако, это больше не требуется благодаря новой функции компоновки компилятора. Компилятор достаточно умен, чтобы связать проект с соответствующими библиотеками и фреймворками, что означает, что нам не нужно беспокоиться об этом.

Поскольку класс MPCHandler будет отвечать за управление многопользовательским сеансом, он должен соответствовать протоколу MCSessionDelegate , как показано ниже. Мы также объявляем четыре свойства, которые помогут нам в этой задаче.

01
02
03
04
05
06
07
08
09
10
11
#import <Foundation/Foundation.h>
#import <MultipeerConnectivity/MultipeerConnectivity.h>
 
@interface MPCHandler : NSObject <MCSessionDelegate>
 
@property (nonatomic, strong) MCPeerID *peerID;
@property (nonatomic, strong) MCSession *session;
@property (nonatomic, strong) MCBrowserViewController *browser;
@property (nonatomic, strong) MCAdvertiserAssistant *advertiser;
 
@end

Обратите внимание, что это те классы, которые я упоминал ранее в этом уроке. Объект peerID будет представлять устройство, и он также будет включен в каждую связь между одноранговыми узлами вместе с любыми данными, которыми обмениваются одноранговые узлы. session объект содержит и управляет многопользовательским сеансом. Мы поговорим об этом классе позже в этом уроке. Объект browser содержит ссылку на экземпляр MCBrowserViewController , который мы будем использовать для обнаружения соседних устройств и создания многопользовательского сеанса между ними. Объект advertiser заботится о рекламе устройства, а также об обработке входящих приглашений.

Прежде чем мы взглянем на реализацию класса MPCHandler , нам нужно объявить четыре метода, как показано ниже. Первый метод, setupPeerWithDisplayName: принимает один аргумент — отображение или публичное имя, которое будет использоваться для устройства. Метод advertiseSelf: также принимает один аргумент, чтобы указать, должно ли устройство быть видимым для других устройств. Метод setupSession будет отвечать за настройку многопользовательского сеанса, а метод setupBrowser будет отвечать за настройку экземпляра MCBrowserViewController .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>
#import <MultipeerConnectivity/MultipeerConnectivity.h>
 
@interface MPCHandler : NSObject <MCSessionDelegate>
 
@property (nonatomic, strong) MCPeerID *peerID;
@property (nonatomic, strong) MCSession *session;
@property (nonatomic, strong) MCBrowserViewController *browser;
@property (nonatomic, strong) MCAdvertiserAssistant *advertiser;
 
— (void)setupPeerWithDisplayName:(NSString *)displayName;
— (void)setupSession;
— (void)setupBrowser;
— (void)advertiseSelf:(BOOL)advertise;
 
@end

Давайте начнем с реализации метода setupPeerWithDisplayName: В этом методе мы создаем экземпляр класса MCPeerID и передаем его displayName . Вот и все.

1
2
3
— (void)setupPeerWithDisplayName:(NSString *)displayName {
    self.peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
}

В setupSession мы создаем экземпляр MCSession путем передачи peerID экземпляра peerID и устанавливаем делегат сеанса в наш экземпляр MPCHandler . Важно, чтобы мы setupPeerWithDisplayName: перед настройкой сеанса, потому что переменная экземпляра peerID не может быть nil .

1
2
3
4
— (void)setupSession {
    self.session = [[MCSession alloc] initWithPeer:self.peerID];
    self.session.delegate = self;
}

Реализация setupBrowser не может быть проще. Все, что мы делаем в этом методе, это инициализируем экземпляр MCBrowserViewController , передавая тип сервиса и сеанс, который мы создали в setupSession . Первый аргумент — это тип службы для поиска. Он однозначно идентифицирует службу и должен содержать от 1 до 15 символов. Обратите внимание, что тип сервиса может содержать только строчные буквы ASCII, цифры и дефисы. Мы сохраняем ссылку на контроллер представления браузера в переменной экземпляра _browser .

1
2
3
— (void)setupBrowser {
    self.browser = [[MCBrowserViewController alloc] initWithServiceType:@»my-game» session:_session];
}

В advertiseSelf: мы инициализируем экземпляр класса MCAdvertiserAssistant , передавая тот же тип сервиса, который мы использовали для создания экземпляра MCBrowserViewController . Мы также передаем объект сеанса и устанавливаем информацию об обнаружении nil так как мы не предоставляем информацию об обнаружении устройства соседним устройствам на этапе обнаружения.

Если для параметра advertise в advertiseSelf: задано значение YES , мы вызываем start объекта MCAdvertiserAssistant чтобы запустить помощника и начать рекламу службы. Если для advertise задано значение NO , мы останавливаем помощника рекламодателя и устанавливаем для свойства advertiser nil .

01
02
03
04
05
06
07
08
09
10
— (void)advertiseSelf:(BOOL)advertise {
    if (advertise) {
        self.advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:@»my-game» discoveryInfo:nil session:self.session];
        [self.advertiser start];
         
    } else {
        [self.advertiser stop];
        self.advertiser = nil;
    }
}

Возможно, вы заметили, что XCode отображает несколько предупреждений, так как мы еще не реализовали необходимые методы MCSessionDelegate . Давайте избавимся от этих предупреждений путем реализации протокола MCSessionDelegate . Методы, которые мы реализуем, показаны ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
— (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
 
}
 
— (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
 
}
 
— (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress {
 
}
 
— (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error {
 
}
 
— (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID {
 
}

Давайте посмотрим на каждый метод протокола.

  • session:peer:didChangeState: этот метод делегата вызывается каждый раз, когда изменяется состояние соединения session:peer:didChangeState: . Помните, что есть три возможных состояния: не подключен, подключен и подключен. Мы будем использовать этот метод делегата для отслеживания пиров, которые подключаются и отключаются от игры.
  • session:didReceiveData:fromPeer: этот метод вызывается каждый раз, когда устройство получает данные от другого узла.
  • session:didStartReceivingResourceWithName:fromPeer:withProgress: Когда приложение начало получать ресурс, такой как файл, от другого узла, этот метод вызывается. Мы не будем описывать этот метод в этом уроке.
  • session:didFinishReceivingResourceWithName:fromPeer:atURL:withError: Как следует из его названия, этот метод вызывается, когда ресурс был получен нашим приложением.
  • session: didReceiveStream: withName: fromPeer :: Этот метод вызывается, когда приложение получает поток. Поскольку мы не будем работать с потоками, мы также не будем рассматривать этот метод.

Несмотря на то, что мы не будем использовать последние три метода делегата в этом руководстве, нам нужно реализовать их в нашем контроллере представления, чтобы избавиться от предупреждений компилятора.

В session:peer:didChangeState: и session:didReceiveData:fromPeer: мы session:didReceiveData:fromPeer: уведомление с помощью центра уведомлений, чтобы убедиться, что любая часть приложения, которая заинтересована в событии, может быть уведомлена о событии. Это позволяет контроллерам представления нашего приложения добавлять себя в качестве наблюдателя этих уведомлений и отвечать на них, если это необходимо. Это также означает, что реализация этих методов делегата довольно проста, как вы можете видеть ниже.

01
02
03
04
05
06
07
08
09
10
— (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
    NSDictionary *userInfo = @{ @»peerID»: peerID,
                                @»state» : @(state) };
     
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@»MPCDemo_DidChangeStateNotification»
                                                            object:nil
                                                          userInfo:userInfo];
    });
}

Мы начинаем с создания словаря userInfo уведомления и userInfo его, передавая словарь в качестве третьего аргумента postNotificationName:object:userInfo: Обратите внимание, что мы публикуем уведомление внутри блока dispatch_async чтобы гарантировать, что уведомление будет опубликовано в главном потоке. Это важно, поскольку мы не можем гарантировать, что метод делегата вызывается в основном потоке. Уведомление, однако, должно быть размещено в главном потоке, чтобы убедиться, что каждый наблюдатель получает уведомление.

Реализация session:didReceiveData:fromPeer: выглядит очень похоже, как вы можете видеть ниже.

01
02
03
04
05
06
07
08
09
10
— (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
    NSDictionary *userInfo = @{ @»data»: data,
                                @»peerID»: peerID };
     
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@»MPCDemo_DidReceiveDataNotification»
                                                            object:nil
                                                          userInfo:userInfo];
    });
}

Класс MPCHandler готов к использованию, поэтому давайте инициализируем его в MPCHandler приложения.

Откройте AppDelegate.h, добавьте оператор импорта для класса MPCHandler и объявите свойство типа MPCHandler .

01
02
03
04
05
06
07
08
09
10
11
#import <UIKit/UIKit.h>
 
#import «MPCHandler.h»
 
@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
@property (strong, nonatomic) UIWindow *window;
 
@property (nonatomic, strong) MPCHandler *mpcHandler;
 
@end

Переключитесь на файл AppDelegate класса AppDelegate и инициализируйте переменную экземпляра в application:didFinishLaunchingWithOptions:

1
2
3
4
5
— (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.mpcHandler = [[MPCHandler alloc] init];
     
    return YES;
}

Как я упоминал ранее, приложение будет иметь два контроллера представления. Шаблон единого представления Xcode имеет только один по умолчанию. Давайте добавим второй контроллер вида прямо сейчас. Выберите « Создать»> «Файл …» в меню « Файл» , выберите класс Objective-C в категории « Какао для iOS» слева и нажмите « Далее» .


Убедитесь, что новый класс наследуется от UIViewController и установите для поля Class значение OptionsViewController . Флажки должны оставаться не отмеченными. Нажмите Далее, чтобы продолжить.


Скажите Xcode, где вы хотите сохранить файлы классов, и нажмите « Create .

Пришло время создать пользовательский интерфейс приложения. Откройте основную раскадровку проекта (Main.storyboard), чтобы начать.

Выберите контроллер представления в раскадровке и выберите « Встроить»> «Контроллер навигации» в меню « Редактор» . В результате контроллер представления встроен в контроллер навигации, а панель навигации добавлена ​​в верхнюю часть контроллера представления.


Выберите панель навигации контроллера представления, откройте инспектор атрибутов справа и установите в поле « Заголовок» значение MPCDemo.


Перетащите два экземпляра UIBarButtonItem из библиотеки объектов на панель навигации и обновите их заголовки, как показано ниже.


Это представление, которое игроки увидят и используют для ввода предположения, отправки его другим игрокам и отслеживания предыдущих предположений, сделанных в игре. Чтобы сделать все это возможным, нам нужно добавить текстовое поле, две кнопки и текстовое представление. Посмотрите на следующий скриншот для уточнения. Я также включил атрибуты каждого подпредставления, чтобы помочь вам с этой задачей.

  • UITextField
    • Рамка: X = 20, Y = 77, Ширина = 280, Высота = 30
    • Текст заполнителя: Угадай число (1 — 100) …
  • UIButton
    • Рамка: X = 254, Y = 115, ширина = 46, высота = 30
    • Название: Отправить
  • UIButton
    • Рамка: X = 20, Y = 115, ширина = 48, высота = 30
    • Название: Отмена
  • UITextView
    • Рамка: X = 20, Y = 153, ширина = 280, высота = 395
    • Текст: Ничего (Удалить существующий текст)
    • Цвет фона: светло-серый цвет
    • Цвет текста: белый цвет
    • Редактируемый: нет

Вот так должен выглядеть контроллер представления после того, как вы добавили четыре подпредставления в его представление.


Прежде чем мы перейдем к OptionsViewController , давайте объявим и подключим необходимые выходы и действия в классе ViewController . Откройте ViewController.h и обновите его содержимое, как показано ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
#import <UIKit/UIKit.h>
 
@interface ViewController : UIViewController
 
@property (weak, nonatomic) IBOutlet UITextField *txtGuess;
@property (weak, nonatomic) IBOutlet UITextView *tvHistory;
@property (weak, nonatomic) IBOutlet UIButton *btnSend;
@property (weak, nonatomic) IBOutlet UIButton *btnCancel;
 
— (IBAction)startGame:(id)sender;
— (IBAction)sendGuess:(id)sender;
— (IBAction)cancelGuessing:(id)sender;
 
@end

Пересмотрите основную раскадровку проекта, выберите экземпляр ViewController и соедините выходы и действия с интерфейсом контроллера представления. Если вы не знакомы с этим шагом, позвольте мне показать вам, как это работает. Начните с расширения сцены контроллера представления, чтобы вы могли видеть связанные с ней объекты.


Следующим шагом является нажатие Ctrl или щелчок правой кнопкой мыши на объекте View Controller — MPCDemo в списке, который должен вызвать черное всплывающее окно. В этом окне вы должны увидеть розетки и действия, которые мы объявили ранее, а также ряд других предметов. Чтобы подключить выход текстового поля, щелкните кружок справа от выхода txtGuess и перетащите его в текстовое поле в пользовательском интерфейсе. Появляется синяя линия для визуализации связи. Посмотрите на следующий скриншот, чтобы лучше понять процесс.


Прежде чем продолжить, убедитесь, что вы подключили каждую розетку и действие к соответствующему представлению в пользовательском интерфейсе.

Для класса OptionsViewController нам нужно добавить новую сцену в интерфейс. Перетащите экземпляр UIViewController из библиотеки объектов на холст. Чтобы связать его с существующей сценой, нажмите Ctrl или щелкните правой кнопкой мыши на элементе панели, обозначенном Options, и перетащите его в новый контроллер вида. В появившемся черном всплывающем окне выберите Push из списка параметров.



Имя класса новой сцены по умолчанию — UIViewController , но это то, что мы хотим изменить. В нижней части второй сцены выберите первый элемент, откройте Identity Inspector справа и установите для поля Class значение OptionsViewController в разделе Custom Class .


Выберите панель навигации контроллера представления параметров и установите заголовок « Параметры» в Инспекторе атрибутов . Добавьте пять подпредставлений к представлению контроллера представления, как показано ниже.

  • UITextField
    • Рамка: X = 20, Y = 77, Ширина = 280, Высота = 30
    • Текст заполнителя: Ваше имя игрока …
  • UISwitch
    • Рамка: X = 136, Y = 141, ширина = 51, высота = 31
  • UIButton
    • Рамка: X = 76, Y = 231, ширина = 168, высота = 30
    • Название: Обзор для других игроков
  • UITextView
    • Рамка: X = 20, Y = 269, ширина = 280, высота = 241
    • Текст: Ничего (Удалить существующее содержимое)
    • Редактируемый: НЕТ
  • UIButton
    • Рамка: X = 121, Y = 518, ширина = 78, высота = 30
    • Название: Отключить

Следующий скриншот должен дать вам представление о том, как должен выглядеть конечный результат.


Чтобы завершить пользовательский интерфейс контроллера представления параметров, давайте объявим некоторые выходы и действия в заголовочном файле контроллера представления. Откройте OptionsViewController.h и обновите его содержимое, как показано ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
#import <UIKit/UIKit.h>
 
@interface OptionsViewController : UIViewController
 
@property (weak, nonatomic) IBOutlet UITextField *txtPlayerName;
@property (weak, nonatomic) IBOutlet UISwitch *swVisible;
@property (weak, nonatomic) IBOutlet UITextView *tvPlayerList;
 
— (IBAction)disconnect:(id)sender;
— (IBAction)searchForPlayers:(id)sender;
— (IBAction)toggleVisibility:(id)sender;
 
@end

Пересмотрите основную раскадровку проекта и соедините выходы и действия с пользовательским интерфейсом. Мы завершили пользовательский интерфейс игры и готовы приступить к написанию некоторого кода, чтобы увидеть платформу Multipeer Connectivity в действии.

Следующим шагом является реализация класса OptionsViewController , потому что нам нужно соединение между двумя устройствами, прежде чем мы сможем играть в игру и сосредоточиться на логике игры. Я хотел бы начать с показа вам, как обнаружить соседних игроков (устройств) с помощью встроенного решения Multipeer Connectivity Framework.

Первым шагом является получение объекта mpcHandler мы создали ранее в классе AppDelegate . Это подразумевает, что нам нужен доступ к объекту mpcHandler через делегат приложения. Откройте OptionsViewController.m, перейдите к началу файла и добавьте оператор импорта для файла AppDelegate класса AppDelegate .

1
2
3
4
5
6
7
#import «OptionsViewController.h»
 
#import «AppDelegate.h»
 
@interface OptionsViewController ()
 
@end

В расширении класса объявите свойство, которое будет хранить ссылку на экземпляр AppDelegate .

1
2
3
4
5
6
7
8
9
#import «OptionsViewController.h»
 
#import «AppDelegate.h»
 
@interface OptionsViewController ()
 
@property (strong, nonatomic) AppDelegate *appDelegate;
 
@end

Нет необходимости создавать экземпляр экземпляра AppDelegate поскольку это автоматически выполняется во время запуска приложения. В методе viewDidLoad контроллера viewDidLoad мы получаем ссылку на делегат приложения и настраиваем объект mpcHandler . Мы инициализируем peerID и свойства MPCHandler класса MPCHandler , вызывая методы, которые мы объявили и реализовали ранее в этом руководстве.

1
2
3
4
5
6
7
8
9
— (void)viewDidLoad {
    [super viewDidLoad];
 
    self.appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
 
    [self.appDelegate.mpcHandler setupPeerWithDisplayName:[UIDevice currentDevice].name];
    [self.appDelegate.mpcHandler setupSession];
    [self.appDelegate.mpcHandler advertiseSelf:self.swVisible.isOn];
}

В viewDidLoad мы настраиваем одноранговый viewDidLoad , передавая имя устройства в качестве отображаемого имени. Мы также инициализируем свойство MPCHandler экземпляра MPCHandler которое мы будем использовать позже для установления соединения между узлами. Важно, чтобы мы сделали это после инициализации экземпляра MCPeerID , потому что сеанс требует допустимого объекта peerID во время его инициализации. Наконец, мы вызываем advertiseSelf: и передаем состояние коммутатора в пользовательский интерфейс контроллера представления параметров. Состояние по умолчанию коммутатора — YES , что означает, что наше устройство может быть обнаружено другими устройствами.

Представить контроллер представления браузера так же просто, как и получается. Однако свойство MPCHandler экземпляра MPCHandler необходимо сначала инициализировать. Мы хотим показать контроллер представления браузера, когда пользователь нажимает кнопку « Обзор других игроков» . Давайте реализуем searchForPlayers: действие дальше.

1
2
3
4
5
6
7
8
9
— (IBAction)searchForPlayers:(id)sender {
    if (self.appDelegate.mpcHandler.session != nil) {
        [[self.appDelegate mpcHandler] setupBrowser];
 
        [self presentViewController:self.appDelegate.mpcHandler.browser
                           animated:YES
                         completion:nil];
    }
}

Представление контроллера представления браузера требует двух шагов. Сначала мы вызываем setupBrowser для объекта mpcHandler делегата приложения, чтобы инициализировать экземпляр MCBrowserViewController а затем представляем этот экземпляр пользователю. Обратите внимание, что сначала мы проверяем, не равен ли объект MPCHandler экземпляра MPCHandler nil , поскольку объект session используется для инициализации экземпляра MCBrowserViewController .

Если вы запустите приложение и выполните поиск других игроков, вы должны увидеть вид, аналогичный показанному на следующем снимке экрана.


Чтобы протестировать приложение, вам нужно как минимум два устройства, например, iOS Simulator и физическое устройство. Чтобы создать скриншот выше, я запустил приложение в iOS Simulator и на устройстве. Если вы нажмете на имя устройства, выбранное устройство будет перемещено в раздел с надписью « Приглашенные», как показано ниже.


В то же время на втором устройстве отображается окно с предупреждением, уведомляющее другого игрока о том, что вы хотите установить соединение. Приглашенный может принять или отклонить приглашение. Если игрок принимает приглашение, браузер устанавливает соединение между двумя устройствами.


Когда второй игрок принимает приглашение, кнопка « Готово» на контроллере представления браузера активируется. Однако, если вы попытаетесь нажать кнопки « Отмена» или « Готово» , вы заметите, что ничего не происходит. Почему это? Объяснение простое, сначала нам нужно реализовать протокол OptionsViewController классе OptionsViewController . Давайте сделаем это сейчас. Однако сначала обновите действие searchForPlayers: установив контроллер представления параметров в качестве делегата контроллера представления браузера.

01
02
03
04
05
06
07
08
09
10
— (IBAction)searchForPlayers:(id)sender {
    if (self.appDelegate.mpcHandler.session != nil) {
        [[self.appDelegate mpcHandler] setupBrowser];
        [[[self.appDelegate mpcHandler] browser] setDelegate:self];
 
        [self presentViewController:self.appDelegate.mpcHandler.browser
                           animated:YES
                         completion:nil];
    }
}

Однако это приводит к другому предупреждению компилятора, поскольку OptionsViewController еще не соответствует протоколу MCBrowserViewControllerDelegate . Чтобы это исправить, откройте OptionsViewController.h, импортируйте файлы заголовков платформы Multipeer Connectivity и OptionsViewController соответствие с протоколом MCBrowserViewControllerDelegate .

1
2
#import <UIKit/UIKit.h>
#import <MultipeerConnectivity/MultipeerConnectivity.h>
01
02
03
04
05
06
07
08
09
10
11
@interface OptionsViewController : UIViewController <MCBrowserViewControllerDelegate>
 
@property (weak, nonatomic) IBOutlet UITextField *txtPlayerName;
@property (weak, nonatomic) IBOutlet UISwitch *swVisible;
@property (weak, nonatomic) IBOutlet UITextView *tvPlayerList;
 
— (IBAction)disconnect:(id)sender;
— (IBAction)searchForPlayers:(id)sender;
— (IBAction)toggleVisibility:(id)sender;
 
@end

Нам нужно реализовать два метода протокола MCBrowserViewControllerDelegate , чтобы включить кнопки « Отмена» и « Done . Реализации идентичны, то есть отклонение контроллера представления браузера. Откройте файл OptionsViewController класса OptionsViewController и добавьте два метода делегата, как показано ниже.

1
2
3
4
5
6
7
— (void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController {
    [self.appDelegate.mpcHandler.browser dismissViewControllerAnimated:YES completion:nil];
}
 
— (void)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController {
    [self.appDelegate.mpcHandler.browser dismissViewControllerAnimated:YES completion:nil];
}

Если вы снова запустите приложение, вы увидите, что кнопки « Отмена» и « Готово» работают должным образом. При нажатии на кнопку « Готово» устанавливается скрытое соединение, а контроллер представления браузера отключается.

Предыдущий раздел был ключевым аспектом структуры Multipeer Connectivity. Однако после нажатия кнопки « Готово» мы не заметили никаких изменений. Это почему? В процессе подключения устройства, которые подключаются, переходят из состояния « Не подключено» в состояние « Соединение» и, если все идет хорошо, переходят в состояние « Подключено» . Пока эти переходы имеют место, session:peer:didChangeState: метод session:peer:didChangeState: делегат платформы Multipeer Connectivity. В результате настраиваемое уведомление, которое мы настраиваем в классе MPCHandler публикуется при каждом изменении состояния.

Наша задача — наблюдать за этими уведомлениями и соответствующим образом реагировать, когда одноранговый узел подключен или отключен от устройства пользователя. Всякий раз, когда состояние соединения изменяется, кроме состояния Соединения , мы обновляем текстовое представление класса OptionsViewController игроками, которые в данный момент подключены к устройству пользователя.

Во-первых, мы должны добавить экземпляр контроллера представления в качестве наблюдателя для уведомлений MPCDemo_DidChangeStateNotification . Обновите метод viewDidLoad контроллера представления, как показано ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
— (void)viewDidLoad {
    [super viewDidLoad];
     
    self.appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
     
    [self.appDelegate.mpcHandler setupPeerWithDisplayName:[UIDevice currentDevice].name];
    [self.appDelegate.mpcHandler setupSession];
    [self.appDelegate.mpcHandler advertiseSelf:self.swVisible.isOn];
     
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(peerChangedStateWithNotification:)
                                                 name:@»MPCDemo_DidChangeStateNotification»
                                               object:nil];
}

Помните, что уведомление с именем MPCDemo_DidChangeStateNotification публикуется каждый раз, когда изменяется состояние соединения однорангового MPCDemo_DidChangeStateNotification . Когда это происходит, вызывается peerChangedStateWithNotification: контроллера представления. Реализация peerChangedStateWithNotification: не сложно, как вы можете видеть ниже. Мы извлекаем состояние однорангового userInfo словаря userInfo уведомления и, если оно не равно MCSessionStateConnecting , мы обновляем текстовое представление контроллера представления для отображения состояний каждого однорангового MCSessionStateConnecting .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
— (void)peerChangedStateWithNotification:(NSNotification *)notification {
    // Get the state of the peer.
    int state = [[[notification userInfo] objectForKey:@»state»] intValue];
 
    // We care only for the Connected and the Not Connected states.
    // The Connecting state will be simply ignored.
    if (state != MCSessionStateConnecting) {
        // We’ll just display all the connected peers (players) to the text view.
        NSString *allPlayers = @»Other players connected with:\n\n»;
 
        for (int i = 0; i < self.appDelegate.mpcHandler.session.connectedPeers.count; i++) {
            NSString *displayName = [[self.appDelegate.mpcHandler.session.connectedPeers objectAtIndex:i] displayName];
 
            allPlayers = [allPlayers stringByAppendingString:@»\n»];
            allPlayers = [allPlayers stringByAppendingString:displayName];
        }
 
        [self.tvPlayerList setText:allPlayers];
    }
}

Как следует из его названия, свойство connectedPeers объекта session является массивом, который содержит подключенные узлы. Мы используем цикл for чтобы перебрать этот массив и заполнить текстовое представление именами связанных пиров.

Запустите два экземпляра приложения и установите соединение, чтобы увидеть результат нашей работы. Теперь, когда вы нажмете кнопку « Готово» , в текстовом представлении отобразится список пиров, подключенных к устройству пользователя.


Это завершает его для первой части этого руководства о структуре многопользовательского подключения. Мы рассмотрели несколько новых API и заложили основы нашей игры. В следующем уроке мы продолжим создание игры путем реализации логики игры, а также интеграции инфраструктуры Multipeer Connectivity для передачи данных между игроками в игре.