ios局域网联机——苹果官方源码之WiTap剖析(三)

这篇文章是"ios局域网联机——苹果官方源码之WiTap剖析"系列的第3部分,它和第2部分紧紧相连,因此阅读此文章的前提是你已经阅读了这个系列的第2部分。、

新的征程

     在此系列的第1部分中,我们讲到要完全弄明白AppController类的setup方法的话,必须先弄清楚这个TCPService类是怎么回事,在此系列第2部分中我们已经完全地分析过这个TCPService类是什么样的了,现在再让我们回过头来看看这个AppController类的setup方法,是不是除了最后一句调用的presentPicker以外,前面所有的部分我们都能很好地明白它到底干了什么了吧。

     在我们讨论这个presentPicker方法之前,我们先把第二部分中的遗留问题解决一下吧,还记得吗?我们在第2部分里讲到,我们实现的NSNetServiceDelegate协议的两个方法里,都是调用了TCPService的委托的协议方法,这个委托就是AppController,好的,那么我们现在来看看AppController实现的TCPServerDelegate协议的方法。

     第一个方法serverDidEnableBonjour:withName:方法,它是在NSNetService发布成功的回调里调用的:

- (void) serverDidEnableBonjour:(TCPServer *)server withName:(NSString *)string
{
    [self presentPicker:string];
}

     看到了吧,这个方法的实现只有一个方法调用,它也是调用presentPicker方法。先不说这个presentPicker方法,这个等下再说。我们先找找NSNetService发布失败的回调里调用的方法server:didNotEnableBonjour:,不用怀疑,你找不到它的,苹果并没有实现它(因为它们都是可选的,所以可以不实现),那也就是说在TCPService类里的NSNetService发布失败的回调方法里,这个方法是不响应的,所以在NSNetService发布失败的回调方法里的判断条件为假,这个回调没有执行后面对委托的方法的调用。

黎明前的最后黑暗

     就像我们说要理解AppController类的setup方法需要先了解TCPService类一样,这里要理解presentPicker方法也有一个必需先了解的类,这就是BrowserViewController类,这个类简单点来说是搜索在当前网络内搜索特定服务(我们在TCPService类里enableBonjourWithDomain方法发布的服务)的。

    同样地我们先来看它的头文件内容,打开BrowserViewController.h文件:

复制代码
#import <UIKit/UIKit.h>     //1
#import <Foundation/NSNetServices.h>

@class BrowserViewController;  //2

@protocol BrowserViewControllerDelegate <NSObject>   //3
@required
// This method will be invoked when the user selects one of the service instances from the list.
// The ref parameter will be the selected (already resolved) instance or nil if the user taps the 'Cancel' button (if shown).
- (void) browserViewController:(BrowserViewController *)bvc didResolveInstance:(NSNetService *)ref;
@end
复制代码

     注释1,导入需要的框架。

     注释2,声明这个BrowserViewController类。

     注释3,这里是定义了一个BrowserViewControllerDelegate协议,这个协议里有一个方法browserViewController:didResolveInstance:,而这个方法是被@required修饰的,表明这个方法是必需的,也就是说如果一个类声明要遵守这个BrowserViewControllerDelegate协议,它就必需要实现这个方法。我们之前看到的协议方法都是@optional修饰的,被@optional修饰的协议方法是可选的,可以不实现。这个方法是干什么的呢?这个方法是在当用户从我们搜索到的服务列表里选择的服务完成解析的时候调用的。(后面会详述)

     下面看这个类的具体定义:

