Multipeer Connectivity.Framwork实战

来源:互联网 发布:ps4全境封锁网络卡 编辑:程序博客网 时间:2024/04/19 12:44
[声明:本文为转载]

Multipeer Connectivity Framework 是iOS 7 推出的众多新框架的一种,它拓宽了操作系统中应用的范围。其目的是使开发者可以创建通过Wi-Fi或蓝牙在近距离建立连接的应用。是在近距离设备间建立互动,交换数据和其他资源的很好的简单工具。

继续我们的介绍,在看到例子之前我们有必要讨论一下Multipeer Connectivity Framework的细节部分使让我们对它更熟悉。首先,我们必须强调一下这种框架合作只适用于近距离的设备,意思是只能在同一个网络设备下进行(Wi-Fi或者蓝牙),也就是说不要期望在远距离的环境下工作。在多用户端连接下,一个设备可以和很多设备连接并且和其所有的同时进行互动。一个单一的设备就是一个端口。当两个端口连接起来后,就成了一个Multipeer Connectivity的会话, 这个会话负责管理所有端口间的互动和数据交换。在多个设备下,可以建立多个会话。

multipeer connectivity featured

在讨论建立连接之前,(需要连接的)设备们首先需要发现对方的存在。在应用中使用 Multipeer Connectivity,“发现”是第一个阶段。那么设备是如何发现彼此的呢?我们假设,现在有两个设备想要连接。那么至少要有一个设备作为浏览器(browser),用来搜寻其他设备;第二个设备必须是可发现的,它要声明它在这里,并且它想要与别的设备连接。换句话说,第二个设备需要宣传自己。一般来说,两个设备都要宣传自己,但至少要有一个能浏览其他设备,从而建立连接。

在浏览方面,Apple 提供了两种方法。第一种简单一些,是一个框架中直接嵌入的浏览器UI,被调用的时候就弹出一个浮层,上面列出所有可连接的设备。第二种为开发者提供了更强的灵活性,是一种完全用代码实现的方式,因此您可以根据应用需求来定制这个浏览器。本文是对这个框架的入门教程,因此接下来我们只用第一种方法。

一旦发现了其他对等实体,查询广播实体的对等实体就会发送请求建立连接的信息。如果第二个对等实体(也就是广播实体)接收这条消息,那么就会建立一个会话,至此这两个对等实体就可以交换数据了。

接下来说说数据,多对等实体连接框架可以用来发送和接收三种类型的数据。这三种数据是:

  • 消息数据(包括文本、图像以及可以转换为NSData对象的任何其他数据)

  • 流数据

  • 资源数据

接下来更详细的讨论一下消息数据,传输这样的数据可以使用两种模式:可靠传输模式和不可靠传输模式。框架在使用可靠模式发送数据的时候,它一定会确保发送的任何数据都会到达接收者,而且是按照发送的顺序到达接收者的。不过,要完成可靠模式的数据传输就需要更多的时间。然而,使用不可靠模式发送数据几乎不花时间,发送也真的很快,不过它不能确保发送的所有数据都能到达另一端,当然,接收数据的顺序也不是按照发送顺序的。究竟哪一种模式是最优选择这一点是完全由每个应用的要求来确定的,因此开发者要决定使用哪种模式。

通常,多实体连接框架为高级语言开发者提供所需的类和库,因此不需要有任何C或者其他低级语言的编程经验。在这个框架下,你可以使用许多内置的功能,而不会为实现或者解决任何与网络相关的问题而困惑。在这篇编程指南里,我们不会耗费许多时间对这个框架做一些理论性的介绍,因此我们强烈建议你访问Apple文档,进行一些学习,当然,也可以看看苹果全球开发者大会(WWDC)2013的708号视频会话。特别要注意的是:要对多实体连接框架进行测试的话,你手头最少要有两台设备,或者一台设备和一个iPhone模拟器。

为了很容易地在实践中使用多实体连接功能以及搞清楚多实体连接框架是怎样进行设备通信的,请向下阅读!

示例应用概览

在这篇编程指南里,我们将创建一个应用样例,用以说明多实体连接框架最重要的部分。前面我已经提到使用这个框架可以交换三种类型的数据:消息数据、流数据和资源数据。在我们即将实现的示例应用里,我们将看到如何通过设备间发送NSData对象来发送消息数据,以及如何共享资源数据的,比如共享文件。不过,在我们创建示例应用之前,我们需要快速浏览一下示例应用究竟做了哪些事情。

我们将创建一个带有标签的应用, 总共带有三个标签。让我们从最后一个名称为我的连接的标签说起,我们将创建一个视图控制器,用它来管理对等实体(设备)名,正在广播的实体和这台设备上已经建立的所有连接(实际上是会话)。尤其要注意的是,我们使用了文本输入,这样可以为这台设备自定义一个名称,以便在其他对等实体上显示这台设备。再还有一个按钮,一旦轻击这个按钮就会显示默认的浏览器界面,同时控制开关用来打开或者关闭这台设备的广播功能。接下来是表视图,在这里列出了这台设备正在进行的所有连接。表视图下有一个或者多个按钮,通过这些按钮可以断开这个设备上的会话。下图显示了简要的视图控制器:

Multipeer Connectivity Demo App1

接下来介绍的另一个视图控制器是位于第一个标签下。这个视图控制器的名字为聊天窗口,用它来在设备间通过多实体连接框架发送文本消息。这个视图控制器由一个文本输入框、一个文本视图和两个按钮组成, 文本输入框用来书写消息,文本视图用来显示整个聊天内容,而两个按钮中一个用来发送消息,一个用来取消消息发送 。下面是这样一个视图控制器的屏幕截图:

Multipeer Connectivity Demo App2


