你好,歡迎來到IOS教程網

 Ios教程網 >> IOS訊息 >> 關於IOS >> ios局域網聯機源碼WiTap剖析(三)

ios局域網聯機源碼WiTap剖析(三)

編輯:關於IOS

這篇文章是"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類就算講完了。

再無戰事
終於所有的大塊頭都講完了,我承認,這篇文章有點草草的意思,不過確實是代碼太多,而且大部分都是正常的邏輯,沒有太多特殊的知識點需要詳述,所以就有很多地方都只是大概地講了作用及流程。再往後的話,應該就只剩下正常的邏輯執行順序需要理清楚就可以了。下一篇會是這個系列的完結篇,在完結篇裡我會給出一個打印出來的這個程序的整個執行流程,根據這個流程,再來看我們講到的所有方法會更好。

(能力有限,文中可能會有不對的地方,希望大家指教。謝謝!!)

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved