source: trunk/source/examples/cocoa/nib-loading/HOWTO.html @ 8503

Last change on this file since 8503 was 8503, checked in by mikel, 12 years ago

created nib-loading fnction

File size: 19.5 KB
Line 
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3<html xmlns="http://www.w3.org/1999/xhtml">
4  <head>
5    <title>Nib-Loading HOWTO</title>
6    <link rel="stylesheet" type="text/css" href="HOWTO_files/stylesheets/styles.css" />
7  </head>
8
9  <body>
10
11    <div class="title">
12      <h1>Nib-Loading HOWTO</h1>
13    </div>
14
15    <div class="body-text">
16      <p>This HOWTO shows how you can load <strong>nibfiles</strong>
17        into a running copy of Clozure CL by evaluating Lisp forms. You
18        might want to load nibfiles this way to test user-interface
19        elements that you are working on for an application project, or
20        to enable an application to dynamically load optional
21        user-interface elements.</p>
22
23    </div>
24
25    <div class="section-head">
26      <h2>Nibfiles</h2>
27    </div>
28
29    <div class="body-text">
30      <p>A large part of developing Cocoa applications is creating
31        user-interface elements using the Cocoa frameworks. Although
32        it's perfectly possible to create any user-interface element
33        just by making method calls against the frameworks, the more
34        standard way to design a user interface is to use Apple's
35        InterfaceBuilder application to
36        create <strong>nibfiles</strong>&mdash;files of archived
37        Objective-C objects that implement the user-interface
38        elements.</p>
39     
40      <p>InterfaceBuilder is an application that ships with Apple's
41        Developer Tools. The Developer Tools are an optional install
42        that comes with Mac OS X. Before you can use this HOWTO, you'll
43        need to make sure that Apple's Developer Tools are installed on
44        your system. Members of Apple's developer programs may download
45        the tools for free from
46        Apple's <href="http://developer.apple.com">developer
47          website</href>, but normally there is no need. You can simply
48        use the optional Developer Tools installer on the Mac OS X
49        system disks to install the tools.</p>
50    </div>
51
52    <div class="section-head">
53      <h2>Using Nibfiles</h2> 
54    </div>
55
56    <div class="body-text">
57      <p>Using InterfaceBuilder, you can quickly and easily create
58        windows, dialog boxes, text fields, buttons, and other
59        user-interface elements. The elements you create with
60        InterfaceBuilder have the standard appearance and behavior
61        specified by Apple's Human Interface Guidelines.</p>
62
63      <p>InterfaceBuilder saves descriptions of these objects
64        in <strong>nibfiles</strong>. These files contain archived
65        representations of Objective-C classes and objects. When you
66        launch an application and it loads a nibfile, the Cocoa runtime
67        creates these Objective-C objects in memory, complete with any
68        instance-variable references to other objects that might have
69        been saved in the nibfile. In short, a nibfile is an archived
70        collection of user-interface objects that Cocoa can quickly and
71        easily revive in memory.</p>
72
73      <p>The normal way that Objective-C programmers use nibfiles is
74        by storing them in an application bundle. The application's
75        Info.plist file (also stored in the bundle) specifies which
76        nibfile is the application's main nibfile, and that file is
77        loaded automatically when the application starts up. The
78        application can dynamically load other nibfiles from the bundle
79        by making method calls.</p>
80
81      <p>Lisp applications written with Clozure CL can also use
82        nibfiles in this same fashion (see the "currency-converter"
83        HOWTO in the "cocoa" examples folder), but Lisp programmers are
84        accustomed to highly interactive development, and might want to
85        simply load an arbitrary nibfile into a running Clozure CL
86        session. Fortunately, this is easy to do.</p>
87    </div>
88
89    <div class="section-head">
90      <h2>How To Load a Nibfile</h2> 
91    </div>
92
93    <div class="body-text">
94      <p>Let's start by loading a very simple nibfile from the Clozure
95        CL Listener window. Start by launching the Clozure CL
96        application.</p>
97
98      <p>In the same directory as this HOWTO file, you'll find a
99        nibfile named "hello.nib". This is an extremely simple nibfile
100        that creates a single Cocoa window with a greeting in it. We'll
101        use forms typed into the Listener window to load it.</p>
102
103      <p>We're going to call the Objective-C class
104        method <code>loadNibFile:externalNameTable:withZone:</code> to
105        load the nibfile into memory, creating the window that is
106        described in the file. First, though, we need to set up some
107        data structures that we'll pass to this method.</p>
108
109      <p>The arguments
110        to <code>loadNibFile:externalNameTable:withZone:</code> are a
111        pathname, a dictionary object, and a memory zone. As with every
112        Objective-C method call, we also pass the object that receives
113        the message, which in this case is the class NSBundle.</p>
114
115      <p>The pathname is just a reference to the nibfile we want to
116        load. The dictionary holds references to objects. In this
117        first simple example, we'll use it only to identify the
118        nibfile's owner, which in this case is the application
119        itself. The zone is a reference to the area of memory where
120        the nibfile objects will be allocated.</p>
121
122      <p>Don't worry if none of this makes sense to you; the code to
123        create these objects is simple and straightforward, and should
124        help clarify what's going on.</p>
125
126      <div class="section-head">
127        <h3>1. Get the Zone</h3> 
128      </div>
129
130      <p>First, we'll get a memory zone. We'll tell Cocoa to allocate
131        the nibfile objects in the same zone that the application
132        uses, so getting a zone is a simple matter of asking the
133        application for the one it's using.</p>
134
135      <p>Before we can ask the application anything, we need a
136        reference to it. We'll ask the class NSApplication to give us a
137        reference to the running application.</p>
138
139      <p>Start by changing to the CCL package; most of the utility
140        functions we'll use are defined in that package:</p>
141
142      <pre>
143        ? (in-package :ccl)
144        #&lt;Package "CCL"&gt;
145      </pre>
146
147      <p>Next, get a reference to the NSApplication class:</p>
148
149      <pre>
150        ? (setf  *my-app*
151        (let* ((class-name (%make-nsstring "NSApplication"))
152        (appclass (#_NSClassFromString class-name)))
153        (#/release class-name)
154        (#/sharedApplication appclass)))
155        #&lt;LISP-APPLICATION &lt;LispApplication: 0x1b8de0&gt; (#x1B8DE0)&gt;
156      </pre>
157
158      <p>Let's review this form step-by-step.</p>
159
160      <p>First of all, it's going to store the returned application
161        object in the variable <code>*my-app*</code>, so that we have it
162        convenient for later use.</p>
163
164      <p>We need an <code>NSString</code> object that contains the
165        name of the application class, so the code allocates one by
166        calling <code>%make-nsstring</code>. The <code>NSString</code>
167        object is a dynamically-allocated foreign object, not managed by
168        Lisp's garbage-collector, so we'll have to be sure to release it
169        later.</p>
170
171      <p>The code next uses the class-name to get the
172        actual <code>NSApplication</code> class object, by
173        calling <code>#_NSClassFromString</code>.</p>
174
175      <p>Finally, after first releasing the <code>NSString</code>
176        object, it calls <code>#/sharedApplication</code> to get the
177        running application object, which turns out to be an instance
178        of <code>LispApplication</code>.</p>
179
180      <p>Voilà! We have a reference to the running Clozure CL
181        application object! Now we can ask it for its zone, where it
182        allocates objects in memory:</p>
183
184      <pre>
185        ? (setf *my-zone* (#/zone *my-app*))
186        #&lt;A Foreign Pointer #x8B000&gt;
187      </pre>
188
189      <p>Now we have a reference to the application's zone, which is
190        one of the parameters we need to pass
191        to <code>loadNibFile:externalNameTable:withZone:</code>.</p>
192
193      <div class="section-head">
194        <h3>2. Make a Dictionary</h3> 
195      </div>
196
197      <p>The dictionary argument
198        to <code>loadNibFile:externalNameTable:withZone:</code> is
199        used for two purposes: to identify the nibfile's owner, and
200        to collect toplevel objects.</p>
201
202      <p>The nibfile's owner becomes the owner of all the toplevel
203        objects created when the nibfile is loaded, objects such as
204        windows, buttons, and so on. A nibfile's owner manages the
205        objects created when the nibfile is loaded, and provides a
206        way for your code to get references to those objects. You
207        supply an owner object in the dictionary, under the
208        key <code>"NSNibOwner"</code>.</p>
209
210      <p>The toplevel objects are objects, such as windows, that are
211        created when the nibfile is loaded. To collect these, you
212        can pass an <code>NSMutableArray</code> object under the
213        key <code>NSNibTopLevelObjects</code>.</p>
214
215      <p>For this first example, we'll pass an owner object (the
216        application object), but we don't need to collect toplevel
217        objects, so we'll omit
218        the <code>NSNibTopLevelObjects</code> key.</p>
219
220      <pre>
221        ? (setf *my-dict*
222        (#/dictionaryWithObject:forKey: (@class ns-mutable-dictionary)
223        *my-app*
224        #@"NSNibOwner"))
225        #&lt;NS-MUTABLE-DICTIONARY {
226        NSNibOwner = &lt;LispApplication: 0x1b8e10&gt;;
227        } (#x137F3DD0)&gt;
228       
229      </pre>
230
231      <div class="section-head">
232        <h3>3. Load the Nibfile</h3> 
233      </div>
234
235      <p>Now that we have the zone and the dictionary we need, we
236        can load the nibfile. We just need to create an NSString with
237        the proper pathname first:</p>
238
239      <pre>
240        ? (setf *nib-path*
241        (%make-nsstring
242        (namestring "/usr/local/openmcl/ccl/examples/cocoa/nib-loading/hello.nib")))
243        #&lt;NS-MUTABLE-STRING "/usr/local/openmcl/ccl/examples/cocoa/nib-loading/hello.nib" (#x13902C10)&gt;
244      </pre>
245
246      <p>Now we can actually load the nibfile, passing the method
247        the objects we've created:</p>
248
249      <pre>
250        ? (#/loadNibFile:externalNameTable:withZone:
251        (@class ns-bundle)
252        *nib-path*
253        *my-dict*
254        *my-zone*)
255        T
256      </pre>
257
258      <p>The window defined in the "hello.nib" file should appear
259        on the
260        screen. The <code>loadNibFile:externalNameTable:withZone:</code>
261        method returns <code>T</code> to indicate it loaded the
262        nibfile successfully; if it had failed, it would have
263        returned <code>NIL</code>.</p>
264
265      <p>At this point we no longer need the pathname and
266        dictionary objects, and we can release them:</p>
267
268      <pre>
269        ? (setf *nib-path* (#/release *nib-path*))
270        NIL
271        ? (setf *my-dict* (#/release *my-dict*))
272        NIL
273      </pre>
274
275      <div class="section-head">
276        <h2>Making a Nib-loading Function</h2> 
277      </div>
278
279      <p>Loading a nibfile seems like something we might want to do
280        repeatedly, and so it makes sense to make it as easy as possible
281        to do. Let's make a single function we can call to load a nib as
282        needed.</p>
283
284      <p>The nib-loading function can take the file to be loaded as a
285      parameter, and then perform the sequence of steps covered in the
286      previous section. If we just literally do that, the result will
287      look something like this:</p>
288
289      <pre>
290(defun load-nibfile (nib-path)
291  (let* ((app-class-name (%make-nsstring "NSApplication"))
292         (app-class (#_NSClassFromString class-name))
293         (app (#/sharedApplication appclass))
294         (app-zone (#/zone app))
295         (nib-name (%make-nsstring (namestring nib-path)))
296         (dict (#/dictionaryWithObject:forKey:
297                (@class ns-mutable-dictionary) app #@"NSNibOwner")))
298    (#/loadNibFile:externalNameTable:withZone: (@class ns-bundle)
299                                               nib-name
300                                               dict
301                                               app-zone)))
302      </pre>
303
304      <p>The trouble with this function is that it leaks two strings
305      and a dictionary every time we call it. We need to release the
306      variables <code>app-class-name</code>, <code>nib-name</code>,
307      and <code>dict</code> before returning. So how about this
308      version instead?</p>
309
310      <pre>
311(defun load-nibfile (nib-path)
312  (let* ((app-class-name (%make-nsstring "NSApplication"))
313         (app-class (#_NSClassFromString class-name))
314         (app (#/sharedApplication appclass))
315         (app-zone (#/zone app))
316         (nib-name (%make-nsstring (namestring nib-path)))
317         (dict (#/dictionaryWithObject:forKey:
318                (@class ns-mutable-dictionary) app #@"NSNibOwner"))
319         (result (#/loadNibFile:externalNameTable:withZone: (@class ns-bundle)
320                                                            nib-name
321                                                            dict
322                                                            app-zone)))
323    (#/release app-class-name)
324    (#/release nib-name)
325    (#/release dict)
326    result))
327      </pre>
328
329      <p>This version solves the leaking problem by binding the result
330      of the load call to <code>result</code>, then releasing the
331      variables in question before returning the result of the
332      load.</p>
333
334      <p>There's just one more problem: what if we want to use the
335      dictionary to collect the nibfile's toplevel objects, so that we
336      can get access to them from our code? We'll need another version
337      of our function.</p>
338
339      <p>In order to collect toplevel objects, we'll want to pass an
340      NSMutableArray object in the dictionary, stored under the key
341      <code>NSNibTopLevelObjects</code>. So we first need to create such an
342      array object in the <code>let</code> form:</p>
343
344      <pre>
345(let* (...
346       (objects-array (#/arrayWithCapacity: (@class ns-mutable-array) 16))
347       ...)
348  ...)
349      </pre>
350
351      <p>Now that we have the array in which to store the nibfile's
352      toplevel objects, we need to change the code that creates the
353      dictionary, so that it contains not only the owner object, but
354      also the array we just created:</p>
355
356      <pre>
357  (let* (...
358         (dict (#/dictionaryWithObjectsAndKeys: (@class ns-mutable-dictionary)
359                    app #@"NSNibOwner"
360                    objects-array #&amp;NSToplevelObjects))
361         ...)
362    ...)
363 
364      </pre>
365
366      <p>We'll want to release the <code>NSMutableArray</code>
367      object before returning, but first we need to collect the
368      objects in it. We'll do that by making a local variable to
369      store them, then iterating over the array object to get them all.</p>
370
371      <pre>
372  (let* (...
373         (toplevel-objects (list))
374         ...)
375    (dotimes (i (#/count objects-array))
376      (setf toplevel-objects
377            (cons (#/objectAtIndex: objects-array i)
378                  toplevel-objects)))
379    ...)
380      </pre>
381
382      <p>After collecting the objects, we can release the array, then
383      return the list of objects. It's still possible we might want
384      to know whether the load succeeded, so we
385      use <code>values</code> to return both the toplevel objects and
386      the success or failure of the load.</p>
387
388      <p>The final version of the nib-loading code looks like
389      this:</p>
390
391      <pre>
392(defun load-nibfile (nib-path)
393  (let* ((app-class-name (%make-nsstring "NSApplication"))
394         (app-class (#_NSClassFromString app-class-name))
395         (app (#/sharedApplication app-class))
396         (app-zone (#/zone app))
397         (nib-name (%make-nsstring (namestring nib-path)))
398         (objects-array (#/arrayWithCapacity: (@class ns-mutable-array) 16))
399         (dict (#/dictionaryWithObjectsAndKeys: (@class ns-mutable-dictionary)
400                    app #@"NSNibOwner"
401                    objects-array #&amp;NSNibToplevelObjects))
402         (toplevel-objects (list))
403         (result (#/loadNibFile:externalNameTable:withZone: (@class ns-bundle)
404                                                            nib-name
405                                                            dict
406                                                            app-zone)))
407    (dotimes (i (#/count objects-array))
408      (setf toplevel-objects
409            (cons (#/objectAtIndex: objects-array i)
410                  toplevel-objects)))
411    (#/release app-class-name)
412    (#/release nib-name)
413    (#/release dict)
414    (#/release objects-array)
415    (values toplevel-objects result)))
416      </pre>
417
418      <p>Now we can call this function with some suitable nibfile,
419      such as simple "hello.nib" that comes with this HOWTO:</p>
420
421      <pre>
422? (ccl::load-nibfile "hello.nib")
423(#&lt;LISP-APPLICATION &lt;LispApplication: 0x1b8da0&gt; (#x1B8DA0)&gt;
424 #&lt;NS-WINDOW &lt;NSWindow: 0x171344d0&gt; (#x171344D0)&gt;)
425T
426
427      </pre>
428
429      <p>The "Hello!" window appears on the screen, and two values are
430      returned. The first value is the list of toplevel objects that
431      were loaded. The second value, <code>T</code> indicates that the
432      nibfile was loaded successfully.</p>
433
434      <div class="section-head">
435        <h2>What About Unloading Nibfiles?</h2> 
436      </div>
437     
438      <p>Cocoa provides no general nibfile-unloading API. Instead, if
439      you want to unload a nib, the accepted approach is to close all
440      the windows associated with a nibfile and release all the
441      toplevel objects. This is one reason that you might want to use
442      the <code>"NSNibTopLevelObjects"</code> key with the dictionary
443      object that you pass
444      to <code>loadNibFile:externalNameTable:withZone:</code>&mdash;to
445      obtain a collection of toplevel objects that you release when
446      the nibfile is no longer needed.</p>
447
448      <p>In document-based Cocoa applications, the main nibfile is
449      usually owned by the application object, and is never unloaded
450      while the application runs. Auxliliary nibfiles are normally
451      owned by controller objects, usually instances of
452      <code>NSWindowController</code> subclasses. When you
453      use <code>NSWindowController</code> objects to load nibfiles,
454      they take responsibility for loading and unloading nibfile
455      objects.</p>
456
457      <p>When you're experimenting interactively with nibfile loading,
458      you may not start out by
459      creating <code>NSWindowController</code> objects to load
460      nibfiles, and so you may need to do more of the object
461      management yourself. On the one hand, loading nibfiles by hand
462      is not likely to be the source of major application problems. On
463      the other hand, if you experiment with nib-loading for a long
464      time in an interactive session, it's possible that you'll end up
465      with numerous discarded objects cluttering memory, along with
466      various references to live and possibly released objects. Keep
467      this in mind when using the Listener to explore Cocoa. You can
468      always restore your Lisp system to a clean state by restarting
469      it, but of course you then lose whatever state you have built up
470      in your explorations. It's often a good idea to work from a text
471      file rather than directly in the Listener, so that you have a
472      record of the experimenting you've done. That way, if you need
473      to start fresh (or if you accidentally cause the application to
474      crash), you don't lose all the information you gained.</p>
475
476    </div>
477
478  </body>
479</html>
480
Note: See TracBrowser for help on using the repository browser.