一、前言
写该篇文章的缘由是:我想手动控制macOS的主程序循环,用来添加自己想要的处理。
正文 I ~ IV 部分 讲解我理解macOS应用结构。
正文 V 部分 仿写[NSApp run],添加自己想要的处理
二、正文
Ⅰ、我理解的应用
我把OS应用抽象出两个概念:
1、视图显示(当然是对于有界面的应用)
2、事件处理机
Ⅱ、视图显示
先从窗口(NSWindow)开始谈起。窗口可以分为标题头,和内容。标题头包括几个通用按钮和标题头。分别可以通过如下方式进行设置:
NSWindowStyleMask style = NSWindowStyleMaskClosable |
NSWindowStyleMaskTitled |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskResizable;
//在窗口初始化的时候 设置窗口坐标和大小、窗口类型、缓冲类型、是否延迟创建窗口。
id window = [[NSWindow alloc]initWithContentRect :
NSMakeRect(10, 10, 100, 100)
styleMask : style
backing:NSBackingStoreBuffered
defer:NO];
//设置标题
[window setTitle:[NSString stringWithUTF8String : "hello world"]];
窗口被看成一种特殊的视图,意思是它还是一个视图,只不过它是基本视图。
这也就意味着你把窗口全关了也不会导致应用的终止。
Ⅲ、事件处理机
一个应用的事件可以分为很多种。具体可以在NSEvent里面找到。
一个应用离不开事件的接收和处理。我觉的事件的最大作用体现在人机交互方面。鼠标、键盘等等输入设备被相应的时候,会形成一个事件包,由底层逐步向应用层传递,并且存放在应用的事件队列中。接下来我们应用层开发者只需要获取到相应的事件,并处理响应它。这样一个事件的生命就结束了。
理基本就这么一个理。至于代码,就接着看吧。
Ⅳ、代码实现
[NSApplication sharedApplication]
首先必须创建初始化一个应用对象。
应用的初始化的过程中,会创建一个事件池,用来存放事件。有了事件池还是不够的,需要一个事件接收端口(port),用来接收从底层传来的事件。(其实是从Window Server那传来的)
接下来就创建一个窗口,假如你的应用需要窗口。
窗口的创建十分简单,只要调用NSWindow 的 初始化方法就行了,在初始化方法中进行各种窗口设置。个人认为窗口在初始化时会自动与创建的应用进行关联。不需要将它添加进NSApplication中。
同样也可以给窗口添加一个其他视图(NSView)
//设置window的内容视图。
[window setContentView : cView];
//将该视图设置为第一响应者。
[window makeFirstResponder : cView];
关于事件响应部分我也做了一点点的记录
Cocoa Event Overview
万事俱备,只欠一个run了。
调用[NSApp run],让程序跑起来。这个方法里面会有一个循环,因为这个循环程序才会一直运行,不会结束。除非调用终止方法,才会有返回值。继续进行之后的语句。
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
int main(int argc , const char* argv[])
{
[NSApplication sharedApplication];
//设置应用的显示策略(ActivationPolicy)。可以在我的另一篇文章中找到具体说明。
[NSApp setActivationPolicy : NSApplicationActivationPolicyRegular];
NSWindowStyleMask style = NSWindowStyleMaskClosable |
NSWindowStyleMaskTitled |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskResizable;
id window = [[NSWindow alloc]initWithContentRect :
NSMakeRect(10, 10, 100, 100)
styleMask : style
backing:NSBackingStoreBuffered
defer:NO];
[window setTitle:[NSString stringWithUTF8String:"hello world"]];
//如下没有这句话,会导致窗口不会显示出来
[window makeKeyAndOrderFront : nil];
//使得应用打开时就获取到用户焦点
[NSApp activateIgnoringOtherApps:YES];
[NSApp run];
}
代码中提及的显示策略链接 Activation Policy
对于一个“正常”的应用来说这已经足够了。但是如果想在程序的每次循环中干点自己想要的事,那么就需要写一个类似[NSApp run]的循环,然后在每个循环中添加自己的处理。
Ⅴ、仿写[NSApp run]
首先得有个循环,使得程序一直跑下去。用以下代码,代替[NSApp run];
while(true)
{
PollEvents();
//do something you like . :D
}
之前说了事件是至关重要的,所以在循环中需要接收每个事件。然后对事件进行相应的处理。这样就达到了[NSApp run]的基本要求。
void PollEvents()
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];
for(;;)
{
NSEvent* event = [NSApp nextEventMatchingMask : NSEventMaskAny
untilDate : [NSDatedistantPast] inMode : NSDefaultRunLoopMode
dequeue:YES];
if(event == nil)
break;
[NSApp sendEvent : event];
}
[pool release];
}
三、传送门
阅读导向
笔者相关文档
Cocoa 事件处理大纲 Event Overview
应用显示策略Activation Policy
网友评论
NSEvent* event = [NSApp nextEventMatchingMask : NSEventMaskAny untilDate : [NSDate distantPast] inMode : NSDefaultRunLoopMode dequeue:YES];
应为
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
用distantPast的NSDate的对象的话,无论是否收到事件都会获得一个NSEvent对象,这样会导致循环中长期空转严重占用系统资源,而distantFuture的NSDate会一直阻塞直到接收事件,这样是符合预期效果的。
GNUStep的-[NSApplication run]方法就是这样设计的。