本文基于数据字典和数据流图两种工具讲述一个完整微博客户端的实现。数据字典和数据流图都可以用来表达线程的执行流程,同时定义了需要的类,是进一步设计类的基础。
数据字典实际上是一张表,表的第一个字段是程序代码中的标识符,其它字段具体描述它在线程中被如何使用,以及它所依赖的其它元素,数据字典中各个标识符基本上也是按照线程的执行流程来排序。
数据流图是一个平面拓扑结构,每个节点或者是外部数据,或者是可被线程执行的代码模块。从外部数据到代码模块的边意味着线程在执行代码模块的时候需要用到外部数据,从代码模块到代码模块的边则表示线程执行完成一个代码模块后跳转到另一个代码模块开始执行。其它类型的边没有具体含义。
数据字典
· 线程启动,执行函数main。
· int main(int argc, char *argv[])
—— 线程首先跳转到函数main所在的代码块,然后可以跳转到函数UIApplicationMain。
· int UIApplicationMain (
int argc,
char *argv[],
NSString *principalClassName,
NSString *delegateClassName
);
—— 线程调转到函数UIApplicationMain所在的代码块,然后在内存中创建两个分别引用了应用本身和应用代理的内存块,其实就是UIApplication类(子类)和SAAppDelegate类(子类)的实例;然后调转到 app delegate 实例,执行 applicationDidFinishLaunching 方法中定义的代码;然后线程跳转到UIApplication的实例,根据实例中的字段和类中定义的代码,进入等待接收事件的状态;如果线程接收到界面控件点击事件,就跳转到引用了这个控件的内存块(控件对象),根据内存块中的字段值和控件类中定义的代码执行相应的过程,最后重新等待其它事件;如果线程接收到用户终止了进程的事件,就跳转到 app delegate 实例,执行 applicationWillTerminate 中的代码,然后再跳转到main函数,执行 return 语句终止自己。
· UIApplication
—— 类UIApplication以及它在内存中的实例中定义的代码和数据, 可以告诉线程应该如何修改和获得与应用程序相关的信息,如何进入事件循环,线程等待各种事件,跳转到相应的代码位置。
· SAAppDelegate
—— 类SAAppDelegate以及它在内存中的实例中定义的代码和数据,可以告诉线程如何处理与应用程序相关的事件,如何获取数据模型等等。
· applicationDidFinishLaunching
—— 线程从UIApplication类或实例中的代码跳转到这个函数中,然后在内存中创建一个UIWindow类的实例,这个实例可以引用一个可以承载可视化元素的窗口,然后线程在内存创建一个控制器类SAMainController或SAOAuthController或SANewfeatureController类的实例,在把UIView的实例创建到内存后,调用 viewDidLoad 方法配置 view 对象中的各个字段。然后跳转到UIWindow类的代码中,设置内存实例的字段值,并把这个ViewController的实例作为窗口的根元素,然后跳转到 makeKeyAndVisible的代码中,根据根元素实例包含的UIView实例,画出窗口中的界面元素,期间会跳转到控制器实例的 viewWilAppear 等方法,执行完成后跳转回UIApplication实例,进入等待事件的状态。
ps:makeKeyAndVisible就是一个设置主窗口的函数,表示这个窗口下的视图对象需要被渲染(rendering)。一个内存对象如果不经过渲染,也就不会对应屏幕上的元素。
数据流图(顺序图)
ps:
关于为什么要调用 performSelectorOnMainThread 才能更新界面: 因为程序员无法直接调用绘图函数,如果让程序员创建的线程来更改控件对象,那么就仅仅是改变了内存中某个实例的字段值,主线程却没有收到界面更新的事件,仍然处于阻塞状态,界面就不会更新。问题在于主线程是否会在下一次事件循环结束后更新界面。
如果我们使用模板来新建一个project,比如Master Detail Application或者tabbed Application之类,那么Xcode将会为我们自动生成Main.storyboard文件,这样一来那些本来实在源代码中间中被定义的线程执行流程,现在全部在这个故事版文件中被定义了。用故事版当然有故事版的好处,只能用来定义的可视化元素现在允许程序员用可视化的方式来定义,不过对于并不是非常熟悉iOS编程的程序员来说,这种搭建UI的方式并不利于维护修改,因为程序员不容易看到线程的实际执行过程。
如果用代码的方式,程序员就可以看到什么时候线程创建了UIWindow的实例,并定义这个窗口要承载的界面元素,以及什么时候根据UIWindow实例中的代码和数据绘出图形。方法makeKeyAndVisible告诉了线程应该如何绘图。
主线程执行流程
iOS应用主线程执行流程如下图,方块代表了代码集合或者线程需要获得的其它资源,箭头方向代表了线程的转移方向。
上图就是主线程的声明周期。我们看看main函数:
#import <UIKit/UIKit.h> #import "QSAppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
这是线程的入口,之后执行函数UIApplicationMain.
UIApplicationMain()
函数是初始化程序的核心,它接受4个参数。
int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
argc和argv:
来自于main()接受的两个参数;
第三个参数:
主要类的名字(principal class name),必须是UIApplication或其子类的名字,这个类的一个实例引用了当前iPhone应用,定义了事件循环的代码。它会告诉线程去读info.plist文件获取配置信息,以及Main.storyboard;如果这个参数为nil,则默认为@"UIApplication";
第四个参数:
代理类的名字(delegate class name),线程在内存中创建一个代理类的实例,定义一些处理与操作系统相关的事件的代码。
用代码替换故事版
说了很多iOS程序执行的真正流程,我们来检验一下我们的理解吧。
假如你的工程类都是以TC开头。
1.import你的appdelegate类,并修改第四个参数如下:
UIApplicationMain(argc, argv, nil, NSStringFromClass([TCAppDelegate class]));
2.删除Main.storyboard文件
3.在工程的Info.plist文件中删除Main storyboard file base name
4、修改delegate类中定义的方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; UIViewController *webPageViewController = [[WebPageViewController alloc] init]; UIViewController *personalViewController = [[PersonalViewController alloc]init]; UIViewController *meViewController = [[MeViewController alloc]init]; UINavigationController *personalNavigationController = [[UINavigationController alloc]init]; personalNavigationController.viewControllers = [NSArray arrayWithObjects:personalViewController, nil]; UINavigationController *meNavigationController = [[UINavigationController alloc]init]; meNavigationController.viewControllers = [NSArray arrayWithObjects:meViewController, nil]; _tabBarController = [[UITabBarController alloc]init]; _tabBarController.viewControllers = [NSArray arrayWithObjects:webPageViewController, personalNavigationController, meNavigationController,nil]; self.window.rootViewController = _tabBarController; _tabBarController.selectedIndex = 0; self.window.backgroundColor = [UIColor whiteColor]; [self customizeAppearance]; [self.window makeKeyAndVisible]; return YES; }
这样就避免了根据故事版文件的信息来加载窗口以及它所承载的界面元素。