复制代码
 1 @interface BrowserViewController : UITableViewController <NSNetServiceDelegate, NSNetServiceBrowserDelegate> {
 2 
 3 @private
 4     id<BrowserViewControllerDelegate> _delegate;    
 5     NSString *_searchingForServicesString;
 6     NSString *_ownName;
 7     NSNetService *_ownEntry;
 8     BOOL _showDisclosureIndicators;
 9     NSMutableArray *_services;
10     NSNetServiceBrowser *_netServiceBrowser;
11     NSNetService *_currentResolve;
12     NSTimer *_timer;
13     BOOL _needsActivityIndicator;
14     BOOL _initialWaitOver;
15 }
16 
17 @property (nonatomic, assign) id<BrowserViewControllerDelegate> delegate;
18 @property (nonatomic, copy) NSString *searchingForServicesString;
19 @property (nonatomic, copy) NSString *ownName;
20 
21 - (id)initWithTitle:(NSString *)title showDisclosureIndicators:(BOOL)showDisclosureIndicators showCancelButton:(BOOL)showCancelButton;
22 - (BOOL)searchForServicesOfType:(NSString *)type inDomain:(NSString *)domain;
23 
24 @end
复制代码

    在第1行,我们看到这个类是继承自UITableViewController的,那么它会有一个表视图(在这个类里这个表视图就是用来列出搜索到的服务的列表的)。并且这个类声明自己符合NSNetServiceDelegate和NSNetServiceBrowserDelegate两个协议,NSNetServiceDelegate协议我们在TCPService类里已经说了,这个NSNetServiceBrowserDelegate协议就是NSNetServiceBrowser类的一些回调方法,是当NSNetServiceBrowser类搜索到服务时,或其它特定情况发生时,分别相对应的一些回调方法。

    第3-14行,声明了一堆私有变量_delegate是一个符合BrowserViewControllerDelegate的委托,_searchingForServicesString是我们要搜索的服务的完整类型名(这个例子里事实上就是TCPService类里的bonjourTypeFromIdentifier:返回的字符串),_ownName是这个程序自己发布的服务的名字,_ownEntry是这个我们程序自己发布的服务,这_showDisclosureIndicators是决定是否显示这个tableView的cell的accessoryView的,_services是程序搜索到的所有符合自己搜索条件的服务数组,_netServiceBrowser是我们用来搜索发现服务的类,_currentResolve这个是当前正在解析的服务,_timer一个定时器在例子里作用是延迟调用方法,_needsActivityIndicator当前活动指示器,_initialWaitOver标识initialWaitOver:方法是否执行完毕。

    第17-19行,添加三个属性。

    第21-22行,定义两个方法,一个是初始化这个BroswerViewController类的,一个是在特定域中搜索特定服务的。

    现在轮到看下这个BroswerViewController类的实现了,打开BroswerViewController.m文件:

复制代码
 1 #import "BrowserViewController.h"
 2 
 3 #define kProgressIndicatorSize 20.0
 4 
 5 // A category on NSNetService that's used to sort NSNetService objects by their name.
 6 @interface NSNetService (BrowserViewControllerAdditions)
 7 - (NSComparisonResult) localizedCaseInsensitiveCompareByName:(NSNetService *)aService;
 8 @end
 9 
10 @implementation NSNetService (BrowserViewControllerAdditions)
11 - (NSComparisonResult) localizedCaseInsensitiveCompareByName:(NSNetService *)aService {
12     return [[self name] localizedCaseInsensitiveCompare:[aService name]];
13 }
14 @end
复制代码

     先包含头文件,然后定义一个宏,接着为NSNetService创建一个分类,为这个分类添加一个方法,这个方法是按照NSNetService服务的名字来给它们排序的。

     接着下面是:

复制代码
 1 @interface BrowserViewController()
 2 @property (nonatomic, retain, readwrite) NSNetService *ownEntry;
 3 @property (nonatomic, assign, readwrite) BOOL showDisclosureIndicators;
 4 @property (nonatomic, retain, readwrite) NSMutableArray *services;
 5 @property (nonatomic, retain, readwrite) NSNetServiceBrowser *netServiceBrowser;
 6 @property (nonatomic, retain, readwrite) NSNetService *currentResolve;
 7 @property (nonatomic, retain, readwrite) NSTimer *timer;
 8 @property (nonatomic, assign, readwrite) BOOL needsActivityIndicator;
 9 @property (nonatomic, assign, readwrite) BOOL initialWaitOver;
10 
11 - (void)stopCurrentResolve;
12 - (void)initialWaitOver:(NSTimer *)timer;
13 @end
复制代码

     为在BrowserViewController.h文件中没有添加属性的剩余私有变量添加属性声明,并添加了两个方法定义,stopCurrentResolve方法是用来停止当前正在解析的NSNetService服务的发布或者解析动作的,initialWaitOver:方法就是用来延迟处理一些操作的方法。

    BrowserViewController的具体实现:

复制代码
 1 @implementation BrowserViewController
 2 
 3 @synthesize delegate = _delegate;
 4 @synthesize ownEntry = _ownEntry;
 5 @synthesize showDisclosureIndicators = _showDisclosureIndicators;
 6 @synthesize currentResolve = _currentResolve;
 7 @synthesize netServiceBrowser = _netServiceBrowser;
 8 @synthesize services = _services;
 9 @synthesize needsActivityIndicator = _needsActivityIndicator;
10 @dynamic timer;
11 @synthesize initialWaitOver = _initialWaitOver;
复制代码

     为这些相应的属性合成set和get方法,这里值得说一个的是,@synthesize这个是说如果你自己不实现set和get方法,系统会根据属性给你生成全适的set和get方法,如果你自己实现了set和get的话,系统就不帮你生成了;@dynamic是说这个set和get方法必须由程序员自己实现。

    看第一个方法,初始化这个BrowserViewController类的方法:

复制代码
 1 - (id)initWithTitle:(NSString *)title showDisclosureIndicators:(BOOL)show showCancelButton:(BOOL)showCancelButton {
 2 
 3     if ((self = [super initWithStyle:UITableViewStylePlain])) {
 4         self.title = title;
 5         _services = [[NSMutableArray alloc] init];
 6         self.showDisclosureIndicators = show;
 7 
 8         if (showCancelButton) {
 9             // add Cancel button as the nav bar's custom right view
10             UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
11                                           initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAction)];
12             self.navigationItem.rightBarButtonItem = addButton;
13             [addButton release];
14         }
15 
16         // Make sure we have a chance to discover devices before showing the user that nothing was found (yet)
17         [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(initialWaitOver:) userInfo:nil repeats:NO];
18     }
19 
20     return self;
21 }
复制代码

     前面大量的代码我们一般都是比较详细地一句一句地讲它的作用,由于前面的讲述已经把基本的正常该讲述的都讲过了,现在开始我们只讲逻辑以提高效率,当然如果有该着重讲解的知识点的话,我还是会细致地讲的。

    第3句是调用它的父类(UITableViewController)的初始化方法,选择一种风格为这个类进行初始化。

    然后设置它的标题(继承自父类的),为这个服务数组进行初始化工作,接着给决定是否显示cell的accessoryView的属性赋值。

    如果需要显示取消按钮的话,创建一个取消按钮,设置这个按钮的响应函数为cancelAction,并把按钮设为导航栏的右边按钮。

    在一秒钟后调用initialWaitOver方法,主要是用来保证在显示给用户之前,有充足的时间让它搜索到服务的。

    最后返回自己。

    现在看一下cancelAction的实现:

1 - (void)cancelAction {
2     [self.delegate browserViewController:self didResolveInstance:nil];
3 }

   很简单地让它的委托去处理了,这个委托方法后面遇到时再说吧。

   再看initialWaitOver方法的实现:

1 - (void)initialWaitOver:(NSTimer *)timer {
2     self.initialWaitOver= YES;
3     if (![self.services count])
4         [self.tableView reloadData];
5 }

    设置这个initialWaitOver属性为真,如果搜索到的服务数为0的话,重新刷新这个表视图。之所以要在1秒钟后调用这个方法,是要给我们留出足够的时间来搜索发现服务。

    下面是这个类自己实现的属性searchingForServicesString的set和get方法:

复制代码
 1 - (NSString *)searchingForServicesString {
 2     return _searchingForServicesString;
 3 }
 4 
 5 // Holds the string that's displayed in the table view during service discovery.
 6 - (void)setSearchingForServicesString:(NSString *)searchingForServicesString {
 7     if (_searchingForServicesString != searchingForServicesString) {
 8         [_searchingForServicesString release];
 9         _searchingForServicesString = [searchingForServicesString copy];
10 
11         // If there are no services, reload the table to ensure that searchingForServicesString appears.
12         if ([self.services count] == 0) {
13             [self.tableView reloadData];
14         }
15     }
16 }
复制代码

     这个属性的get方法就是正常的get方法没什么好说的,在set方法里,多了一点东西,也是当搜索到的服务数为0的时候,重新刷新表视图,目地是让这个搜索的服务的类型名字出现(后面在表视图的数据源方法里会看到)。

    再看ownName属性的set和get方法:

复制代码
 1 - (NSString *)ownName {
 2     return _ownName;
 3 }
 4 
 5 // Holds the string that's displayed in the table view during service discovery.
 6 - (void)setOwnName:(NSString *)name {
 7 
 8     if (_ownName != name) {
 9         _ownName = [name copy];
10         
11         if (self.ownEntry)
12             [self.services addObject:self.ownEntry];
13         
14         NSNetService* service;
15         
16         for (service in self.services) {
17             if ([service.name isEqual:name]) {
18                 self.ownEntry = service;
19                 [_services removeObject:service];
20                 break;
21             }
22         }
23         
24         [self.tableView reloadData];
25     }
26 }
复制代码

     同样的只有set方法是有额外操作的,如果这个ownEntry这个属性就是自己发布的服务是有效的话,就把这个服务加入到services这个服务数组里,然后对这个数组里的所有服务的名字进行比较,如果有服务的名字和我们想要让ownName这个属性设置成的名字一样的话,就把这个服务设为ownEntry,同时从services服务数组里把这个服务移除。这样做的用意是,当我们的ownEntry属性当前是有效的前提下,想换一个服务为作为自己的服务的话,这样就可以完成两个服务的交换。然后再次重新刷新表视图。

     下一个方法是真正开始搜索相应的服务的方法:

复制代码
 1 - (BOOL)searchForServicesOfType:(NSString *)type inDomain:(NSString *)domain {
 2 
 3     [self stopCurrentResolve];
 4     [self.netServiceBrowser stop];
 5     [self.services removeAllObjects];
 6 
 7     NSNetServiceBrowser *aNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
 8     if(!aNetServiceBrowser) {
 9         // The NSNetServiceBrowser couldn't be allocated and initialized.
10         return NO;
11     }
12 
13     aNetServiceBrowser.delegate = self;
14     self.netServiceBrowser = aNetServiceBrowser;
15     [aNetServiceBrowser release];
16     [self.netServiceBrowser searchForServicesOfType:type inDomain:domain];
17 
18     [self.tableView reloadData];
19     return YES;
20 }
复制代码

     第3-6行是一些清理性的工作,当我们在进行服务搜索的时候,调用stopCurrentResolve方法是假设有服务正在服务的话就先停止这个解析,然后对netServiceBrowser属性停止搜索服务的动作,它也是假设当这次搜索服务进行之前的状态是正在进行搜索服务的状态,把之前搜索到的所有服务从服务数组中移除。

     初始化一个NSNetServiceBrowser对象,如果初始化失败,返回搜索失败。

     把这个NSNetServiceBrowser对象的委托设为这个类本身(因为这个类已经声明自己符合它的协议了),然后把这个对象赋给netServiceBrowser属性。

     用这个searchForServicesOfType:inDomain:来搜索服务,第一个参数是要搜索的服务的类型名,第二个参数是要搜索的域的名字。

     重新刷新表视图,返回搜索成功。

     来看下这个stopCurrentResolve的具体内容:

复制代码
1 - (void)stopCurrentResolve {
2     self.needsActivityIndicator = NO;
3     self.timer = nil;
4 
5     [self.currentResolve stop];
6     self.currentResolve = nil;
7 }
复制代码

    给当前活动指示器赋值为假,调用timer属性的set方法,把这个timer置为nil,在这个timer的set方法里,把这个timer设为新的值之前先释放了之前的timer;停止当前正在尝试解析或发布的服务,把currentResolve属性置为nil。

    来看一下timer的set和get方法:

复制代码
 1 - (NSTimer *)timer {
 2 
 3     return _timer;
 4 }
 5 
 6 // When this is called, invalidate the existing timer before releasing it.
 7 - (void)setTimer:(NSTimer *)newTimer {
 8 
 9     [_timer invalidate];
10     [newTimer retain];
11     [_timer release];
12     _timer = newTimer;
13 }
复制代码

    值得说的是在这个timer的set方法里,在把timer设为新的newTimer前,先停止并释放原先的timer。

     下面该看看我们的表视图的数据源方法了,tableView就是通过这些数据源方法获取信息来往它自己的行里填充数据的。这个类里实现了3个数据源方法,第一个:

1 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
2     return 1;
3 }

     返回这个tableView的分组数,这里就是一个分组,所以返回1。

复制代码
1 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
2     // If there are no services and searchingForServicesString is set, show one row to tell the user.
3     NSUInteger count = [self.services count];
4     if (count == 0 && self.searchingForServicesString && self.initialWaitOver)
5         return 1;
6 
7     return count;
8 }
复制代码

     这个用来返回分组中的行数。先获取存储服务的数组的元素个数,如果这个元素个数是0,并且我们搜索的服务类型的名字有效,并且initialWaitOver属性为真(这个代表着我们已经等待了一秒钟了,initialWaitOver方法执行完了)的话,我们就返回1,否则则直接返回服务数组的元素的个数。

复制代码
 1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 2 
 3     static NSString *tableCellIdentifier = @"UITableViewCell";
 4     UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:tableCellIdentifier];
 5     if (cell == nil) {
 6         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableCellIdentifier] autorelease];
 7     }
 8     
 9     NSUInteger count = [self.services count];