最后介绍的这个视图控制器是位于第二个标签下的,名字为文件共享。这儿有一个表视图,它包含一些(样例)文件列表。当选中一行时,就会有显示这样的选项:动作选项和所有对等实体的名字。选择好对等实体后,就会直接把所选的文件发送给该对等实体。为了让这个视图更吸引用户并且更具有交互性,当设备接收一个文件的时候,在这个表视图的最后一行显示文件名、发送实体以及正在接收文件的进度。当这个设备发送一个文件的时候 ,这个文件名的右边将以发送百分比来显示发送的进度。下图是第二个标签下的视图控制器起始状态的截图:

Multipeer Connectivity Demo App3

我已经介绍了示例应用的所有视图控制器,特别注意的是我从第三个标签开始介绍 ,因为在这篇编程指南里,处于第三个标签下的视图控制器对我们来说最重要。通过对这个视图的了解,我们就能明白如何处理和管理发现过程和会话建立过程。

在我们结束简要地介绍示例应用之前,还有一些十分重要的事情必须提及一下。为了在该应用范围内使用多实体连接框架和避免重复编写同样的功能三次(每个视图控制器各一次),我们创建一个用于管理所有与这个框架相关的对象和任务的类。接着我们就可以在应用的委派类(AppDelegate)里初始化这个类对应的对象。另外,通过应用委派,我们就可以跨应用访问该对象了。毕竟与仅构建一次并把其当工具多次使用相比,对某个东西开发多次是一个非常糟糕的编程实践。

在我们浏览完这个应用之后,我们就要进行开发了,现在是时候进行这个应用的开发了 。如果你打算逐步构建这个应用,请你下载位于这篇编程指南末尾的两个样例文件。另外,为了便利起见,你可以下载整个示例应用,它包含了所有的代码。

演示应用创建

启动 Xcode 通过点击欢迎屏幕左侧相应的按钮创建一个新工程:

Xcode Welcome Dialog

在向导的第一步,在 iOS 大类下的 Application 类别中选择 Tabbed Application 选项。

Xcode New Project Template

点击 Next 按钮转入向导的下一步。在 Product Name 输入框中我命名这个应用的名称为 MCDemo,但你也可以命名为你喜欢的其它名称。 此外,确保在Devices 的下拉菜单中选择 iPhone。其它保持原样,点击 Next 按钮前进。

Xcode Project Options

最后,为你的工程选择一个存储位置,点击 Create 按钮结束向导。现在工程已经准备好了。

添加新Tab

就像前面提到的,这个应用里总共有三个tab,但默认创建的只有两个。所以,我们的第一个任务是添加一个新tab,然后把这个新tab连接上一个新类。除此之外,我们还要为所有tab设置标题和icon。

首先在项目里添加一个新的 UIViewController 类,用于连接我们一会儿将要添加的新tab。在项目导航(左边栏)中, 按着Ctrl点击或者右击 MCDemo 这个 group ,然后从弹出菜单中选择 New File…选项,如下图所示:

Multipeer Connectivity - Add New File

在左边的 iOS 一项下,要确定选的是 Cocoa Touch 分类。然后,选择 Objective-C class 图标,点击 Next 按钮继续。

Multipeer Connectivity - Add New File

在 Class 输入框,填写 ConnectionsViewController 。并且,要确保Subclass of 这一栏选择的是 UIViewController 。不要勾选下面两个选项。点击 Next 进入下一步。

Multipeer Connectivity - Add New File

最后一步,点击 Create 按钮,就大功告成啦。现在,在左边栏的项目导航中应该可以看到 ConnectionsViewController.h 和 ConnectionsViewController.m 两个文件了。

Multipeer Connectivity - Add New File

现在点击 Main.storyboard ,让它打开 Interface Builder 。首先,添加一个新的 view controller ,,方法是把它从 Object Library 中拖出来放在 canvas 上,就放在 second view controller 的下面。

Multipeer Connectivity - Designing Interface

然后,一直按着键盘上的 Ctrl 键,点击 Tab Bar Controller ,然后一直按着鼠标左键,拖到我们刚添加的新 view controller 上。现在放开 Ctrl 键和鼠标左键了。会弹出一个黑色的窗口,其中的 Relationship Segue 部分你必须点击 view controllers 选项,这样这个新的 view controller 就会被加为 tab bar controller 的一个新 tab。

Multipeer Connectivity - Designing Interface

tab bar controller 目前包含了3个 tab,并且连接上了我们新创建的 view。我们现在需要将新 view controller 的类设置为之前添加的 ConnectionsViewController 。为此,首先点击新 view controller,然后在 Utilities Pane(右边栏)中,打开 Identity Inspector (第3栏),之后在 Custom Class 部分将ConnectionsViewController 填进 Class 对应的输入框。

Multipeer Connectivity - Set Class Name

最后,我们只需为 tab 们设置正确的标题。要设置一个 tab 的标题,需要首先在场景中(而不是 tab bar controller上)选中需要修改的 tab。然后,再回到 Utilities pane(右边栏),打开 Attributes Inspector(第4栏),然后在 Bar Title 部分将标题填入 Title 对应的输入框。

Multipeer Connectivity - Set Title

设置标题如下:

  • First View Controller: Chat Box

  • Second View Controller: File Sharing

  • Connections View Controller: My Connections

注意,教程中所有在 Interface Builder 进行的可视化配置都是针对 4 寸屏幕设备的,如 iPhone 5 或 5S。如果你想要让这个应用在较老的3.5寸设备上运行,只需变通一下,应用 Xcode 的推荐约束,然后就可以轻松运行了。

连接视图控制器:设置界面

现在,第三个标签连同其自己的视图控制器已经被添加了,是时候从最后一个 tab 的视图控制器,连接视图控制器,开始建立我们的应用程序了。我们的第一个任务是设置它的界面,以及声明和连接所需的任何 IBOutlet 属性和 IBAction 方法。所以,在界面生成器中,从对象库中拖放下面提到的控件到连接视图控制器场景中。请注意,提供的每个控件的所有这些属性你都应该修改:

  1. UITextField

    • Frame: X=20, Y=20, Width=280, Height=30

    • Placeholder: The device name displayed to others…

  2. UILabel

    • Frame: X=20, Y=63, Width=180, Y=21

    • Text: Visible to others?

  3. UISwitch

    • Frame: X=251, Y=58, Width=51, Y=31

    • State: ON

  4. UIButton

    • Frame: X=94, Y=92, Width=132, Y=30

    • Title: Browse for devices

  5. UITableView

    • Frame: X=0, Y=130, Width=320, Y=352

  6. UIButton

    • Frame: X=121, Y=490, Width=78, Y=30

    • Text: Disconnect

    • Enabled: False (Unchecked)

添加完所有上面这些控件之后,你的场景现在看来应该会象这样:

Multipeer Connectivity - UI

让我们现在声明一些 IBOutlet 属性和一些 IBAction 方法。打开ConnectionsViewController.h 文件,在 interface body 内添加下面的属性:

?
1
2
3
4
@property (weak, nonatomic) IBOutlet UITextField *txtName;
@property (weak, nonatomic) IBOutlet UISwitch *swVisible;
@property (weak, nonatomic) IBOutlet UITableView *tblConnectedDevices;
@property (weak, nonatomic) IBOutlet UIButton *btnDisconnect;

另外,添加这些 IBAction 方法:

?
1
2
3
- (IBAction)browseForDevices:(id)sender;
- (IBAction)toggleVisibility:(id)sender;
- (IBAction)disconnect:(id)sender;

现在回到Main.storyboard把控件与它连接起来。做之前,确保左侧的Document Outline面板打开。下面,按下Ctrl键或在Connections View Controller – Connections对象上右击,就会弹出一个黑色窗口。在每一个属性(和IBAction方法)旁边都有一个圆。点击IBOutlet属性旁边的圆,不要释放鼠标按钮,拖放到对应控件的屏幕上。下图是详细操作:

Multipeer Connectivity - Setup IBOutlet

连接应按以下规则建立:

  • txtName属性放到 UITextField对象.

  • swVisible 属性放到 UISwitch 对象.

  • blConnectedDevices 属性放到 UITableView 对象.

  • btnDisconnect 属性放到 UIButton (Disconnect) 对象.

其余屏幕中未出现的属性,就不需要使用 IBOutlet 属性.

用上述相同的方法,把 IBAction方法连接到相应的控件. 下面告诉你如何搭配各种方法和控件s:

  • browseForDevices: 连接到第一个 UIButton (Browse for devices) 对象.

  • toggleVisibility: 连接到 UISwitch 对象.

  • disconnect: 连接到第二个 UIButton 对象.

 Connections View Controller的接口已经就绪。我们接着讨论我们上面添加的每一个控件的功能和目的。 现在到了我们第一次需要关注Multipeer Connectivity框架细节的时候了。

一个基于Multipeer Connectivity框架的类

现在,我们要做点大转变:先不管Interface Builder和其他任何的可视化设置,我们将要全部注意力集中在代码上面。在这一章节,我们的目标是创建一个新类,在这个类中,我们需要实现框架相关的全部逻辑,为此我们将会使用任何必须的框架类和执行任何必须的任务。说明一下,通过框架来做到这些并不是必须的,然而,这确实是最简单的方式来使这个简单应用能够执行完全部应用端的任务而不需要多次重新编码。

在开始做上述事情之前,首先要创建一个新类。所以,在项目导航窗口右键点击MCDemo组,在弹出菜单中选择New File...选项,或使用快捷键Command-N(Ctrl+N)。

在向导窗口中,选择Object-C class选项来作为新文件的模板。点击Next继续。接下来,在Subclass of框中,设置为NSObject,在正上方的Class框中,必须填入新类的名字。这里我填写的是MCManager,当然,你和我命名一致会是个不错的选择,仅仅为了能保证正确的学习本教程。

Multipeer Connectivity - Create new class mcmanager

最后,点击Next,然后点击Creat按钮为项目中添加新类。添加成功之后,项目导航中应该会列出MCManager.hMCManager.m这两个文件

让我们现在写点代码。打开 MCManager.h 声明一些所需对象。但在做那些之前,我们必须引入 Multipeer Connectivity 库到我们的项目,因此,开头在文件的顶部添加下面一行:

#import <MultipeerConnectivity/MultipeerConnectivity.h>

如你所见,我没有手动添加框架到项目中,而是直接使用它。编译器会为我们做这件事,这要感谢它采用的一个新功能,叫作自动链接(Auto Linking)

现在,修改界面的header行来采用 MCSesssionDelegate 协议,如下:

?
1
@interface MCManager : NSObject <MCSessionDelegate>

然后,声明下面这些对象:

?
1
2
3
4
@property (nonatomic, strong) MCPeerID *peerID;
@property (nonatomic, strong) MCSession *session;
@property (nonatomic, strong) MCBrowserViewController *browser;
@property (nonatomic, strong) MCAdvertiserAssistant *advertiser;

PeerID 对象表示设备,它包含发现设备和建立会话阶段所需的各种属性。Session对象是最重要的,因为它代表目前的对等点(这个程序将运行的设备)将创建的会话。任何数据交换和通信细节都由该对象控制。浏览器对象实际上是代表苹果提供的用于浏览其他对等点的默认UI,我们将为此目的而使用它。对于框架的浏览功能更先进的处理,苹果提供了一个可编程的替代方式,但它超出了现在本文的范围。最后,有一个广告对象,这是用来从目前的设备去宣传自己,使其容易被发现。

注意所有这些对象的类都属于 Multipeer Connectivity framework。你会看到我们将如何使用它们,但是现在让我们声明一些接下来会用到的公有方法:

?
1
2
3
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName;
-(void)setupMCBrowser;
-(void)advertiseSelf:(BOOL)shouldAdvertise;

现在来到 MCManager.m 文件,加入如下 init 方法来书实话我们的类:

?
1
2
3
4
5
6
7
8
9
10
11
12
-(id)init{
    self = [super init];
     
    if (self) {
        _peerID = nil;
        _session = nil;
        _browser = nil;
        _advertiser = nil;
    }
     
    return self;
}

这时 Xcode 通常会报一些警告了。这是因为我们还没有实现这些公有方法,还有 MCSessionDelegate 协议中的代理方法。为了让这些警告消失,我们首先增加如下必要的代理方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(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{
     
}

尽管这些方法的名字显而易见的说明了它们是干嘛的,我们还是快速提及一下。第一个方法在节点改变状态的时候被调用,已连接或已断开。有三个状态: MCSessionStateConnected, MCSessionStateConnecting and  MCSessionStateNotConnected。最后一个状态在节点从连接断开后依然有效。第二个方法在有新数据从节点过来时被调用。记住有三种数据可以交换:消息,流和资源。这个是消息的代理。下面两个方法在收到资源时被调用,最后一个用来接收流。在这个教程里除了最后一个我们全都会用到。

现在让我们去实现上面的公有方法。然后我们接下来开始下一个代码片段。

?
1
2
3
4
5
6
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{
     _peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
      
     _session = [[MCSession alloc] initWithPeer:_peerID];
     _session.delegate = self;
 }

首先,peerID对象初始化,因为一切都基于它。displayName字符串值提供给init方法(作为该方法的参数)一个设备名字,这个名字会出现在其他对等实体上。这可以是设备的名称(如“约翰的iPhone”),或一个自定义名称。如果你还记得,我们 为此在界面添加一个文本域,以后,我们将看到更多关于它的东西。

然后,我们初始化session对象,这是最重要的,因为一切都基于它。我们为其初始化提供peerID,然后我们在最后一行把自己赋给delegate。

接下来:

?
1
2
3
-(void)setupMCBrowser{
     _browser = [[MCBrowserViewController alloc] initWithServiceType:@"chat-files"session:_session];
 }

该方法仅有一行重要的代码。这是默认的初始化,由苹果预制视图控制器,显示一个浏览器搜索其他对等实体。在初始化上它接受两个参数:serviceType定义浏览器应该寻找的类型的服务,通过一个小的文本描述。为了浏览器能够发现广播者,这个小文本应该是相同的。  关于它的名字有两个规则:

  1. 必须是1 - 15字符。

  2. 只能包含ASCII小写字母,数字和连字符。

第二个参数是先前初始化的session对象

一个公共方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
-(void)advertiseSelf:(BOOL)shouldAdvertise{
     if (shouldAdvertise) {
         _advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:@"chat-files"
                                                            discoveryInfo:nil
                                                                  session:_session];
         [_advertiser start];
     }
     else{
         [_advertiser stop];
         _advertiser = nil;
     }
 }

我们将使用这个公共方法切换设备的广播状态。你注意到,参数定义设备是否应该广播自己,取决于我们的应用程序的设置。记住,我们添加了一个UISwitch对象设置我们是否对其他设备可见。

当我们希望打开广播时,我们初始化它,我们开始它。注意serviceType文本要与浏览器的相匹配。当我们想要关掉广播,我们简单地停止它,我们使我们的对象置为nil。

那么,现在本类已经准备被用作一个工具。我们将用它很多次,我们会将代码添加到session的delegate里面。对你来说也许不是所有的东西都是有意义的,但是别担心,这些都会在用的时候清除。

最后一件事留给我们了,去在AppDelegate.h文件,声明MCManager对象:

?
1
@property (nonatomic, strong) MCManager *mcManager;

不要忘记引入这个类:

?
1
#import "MCManager.h"

然后,打开 AppDelegate.m文件,application:didFinishLaunchingWithOptions:  delegate方法里面加入下面的一行初始化代码:

?
1
2
3
4
5
6
7
8
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
     // Override point for customization after application launch.
      
     _mcManager = [[MCManager alloc] init];
      
     return YES;
 }

我们通过应用程序的代理来使用 MCManager类。让我们继续 Mutlipeer Connectivity 吧!

发现阶段

通过我们之前所创建的ConnectionsViewController这个类,浏览器视图控制器将会被显示出来,因此我们需要使它遵照MCBrowserViewControllerDelegate这个协议,来使它能够操作浏览器。打开ConnectionsViewController.h文件,并像你以前在McManager类中那样引入Multipeer Connectivity架构:

1 #import <MultipeerConnectivity/MultipeerConnectivity.h>

接下来,修改像下面这样修改类头部:

1 @interface ConnectionsViewController : UIViewController <MCBrowserViewControllerDelegate>

我已经提到过很多次,我们会通过应用程序的代理来使用我们自定义的类(MCManager这个类)。这意味着我们需要访问它,所以打开ConnectionsViewController.m文件,转到类的私有区域。在那里定义下一个对象:

1 @property (nonatomic, strong) AppDelegate*appDelegate;

不用提示XCode提示错误。你只需要像下面这样导入:

1 #import "AppDelegate.h"

现在我们可以访问应用程序代理的mcManager对象,但是如何操作呢?直接跳转到viewDidLoad方法。在那里我们可以添加以下三行:

- (void)viewDidLoad
{
    [super viewDidLoad];
   
    _appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    [[_appDelegate mcManager] setupPeerAndSessionWithDisplayName:[UIDevice currentDevice].name];
    [[_appDelegate mcManager] advertiseSelf:_swVisible.isOn];
}

第一行我们使用sharedApplication类方法初始化appDelegate对象。这样操作之后,我们能够调用mcManager对象任何需要的公共方法,这正是我们接下来要做的。当我们在初始化我们自定义的类时,调用setupPeerAndSessionWithDisplayName:这个方法,我们为设备的显示名字指定其真实的名称。在执行的过程当中,如果没有自定义的名字被指定到输入框中,这个名字将会出现到其它端点上。最终,我们调用advertiseSelf:方法,传入当中开关的状态来使能或者置空advertiser这个对象。

现在让我们添加一些必要的代码到browseForDevices:这个IBAction方法中来实现这个浏览器视图控制器的外观。在你的ConnectionsViewController.m这个文件中添加下面这个代码段:

