Xcode和Interface Builder

Sun 23 June 2019 / In categories Tools

Cocoa, Interface Builder, Xcode

作为APP农场主,苹果公司对提供工具和原材料给果农(APP开发者)这方面做得简直太尽心尽力。生怕果农没有合适的工具,易于阅读的文档。作为开发工具Xcode既成熟又强大;在文档方面,苹果公司也做得相当出色,只要好好看官方文档,学习mac和ios开发没有任何问题(对英文文档过敏的除外)。

苹果在2013就推出了Start Developing Mac Apps Today这种宣传册式的教程,煞费苦心告诉大家,开发mac上的app是一件很容易的事情。如果我当时水平够,看到这宣传册,就应该去买苹果的股票。当时70美元左右,现如今200美元。苹果的文档做得真的好阿,虽然现在微软像苹果学习,但是微软的文档依然跟苹果的文档在细致性上面有差距。苹果的文档图文并茂不说,还有各种标识,重点突出,理解起来毫不困难。

就拿Xcode Help来说,结构清晰,组织良好,让读者很容易根据自己的目的和意图寻找相应的信息。拿其中的一篇做例子Create the user interface ,这个页面就很详细介绍了如何使用Xcode用户界面,让一开始面对Xcode繁复的界面不知所措的小白一下子就能抓住要点。更关键的是Xcode Help不是逐渐改善的,直接去看若干年前的老版本的Xcode Overview,依然是高水准。

扯的有点远了,今天的主题其实是Start Developing Mac Apps TodayJump Right In步骤中的About Creating Your First Mac App。这是一个例子,告诉初学者怎么上手开发一个Cocoa APP。

Cocoa APP是mac上的图形化界面APP,使用的是AppKit框架。Xcode中主要是使用Interface Builder来通过拖拉扯拽的方式构建图形化界面。所以做Cocoa APP,重点是先过Interface Builder这一关。Interface Builder诞生于NeXT,所以一开始叫NeXT Interface Builder,文件名以.nib结尾。但nib是一种二进制格式,不利于编辑查看。后来改成了XML格式,文件名就变成了.xib结尾。xib可以编译成nib格式,Cocoa APP可以加载nib获取其中定义好的图形化界面。

xib是一种资源文件(和源代码文件有所区分),可以参考ADA: Resource Programming Guide。微软后来也用XML来定义图形化界面了,其格式叫做XAML

Storyboard是Interface Builder所支持的另外一种图形化界面构建工具,和xib不同,storyboard中可以用segues来联系不同的视图,而xib只是一个视图相关资源的合集。

学习Xcode创建的Cocoa项目如何初始化并加载nib对理解其编程挺有帮助的。

首先,创建好的项目中有一个info.plist文件,这是一个App描述文件,其中的”Main nib file base name”默认设置为MainMenu,也就是说MainMenu.xib中定义的视图资源会被默认加载。

看一下main.m中的内容:

#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[]) {
    return NSApplicationMain(argc, argv);
}

Cocoa APP是通过NSApplicationMain(argc, argv)初始化的。初始化的时候会创建一个NSApplication的实例(一个APP有一个实例就够了)。这个实例会处理事件循环(runloop)。AppDelegate作为NSApplication实例的一个属性,也会被初始化。NSApplication定义了APP的通用框架,而AppDelegate定义某个APP特有的行为。

AppDelegate和MainMenu.xib是息息相关的。双击MainMenu.xib,在Interface Builder打开它,会发现MainMenu.xib有有三个占位符(Placeholder)

  • File’s Owner
  • First Repsonder
  • Application

Application就是一开始在main中创建的NSApplication的实例没有什么好说的。File’s Owner在MainMenu.xib也是那个NSApplication的实例;这是因为MainMenu.xib是被NSApplication的实例所加载的,File’s Owner用来代表xib的加载者。用户与MainMenu.xib中定义的界面交互时会产生各种事件,First Repsonder就是用来相应这些事件的,AppKit会自动设置First Repsonder,其实也大概是设置成NSApplication的实例。

这三个叫做占位符,那是因为这三个不是在xib中定义的,而是加载二进制格式nib时,从外部传入的。如果你以XML格式打开xib,会发现:

        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
            <connections>
                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
            </connections>
        </customObject>
        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
        <customObject id="-3" userLabel="Application" customClass="NSApplication"/>

这三个占位符的id都是负数哦。

上面的XML片段有一个地方值得关注,也就是<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>

            <connections>
                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
            </connections>

上面这个delegate是NSApplication的一个outlet。所谓outlet,其实是一个弱引用类型的熟悉:

@property(weak) id<NSApplicationDelegate> delegate;

此弱引用的实例是不NSApplication创建的,而是在其他地方创建,然后将引用传递给NSApplication的delegate属性。当然,弱引用所指向的对象也不属于NSApplication。

那上面的delegate这个outlet是谁创建的呢?其实是由MainMenu.xib创建的。在MainMenu.xib中不仅可以创建视图对象,还可以创建自定义对象。在Interface Builder里面可以看到一个蓝色立方体标识的Delegate对象,类型为AppDelegate(就是由Xcode生成的AppDelegate.h中定义的那个AppDelegate类型)。从Connections Inspector可以看到Delegate对象的连接关系:

  • referencing outlets: delegate -> File’s Owner
  • outlets: window -> Window

Delegate对象有一个referencing outlet,就是delegate -> File's Owner。也就是说,在加载这个MainMenu.xib生成的nib的时候,会从AppDelegate实例化出Delegate对象,并将Delegate对象的引用传给File’s Owner(也就是NSApplicatoin)的delegate属性。

同时Delegate对象还有一个outlet,也就是window -> Window。这对应这AppDelegate的这个弱引用属性:

@property (weak) IBOutlet NSWindow *window;

Delegate中的Window对象初始化完成的时候会把引用传给Delegate的window属性,从而搭建好相应的outlet。和Window一样,在MainMenu.xib中创建(按住Ctrl键拖动)的其他outlet或者action,都是通过此种方式,连接到Delegate的某个弱属性上的。这些都会在MainMenu.xib中记录下来(而不是在ObjC或者Swift代码中)。

关于Objc的IBAction以及IBOutlet,文档中有解释:

Note: IBAction is a constant (defined as void) that is used to tell Xcode to treat a method as an action for target-action connections.

Note: IBOutlet is a constant that is used only to tell Xcode to treat the object as an outlet. It’s actually defined as nothing, so it has no effect at compile time.

When the user activates a UI element, the element can send a message to an object that knows how to perform the corresponding action. This interaction is part of the target-action mechanism, a Cocoa design pattern.

另外关于弱引用,文档中也有相应结束:

The property specifies the strong attribute rather than the weak attribute, indicating that the Track object is owned by the app delegate. As long as the app delegate references it, the Track object won’t go away. The outlet property declarations you created earlier use the weak attribute because the corresponding UI elements are contained in the view hierarchy that is owned by the window, not the app delegate. The window and its view hierarchy can go away independent of the app delegate.

其他

About Creating Your First Mac App有段话说得很好:

Your First Mac App introduces you to the Three Ts of Mac app development: * Tools. How to use Xcode to create and manage a project * Technologies. How to create an app that responds to user input * Techniques. How to take advantage of some of the fundamental design patterns that underlie all Mac app development

参考链接

(完)

Load Disqus Comments