Application Technologies • 54:15
In this hands-on session we will walk step-by-step through the dictionary designed in the "Designing a Scripting Interface" session. We'll focus primarily on Cocoa scripting; however, Carbon scripting techniques and issues will be addressed. Learn what you need to know to implement a scripting interface for your application.
Speaker: John Comiskey
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning everyone. I'm John Montprion from Apple Developer Technical Support. I'm very pleased to introduce John Comiskey, Senior Apple Script Engineer, who will be guiding you through this hands-on session. I'd like to point out that the download materials, Sketch 1.1.2, was not available on the DMG and you would have had to download that from the Secure Developer Conference website before this session begins. Because of our network situation today you might not be able to download that during the session, so please just follow along. You can download that later. But here's John, maybe give him a round of applause and thank you.
Good morning, thank you very much. My name is John Comiskey and I'm an engineer in the Apple Script Group and I'm here to talk to you today about implementing a scripting interface. Before we start, there are some assumptions that we've made about who's here today. We're assuming that you know something about Cocoa and Objective-C. It may not be the only language you use or even the one you use the most, but if you're going to do Cocoa scripting you're going to need to know how to write Cocoa code.
We're also presuming that you've got an application that's been designed and maybe even implemented and that you want to add scriptability to it. Your dictionary, by the time you reach this point in development, should already be designed. If you were at session 103 yesterday, you found out about how to write an SDEF, how to express your scripting implementation or your scripting interface in the form of an SDEF, which then presents to your user an AppleScript dictionary.
We're also presuming that you've identified one or more real-world test cases that are going to prove not only the functionality of your scripting, but its usefulness to your end users. This could be some kind of feature extension, something that your program is capable of doing, but only after a certain number of steps have been performed. You want to streamline this, allow your users to do it all in one step and use scriptability as a way to do it. You're going to implement that feature extension in an Apple script.
and that Apple Script needs to solve a real-world problem that your users actually have. Scripting is interesting as an academic exercise, but it's only going to be a selling point for you if it solves your end-user's problems. And as John just said, if you haven't already downloaded the sample code, you may not be able to get it during the session.
But you can follow along and you'll have ample opportunity to download it later. And if you want to come and discuss it with us in our lab this afternoon, you can do that as well. This is where you're going to need to go to get documentation and sample code. In addition to the sample code for this session, there's lots of other interesting things here. Documentation that will help you design your scripting interface and implement the code that supports it.
The procedures that we're going to cover today are equally applicable to both Cocoa and Carbon programs. You can use Cocoa scripting in an otherwise pure Carbon program. There is a few things that you need to do special to hook up Cocoa scripting in your main.c file. And if you are in that situation and you want to do that, come to the lab and we'll show you how to do it. It's not hard, it's just a little arcane for presentation here.
Here's what we're going to be talking about today. We're going to talk about logging. Logging has proven to be invaluable to me as I've developed scriptable applications. Cocoa scripting calls into your objects many times in many places. The patterns are absolutely predictable once you get to know them, but you have to get to know them, and logging can help you do that. We're going to talk about element accessors. Your application has objects in it.
The scripters need to be able to get at those objects, and element accessors are what they use to do that. We're going to talk about property accessors. Two objects of the same class differ from each other in the values of their various properties. This is what makes scripting your objects interesting, the fact that your users can tell them apart by looking at their properties and seeing what they say. A typical property that's going to tell most objects apart is its name.
We're also going to talk about implementing commands, and there's two different ways to do that. In Cocoa scripting your commands can be dispatched to an object which knows what to do with that command, or if the command inherently acts on a set of objects, the command can be dispatched directly to a command handler that collects the relevant objects and acts on all of them at once. We'll be talking about both of those.
We'll also be talking about automated regression testing. As you implement your scripting interface, you're going to build up a collection of small unit test scripts. You're going to want to keep those. You're going to want to record the results, and you're going to want to run them over and over again to make sure that you haven't broken anything.
And if you can run your regression tests over and over again and get the same results each time, you know that the changes you've made to your program haven't hurt your scripting. And this can be valuable whether the changes you've made had anything to do with scripting or not. that.
We're also going to get back to testing our real-world test cases. This is the value of scriptability. If your end users identify a real-world objective, some mission-critical activity that they need to perform over and over and over again, and they use scriptability and they use your application to implement that, then they are your customer for life. As long as they keep using that automated workflow, as long as that's part of their job, then your application is going to be part of their job. And that's one of the greatest benefits to you.
This is the macro that I use for logging. It's very simple. Based on a simple if statement, this macro either expands into an NSLog statement or it doesn't. Once that switch is turned off, all of this disappears from your code, it adds no overhead at all, but when you turn that switch on, if you change that to if1 instead of if0, then you can watch what Cocoa scripting does as it calls the various accessors in your program.
Your logging should be ubiquitous. You should put it absolutely everywhere. In every accessor, every element accessor, every property accessor, every event handler. And as the event handler becomes complex and starts to do various processing based on its input, you'll want to log the significant events in that as well.
Element accessors are what allow scripters to get at the objects that live inside your application. And there's different kinds. There's one very simple--each application is going to have one very simple accessor for the entire collection of objects. We're going to be talking about Sketch, which is a simple graphics program, and one of the objects that it has a collection of is rectangles. So an accessor that would fetch all the rectangles at once would simply be called rectangles. That's sometimes not terribly efficient.
In Sketch, a document might have ten rectangles in it. That's not too many. In another application, a database application for instance, you might have millions of records. Returning the entire collection all at once isn't terribly practical. So there's other ways to handle that. There's accelerated accessors that allow you to get at your objects by name, by index, or by unique ID. You shouldn't simply grab the entire collection and look for things by name yourself.
Cocoa Scripting will do that for you. You shouldn't have to write the entire collection. You can write that code because someone else has already written it and you don't have to maintain it. But if you've got a database application and you can get at one record out of a million and you can do it really fast by using its unique ID, then you should definitely have an accelerated accessor for that. Your scripters will be much happier with the performance of their scripts if you provide this kind of support.
Element accessors also include setter-type functions. If you're familiar with AppleScript, you know that you can make new objects. You can also delete existing objects and you can replace objects in place. These accessors are what support that. You'll notice that there's two kinds of insert accessors: one that inserts at a particular index, one that does not.
This depends upon the nature of your application. In some applications the order of the objects matters. For instance, the order of windows in an application is always front to back. In other applications, order may not be particularly important. If you do have a big random access database, the index of a particular record isn't as important. So, creating a new record, you might just want to create it at the end. So use whichever of these accessors is appropriate for the kind of object that the scripter would be creating. That's a decision that you make based on your knowledge of your application.
So now we're going to look at logging and element accessors on demo 2. This is the Sketch project which is available in the developer folder on any machine where you've installed the developer tools. It's an example application, simple graphics, it's already scriptable and we're going to add scripting capabilities to it.
[Transcript missing]
is going to be called "Rectangles" and so we'll search for a whole word and see if we can find exactly that. And we have.
[Transcript missing]
And we're going to find that there's some more, as a matter of fact. That here is the insert accessor. When a new rectangle is created, it's inserted in the collection by calling this. There's a remove accessor, so when a rectangle is deleted via Scriptability, this code will be called and the rectangle will go away.
This is the replace accessor, same thing that replaces an existing rectangle in place. Back here at the beginning, I've got a header file and in my header file, is where I have my macros defined. And I'm going to take this switch and I'm going to change it to a 1 so that my macros will expand while we're doing this exercise. You would want to make sure that when you check this into any code repository you always have the switch off so that none of your fellow programmers get any surprises.
So those are element accessors and logging. That's what they look like. So let's come back here and build this. I usually like to run from the debugger because then if I find something interesting I can just immediately add a breakpoint and try it again and see what's really happening. And I like to open the console log because that allows me to watch my logging messages as they go by.
So we'll run this. And here's Sketch. And since we're interested in rectangles, we'll draw a few of them. And we'll set that over to the side. And I've already got the script editor running. And we want to drive that simple accessor. We want to make it fetch all the rectangles and see what happens.
So, I'm going to select Event Log down here at the bottom because then that will show me the results of each event as they go by. I'm going to tell Document 1 that I want to get every rectangle. And I usually put a healthy timeout in my test scripts because if I am debugging and I hit a breakpoint, I would rather that the script not fail because of a timeout while I'm poking around looking at variables.
So we'll run that. And as you can see, I've got three rectangles. They're untitled at this point. They don't have any names. And you can also see down here in the log, it's hit my logging message and it shows up here. So now I know when I execute a "get every rectangle" line of Apple script, it's going to call my rectangles method inside my object. And if we can go back to the slides, please.
Logging and Element Accessors No new code there because Sketch is already scriptable and already handles a collection of rectangles. Now we're going to talk about Property Accessors. The things that make one particular instance of a class different from another are its properties. For a rectangle it's going to be height and width, fill color, stroke color, other things like that.
Properties can be of two types. They can be read-only or read-write. Read-only properties are going to be things that you can't change. For example, the physical dimensions of a display. No matter how much you'd like to write a line of Apple script that says, "Give me a 30-inch cinema display," you can't do that.
So that property is going to be read-only. However, the pixel resolution of a given display at any point in time can be changed. So that would be a read-write property. And you're going to want to implement the properties in your application according to your design as read-only or read-write as appropriate.
The property accessors, again, they leverage the key value coding, which is an important technology. provides for consistency of access and that's what Apple Scripts all about. We want consistency of access to your application and across applications. So the fact that Cocoa scripting leverages key value coding is highly appropriate. It means that all Cocoa scripting programs are working the same way when they go after your objects. And that means that your implementation and everybody else's scripting implementation are going to be more consistent. And consistency across applications is greatly desired by scripters.
Cocoa Bindings also leverages key value coding. It's an easy way to keep your view and your model in sync without having to write a lot of controller code. And less code is less bugs. So you should use things like Cocoa Bindings and leverage KVC even further. So now we're going to go back to demo two and look at property accessors.
We could go in and modify existing files, and you can do that certainly with your project. But if you work in a large team and more than one person might be touching the same files, more than one person might be modifying the same object, you might find it convenient to use a Objective C categories instead, and that's what I've done here. And the categories each live in a separate file. So now it's not necessary.
"It's up to you to modify the existing files. If someone else happens to be working on the project at the same time as me, I can work on scripting, he can do what he's doing, and we won't bother each other. So I'm going to add two files.
[Transcript missing]
Here's the code. If you were in 103 yesterday, you'll know that orientation is one of three enumerated values: landscape, portrait, and square. We're going to support all three of those. Here's the code that does it.
When we're getting the orientation it's really pretty easy. Something might be a square, we're not sure. So we're going to set the initial value to square. Then we're going to compare the height and width and if it's taller then that's portrait orientation and if it's wider that's landscape orientation and then we're going to return that value.
You notice that we return the raw 4 byte codes and we say that this routine returns an unsigned value. Cocoa scripting will take care of turning that into an NSNumber for you and sending it back through AppleScript and AppleScript will take care of turning it back into a human readable representation of that enumerated value and your scripting users will see a very nice portrait landscape square as the possible orientations.
The setter is a little bit more complex. First thing we do is we look at the incoming orientation to make sure that it's one of the ones that we support. And if we don't, if it's not one of the ones that we support, down here at the bottom of this switch statement, We set an error because we know that we can't set something to orientation 9. That's not going to work.
So if it's not one of the valid inputs, we reject it by generating an error, and we'll show you that in a second. Otherwise, if it's one of the orientations that we do know about, we handle it in various ways. If someone requests that the orientation be set to square, well, that's not really appropriate unless it already is a square. So if the existing rectangle is not already square, we're going to generate an error for that as well.
The one case where something good happens is if we're changing from portrait to landscape or from landscape to portrait. And we make that test and verify that that is in fact what we're doing. And if so, we simply set the width to the height and the height to the width and we reverse the orientation.
Quit the copy that we had running and build and run a new one. Okay, and again, Sketch comes up. We draw a couple of rectangles. Try hard to draw a square here. See how well I did? Not too bad. And now that's a square. We'll go back to the script editor. Close that script.
[Transcript missing]
That's a little easier to read. Here's our test for orientation. We're going to look at what the orientation is, which will cause us to call the "get" accessor, and then we're going to set it to what it wasn't, which will cause us to call the "set" accessor. And if everything's working right, we should see the logging happening down here while we do it.
So, as you can see, these two rectangles have changed their orientation. And down here in the log, we can see that the Rectangles accessor is being called, the Orientation accessor is being called, and the Set Orientation accessor is also being called. So everything we expect to have happen, happened.
And now we're learning something about the order in which these things occur, and we're learning how to map what happens in the log back to what happens in the script. And you can see we even generated an error. We tried to set the orientation of rectangle 1 to 5. That's obviously not going to work. And sure enough, we got an error when we tried to do that. And I'll show you exactly how we saw that error.
We set a variable to an error value and then here at the bottom We went and found the "reply" Apple event, which is hanging around ready for your use. We went and found that by asking the Apple Event Manager for its single shared instance, getting the current "reply" Apple event from that, creating a descriptor with our error number in it, and then sticking that in the "reply" event with the key error number. What happens is Cocoa scripting takes that and sends it back to the descriptor as the result of this operation. Since the event has an error in it, the script editor will report that error.
Okay, if we can go back to the slides please. Okay, so that's Property Accessors. Now commands. Apple Script has a set of, depending upon how you count them, 13 to 15 built-in verbs. And we encourage people "To use those to the greatest extent possible. Most of the things you need to do are going to be accomplished by those verbs. As a matter of fact, Sketch is considered to be a fully scriptable application and it does not add any commands at all before today." Get and Set.
As we just saw, you can use Get and Set of a property like Orientation to actually change the shape of an object. You don't need a Change Shape verb. You can just change a property which would cause it to change its shape. That's the way we like you to do things. We like you to use Get and Set as much as possible. If you've got some kind of persistent state that you want to start and stop, you don't need a verb to do it. You just need a Boolean that you can turn on and off.
But, that being said, every application does something that no other application does. Every application does something that isn't in that set of 13 verbs. In this case, since we're talking about a graphics program, Rotate is one of them. Where did we find Rotate? We found it in the menu bar. Rotate is a menu command inside of Sketch. It's something that Sketch can do, but the scripting up until now didn't have any name for it.
Alignment is the same thing. Alignment is something that's in the menu bar of Sketch and now we're going to make it scriptable as well. The difference here is Rotate acts on single objects. Even if you want Rotate to act on a collection of objects, it can act on each of them singly.
The objects themselves don't need to know anything else. They don't need to know anything else other than that they're supposed to Rotate and by how much. So that's an excellent candidate for object-first dispatching. All we really need to do is tell each and every object that we care about to go ahead and Rotate.
Alignment is a different deal. The alignment has to be to something. In the case of Sketch, the first element in the array, which turns out to be the last graphic that you created, is the match point. The match point is the line to that. But you can't send an align command to a collection. You have to send the align command somewhere. We do it with verb-first dispatching. We send the align command to a verb handler. The verb handler finds the collection of objects that needs to be aligned and performs the operation collectively on all of them at once.
So, in object-first dispatching you're going to want to have an entry in your SDEF that looks something along the lines of this. And if you were at 103 yesterday you saw pretty much exactly this. The human readable name of this command is going to be "rotate" and that's highlighted up there.
The kind of object that it's going to create is an NS script command, which is the generic handler for all incoming verbs. This is actually the default. We could have just as easily left it out of this entry, but I put it up there to show you a distinction between this and how the verb-first dispatching works, which we'll see in a moment.
There's also going to need to be a parameter, "rotate acts on a collection of objects," but we also need to tell them how much we want them to rotate. So we also support a parameter, which in this case is a number, the number of degrees through which to rotate the object.
Since this is object-first dispatching, we're going to need to say which objects know how to rotate. As it turns out, all Sketch graphics know how to rotate. Back in the definition of the GraphicsObject, which is the base class for all of the various shapes in Sketch, we're going to say that GraphicsObjects respond to the Rotate command. They do so by calling the "Rotate:" method on the object.
Then, of course, the object itself has to have a "Rotate:" method. You'll notice that the handler for an object-first dispatch receives the NSScript command as its input. That's because you'll need to be able to probe the NSScript command to find out what the parameters are and other information about what it is you're supposed to do. You only get one input, but it's one very powerful input that has a lot of information in it. Now we're going to go back to Demo 2 and look at object-first dispatching.
So again, I'm going to add a couple of files. To my project, rectangle rotate .h and .m. And as you can see, what we're going to do is we're going to add a single method to the SKT rectangle object that already exists. We're going to do it by adding a category, and the method that we're going to add is called "rotate:" and its sole input is an NSScript command. And here's the implementation. We have our same definition for orientations, which I'm not sure we're going to use. And this is the implementation.
These two lines show you why you need that command as an input. The command has something called evaluated arguments, and these are very nice. Cocoa scripting has already gone through the incoming arguments and done as much resolution on them as it possibly can. If one of your incoming arguments is an object, is a scriptable Cocoa object, it will have already been resolved. So instead of getting an Apple descriptor of which object you should be acting on, Cocoa scripting will resolve that and actually give you the object itself. It saves a lot of time, it saves a lot of messing around.
Whenever Cocoa scripting can evaluate an incoming object for you, it will. Since our incoming object is just a number, there isn't much for Cocoa scripting to do. We get the evaluated arguments and then from inside the evaluated arguments we get the "by degrees" "The first step is to create a scripting object, which turns out to be an NSNumber. Before we go any farther, we make absolutely sure that it is an NSNumber, and if so, we pull the value out." And then we do some algebra.
I know you thought you'd never use your algebra again, but you will. And we're going to make some restrictions here. We're only going to allow rotation by even multiples of 90 degrees. So this calculation here determines whether or not we're going to have an even number of 90 degree turns.
And this calculation here normalizes that to some number between 0 and 4. Well, if you rotate something an even number of turns, it doesn't actually change. If you rotate a rectangle an even number of turns, it doesn't actually change shape. So we're just going to skip those and not do anything.
But if we're rotating an odd number of turns, then there's some work to do. And what this calculation here does effectively The first thing we'll talk about is the "Compute Rectangle" function. It computes the original center of the rectangle and puts it back where it started originally based on its center. As we rotate the rectangle, it doesn't rotate around one of its corners, it rotates around its own center.
Again, there's the possibility that we could get some data in that's not going to be meaningful since we're only allowing 90 degree turns. Any number other than 90, 50, 60, 70 isn't going to work. If the original input value was not an NSNumber at all, if you had happened to pick an enumerated value that wasn't applicable, that would also generate an error.
Returning the error is a little bit simpler here. Since we have access to the command itself, all we actually need to do is tell it to set the error number. There's a little bit less work to do because you've already got the command; it's one of your inputs. So that's a little bit easier. We'll sketch again. And we'll draw our rectangles again. And we'll move on to our next test.
So as you can see in this test, we're going to get the orientation of every rectangle. We've already implemented that, so we expect that to work. Then we're going to look at each rectangle. We're going to fetch the array, the entire collection, and keep that in an AppleScript variable called "x". And then we're going to walk through that array and tell each of the objects in it to rotate by 90 degrees. Then we're going to tell one of them to rotate by 80 degrees, which should cause an error. And then we're going to get the orientation again, and we should see all the landscapes change to portrait, and all the portraits change to landscape. That's the theory.
And that's pretty much what we got. As you can see, The orientation started out landscape, landscape, portrait, and now it's portrait, portrait, landscape. That's exactly what we expected. And the one erroneous input value did indeed return an error. So our error reporting is working, our rotating is working, and our logging is working. We can see that the rotate command did in fact get called. And Cocoa scripting, since this is a development software, will log some of its activities as well. So you can see what Cocoa scripting is doing interleaved with what you're doing and that can be very informative.
So, back to the slides please. So that's an object-first dispatching command. There's another kind of command, verb-first dispatching, and that's going to have a slightly different dictionary entry. In this case the verb is going to be called "align" so the human readable term "align" is highlighted there. But the Cocoa class that we're going to use to implement this is no longer NSScriptCommand, it's going to be SKTAlignCommand.
SKTAlignCommand is going to inherit from NSScriptCommand, but it's going to add some additional functionality that allows this to be a verb-first dispatch. We're going to need another parameter, we need an edge to which to align everything. If you saw session 103 you know that that's also implemented as an enumeration and gives us six different choices of how to align.
When we do our support for our verb-first dispatching, we're going to create a subclass of NSScriptCommand and we're going to override its "performDefaultImplementation" method. And so here's the code to do that. We're not going to add any instance variables to this at all. We're just going to override one method. And so if we can go back to demo 2, we'll do some verb-first dispatching.
One more time. We will be adding two files to our project. You can see here pretty much exactly what you saw on the slide. We're creating a subclass of NSScriptCommand and we're going to define one method and that's performDefaultImplementation. So here you can see we've got six different choices of what it is you can do.
And so as we move down here we're going to have six different ways of handling it. But there's some important stuff that we have to do first. In verb-first dispatching, Cocoa scripting doesn't help you out quite so much. When you get the direct parameter, it may not be as fully evaluated as it would normally be in an object-first dispatching. Obviously, object-first dispatching, the direct parameter's been fully reconciled by Cocoa scripting because the direct parameter is the object that actually receives the message. In verb-first dispatching it's not so simple.
So you're going to need to get the direct parameter yourself. When you're doing verb-first dispatching, you don't get any inputs. But you don't need them because the verb-first dispatching handler is itself the NS script command. So if you need anything from NS script command, you get it from yourself. So anything in object-first dispatching that would refer to the command, in verb-first dispatching you refer to self instead.
So you grab your direct parameter and now we're going to look at it. It can be any one of, and here's an instance of, I'm logging something about what's going on. I'm not just logging the fact that we arrived here. I'm logging what my direct parameter looks like because if I have any trouble handling my direct parameter, it might be because I'm seeing a new case that hadn't occurred before. So if I log what the direct parameter is, it's going to help me find some bugs later on.
When I'm done, I know that what I'm going to end up with is an array of graphics objects. So I create an empty array here to collect them as I find them. Then there's a couple of possibilities. If the direct parameter is itself an array, then I'm going to want to iterate through that array and find out what's inside of it. And so I do that. I create an enumerator for that array and I start to walk through the array itself.
Now what turns out to be inside of this array is NS Script Object Specifiers. What these are is unresolved pointers to your objects. Cocoa scripting hasn't taken a look at these yet to try to figure out which objects they really represent. You're receiving an array of just the pointers themselves. But Cocoa scripting is still your friend.
In one line we can ask Cocoa scripting to tell us, "Okay, resolve this thing and give me the object that it really points to." It could occur that that itself is an array, because there are such things as range specifiers. Every rectangle from 3 to 5. That's actually going to give you back an array of three different rectangles. So you're going to want to notice if that's happening, and if that's the case, you're going to want to add that entire array to the end of your collection as you're building it up.
And if it's just a single object, you'll add that to the end of your array. So, by the time we get to here, we actually know the collection of objects that we're going to be working on. And that's really the purpose of verb-first dispatching, is we're going to act on a bunch of objects all at once. And it's up to us, we have to do a little bit of coding to find out what those objects are.
Now we're pretty much back to where we were before. We know the collection that we're going to act on, now we just need to know what it is we're going to do. So we get the evaluated arguments, and we get the edge arguments. And we get the argument from out of that. And that should be an NSNumber with a long value that corresponds to one of those 4 byte codes that we defined.
And there's different, we're going to switch on that value and do one of six different things. I'm not going to go through all six of them. But again, you get to use your algebra. We're going to... I've already done this here. I've grabbed the bounds of the first element of the array.
That's going to be the master. That's the fixed point to which all the others are going to align. Then when I get down here, I'm going to iterate through the entire collection and align them all to the original. The smart students have already figured out I could have started with the second member of the array.
We do a little bit of algebra here and we align the objects to the first object in the collection. As you can see, we do that one of six different ways, depending upon which edge it is they've asked us to align to. Let's build that. And this time we're going to go nuts. We're going to make some rectangles. We're going to make some circles, ovals. We're going to add some lines. And then we're going to run our test script to align these things.
[Transcript missing]
Run that. And sure enough they align part way. And then boom, they align the rest of the way. So now all of these objects are aligned by both their vertical and horizontal centers. And again, you can see down here in the log, we've got lots of information, including some that Sketch itself logs about the size and shapes of various objects. So again, you can see your scriptability code running interleaved with the Cocoa scripting logging that goes on in a developer version, in a developer install of the OS, and also any logging that the application might already be doing for its own purposes.
Again, like I said, this can be very educational. The patterns by which Cocoa scripting calls your accessors are very predictable, but you need to learn what they are. And then when you see something unpredictable occur, that's your hint that maybe something's going wrong. and you need to step more carefully and more closely through your code and make sure it's doing what you want it to do. If we can go back to the slides please. So that's verb-first dispatching. You should use verb-first dispatching where it's appropriate, but you should always try to use object-first dispatching if you can.
There are other aspects of handling commands that may or may not apply to your particular application. If you've got some long-running process, if your application is threaded, if you have to wait for things to come in across a network, then you might need to suspend an Apple event while it's in the process of being handled and resume it later on when you've finally got the answer. There's a relatively easy way to do that. In an object-first dispatch, you can just say command suspend execution.
And in a verb-first, you say self suspend execution. At some point later, your program will receive a callback from some long-running function to say I'm done. And at that point, you can simply resume the command and pass back whatever response you would have passed back had you handled it synchronously. If you need to do this kind of thing, come to the lab. We can show you how to do it. It's not hard, but it can be very, very important for certain kinds of applications.
As we've been going along here, I've been showing you little test scripts. When you write these test scripts, you're going to want to cover your code as much as you possibly can. When you're writing a test script to access elements, you want to get those elements, and you want to get them as many different ways as you can. You'll want to get every element of a certain kind. That'll call your simple accessor. You'll also want to get elements by name, by ID, and by index. If you've provided the accelerated accessors, they'll get called. If you haven't, Cocoa scripting will do the necessary look-ups for you.
You're also going to want to call your setting accessors. You're going to want to make sure that you make a new object of each kind that you support. You're going to want to make sure that you delete an object of each kind you support. You're going to want to delete from the beginning, the end, and the middle of your collection. Make sure that all works. You're going to want to do a replace in place if that's something that you support. Not every application does.
For properties, the same thing. You're going to want to get all the properties all at once by using "getProperties" which is something Cocoa scripting, again, is your friend. "getProperties" is not something that you define in your code, but if an Apple script comes in that says "getProperties" Cocoa scripting will call your object over and over again, once for each property accessor, make a record out of that and pass it back to the user. But you want to make sure that that works. You want to make sure that Cocoa scripting is calling the right routines.
You'll also want to get at each of the properties by their name. So you're going to want to get the height of a rectangle, you're going to want to get the width, you're going to want to get the fill color. You want to exercise each one of those things. Make sure that you touch each of the leaves in the tree.
You're going to want to write test scripts that set your values, but you're going to want them to be repeatable. So you're going to want to do a "get set reset" kind of pattern where you leave things the way you found them. So that this script can be run over and over and over again and give the same results. You're going to want to take a copy of the event log and keep that so that you can compare it to the results you got the last time.
So that you can make sure that you haven't regressed anything. This can be very valuable whether you're working on scripting or not. If you're making any kind of changes to your model objects, you're going to want to run these tests over again. Make sure that you haven't damaged the model objects in any such way that these tests don't pass any longer.
You're going to want to drive your program to the edges. Like I said, delete the first object in the collection, delete the last. Well you're also going to want to set your values to the entire range that they support. And you're also going to want to generate error cases. You're going to want to set your values to things that they don't support.
And make sure that something is in the right place. Make sure that something sensible happens. That depending upon your design, you may want to fail silently. You may want to provide an error message. It's up to you what you think your scripting users are going to expect you to do.
For the commands, you're going to want to exercise them thoroughly as well. For their parameters, you're going to want to pass them parameters, but you're also going to want to skip passing them parameters and see what happens when they take their defaults. Make sure that that works the way you expected it to.
Again, you're going to want to drive these commands all the way up to their edge cases, and then you're going to want to drive them past the edge cases and create errors. Make sure that they get handled in a reasonable fashion. Record the results, hang on to them, and the next time you make changes, run the scripts again and make sure you get the same results. or if you get the differences, you get the differences that you wanted.
Then the final proof here that you've really created something valuable is to test your use cases. You need to have at least one and hopefully several real-world use cases that your application's users are going to care about. Things that people have called you up and said, gee, it would be really easy if I had a menu command to do this. And you realize, eh, only one guy wants that. I'm not going to make it a menu command, but I am going to make it accessible through scriptability.
And once I've done that, I know I've got a customer out there that's depending upon that working just as much as if it were a menu command. He's going to consider my application to be broken if that script breaks just as much as some other user is going to consider my application to be broken if they pull down the print menu and it won't print.
Once you've added a feature through scriptability, you have to preserve that feature just like any other feature of your program. So you're going to want to test your real-world cases over and over again. Make sure they're repeatable. Make sure that they set themselves back the way they were when they started. And when you run them over and over again, you want to get the same results over and over again.
So, to review, here's what we talked about today. We talked about logging. I think it's very important. It's hard to know what Cocoa scripting is asking of you unless you log your activities when you respond to Cocoa scripting. We talked about element accessors. This is how the scripter gets at the objects inside your application. Element accessors are what make AppleScript object-oriented. Property accessors are important because one object is pretty much the same as another except for its size, shape, color, etc.
We've talked about commands and the two different kinds: object-first dispatching and verb-first dispatching. You will find instances of both in your program and you'll need to learn to support both. We've talked about the importance of automated regression testing, how it can help you make sure your scriptability works. It can also help you make sure that other parts of your program haven't regressed when you're making changes that have nothing whatsoever to do with scriptability.
And the real payoff: testing those real-world use cases that prove that your scriptability is of real value to your end users. Once you're locked in to an automated workflow at your end user's site, they will never get rid of your program. They may augment your application with others that do things differently, but as long as you're part of an automated workflow, you're part of their collection of applications forever.
If you want to get documentation or the sample code that I used today, you can go to this site later today or when you get home. There's a wealth of information there about this session and all the others. You'll be going back to this site over and over again, I'm sure.
We have a lab this afternoon at 2 o'clock. I'm not sure exactly physically where it is, but it's called the Apple Script Lab. And we'll be there to help you bring your application, bring your source code, and we'll try to help you make it scriptable at least a little bit right here at the conference this week.
And then when you go home next week you can finish the job. Once you get started it's not hard. Once you've got a few things working it's pretty easy to make more things work the same way. So come to the lab, we'll get you started, and in a short period of time you'll have a scriptable application.
There are some people that you may want to contact if you have questions after we leave here this week. Sal Segoyans, our marketing manager, many of you probably already met him. He can tell you almost everything you need to know. And if you need more technical information, if you need more direct access to us, you can contact my boss, Todd Fernandez. He's the manager of the Apple Script and Automator group. And there's a mailing list. You can send your questions to the mailing list. I see several people here today that are very active on the mailing list that will help you out.