- (IBAction)browseForDevices:(id)sender{
    [[_appDelegate mcManager] setupMCBrowser];
    [[[_appDelegate mcManager] browser] setDelegate:self];
    [self presentViewController:[[_appDelegate mcManager] browser] animated:YES completion:nil];
}

第一行,我们调用MCManager这个类的公共方法setupMCBrowser。接下来,我们设置self对象(就是这个类的对象本身)作为它的代理,最后以模态的方式将它呈现出来。在实现这个方法之后,带着"Browse for devices"标题的按钮就可以完美的工作了,如果你编译并运行这个应用,然后点击这个按钮,这就是你希望看到的:

Multipeer Connectivity - Browser Running

太棒了!我们刚从Multipeer Connectivity框架中尝到了一点甜头。然而,如果你尝试使用这个视图控制器的Done或者Cancel按钮,你会发现它们没有正常工作。为了激活它们,我们必须实现MCBrowserViewControllerDelegate协议的两个代理方法。所以,回到XCode并添加这两个方法:

-(void)browserViewControllerDidFinish:(MCBrowserViewController*)browserViewController{
    [_appDelegate.mcManager.browser dismissViewControllerAnimated:YES completion:nil];
}

-(void)browserViewControllerWasCancelled:(MCBrowserViewController*)browserViewController{
    [_appDelegate.mcManager.browser dismissViewControllerAnimated:YES completion:nil];
}

当点击这个视图控制器的Done按钮时,我们简单的让它消失,当点击Cancel按钮里也希望这个浏览器有同样的动作。因此,所有这些方法包含同样的代码,都让这个浏览器视图控制器消失。第一个方法是Done按钮的代理,第二个方法是Cancel按钮的代理。

在深入研究之前,只需要记住如何使用下面的代码来访问browser这个对象:

1 _appDelegate.mcManager.browser

现在,视图控制器的两个按钮都正常工作了。然后,你还不能测试Done按钮,因为Done按钮被禁用了,除非其它设备发现并连接到当前这个端点。不过在我们理解它之前,让我们再实现两个功能:如何为设备自定义名字;如果使用/禁用广告。

对等点显示名称和广告状态

让我们从这个文本域开始。转到 ConnectionsViewController.h 文件,采用 UITextFieldDelegate 协议,如下所示:

?
1
@interface ConnectionsViewController : UIViewController <MCBrowserViewControllerDelegate, UITextFieldDelegate>

现在,在 ConnectionsViewController.m 文件的viewDidLoad 方法中,加入下面内容:

?
1
2
3
4
5
6
- (void)viewDidLoad
{
    ...
    
    [_txtName setDelegate:self];
}

我们将实现文本字段的 textFieldShouldReturn: 委托方法,因为我们希望返回按钮被按下后键盘消失,并且 MCManager类的 PeerID 对象得到我们设置到文本字段的名称。然而,在 viewDidLoad 方法中我们已经初始化了 PeerID 对象和会话对象,所以我们首先需要设置它们为 nil,然后通过调用setupPeerAndSessionWithDisplayName: 方法使用指定名称重新初始化它们。实现如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
    [_txtName resignFirstResponder];
    
    _appDelegate.mcManager.peerID = nil;
    _appDelegate.mcManager.session = nil;
    _appDelegate.mcManager.browser = nil;
    
    if ([_swVisible isOn]) {
        [_appDelegate.mcManager.advertiser stop];
    }
    _appDelegate.mcManager.advertiser = nil;
    
    
    [_appDelegate.mcManager setupPeerAndSessionWithDisplayName:_txtName.text];
    [_appDelegate.mcManager setupMCBrowser];
    [_appDelegate.mcManager advertiseSelf:_swVisible.isOn];
    
    return YES;
}
请注意,我们检查广告是否开启,如果是这样的话,我们首先停止它,然后设定相应的对象为 nil。

在这里我要指出一个重要的事实:一旦我们的设备连接到另一个点,我们就不应该再改变其显示名称,因为一个会话已经被建立,任何数据交换都可能已经在前进。因此,为了防止我们在设备连接上之后改变名字,我们将文本域禁用。只有当没有连接到其他点时,文本字段才被启用,我们稍后会把这个功能与“断开连接”按钮关联起来。更具体地说,一旦连接被建立,文本字段将被禁用,当“断开连接”按钮被用来终止一个连接时,文本域将再次成为可用。所以,对现在来说,只把它记在心里,让我们继续前进。

接下来让我们使开关控件能启用和禁用广告。执行以下IBAction方法:

?
1
2
3
- (IBAction)toggleVisibility:(id)sender {
    [_appDelegate.mcManager advertiseSelf:_swVisible.isOn];
}

很简单的实现,对吗?我们只要调用advertiseSelf方法就能根据开关的状态设置广告的状态。

现在如果你测试这个应用程序(用两个设备,或一个设备和一个模拟器),你可以玩玩对等点发现和我们这里所加的另外两个功能。这里是一些截图:

当发现附近的设备时:

Search for Nearby Devices

开关第二个设备的广播功能:

Multipeer Advertising

更改设置的显示名称:

Multipeer - Changing Device Name

请注意,设备的显示名称和广播状态都是可以改变的,因为我们还没有建立任何连接。在接下来的部分,我们要做的就是建立连接。我们也将看到我们如何得到关于连接的节点的通知,如何在表格视图中显示其他节点的名称,如何使 Disconnect 按钮工作。

连接

连接真的很简单。在浏览视图控制器中,你必须点击一个附近设备的名称,等到它被连通。视图控制器显示着连接的状态,从“未连接”到“连接中”,最后到“已连接”。一旦已连接,Done 按钮成为可用状态。

Multipeer - Making Connection

在其他设备上会出现类似于下面的报警视图,提示用户接受或拒绝连接:

Multipeer - Accept Connection

一旦用户点击 Accept 按钮,连接就会被建立。重要的是现在我们如何处理它,首先,我们如何能一直获知其他对等连接的状态。值得庆幸的是,多点连接框架给了我们一些 MCSession delegate 方法,这些我们以前在 MCManager 类中实现了,我们不用再为此写任何代码。

现在打开 MCManager.m 文件,定位到session:peer:didChangeState: 方法。当新的连接发生时,它会被调用,我们的工作就是用它处理所提供的信息。在我们的例子中,我们要做的很简单:因为这是一个和我们用来管理我们连接的类(ConnectionsViewController 类)不同的一个类,所以我们将发送一个通知让后者知道peer的状态变化,并且我们将所提供的所有信息跟通知一起发送过去。让我们看看实现代码:

?
1
2
3
4
5
6
7
8
9
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
    NSDictionary *dict = @{@"peerID": peerID,
           @"state" : [NSNumber numberWithInt:state]
           };
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MCDidChangeStateNotification"
            object:nil
          userInfo:dict];
}

首先,我们创建一个 NSDictionary 对象,设置 PeerID 和 state 参数值作为它的内容。接下来,我们用 MCDidChangeStateNotification 名称和字典对象作为用户信息字典发送通知。通过完成所有这些,我们确保每一次一个peer的状态被改变时,我们的类就能知道。

然而,随着时间的推移,只完成了一半的工作。我们需要使 ConnectionsViewController 类观察到这个通知并在收到时能采取适当的行动。所以,打开ConnectionsViewController.m 文件,前往 viewDidLoad 方法。给它添加下面内容:

?
1
2
3
4
5
6
7
8
9
- (void)viewDidLoad
{
    ...
    
    [[NSNotificationCenter defaultCenter] addObserver:self
         selector:@selector(peerDidChangeStateWithNotification:)
             name:@"MCDidChangeStateNotification"
           object:nil];
}

通过这个命令,我们让我们的类观察到特定的通知。当这样的通知到达时,一个方法将被调用,需要采取的任何行为应写在那个方法中。在我们的例子中,这个方法是 peerDidChangeStateWithNotification:,它是一个自定义的私有方法,我们现在要声明它。转到界面的私有部分,加入下面的声明:

?
1
2
3
4
5
6
@interface ConnectionsViewController ()
...
 
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification;
 
@end

接下来,我们必须实现它,但让我们对它讨论一下。当一个新的连接建立时我们想做什么?简单地添加连接点的显示名称到表格视图中,使“断开连接”按钮可用,禁用文本字段(我之前解释过为什么)。另一方面,当一个对等点断开时,我们只要从表格视图中清除它的名字,如果没有其他对等点存在,禁用“断开连接”按钮,使文本字段可用。

为了实现这些,我们需要给列表提供一个数组作为数据源。所以,我们声明并初始化这个数组,然后就可以实现这个私有方法了。

再次来到私有声明部分并添加以下声明:

?
1
2
3
4
5
6
@interface ConnectionsViewController ()
...
 
@property (nonatomic, strong) NSMutableArray *arrConnectedDevices;
 
@end

然后,在  viewDidLoad 方法里初始化它:

?
1
2
3
4
5
6
- (void)viewDidLoad
{
    ...
     
    _arrConnectedDevices = [[NSMutableArray alloc] init];
}

此外,为了列表能相应我们所要的,我们必须把类设为它的代理和数据源对象。所以在 viewDidLoad  方法种添加一下两行:

?
1
2
3
4
5
6
7
- (void)viewDidLoad
{
    ...
     
    [_tblConnectedDevices setDelegate:self];
    [_tblConnectedDevices setDataSource:self];
}

Xcode 会对这两个命令提示一些新的警告。这是因为我们还没有采用两个必需的代理方法, UITableViewDelegate 和  UITableViewDatasource。要解决这个问题,我们要在  ConnectionsViewController.h 类种修改声明头这行:

?
1
@interface ConnectionsViewController : UIViewController <MCBrowserViewControllerDelegate, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource>

现在我们已经完全准备好去这个实现收到通知时调用的私有方法了。再次打开  ConnectionsViewController.m 输入以下代码:

?
1
2
3
4
5
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
    MCPeerID *peerID = [[notification userInfo] objectForKey:@"peerID"];
    NSString *peerDisplayName = peerID.displayName;
    MCSessionState state = [[[notification userInfo] objectForKey:@"state"] intValue];
}

我们在这里所做的不难理解。我们从  userInfo 取出两个对象( peerID 和  state),赋值给 NSString 对象。我们继续:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
    ...
     
    if (state != MCSessionStateConnecting) {
        if (state == MCSessionStateConnected) {
            [_arrConnectedDevices addObject:peerDisplayName];
        }
        else if (state == MCSessionStateNotConnected){
            if ([_arrConnectedDevices count] > 0) {
                int indexOfPeer = [_arrConnectedDevices indexOfObject:peerDisplayName];
                [_arrConnectedDevices removeObjectAtIndex:indexOfPeer];
            }
        }
     }
}
Wisdom90s
Wisdom90s
翻译于 6个月前

0人顶

 翻译的不错哦!

首先,只有当 当前状态 不是 MCSessionStateConnecting 时我们才去执行某些行为。因此,如果 当前状态 是 MCSessionStateConnected 时,我们把 peer 显示名称加入 arrConnectedDevices 数组中。 否则,如果状态是MCSessionStateNotConnected ,我们就在数组找到当前 peer 的索引,简单地删除掉。这个代码是我们的方法的心脏。然而,我们还没有完成。添加缺少的这部分:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
    ...
    
    if (state != MCSessionStateConnecting) {
        ...
 
        [_tblConnectedDevices reloadData];
        
        BOOL peersExist = ([[_appDelegate.mcManager.session connectedPeers] count] == 0);
        [_btnDisconnect setEnabled:!peersExist];
        [_txtName setEnabled:peersExist];
    }
}

首先,我们重载表格视图的数据。之后,我们检查是否有任何peer留下连接,我们指定这个比较的结果为布尔值。请注意,会话对象的connectedPeers方法返回一个与会话连接的所有peer的数组。无论如何,我们设置文本字段和“断开连接”按钮的可用状态,都要取决于那个布尔值。

方法已经准备好了, 但是如果我们运行程序会发现什么结果也没有。 为什么呢? 因为我们仍然没有实现最基本的 table view 委托和 datasource 方法。让我们现在开始做。下面,你将一次得到所有这些方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
     return 1;
 }
  
  
 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
     return [_arrConnectedDevices count];
 }
  
  
 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier"];
      
     if (cell == nil) {
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CellIdentifier"];
     }
      
     cell.textLabel.text = [_arrConnectedDevices objectAtIndex:indexPath.row];
      
     return cell;
 }
  
  
 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
     return 60.0;
 }

这里没有什么难以理解的地方. 我们只需要显示table view的内容.如果你现在测试并连接这个app, 你可能看到类似如下的内容:

Multipeer - Peer Connected

最后, 做最后一件事就会使我们的view controller 功能齐全。 那就是使disconnect 按钮生效。只需要实现这个 disconnect: 方法:

?
1
2
3
4
5
6
7
8
- (IBAction)disconnect:(id)sender {
     [_appDelegate.mcManager.session disconnect];
      
     _txtName.enabled = YES;
      
     [_arrConnectedDevices removeAllObjects];
     [_tblConnectedDevices reloadData];
 }

如你所见, session 对象有一个能够断开连接的 disconnect 方法。 剩下的就比较好理解了。

再次测试这个程序.当你获得一个连接的时候, 按 Disconnect 按钮,然后你会看到,在这两种设备上的其他同行的显示名称会消失,而文字字段再次被启用。

在这一点上,连接视图控制器是准备好了!到现在为止,我们已经看到了有关框架的很多东西,让我们继续发现更为有趣的东西!

卖茄子
卖茄子
翻译于 6个月前

0人顶

 翻译的不错哦!

设置聊天界面

一旦连接建立,我们就能交换所需的数据。在这部份中我们要建立第一个视图的界面,然后实现聊天功能。开始吧!

点击 Main.storyboard 文件打开 Interface Builder。删除 First View Controller 中的默认内容。Xcode Storyboard Delete Default Content

然后,从 Objects Library 中拖入以下控件并设置它们的属性:

  1. UITextField

    • Frame: X=20, Y=20, Width=280, Height=30

    • Placeholder: Your message…

  2. UIButton

    • Frame: X=254, Y=58, Width=46, Height=30

    • Title: Send

  3. UIButton

    • Frame: X=25, Y=58, Width=48, Height=30

    • Title: Cancel

  4. UITextView

    • Frame: X=0, Y=96, Width=320, Height=422

    • Text: None

    • Background Color: Light Gray

现在这个 scene 看起来会是这样:

Multipeer Demo - First VC Scene

现在我们要声明一系列 IBOutlet 属性。打开 FirstViewController.h 文件并添加这两行:

?
1
2
3
4
@interface FirstViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *txtMessage;
@property (weak, nonatomic) IBOutlet UITextView *tvChat;
@end

同样的,每个按钮需要一个 IBAction 方法,所以添加这几行:

?
1
2
3
4
5
6
7
@interface FirstViewController : UIViewController
...
 
- (IBAction)sendMessage:(id)sender;
- (IBAction)cancelMessage:(id)sender;
 
@end

回到 Interface Builder 之前,我们要让这个类采用  UITextField 协议,我们需要在后面实现它的代理方法。

?
1
@interface FirstViewController : UIViewController <UITextFieldDelegate>

跟之前在 Connections View Controller 中连接 IBOutlet 和 IBAction 属性的操作一样,连接 txtMessage 属性到 text field,tvChat 属性到 text view。然后连接 sendMessage: IBAction 方法到 Send 按钮, cancelMessage: 到 Cancel 按钮。完成这些操作后就可以了。

开始聊天

章节名宣告了我们的目标。我们要做一些必要的操作,让通过 Multipeer Connectivity framework 连接的设备能用文字来聊天。

我们从给三个视图控制器做一些相同的操作开始:声明并实例化一个 AppDelegate 对象。在 FirstViewController.m 中引入AppDelegate.h 文件:

?
1
#import "AppDelegate.h"

然后,在私有接口部分做如下声明:

?
1
2
3
@interface FirstViewController ()
@property (nonatomic, strong) AppDelegate *appDelegate;
@end

最后,在 viewDidLoad 方法中实例化对象:

?
1
2
3
4
5
6
- (void)viewDidLoad
{
    [super viewDidLoad];
 
   _appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}

现在我们就能访问在 application 代理中的 mcManager 对象了。接着实现 textFieldShouldReturn: 代理方法。之所以需要这个方法是因为我们要让返回按钮的行为与发送按钮一样,就是发送消息给其他节点。不过在这之前,我们还要在viewDidLoad 方法中让这个类成为 text field 的代理:

?
1
2
3
4
5
6
- (void)viewDidLoad
{
    ...
     
    _txtMessage.delegate = self;
}

现在我们可以实现 textFieldShouldReturn: 方法了:

?
1
2
3
4
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
    [self sendMyMessage];
    return YES;
}

我们在这里调用的  sendMyMessage 方法是一个自定义的私有方法,我们接下来要实现并让它做发送的工作。现在我们弦实现之前声明的两个 IBAction 方法:

?
1
2
3
4
5
6
7
- (IBAction)sendMessage:(id)sender {
    [self sendMyMessage];
}
 
- (IBAction)cancelMessage:(id)sender {
    [_txtMessage resignFirstResponder];
}

他们已经不能再简单了。第一个调用了 sendMyMessage 方法,第二个让键盘消失,不发送任何消息。

 

现在我们已经进入到所需功能的核心。我们将实现这个私有方法,将会接触到框架中允许我们发送信息的新方法。

转到界面的私有部分去声明方法:

?
1
2
3
4
5
6
@interface FirstViewController ()
...
 
-(void)sendMyMessage;
 
@end