10     if (count == 0 && self.searchingForServicesString) {
11         // If there are no services and searchingForServicesString is set, show one row explaining that to the user.
12         cell.textLabel.text = self.searchingForServicesString;
13         cell.textLabel.textColor = [UIColor colorWithWhite:0.5 alpha:0.5];
14         cell.accessoryType = UITableViewCellAccessoryNone;
15         // Make sure to get rid of the activity indicator that may be showing if we were resolving cell zero but
16         // then got didRemoveService callbacks for all services (e.g. the network connection went down).
17         if (cell.accessoryView)
18             cell.accessoryView = nil;
19         return cell;
20     }
21     
22     // Set up the text for the cell
23     NSNetService *service = [self.services objectAtIndex:indexPath.row];
24     cell.textLabel.text = [service name];
25     cell.textLabel.textColor = [UIColor blackColor];
26     cell.accessoryType = self.showDisclosureIndicators ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
27     
28     // Note that the underlying array could have changed, and we want to show the activity indicator on the correct cell
29     if (self.needsActivityIndicator && self.currentResolve == service) {
30         if (!cell.accessoryView) {
31             CGRect frame = CGRectMake(0.0, 0.0, kProgressIndicatorSize, kProgressIndicatorSize);
32             UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithFrame:frame];
33             [spinner startAnimating];
34             spinner.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
35             [spinner sizeToFit];
36             spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
37                                         UIViewAutoresizingFlexibleRightMargin |
38                                         UIViewAutoresizingFlexibleTopMargin |
39                                         UIViewAutoresizingFlexibleBottomMargin);
40             cell.accessoryView = spinner;
41             [spinner release];
42         }
43     } else if (cell.accessoryView) {
44         cell.accessoryView = nil;
45     }
46     
47     return cell;
48 }
复制代码

       这个方法向数据源请求一个cell走入到tableView相应的位置。具体就是配备cell的具体内容。

       第3-7行,这是苹果针对tableView的一种优化方式。

       这里定义了一个字符串来代表被用的cell对象,默认情况下这个字符串应该是你用的cell类的类名。不过其实你可以改变它为随意的值。

       这里调用了一个帮助方法"dequeueReusableCellWithIdentifier"来返回一个重用的cell,这到底是干什么的呢?这是一个很重要的性能优化,设想一下表视图可能包含一下非常大的数量的行数据,但是同一时间只有特定数量的行能显示在屏幕上。所以不用当一个行滚动到屏幕内的时候,不用每次都创建一个新的cell,系统可以通过重用这些已经创建过的但是不在屏幕显示循环内的cell来提升性能。这就是这个dequeueReusableCellWithIdentifier调用干了什么。

      如果没有可以重用的cell,我们会创建一个新的cell。

      第9行,得到服务数组的元素个数。

      如果服务数为0,并且searchingForServicesString属性有效,就显示一行,这一行的文本信息是这个searchingForServicesStirng属性,就是要搜索的服务类型的名字。然后,如果cell的accessoryView有效,把accessoryView置为nil(为了防止当我们的连接断开的时候,之前显示的服务已经移除的情况下这里还在显示信息)。

      第22-26行,正常地根据相应的索引信息设置相应的cell的textLable的内容为数组里相应的服务的名字,并且对显示的字的颜色进行设置,还根据showDisclosureIndicators 

属性对cell的accessoryView的风格进行了设置。

      第29-41行,如果needsActivityIndicator属性为真,就是说我们需要显示活动指示器(就是我们常看到的齿轮状的转动的进度指示器),并且当前正在解析的服务就是当前这个行的服务的话,初始化并设置一个活动指示器,把它设为当前cell的accessoryView。否则的话,就判断这个当前cell的accessoryView是否有效,有效的话就把它置为nil。

      最后返回这个配置过的cell。

      看过了tableView的数据源方法了,现在该看一看它的相应委托方法了,一个tableView通过它的数据源方法来获取数据,通过它的委托方法来响应对它的操作。第一个:

复制代码
1 - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
2     // Ignore the selection if there are no services as the searchingForServicesString cell
3     // may be visible and tapping it would do nothing
4     if ([self.services count] == 0)
5         return nil;
6 
7     return indexPath;
8 }
复制代码

     这个方法的作用是当我们点击tableView的一行并松开手指的时候,告诉它的委托一个特定行将要被选中,如果服务数组的元素个数为0的话返回nil,表明不希望这个被点的行被选中(在这个例子里就是要忽略没有搜索到服务只有一个cell显示的是要搜索的服务的名字的情况),正常返回indexPath表示,希望被点的这行被选中。

    另一个委托方法:

复制代码
 1 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 2     // If another resolve was running, stop it & remove the activity indicator from that cell
 3 
 4     if (self.currentResolve) {
 5         // Get the indexPath for the active resolve cell
 6         NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[self.services indexOfObject:self.currentResolve] inSection:0];
 7         
 8         // Stop the current resolve, which will also set self.needsActivityIndicator
 9         [self stopCurrentResolve];
