在上一课中,已添加了在主线程中执行的基本同步。建议不要以这种方式阻塞主线程。在本课中,将把同步移动到后台线程,并添加同步观察方法以更新进度显示。
要显示同步的进度,需要把一个带标签的进度条放在底部的浏览工具栏,其外观与下载新消息时的 [邮件] 应用程序相似。要重新创建外观,必须自定义视图:
在 [Groups & Files] 窗格中按住 ctrl 键的同时单击 [Classes] 文件夹。从 [Add] 菜单中单击 [New File]。
在 [Cocoa Touch Class] 下单击 [UIViewController subclass]。
清除 [UITableViewController subclass]。
单击 [With XIB for user interface]。
单击 [Next]。
命名文件:ProgressToolbarViewController.m 并将其放在 Classes 子目录中。单击复选框,创建头文件。
单击 [Finish]。
将 ProgressToolbarViewController.xib 文件移动到 Resources 目录中。
在 [Interface Builder] 中创建布局前,于 [ProgressToolbarViewController] 中用以下内容替换界面定义:
@interface ProgressToolbarViewController : UIViewController { IBOutlet UILabel *label; IBOutlet UIProgressView *progressBar; } @end |
在界面中添加属性:
@property (readonly) IBOutlet UILabel *label; @property (readonly) IBOutlet UIProgressView *progressBar; |
在实现文件中合成属性:
@synthesize label; @synthesize progressBar; |
在 [dealloc] 方法中添加释放调用:
- (void)dealloc { [super dealloc]; [label release]; [progressBar release]; } |
在 Xcode Resources 文件夹中双击 ProgressToolbarViewController.xib,打开 ProgressToolbarViewController。由于此视图在工具栏中显示,所以必须适当调整其大小,并设置其背景属性:
在 [Document]窗口 (Command-0) 中,单击 [View]。
在 [Attributes Inspector] (Command-1) 中,将模拟的状态栏设置为 [Unspecified]。
单击 [Background] 并将 [Background opacity] 设置为 0%。
清除 [Opaque] 设置。
在 [Size Inspector] (Command-3) 中,将宽度设置为 232,将高度设置为 44。
单击 UIProgressView 并将其从 [Library] 拖动到 [View]。
在 [Size Inspector] (Command-3) 中,将进度视图的位置设置为 26,29。
将进度视图的宽度设置为 186。
在 [Attributes Inspector] (Command-1) 中,将风格设置为 [Bar],将进度设置为零。
单击 [UILabel] 并将其从 [Library] 拖动到 [View]。
在 [Size Inspector] (Command-3) 中,将标签的位置设置为 14,5。
将标签的大小设置为 210,16。
在 [Attribute Inspector] (Command-1) 中,将文本设置为 [Sync Progress]。
将布局对齐设置为居中对齐。
将字体设置为 Helvetica 粗体,字号为 12。
将文本颜色设置为白色。
将阴影颜色设置为 RGB:(103,114,130),不透明度为 100%。
在 [Document] 窗口中单击 [File's Owner]。
在 [Connectors Inspector] (Command-2) 中,将标签出口链接到先前步骤中创建的 UILabel。
将进度栏出口链接到先前步骤中创建的 [UIProgressView]。
保存 XIB 文件并关闭 [Interface Builder]。
此 [ProgressBar] 视图将添加到 [RootViewController] 的工具栏。但是,[DataAccess] 对象还使用对其的引用,以显示同步的进度。将以下实例变量添加到界面:
ProgressToolbarViewController * progressToolbar; |
并将属性添加到 DataAccess 类:
@property (retain, readwrite) IBOutlet ProgressToolbarViewController * progressToolbar; |
向 DataAccess 标头中导入 [ProgressToolbarViewController] 标头:
#import "ProgressToolbarViewController.h" |
在实现中合成该属性。
@synthesize progressToolbar |
要将进度视图添加到工具栏,请将以下内容添加到 RootViewController 的 viewDidLoad 方法:
// Create progress display ProgressToolbarViewController * progress = [[ProgressToolbarViewController alloc] initWithNibName:@"ProgressToolbarViewController" bundle:nil]; // Register the toolbar with the DataAccess [[DataAccess sharedInstance] setProgressToolbar:progress]; // Setup UIBarButtonItems UIBarButtonItem * space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; UIBarButtonItem * progressButtonItem = [[UIBarButtonItem alloc] initWithCustomView:progress.view]; // Put them in the toolbar self.toolbarItems = [NSArray arrayWithObjects:space, progressButtonItem, space, nil]; [space release]; [progressButtonItem release]; |
RootViewController 的工具栏现在有了进度视图,尽管工具栏已隐藏。下一节将把同步移动到后台线程,并在同步过程中显示进度。
到目前为止,应用程序所做的一切都在其主线程上执行。由于此线程还用于绘制界面和处理接触等用户事件,建议不要将其阻塞。
同步过程中的应用程序流如下所示:
用户在 RootViewController 中单击同步按钮。将显示工具栏进度视图。
[RootViewController] 在一个分离的线程上初始化同步,将其本身作为一个参数传递到同步函数。主线程继续运行,同步则在另一个线程上运行。
同步函数使用 performSelectorOnMainThread 更新用户界面,以便在整个同步过程中更新进度显示。
同步完成时,后台线程将显示一个警告框,展示同步的结果,并且将 RootViewController 设置为警告的委派,使其了解警告何时解除。
一旦 RootViewController 了解到警告框已解除,工具栏进度视图将被隐藏。
由于 [RootViewController] 必须实现 [UIAlertViewDelegate] 协议以处理警告视图解除,请将其头文件更改为以下内容:
@interface RootViewController : UITableViewController <UIAlertViewDelegate> { } // Displays the screen to add a name. - (void)showAddNameScreen; @end |
RootViewController 需要实现的来自 UIAlertViewDelegate 协议的唯一方法是 alertView:clickedButtonAtIndex: 方法。当用户按下 UIAlertView 的按钮时,将调用此方法。由于视图仅有一个按钮,所以不需要检查哪个按钮被按下。将以下代码放置在 RootViewController.m 文件中。
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { NSLog(@"User dismissed sync alert, refreshing table."); [self.tableView reloadData]; [self.navigationController setToolbarHidden:YES animated: YES]; } |
同样在 RootViewController.m 中,使用 [detachNewThreadSelector] 将同步方法更改为在单独线程上执行同步调用:
- (void)sync { [self.navigationController setToolbarHidden:NO animated: YES]; [NSThread detachNewThreadSelector:@selector(synchronize:) toTarget:[DataAccess sharedInstance] withObject:self]; } |
由于同步在单独的线程上执行,所以需要更改若干内容。正如可能在 RootViewController 的同步方法中注意到的,DataAccess 的同步方法正作为一个参数传递。将其签名更改为以下内容:
- (void)synchronize:(id<UIAlertViewDelegate>)sender; |
用以下内容更新 DataAccess.mm 实现文件:
// Since the synchronization method uses auto-released object instances, you also need // to create an NSAutoreleasePool and release it at the end of the synchronization: - (void)synchronize:(id<UIAlertViewDelegate>)sender { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // Rest of synchronize method... [pool release]; } |
同样在同步方法中,还需要将传入的 RootViewController 设置为警告委派。为此,请更新先前创建的 [UIAlertView] 并将委派从 [nil] 更改为 [sender]:
// Set RootViewController as the alert delegate if (sender != nil) { NSLog(@"Showing Alert with sync result."); [[[[UIAlertView alloc] initWithTitle:@"Synchronization" message:result delegate:sender // changed from nil to sender cancelButtonTitle:nil otherButtonTitles:@"OK", nil] autorelease] show]; } else { NSLog(@"Not showing alert since sender was nil."); } |
现在需要创建同步回调,从而将进度条更新到适当的完成并显示正确的消息。
但是,由于同步回调在非主线程([RootViewController] 创建的同步线程)上执行,所以必须使用 [performSelectorOnMainThread] 更新用户界面。此方法仅允许传递单个参数,因此要传递进度以及消息,需要创建名为 [UpdateInfo] 的 Objective-C 类,其在标头文件中拥有两个属性、一个浮点和一个 [NSString]:
@interface UpdateInfo : NSObject { NSString * message; float progress; } // Message to display in the progress view @property (readonly) NSString * message; // Progress of the sync [0.0-1.0] @property (readonly) float progress; // Preferred initializer - (id)initWithMessage:(NSString*) message andProgress:(float)progress; @end |
在实现文件中添加以下内容:
// The implementation is a single constructor that takes both parameters: @implementation UpdateInfo @synthesize message; @synthesize progress; - (id)initWithMessage:(NSString*) msg andProgress:(float) syncProgress { if (self = [super init]) { message = msg; progress = syncProgress; } return self; } @end |
由于可以捆绑用户界面更新方法所需的全部信息,所以可以在 DataAccess 类中进行定义:
- (void)updateSyncProgress:(UpdateInfo *)info { progressToolbar.label.text = info.message; progressToolbar.progressBar.progress = info.progress; } |
现在在 DataAccess.h 中导入 UpdateInfo.h。
#import "UpdateInfo.h" |
还可以在 DataAccess.mm 实现文件中定义回调方法及其它静态方法:
static void UL_CALLBACK_FN progressCallback(ul_synch_status * status) { // Sync information for the GUI float percentDone = 0.0; NSString * message; // Note: percentDone is approximate. switch (status->state) { case UL_SYNCH_STATE_STARTING: percentDone = 0; message = @"Starting Sync"; break; case UL_SYNCH_STATE_CONNECTING: percentDone = 5; message = @"Connecting to Server"; break; case UL_SYNCH_STATE_SENDING_HEADER: percentDone = 10; message = @"Sending Sync Header"; break; case UL_SYNCH_STATE_SENDING_TABLE: percentDone = 10 + 25 * (status->sync_table_index / status->sync_table_count); message = [NSString stringWithFormat:@"Sending Table %s: %d of %d", status->table_name, status->sync_table_index, status->sync_table_count]; break; case UL_SYNCH_STATE_SENDING_DATA: percentDone = 10 + 25 * (status->sync_table_index / status->sync_table_count); message = @"Sending Name Changes"; break; case UL_SYNCH_STATE_FINISHING_UPLOAD: case UL_SYNCH_STATE_RECEIVING_UPLOAD_ACK: percentDone = 50; message = @"Finishing Upload"; break; case UL_SYNCH_STATE_RECEIVING_TABLE: case UL_SYNCH_STATE_RECEIVING_DATA: percentDone = 50 + 25 * (status->sync_table_index / status->sync_table_count); message = [NSString stringWithFormat:@"Receiving Table %s: %d of %d", status->table_name, status->sync_table_index, status->sync_table_count]; break; case UL_SYNCH_STATE_COMMITTING_DOWNLOAD: case UL_SYNCH_STATE_SENDING_DOWNLOAD_ACK: percentDone = 80; message = @"Committing Downloaded Updates"; break; case UL_SYNCH_STATE_DISCONNECTING: percentDone = 90; message = @"Disconnecting from Server"; break; case UL_SYNCH_STATE_DONE: percentDone = 100; message = @"Finished Sync"; break; case UL_SYNCH_STATE_ERROR: percentDone = 95; message = @"Error During Sync"; break; case UL_SYNCH_STATE_ROLLING_BACK_DOWNLOAD: percentDone = 100; message = @"Rolling Back due to Error"; break; default: percentDone = 100; NSLog(@"Unknown sync state: '%d'", status->state); break; } // Wrap the GUI info in an object and have the main thread update UpdateInfo * info = [[[UpdateInfo alloc] initWithMessage:message andProgress:percentDone / 100] autorelease]; [[DataAccess sharedInstance] performSelectorOnMainThread:@selector(updateSyncProgress:) withObject:info waitUntilDone:YES]; } |
一切就绪后,便可以在 DataAccess.mm 文件的同步方法中将同步观察器设置为回调方法:
// Set the sync parameters info.user_name = (char*)"user"; // Set to your username info.password = (char*)"password"; // Set to your password info.version = (char*)"NamesModel"; info.stream = "tcpip"; info.stream_parms = (char*)"host=localhost"; info.observer = progressCallback; // Add this line |
![]() |
使用DocCommentXchange讨论此页。
|
版权 © 2012, iAnywhere Solutions, Inc. - SQL Anywhere 12.0.1 |