现在我们可以继续实现它,首先让我们看看它的实现代码,稍后再讨论它:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(void)sendMyMessage{
    NSData *dataToSend = [_txtMessage.text dataUsingEncoding:NSUTF8StringEncoding];
    NSArray *allPeers = _appDelegate.mcManager.session.connectedPeers;
    NSError *error;
    
    [_appDelegate.mcManager.session sendData:dataToSend
                                     toPeers:allPeers
                                    withMode:MCSessionSendDataReliable
                                       error:&error];
    
    if (error) {
        NSLog(@"%@", [error localizedDescription]);
    }
    
    [_tvChat setText:[_tvChat.text stringByAppendingString:[NSString stringWithFormat:@"I wrote:\n%@\n\n", _txtMessage.text]]];
    [_txtMessage setText:@""];
    [_txtMessage resignFirstResponder];
}

会话对象具有 sendData:toPeers:withMode:error: 方法,它用来实际发送消息。数据应该是一个NSData对象,这正是在第一行用dataUsingEncoding: 方法产生的。

该方法的第二个参数必须是一个含有应接收该消息的所有点的NSArray。对本教程来说,我们将消息发送到所有其它连接的节点。在实际的应用程序中,可以让用户选择消息的接收人。

第三个参数非常重要,因为这个参数是我们定义我们数据发送的模式的地方。在这本书的开始部分,我曾经说过两种模式reliable 和unreliable。这里我们假设是一个聊天程序,因此我们不希望丢失任何数据包。所以,当调用这个方法时我们选择 MCSessionSendDataReliable 这个值作为参数。

最后一个参数是用于检查当我们调用这个函数后是否有错误发生的的经典的错误对象。事实上,如果有错误发生时,我们只是记录它的描述,因为没有理由在我们的演示程序中以任何其他方式去处理它。

最后,我们还需要执行三个步骤,首先,我们显示消息的文本视图,并明确指出,这是我们谁写的,我们在前面加上I wrote: 文本。接下来,我们从当前内容清除这个文本字段。最后,我们移除虚拟键盘。

与我们刚才遇到的会话对象比起来,这没什么困难或怪异的地方。现在让我们把重点放在当收到节点的消息时会发生什么上来,session:didReceiveData:fromPeer: 方法被调用了,我们要根据需求去处理收到的消息。我们在 MCManager 类中实现了这个方法,但是里面没有任何内容。所以现在正是时候探究一下它并处理接收到的数据。

在 MCManager.m 文件种找到 session:didReceiveData:fromPeer: 方法。在这里我们的做法与 session:peer:didChangeState: 方法相同,这就意味着我们会推送一个通知让我们的类(FirstViewController)接收并采取行动。

下面是它的实现:

?
1
2
3
4
5
6
7
8
9
-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
    NSDictionary *dict = @{@"data": data,
                           @"peerID": peerID
                           };
     
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MCDidReceiveDataNotification"
                                                        object:nil
                                                      userInfo:dict];
}

正如所见,我们创建了一个内容是接受到的 数据和节点的  NSDictionary 对象。接下来,我们推送具有指定名称的通知,并将字典与它一起发送。添加完这段简单的代码之后就可以了,我们回到 FirstViewController.m 文件。

我们下一个任务是让我们的类能观察到这个通知,我们会在 viewDidLoad 方法中做这件事。添加下面的代码片段:

?
1
2
3
4
5
6
7
8
9
- (void)viewDidLoad
{
    ...
    
    [[NSNotificationCenter defaultCenter] addObserver:self
         selector:@selector(didReceiveDataWithNotification:)
             name:@"MCDidReceiveDataNotification"
           object:nil];
}

方法 didReceiveDataWithNotification:选择器中的方法是一个每次这样的通知到达时都会调用的私有方法。我们过会儿要去实现它,但是首先让我们声明它。转到界面的私有部分添加此声明:

?
1
2
3
4
5
6
@interface FirstViewController ()
...
 
-(void)didReceiveDataWithNotification:(NSNotification *)notification;
 
@end

我们想让这个方法去做的事很简单。通知的用户信息字典作为一个NSData对象,包含发送消息的 peer 和消息本身。从 peer 对象,我们能得到它的显示名称,我们将把这个数据转换为一个NSString 对象。一旦完成这些,之后我们就把peer的显示名称跟信息一起添加到文本视图。

实现代码如下:

?
1
2
3
4
5
6
7
8
9
-(void)didReceiveDataWithNotification:(NSNotification *)notification{
    MCPeerID *peerID = [[notification userInfo] objectForKey:@"peerID"];
    NSString *peerDisplayName = peerID.displayName;
    
    NSData *receivedData = [[notification userInfo] objectForKey:@"data"];
    NSString *receivedText = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
    
    [_tvChat performSelectorOnMainThread:@selector(setText:) withObject:[_tvChat.text stringByAppendingString:[NSString stringWithFormat:@"%@ wrote:\n%@\n\n", peerDisplayName, receivedText]] waitUntilDone:NO];
}

请注意,我们通过调用performSelectorOnMainThread:withObject:waitUntilDone: 方法来在文本视图中设置文本。这是因为这些数据是在一个辅助线程中接收的,而任何的可视更新总是发生在应用程序的主线程。

我们的聊天功能已经准备好了!如果你想测试,首先建立一个连接,然后从点到点互相开始发送文本消息。

Multipeer Demo - Chat Sample


摘要

多点连接框架是 iOS 7 的全新功能。在本教程中,我们给了你一个该框架的简要介绍,和演示了如何使用它来建立一个简单的聊天程序。还有很多可以去探索的。在本系列教程的第二部分,我们将继续进行这个工程的工作,看看如何与附近的设备分享文件。敬请期待。

为供你参考,你可以从URL: https://dl.dropboxusercontent.com/u/2857188/MCDemo.zip 下载完整的这个 Xcode 项目。一如既往地,请在下面随时分享你的想法和评论。

更新: 第二部分(http://www.appcoda.com/intro-ios-multipeer-connectivity-programming) 已写完。

0 0
原创粉丝点击