10         
11         // If we found the indexPath for the row, reload that cell to remove the activity indicator
12         if (indexPath.row != NSNotFound)
13             [self.tableView reloadRowsAtIndexPaths:[NSArray    arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
14     }
15      
16     // Then set the current resolve to the service corresponding to the tapped cell
17     self.currentResolve = [self.services objectAtIndex:indexPath.row];
18     [self.currentResolve setDelegate:self];
19 
20     // Attempt to resolve the service. A value of 0.0 sets an unlimited time to resolve it. The user can
21     // choose to cancel the resolve by selecting another service in the table view.
22     [self.currentResolve resolveWithTimeout:0.0];
23     
24     // Make sure we give the user some feedback that the resolve is happening.
25     // We will be called back asynchronously, so we don't want the user to think we're just stuck.
26     // We delay showing this activity indicator in case the service is resolved quickly.
27     self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showWaiting:) userInfo:self.currentResolve repeats:NO];
28 }
复制代码

     这个方法是告诉它的委托,一个行现在被选中了。

     第4-14行,如果在一个行被选中的时候,有另一个服务解析正在运行,停止它,并从它对应的cell里移除活动指示器。

     第17-18行,设置这个当前在解析的服务为我们选中的行对应的服务。

     第22行,开始解析这个选中的服务,Timeout为0是说,对于这个解析过程来说没有超时时间限制。

     第27行,初始化一个NSTimer,用于在1秒钟后调用showWaiting方法,同时这个NSTimer的userInfo被设置为这个当前解析的服务。然后把这个NSTimer赋给timer属性。(还记得timer属性的set方法吗?在把timer赋为新的值之前是先取消并释放之前的timer。)

     还是先来看看showWaiting方法做什么吧,这样才能更好地理解这句话的作用,showWaiting的实现:

复制代码
 1 - (void)showWaiting:(NSTimer *)timer {
 2     if (timer == self.timer) {
 3         NSNetService* service = (NSNetService*)[self.timer userInfo];
 4         if (self.currentResolve == service) {
 5             self.needsActivityIndicator = YES;
 6 
 7             NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[self.services indexOfObject:self.currentResolve] inSection:0];
 8             if (indexPath.row != NSNotFound) {
 9                 [self.tableView reloadRowsAtIndexPaths:[NSArray    arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
10                 // Deselect the row since the activity indicator shows the user something is happening.
11                 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
12             }
13         }
14     }
15 }
复制代码

     这个showWaiting方法是从这个timer里得到在tableView:didSelectRowAtIndexPath方法里选中的服务,然后把needsActivityIndicator属性设为真,就是说现在需要显示活动指示器了,然后根据这个服务在服务数据里的索引号得到这个服务在tableView里的行索引,再根据这个得到的行索引对这个行进行刷新以显示这个活动指示器,并且也取消掉这个行的选中状态(就是取消这个行的高亮)。

     好的,知道了这个showWaiting方法干什么是不是就明白了tableView:didSelectRowAtIndexPath这个方法的第27行做什么了呀?它主要就是在解析选中的服务开始1秒钟后,显示活动指示器的。(事实上解析的过程一般情况下是很快的,会在1秒钟内完成,在解析完成之后会有一个回调方法被调用,在这个回调方法里其实是取消了这个timer的,也就是说如果1秒钟内完成了解析,这个showWaiting方法是不会调用的)

- (void)sortAndUpdateUI {
    // Sort the services by name.
    [self.services sortUsingSelector:@selector(localizedCaseInsensitiveCompareByName:)];
    [self.tableView reloadData];
}

     这是一个排序方法,用我们在NSNetService的分类的里定义的排序方法对这个服务数组里的服务进行排序,然后再根据这个顺序刷新显示tableView。

     该轮到来看一下这个NSNetServiceBrowserDelegate的协议方法了:

复制代码
 1 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing {
 2 
 3     // If a service went away, stop resolving it if it's currently being resolved,
 4     // remove it from the list and update the table view if no more events are queued.
 5     
 6     if (self.currentResolve && [service isEqual:self.currentResolve]) {
 7         [self stopCurrentResolve];
 8     }
 9     [self.services removeObject:service];
10     if (self.ownEntry == service)
11         self.ownEntry = nil;
12     
13     // If moreComing is NO, it means that there are no more messages in the queue from the Bonjour daemon, so we should update the UI.
14     // When moreComing is set, we don't update the UI so that it doesn't 'flash'.
15     if (!moreComing) {
16         [self sortAndUpdateUI];
17     }
18 }    
复制代码

    这个方法会在NSNetServiceBrowser探索到的服务中,有服务变为不可用了或者消失了的时候被调用。

    第6-7行,如果当前正在解析的服务就是这个消失的服务,停止对它的解析。

    第9-11行,把这个不可用的服务从服务数组里移除,如果这个不可用的的服务是我们自己的服务,把这个我们自己的服务设置为nil。

    第15-16行,如果没有更多的服务不可用消息到达的时候,更新服务列表及tableView。

复制代码
 1 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
 2     // If a service came online, add it to the list and update the table view if no more events are queued.
 3     if ([service.name isEqual:self.ownName])
 4         self.ownEntry = service;
 5     else
 6         [self.services addObject:service];
 7 
 8     // If moreComing is NO, it means that there are no more messages in the queue from the Bonjour daemon, so we should update the UI.
 9     // When moreComing is set, we don't update the UI so that it doesn't 'flash'.
10     if (!moreComing) {
11         [self sortAndUpdateUI];
12     }
13 }    
复制代码

       这个会在搜索到可用服务时调用,如果新发现在的这个服务和我们自己程序发布的服务的名字一样的话,我们就把这个用来跟踪自己发布的服务的这个ownEntry属性设为这个服务。如果名字不一样的话,就把它加入到服务数组里。同样地,如果没有更多的可用服务被发现的话,就更新服务列表和tableView。

      下面再看两个NSNetServiceDelegate的协议方法吧,它们是在NSNetService的地址被解析失败或成功时调用的:

1 - (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict {
2     [self stopCurrentResolve];
3     [self.tableView reloadData];
4 }

     很显然这个是在NSNetService解析失败时调用的,在这个例子里这个方法是永远不会被调用的,因为前面设置解析时间限制时我们用的是0,它是说是没有限制的,永远不会超时的。这个方法里,停止当前在解析的服务,重新刷新tableView。    

复制代码
1 - (void)netServiceDidResolveAddress:(NSNetService *)service {
2     assert(service == self.currentResolve);
3     
4     [service retain];
5     [self stopCurrentResolve];
6     
7     [self.delegate browserViewController:self didResolveInstance:service];
8     [service release];
9 }
复制代码

     这个方法是在NSNetService解析成功时调用的。首先是用断言来保证触发这个回调方法的服务是我们正在解析的服务currentResolve。先对这个服务进行一次retain操作,然后停止对这个服务的解析操作,对它的委托调用browserViewController:didResolveInstance:方法(事实上它的委托是AppController类,这个方法后面再讲),先前执行了一次retain,现在对这个服务执行release操作。

     最后这个BrowserViewController类就剩下一个dealloc方法了:

复制代码
 1 - (void)dealloc {
 2 
 3     // Cleanup any running resolve and free memory
 4     [self stopCurrentResolve];
 5     self.services = nil;
 6     [self.netServiceBrowser stop];
 7     self.netServiceBrowser = nil;
 8     [_searchingForServicesString release];
 9     [_ownName release];
10     [_ownEntry release];
11     
12     [super dealloc];
13 }
复制代码

     就是一些必要的清理操作,这样这个BrowserViewController类就算讲完了。

再无战事

     终于所有的大块头都讲完了,我承认,这篇文章有点草草的意思,不过确实是代码太多,而且大部分都是正常的逻辑,没有太多特殊的知识点需要详述,所以就有很多地方都只是大概地讲了作用及流程。再往后的话,应该就只剩下正常的逻辑执行顺序需要理清楚就可以了。下一篇会是这个系列的完结篇,在完结篇里我会给出一个打印出来的这个程序的整个执行流程,根据这个流程,再来看我们讲到的所有方法会更好。

转载:http://www.cnblogs.com/dingwenjie/archive/2012/04/15/2444570.html



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值