Application Frameworks • 1:09:33
This session illustrates how best to implement scriptability using AppleScript in your Carbon and Cocoa applications. Learn about best practices, guidelines, tips, and tricks to create a first-class scriptable application. Achieving this will allow your product to be part of automated workflows widely used in design and publishing, video editing, and other major industries.
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. Thank you for coming. My name is John Comiskey. I'm an engineer in the Apple Script Group. I want to talk to you today about three things. We're going to talk about designing a dictionary for a newer and existing application. We're going to spend most of our time on that. We're also going to talk about implementing scriptable objects to bring that design to reality. We're going to focus mostly on Cocoa scripting. That's what I do most of the time myself. We're going to talk a little bit about Carbon at the end.
And we're also going to talk about developing tests for your scriptability as you go along. So you make sure that you don't backtrack or have any regressions. Designing a dictionary is the most important part of the entire operation. Once you've done that, writing the code is not as hard.
The thing that we really want to encourage you to do is to use an object model in your dictionary. AppleScript functions best when applications have an object model in their dictionary. And why do you want to use an object model in your dictionary? Well, I think you already know the reasons for that. You use object-oriented programming languages yourself. You know the benefits of them. You wouldn't go back to the old ways of doing things anyway.
What we want you to do is to let scripters have all those same advantages. We want them to be able to reuse your objects. You've put time and effort into those objects. If you give the scripter the opportunity to leverage that, that adds value, helps you sell your product, helps you make money, which is why you're in this business.
Scriptable applications are interoperable. I like to say two plus two is five. Two scriptable applications can do things that neither one of them can do alone, neither one of them can do while being worked by hand. If you've got two or more scriptable applications, they can be combined together into a super application that does something that no one of them can do alone.
Scriptability, an object model for scriptability offers your user's flexibility. When I talk to people about making their applications scriptable, they often say, "Well, why would anybody want to do that?" And my answer is, "I don't know why and you don't know why either, but your customer knows. He knows what he wants to do. He knows what problem he needs solved.
If you give him powerful, recombinable, reusable objects, he can build a solution for himself. And you might be surprised sometimes at what your own customers do with your products. They can also create new functionality that's missing. Sooner or later, you have to ship your product and you always have a cool new feature that's almost done, but it isn't ready to go and so you have to leave it out.
That's always a disappointment, but it's a reality of being in business. AppleScript is the feature that you forgot. Your customers can take AppleScript and use it to create features that are missing from your application. If you went to the AppleScript Studio session yesterday, you saw a demo of a-- Yes. --application of your application. And you saw a demo of your application.
And you saw We were able to create a feature that's really nice to have, but it's missing from Xcode. Maybe someday it'll be a native feature of Xcode, but in the meanwhile, you can use AppleScript to write a plugin, plug it into Xcode, and get a cool new feature that wasn't there.
There are several reasons why you want to use an object-oriented language, an object model in your dictionary. And the biggest one is that AppleScript is an object-oriented language itself. It's got all the cool things that are in all the other object-oriented languages: inheritance, data hiding, all of those kinds of things.
But you only get those benefits if you use an object model. AppleScript is also an English-like language. You write simple declarative sentences with a verb acting on a direct object modified by one or more clauses. But you only get that nice English-like flow if you use an object model in your dictionary.
When you're writing an AppleScript, you always use tell blocks, at least to tell applications. But you can also use tell blocks to shorten the Apple events that you have to write. If you've got a complex or deep containment hierarchy, you can nest tell blocks, and then the Apple events that you write inside those tell blocks are shorter and smaller, easier to read, easier to understand. But this only works if you've got a containment hierarchy in your object model.
And as I mentioned before, applications can interoperate. There's synergy between applications. They already share data. What we're talking about is inviting them into a workflow together. You can create a workflow across several different programs from one vendor or several vendors that solve your problem, your end user's problem.
And once you've got a workflow like that, it can be wrapped up in an AppleScript Studio application and become one of these super applications that I mentioned. You could do something, some kind of post-production job that has to be done over and over and over again. It doesn't require a lot of artistic expertise, but it needs to be done, and it needs to be done a lot. That can be turned into an automated workflow.
While we're talking about this today, we're going to be developing some regression tests for your own software. Those can be put together into a workflow so that a tester can just fire up a Studio application, run it, come back later and find out what the results of the tests were. But none of this works if you don't have a software that's able to do that. works unless you use an object model in your dictionary.
So what is an object model? It's a lot easier to say what it's not. This is the implementation of folder actions from system nine. It's not an object model. It's five different verbs. And you can see pretty quickly what goes wrong here. Two of these verbs have remove and add in them.
You could have used make and delete, which are already standard verbs in the standard suite. You can also see that these events have things that ought to be objects just embedded right into the verb. It takes away the flexibility and the recombination that an end user can do when the verb and the object are sealed together into a single event like this. And you'll also see that a lot of these end with a preposition. And you all know you should never end a sentence with a preposition. Again, this takes away flexibility.
Prepositional phrases in Apple script should be parameters and they should usually be optional parameters. You shouldn't force the user to describe prepositional phrases whether he needs them or not. So this is a good example of a bad example. In Panther, we have done something about this. All the old verbs for folder actions are still there and they still work and we actually still use them ourselves internally.
But we've also provided an object model for folder actions. The first thing you notice is the rectangle got smaller. You can express all the same operations in a smaller dictionary if it's an object model. These two objects can do all the things that those five verbs did plus a lot more. You get more for your money with an object model.
When you start to design your object model, you should plan for a full implementation. Look into the future. We know that you've got to ship something and you've got a limited amount of time to do that. But your life's going to be better and easier a year from now if you've planned an object model with room to grow.
Map out as big an object hierarchy as you think is meaningful for your application. Decide now what things are going to be called in the future so that we don't run into the problem of all the good names have been taken. And define as much functionality as possible.
Everything that someone can do by hand with your program is a very good candidate for scriptability. Some high-powered operations that might not be suitable to be done by hand might also be targets for scriptability as well. Your scripting implementation may in the end actually be more powerful than your GUI presentation.
Now you've planned for the future. You've planned this huge object model, and now you don't have time to implement it all. You're going to have to scale back for your first release and only do part of it. When you decide what you are going to do, it's important to include a minimal functional set, enough objects and enough verbs to do at least one interesting operation, something that some customer's been bugging you about. If you pick a set of objects that allows you to perform that function, you know you've got a minimal functional set.
There's sometimes things in your application, it's hard to think how you might use them through scriptability. There's complex interactions. One example in network setup, you've got an IP address and you've got a router address, and they're not independent of each other. If you change one, you might have to change the other, and you have to do it in a synchronous fashion. It's not easy to see how you're going to do that with AppleScript. Maybe the solution for release one is to just leave those properties running.
If you make that read-only, chances are you're using a DHCP server anyway. You need to know what the IP address is, but you don't need to change it. If you make that read-only, the information is available, the value is there for your user, and you've skipped a complex problem until later when you have more time to think about it.
Even if you have a largely read-only implementation in your first release, you should always provide at least one powerful read-write function that can actually do something. In the instance of network setup, that would be the location. If a user can switch his location through scriptability, then he really has the power he needs to do whatever he wants.
He's got to sit down in advance, create a number of configurations, organize them into locations that are meaningful for him. But once he's done that, all he needs to do is change the location as he moves from city to city, or from work to home, or into a conference room, or anywhere else he might go.
The standard suite has a number of verbs that cover almost everything you're ever going to want to do. Get and set are used on properties of your objects. Exists, count, make, and delete are pretty much implemented for you by Cocoa scripting. There's not a lot that you have to do.
If you create scriptable objects according to the pattern that Cocoa scripting expects, these four verbs are just going to work. Then in the standard suite, all that's left is six other verbs that act on objects, things like print and save, where you're going to really have to write some code yourself, and quit to quit the entire application. We think the 13 verbs in the standard suite will cover most of what you're going to want to do, and you should always try to use them first before you create a verb of your own.
If you use an object model along with the standard suite, you get this multiplicative effect. If your object model has, say, three objects with an average of ten properties each, and they can all be get and set, that's 60 operations you can perform right there. If those three objects also respond to the ten other verbs that act on objects, and remember, four of those ten are done for you, that's another 30 operations that your application can perform, plus one more, quit. So that's 91 things that your application can do, and you've hardly written any code at all at this point.
One thing we tell people not to do is to create a bunch of verbs. You saw what happened with folder actions. We created a bunch of verbs. It worked. It covered the problem that we needed solved, but it was kind of a dead end. It couldn't do anything more than that. So we discouraged the creating of a lot of verbs. The temptation is there to do that, to just take an existing API and expose it as a lot of AppleScript verbs, but it doesn't give you the power that an object model does.
[Transcript missing]
If you do need a verb, go ahead and create it. First, you should look at the standard verbs and make sure there's nothing there that you can use. But once you've decided that that's the case, yeah, it's okay to go ahead and make your own verbs. They should be simple operations that your application performs on its own objects.
And the names of these verbs are going to come from probably buttons and menu items that you already have in your program. Now, that doesn't mean you should take every button and every menu item and turn it into a verb. But when you're trying to figure out what to call a verb, you should call it the same thing you call it in your UI.
You should try to create verbs that can be reused on multiple objects. That's what the standard suite is all about. It's 13 verbs that we think you can use everywhere. You should try to do the same thing when you create verbs of your own. If you create a verb that only acts on one object or one kind of object, you're slipping back into that procedural verb problem that we talked about.
And one thing that you want, and you want verbs that can be reused in your application, a good place to find them is to reuse verbs from other applications. Look at the scriptable apps from Apple and see what's there that you might already be able to use. If you copy a verb or any other term from another application, you should copy the human readable term and the four byte code, both.
That's going to make your program interoperate better with other programs. Your dictionary is scoped to your application and you won't end up in collisions with other applications, but you also won't end up in synergy with them either. And you should definitely choose reusable parameters. There's a lot of things in the standard dictionary, little prepositional phrases that modify the verbs, that are going to be of use to you anywhere.
There's one in there for naming the target file that you want to write to. That can be reused. There's others in there for--. Or ask me if I should do something before I quit. That might be useful to you. Reuse those parameters too and recombine them with your verbs in different ways.
[Transcript missing]
Definitely avoid just exposing your raw internal API. Don't just take all of the commands, all of the routines in your API and just turn them into verbs. This is how you end up with a dictionary with two or three hundred verbs in it. It leads to these clunky, one-dimensional procedural verbs that we don't like.
How do I decide what I'm going to call these things? Where do I get the words that I'm going to use in my dictionary? How do I decide what these things are and what they're called? The best way to do this if you can, if you've got an existing application and you've got users that have been bugging you for automation, interview one of them. Sit them down, maybe sit facing away from them, and have them describe to you what it is they're doing.
the what they'll do is they'll lead you through the steps that it takes for them to perform an operation by hand write down what they're saying that that's your apple script it's not gonna compile and it's not gonna run you're gonna have to change the the wording so that it fits in the apple script form but that's not the most important part most important parties you can have to pull out of that description the objects that the person's talking about and make those the scriptable objects in your dictionary use the same words that your customers are already using to describe these things and then they don't have to relearn your program for scriptability they look in the scripting dictionary and they say oh i know what that is and they know what it is because you got the word from them So then you define in your dictionary those objects and properties that the user says he needs to do his job, and you only create new verbs when he's doing something that your program does and other programs don't do that.
If you don't have the opportunity to talk to a real live user about how he does his work, maybe your company has technical writers and produces some nice documentation. In those documents, there's very often a tutorial or a sample session of a user sitting down using the program. This is the same kind of information that came from the interview.
That step-by-step tutorial of how to do something with the program, again, that could be in AppleScript. You've got to massage the syntax into the AppleScript form, but much more importantly, you have to create the objects that the user is manipulating so that scriptability will be as powerful and more so than the GUI.
So you define those objects and properties that are in your documentation, create new verbs if you have to. Now you don't have to have separate documentation for your scripting and for the rest of your program because whatever it's called in the documentation, that's what it's called in the dictionary. dictionary.
If you're out there by yourself and you don't have customers that you can sit down and interview on a frequent basis and you don't have a big tech writing organization, you can look at your own object model. We started out by saying you know what's good about object models. You use them yourself. You're using one in your application. Look at it and abstract from that the objects and properties that your customers are most likely to need to do what it is they want to do.
But if you take this approach, the most important thing is humanizing the programmatic terms that you've used. Just reading your source code, it's too techy, it's too hard to understand. You have to put this in a form that an end user is going to be able to understand.
You definitely don't want to expose absolutely everything that's in your program. You've probably got a few main objects that represent the concepts that the end user is trying to work with. You've also probably got a bunch of helper objects that do interesting and important things that the customer doesn't need to know about.
Those helper objects don't belong in your AppleScript dictionary. Just the main conceptual objects do. When you're pulling terminology out of your application, you've got to humanize it. One of the most important things is get rid of the inner caps. Use separate words with white space in between them instead.
Good terminology in AppleScript doesn't use a lot of capital letters. You only want to use capital letters for acronyms and proper nouns. The reason for this is AppleScript is English-like. These terms show up in different places in an Apple event, and if you've got a capital letter in the middle of an Apple event, it looks funny.
If that capital letter is there because it's a product name, it makes sense. But if it's there because it's one of your verbs or one of your objects, it's going to confuse people. If you have Boolean properties, you might have the word "is" in your code. Leave that out. "Is" means something in AppleScript. You don't want to make it part of your terminology. And again, only make new verbs if you really need them.
The last way that you can go about it is to look at your own GUI and extract an object model from that. This one's tricky because you might have a tendency to script the user interface, and that's something you don't want to do. There's a session tomorrow at 3:11 where we're going to talk about scripting the user interface and try to convince you further that that's not what you want to do. So don't just describe the little widgets that are on the screen and give the user the ability to poke at them. Conceptualize it.
Figure out what it is that widget represents. Maybe it's a property, maybe it's an object. Give it a name and put it in your dictionary. Don't just inventory everything. Again, you'll end up with a long dictionary that's hard to find things in. Conceptualize. What are these things that I'm manipulating? What is it that I'm trying to do with them? What properties do they have that can be gathered? and set.
When you form the terms themselves, don't use articles like a, an, and the. Leave those out. They can be sprinkled in to your AppleScript later. They don't affect the way that the AppleScript executes, but they can make it easier to read. If you've embedded them in your terminology, then it's hard for a person writing an AppleScript to shift them and shuffle them to where they ought to belong.
Also, avoid personal pronouns like me and my. Again, me and my mean something in AppleScript. You don't want to stick them in your terminology. And again, if you do need to define some verbs, you're probably going to find the terminology for them on an existing button or an existing menu item somewhere in your program. Again, don't expose everything. Not every menu item needs to become a verb.
And the menu items that do need to become a verb are already in the standard suite. At least that's our When you're mapping stuff off of the screen into your dictionary, most of the stuff you're looking at is going to be properties of an object. Generally, one window or one pane within a window is going to represent an object and everything inside that is going to be a property. When you map that to your dictionary, you're going to need to pick data types for those. The most important one here on this slide is radio buttons.
If you have a small, finite set of values that a property can take on, that should be expressed as an enumeration. It makes your Apple scripts look nice, makes them easier to read, and it gives the scripter the notion that he can't set this value to just any old thing. He's got to pick from this list.
[Transcript missing]
to richer relationships in your object model. Some pop-ups are just replacements for radio buttons. And in that case they ought to be rendered in your dictionary as an enumeration, just like the radio buttons were. But other pop-ups actually represent an element relationship. If you change a pop-up at the top of your window, and it changes the entire contents of the screen, and now you're looking at a different set of data, then that pop-up really represents an element relationship. And to go back to our example of network setup, the location is a pop-up in the UI, but it actually represents an element relationship in the object model.
Same thing with scrolling lists. Sometimes a list is just a list of strings, and if that's all it is, then maybe that's what you should be passing back to your user. This is a property, the property is an array of strings. But that's not always the case. Sometimes a scrolling list actually represents an element relationship. For instance, in the finder, you've got a scrolling list of files. That you would want to render in your dictionary as an element relationship, a relationship between two objects. Table views are a rich source of element relationships.
This is a simple spreadsheet. There's already a table view suite defined in the standard headers. It's got a table which contains rows and columns. The intersection of a row and column is a cell. And each cell can in turn be a container for an entirely new object hierarchy. The contents of a cell in a spreadsheet might be a chart.
And the chart has an entire object hierarchy of its own. So this is somewhat of a physical mapping from your screen to a dictionary. If you do something like a spreadsheet where the user has the opportunity to put all different kinds of data in here and you don't know in advance what it's going to be, a physical mapping like this might be appropriate.
If you... If you do know a little bit about the data that's expressed in a table view, then you can make an abstraction. In this instance, this is a page from a database program that I wrote just as an experiment. The object here, the table itself, represents a database.
Instead of calling each line a row, we're going to call it a record. And instead of a row being divided into cells, we're going to have fields. And the fields are going to have names, which happen to be the same names as a row. The columns are going to be the same as the columns. This is one step up from the physical mapping. The most interesting thing about this is that Jim Mora is one of the 25 winningest coaches in NFL history.
One step up from that is a conceptual mapping. We really know what this is all about. This table is for one purpose. It's a database, but it's my database and I control what's in it. This is part of the folder action setup user interface that's going to be in Panther. Here the container is the application itself. Nobody owns this.
There isn't a table anymore. There's just folder actions and the application has a bunch of them. And every folder action has a name and a switch that says whether it's enabled or not. The thing that's not showing here is there's another scrolling list right next to it where every folder action has a set of scripts attached to it and that's an element relationship.
So when you're designing your dictionary, we definitely want you to use an object model. It makes your program fit in better with AppleScript. It makes your program fit in better with other scriptable applications. AppleScript Studio lets you be part of a workflow, lets you be part of a super application. Only create new verbs when you have to. Use something in the standard suite if you can. Omit certain words from your terminology that make a mess of things. And don't use uppercase unless it's a proper noun of some kind.
Go ahead and take things from other programs. The reason scriptable applications interact the way they do is because they use similar terminology. This allows you to interact with other programs at a level above just passing back numbers and strings. Lay out the entire future as far as you think you're ever going to be able to go, and then implement what you can in the first release, remembering to give your scripter at least one really interesting thing that he can do. You have to write a sample script, so you need at least one really interesting thing to do.
Now you've got your dictionary and you need to write some code. There's three cases that you can find yourself in. If you're really lucky you're starting from scratch. You're building a whole new application from the ground up. You can do whatever you want. Much more likely you've got an existing application and you've been tasked with adding scriptability to it. You're going to have to keep what's there, keep the GUI functionality, and add scripting to it.
You're going to need to do a little retrofitting. The toughest situation is when you've got a big existing framework that is not itself scriptable and you don't have the opportunity to make it scriptable. Perhaps your application is cross platform and you use a framework that makes being cross platform real easy, but it makes being scriptable real hard.
So the first case, maximum freedom, starting from scratch, I can do anything I want, except interview a user who is using the program because it doesn't exist yet. So how do I do this? How do I find, how do I develop my terminology if the program itself doesn't even exist?
Well, if you're from a large corporation that has a powerful marketing department and they provide you with detailed specifications in advance, that information can be used to develop the object model for your AppleScript dictionary. That's not normally the case, the situation I find myself in. I've got some direction, I've got some goals, but I don't have a lot of real specifications.
[Transcript missing]
And when you're done, you'll have an AppleScript dictionary, which is an object model of the conceptual objects that your end user is going to be using, whether they're scripting or using the GUI. This can actually be a design document for your entire application. You've got a running start here of how the entire application should look, regardless of scripting.
One of the things that I do to make my life easier is I implemented a scriptable base class that does a lot of the common functions. This is the promise of object-oriented programming, reusable code. So my scripting is mostly done by this reusable base object. It gives me consistent behavior across all my objects. All the objects in a particular program are going to behave very much the same. You won't get a lot of chattering between them that makes them hard to use together.
If your company develops a suite of applications and you have a scriptable base class, this is going to give you consistent behavior across your applications. This is beginning to build the synergy. Now these applications can talk to each other. They have similar object models. They have similar ways of describing those object models.
They can pass things back and forth that are a level up from just strings and numbers. Less code is less bugs. If you've got a base class that works, you find you're spending very little time on it. time debugging it and that's great because then you can do other stuff.
My scriptable base class is called Element because every object in a scriptable application's dictionary is an element of some other object. The top-level objects are all elements of the application itself. Good scriptable objects have names, so I have a way to set and get the name of my object.
In some applications, you may find that you want unique IDs for your objects. Databases, especially, the objects will have persistent unique IDs. Unique IDs in AppleScript are guaranteed to be good for one launch of the application. And in order to create a unique ID that's good for one launch of the application, all you need is a serial number. Just never give the same number out to two different objects and you're okay.
There's a slight problem with that, though. If somewhere in the execution of a script, that application quits and then relaunches, it will start reusing those unique IDs. If you've saved an object specifier in a script variable and it's still hanging around after the application has quit and relaunched, it might now refer to a different object, and that could be bad. One way to get around that is just use a timestamp. You can get the timestamp once when your application launches or every time somebody creates an object. Now, even if your application quits and relaunches, it will never generate the same unique ID again.
If you need unique IDs for something like a database, That need to persist across several launches of the application and need to stay the same across several launches of the application. Core Foundation provides something called a UUID, and it's great. It's got a timestamp in it, so it has all the qualities of a timestamp.
It's got your Ethernet hardware address in it, so it can even be differentiated from objects created on other machines. And the first part of it is a hash key, which means if you stick it into a hash table or a binary, the IDs will spread out across the range and they won't cluster together like if you were just using a plain old timestamp.
Every good scriptable object needs to be able to send back an object specifier to describe itself so that that same object can be retrieved later in the script. In my ScriptableBase class, I've implemented a cascade that creates an object specifier for every object and tries to create the best object specifier that it can. If unique ID is defined in the dictionary for this object, I go ahead and use that, unless, of course, there isn't one.
If there is no unique ID defined in the dictionary, then I look and see if name is defined in the dictionary. Every good Apple Scriptable object should have one or the other of those defined in the dictionary. If it turns out that neither of those is available, then I use an index specifier instead. Index specifiers are weak.
They may not even be good for one launch of the application. If you add or delete any objects at all, your index specifiers are going to change, and any that you might have stored in script variables just aren't going to be any good anymore. Or, they're going to point to a different object and that could be bad.
In order to create an object specifier, you're going to call an API that's going to need two pieces of information. What container do I belong in? And what does my container call me? And that's what these first two things are about. Every scriptable object I create has a pointer back to its parent. Do not retain your parent pointer. This creates a loop, and your objects will never go away.
An object also needs to know what its parent calls it. What element relationship does it belong to? I have a method called siblings. It simply passes back the name of the method that vends this collection of objects back to the container. These two pieces of information are going to be necessary to create your own object specifier, so I keep them around all the time in all my objects.
Another thing I do as a favor to myself is every one of my scriptable objects has an NSDictionary in it called attributes. And I use this to store the data for all the properties of the object. Now I can take this NSDictionary, flatten it, write it out to the disk, read it back in later. If I used a UUID to give it a unique ID, I've now got a persistent object that I can use across several launches of the application, several runs of a script, several days from now.
I can go back and get that same data and use it in a scriptable way again. This does not show up in my AppleScript dictionary. Each individual property shows up in my AppleScript dictionary, but physically inside I keep them all in an NSDictionary. It's nice, it's easy, does a lot of stuff for you.
Now you need to create your actual model objects, the things that you've decided to define in your dictionary. You're going to want to create an object that inherits from your scriptable base class, so now you don't have to worry about names or IDs or object specifiers. That's all done.
You are going to need to initialize this object. The way Cocoa scripting works, it creates an object first, then it calls the various set methods to set the parameters. To work with this, you need to have an init method that doesn't take any parameters. It has to create an object that's ready to use right now.
To do that, you might need to do some defensive programming. Make sure that checking pointers for nil and things like that. That object has to be safe to use right away because the way Cocoa is going to use it is it's going to allocate it, it's going to initialize it, and then it's going to call the set methods to set what's in it. If it's not safe to do that, you're going to have trouble. Cocoa scripting is also, well, not just Cocoa scripting, but Cocoa itself, is going to expect you to have a deallocate method so that you can put away all your toys.
AppleScript is very memory intensive. Cocoa scripting is very memory intensive. You create and delete a lot of objects to process a particular Apple event. You want to make sure that you're not leaking. So you want to make sure that you chain to any deallocation methods from any base classes you inherit from as well.
You want your scriptable objects to have properties. And the way you do that in Cocoa scripting is by creating a method. "The property name is the part that you want to change to the property names that you've selected when you designed your dictionary." Your dictionary also has element relationships. And there's varying levels of support for that that you can do in your Cocoa Scriptable Objects. The most basic one is to provide a method that has the same name as the element name.
And it bends back to whoever calls it an array of objects of that type. This is the minimum amount necessary for a Cocoa scripting application. There are more things that you can do to make your application more efficient, but this is the start. You have to start here. The first time you come through this, I just want you to create the methods. They don't really have to do anything.
But the element relationships have to at least fake it a little bit so that you can test this stuff. We're trying to get to the point where we can write some test scripts, see if our program even works at all, and then we're going to go back, we're going to create this skeleton, and then we're going to go back and put some muscles on it.
You also, every, everything is an element of some other object. Your top-level objects are going to be elements of the application itself. In order to do that, you're going to have to add element relationship methods to the NSApplication object. We don't want you to have to subclass NSApplication if you don't want to. If you do that for some other reason, great, but you don't have to do it for scripting. You can use an Objective-C category or a delegate to add the methods that you need to the application object.
You may also have properties of the application. There's no place to put these. You can't change the list of IVARs in the NSApplicationObject. You could if you subclassed it, but we want to try to avoid that. But since there's only one application object, it's okay to go ahead and use globals for these, for any persistent properties that the application itself might have. Normally, you want to avoid using globals, but this is an instance where it might be okay.
Now, you want to be able to create and delete objects. You want to be able to insert them into collections and remove them from collections. And Cocoa Scripting, again, takes the element names that you've decided to use in your dictionary, creates method names out of them, and then you write those methods.
So you want to be able to insert elements into the element relationship, and there's two ways to do that. You can do it positionally, or you can do it independent of position. Sometimes doing it positionally is important. If you're creating and deleting windows, you might want to create them in the front, you might want to create them in the back, you might want to create them in between existing windows. So you'd want to insert things at index.
Other things that you create, there's really no implicit order to them. It's okay if they just go at the end. In that case, you can do a simpler insertion routine that doesn't take an index. Either one of these will work. For various reasons, you may end up wanting to do both.
These are both going to be used anytime you make a new object in your Apple script. You also want to be able to remove things from the collection. That's always done by index. That's the way Cocoa scripting works. The object may be found in any one of a number of ways, but when it finally gets deleted, it's going to get deleted by index inside your code. This gets called by the delete verb.
There's enhanced element accessors that can make things more efficient. For instance, if you are implementing a database and you are using UUIDs to identify the records, you could potentially have millions of these. You don't simply want to have a method that creates an array and passes them all back. That's not practical. Your program is not going to work very well. There are enhanced methods that allow you to get objects directly by index, by name, and by unique ID.
These can be a whole lot more efficient than just searching a big, huge array. The decision of whether or not you want to do this is a performance consideration. You want to build your program the simple way first, measure it, and see if you need it. But in the case of millions of records in a database, it's pretty obvious you're going to need to do this.
The details for this were covered in last year's session. If you've got the DVDs, it was session 303. If not, there's a webpage on the developer site that tells you a lot about this. And on your CDs, there's Cocoa Scripting release notes, which also describes how to do this.
Now we want to test our objects. We've got this skeleton. It doesn't really do anything yet. We've got a bunch of objects. They've got a bunch of phony properties. They've got some phony elements. We can't really do anything useful yet, but we can start writing our test scripts.
First thing that I do-- I put a logging macro in every single method that I write. All the getters, all the setters, all the element accessors. I put a logging macro in there, and I have a switch here that I can turn on and off. Whenever I want to test my application, see what it's doing, try to figure out why it's not handling an Apple event correctly, I turn this switch on and run it, and I get all sorts of stuff in the console log. But it tells me line by line where I've been in my program, what I did, what I did right, and what I did wrong.
It's nice to know where you've been in your program. It's nicer still to know why. Cocoa Scripting will help you out there. This first line here, if you type it into your terminal application, it will turn on Cocoa Scripting's own logging facility. Every time Cocoa Scripting gets an incoming Apple event, it uses your dictionary to parse it, pull it apart, figure out what all the pieces are.
As soon as Cocoa Scripting has reached that point, if this switches on, it logs what it thinks that message, what it thinks that Apple event says. Now that you've got that information of what the incoming Apple event looks like, and then a log of everything that your program did to try to respond to that Apple event, it can help you a lot in debugging it.
If you have certain kinds of errors in your dictionary, Cocoa Scripting may not be able to form a script command out of what it is it's received. In that case, you're going to have to go down one layer lower and get the Apple Event Manager to tell you what that Apple event looks like.
Again, you type these commands into your terminal application. You need the first one and one or more of the others. Then you have to launch your application from inside the terminal. Then in the terminal scroll, you will see Apple Event Manager doing the same thing. Every time he gets an Apple event, he tears it apart, looks at it.
says what he thinks it means and puts it in the terminal log. This is all at a lower level. All you're going to see at this level is four byte codes, not very much in the way of interpretation. But it can tell you probably the reason why Cocoa Scripting is having a hard time with that event.
So then you want to start writing some scripts. And the first script you want to write is one that probes your entire object hierarchy, touches every object, touches every property, gives you maximum coverage so you can see if your program is written correctly. With AppleScript, that's a relatively easy thing to do. AppleScript supports the Every modifier, which is a range specifier that says I want to act over the entire collection of objects.
And Cocoa Scripting supports for you, for free, the Properties property, which returns a record of all the properties of a given object. So here in the course of just about nine lines of AppleScript, I've managed to probe the entire object hierarchy of the folder action suite. This script tells me that everything's there, everything works, it's time to go back and start writing some code.
So I'm going to want to go back to all the methods that I created before, all the properties, all the set properties, all the element relationships. And I want to populate them with real data now. The element relationships are going to get their data from various APIs or collections of data inside your program that may already exist.
And the properties are going to draw their data from those actual objects. Once you've put some muscles into these routines, you can go back and you can rerun all of your test scripts. And you'll see a big change from the first time. The first time, you just got back a bunch of zeros and missing values. This time, you'll start to get back some real data, objects with real names, and real properties set to real values.
Once you've reached this point, you want to write even more test scripts. You want to be able to make sure you don't have any regressions. As you add more features, as you add more capability, you want to make sure you don't break anything that was already working before.
And one of the things you want to do is test accessing your objects by all the various means that they can be accessed. Simplest is index. You can just count them and loop through them and get each one and get its properties. To do a thorough test here for folder actions, you'd have to have an inner loop that did the same thing to every script, but I ran out of room on this slide.
You can also access things by name. You want to make sure that that works. This is going to test an important part of your program. AppleScript does not require that object names be unique. You may, in the implementation of your program, require that these names be unique. But one way or the other, you need to run this test.
If you've got two objects with the same name, if you allow that, you should find in this test that you can only ever really get to the first one. If you support unique IDs, you want to make sure that that kind of access works. So you want to write nested loops here that access every object by its unique ID.
And this is one of the most important. Every scriptable object needs to offer up an object specifier for itself so that you can find that same object again later and do something new to it. You've got to make sure that those object specifiers really work, that they really get you back to the same object that they were supposed to be. And so you want to write a test for that. Loop through every object, get an object specifier for it, then go back and get the properties using that object specifier and make sure that it's the object you thought it was.
And you want to make something new. You want to create a new element and insert it into a collection of elements. You do that with the make new verb that's part of the standard suite. This is particularly interesting. If you do this, If you do this with all of the logging turned on, you'll find out a lot about how your program works, you'll find out a lot about how Cocoa scripting works, and you'll see that just to create a single object and insert it into a collection will end up calling Every property accessor on that object and every element accessor in the hierarchy all the way back to the application.
One of the most powerful features of AppleScript are "whose" clauses that allow you to select objects based on whether or not they satisfy a certain test. So you want to make sure that you write some good tests of "whose" clauses. The folder actions supports an enabled flag for every object. So here I've written some "whose" clauses based on the setting of the enabled flag.
If you go into your folder actions, set some of the enabled flags to "on," some of them to "off," and run this script, you should get varying results. And even though the last two events look very similar, there is a subtle difference. The presence of those parentheses will cause those last two events to actually return different collections of objects.
We talked about some other scenarios that you could find yourself in. Most common is adding scriptability to an existing application. It's a little bit less flexibility than you had when you were starting from scratch, but you do a lot of the same things. One thing that I would suggest is that you use Objective-C categories to add the scripting methods to an existing application. You don't have to go into the source files for the existing objects and actually change them. You can put your scripting in a separate file, create it in the form of a category.
It's a very powerful way of modifying existing objects, but it's also non-intrusive. If there's any point in the development cycle where you have a crisis of faith and you think that your scripting implementation has horribly screwed up your application and you don't know what to do about it, you can go into Xcode and simply uncheck the scripting files, rebuild your app, and try it again and find out that maybe it was that guy next to you that messed it up, not you. Or maybe it was you, and then you can fix it.
Objective-C categories are a great way of building scriptability alongside your existing objects without mucking them up. You still do have the issue of an init method that doesn't take any parameters. You might find that your existing objects aren't built that way, that your existing objects have an init method that takes a whole bunch of parameters, basically setting all of the properties at once at the same time that the object is created.
Cocoa scripting isn't going to work that way. It's going to want an init method with no parameters. And again, you've got to make sure that the result at the end of that init method is a safe object whose methods can be called right now. Objective-C categories are a great way to build a scripting application. and it isn't going to dereference any nil pointers. And again, you're going to need a deallocate method because you're going to be creating and deleting a lot of these things in the course of handling Apple events, and you don't want to leak.
Once you've created your categories alongside your existing objects, you're going to have to implement all of your various properties, the getters and setters, and your element names and your element relationships. You may find that some of the properties that you need already exist in the objects, and you just go ahead and reuse them.
The way that a dictionary is constructed, the external name for a property and its internal name don't have to be closely related. So if you've got a getter and a setter for the name, for instance, you can just go ahead and reuse that. If you find that the AppleScript version of this getter and setter need to be slightly different than the other, then you can wrap one around the other.
But one way or another, you have to satisfy all of these properties by creating methods to access them. Same thing with the element relationships. You'll need to create the element relationships in your scripting categories. You can't reuse any of the existing ones. For instance, NSApplication already has an ordered documents method, and Cocoa Scripting reuses that for its own purposes. There's no reason to create a new one.
And again, you want to extend the NSApplication so that your top level objects are all elements of the application. And again, we would suggest you use an Objective-C category or a delegate to do that for the same reason. You don't have to mess with the real NSApplication object. And while you're doing this, you should add your logging macro to every method that you create.
Then we're going to want to go through the same steps we went through before. We're going to want to write a bunch of scripts to test this with, even though some of the functionality might not be there yet. Then we want to go back and add some muscle to our objects, fill in those property accessors so that they get at real data, fill in those element accessors so they pass back collections of real objects. Then go back and rerun all of your scripts, and make sure that the differences you see in the results are the ones that you expected.
The last situation is the toughest one, and that's adding scriptability To a large application that depends on a framework that is not itself scriptable and cannot easily be made scriptable. Again, though, you want to go through most of the same steps. You want to define a real good dictionary first. Then you want to define the scriptable objects, properties, and element relationships that bring that dictionary to life.
And you want to do this as if you were starting from scratch. You've got this existing framework that you can't penetrate too easily, and you want to add Cocoa Scripting to it. Just create the Cocoa Scripting objects as if you were starting from scratch, as if this other application didn't even exist. Do the same thing we did before. Give them just enough functionality so that you can write some interesting tests.
Then you're going to want to go back and add muscle to these objects by taking all of the property accessors and element accessors and tying them back into existing APIs and existing objects inside the impenetrable framework. This is where the complexity can arise. The object model that you chose to express in your AppleScript dictionary and the object model that exists in this impenetrable framework might not match up.
You might have to do some sorting and searching and recombining of things to get them to work right. This is where this scenario can become difficult. But when you're done, or you think you're done, you can go back and rerun all of your scripts that you wrote and see if you got the results that you were expecting. It should give you an indication that you've done it correctly.
We promised that we would talk about Cocoa and Carbon, and we haven't said anything about Carbon yet. So what about Carbon? Well, you can still script a Carbon application in the same way that you always have. The OSL is still there, it still works, and you can use it.
And that's very well documented in the inter-application communication document. Mac Tech is now the guardian of all the old develop articles. This one is a particularly good one, a cookbook, a recipe for taking a Carbon application and making it scriptable. They weren't even called Carbon applications back when this was written.
So you can do it that way. It's not easy, but we've all done it at least once, and it can be done. I would suggest that you seriously consider migrating to Cocoa. I know it's not trivial. Carbon is a first-class citizen, and it's going to be supported forever.
But Cocoa is the way we encourage you to develop new apps. If you reach a point in your development cycle where you're really considering just chucking it all in and starting over again, consider starting over again in Cocoa. I wouldn't start over again just for scripting, but it's certainly a factor. If you do rewrite your application in Cocoa, Objective-C and Cocoa scripting will make the scripting part of your job very easy.
A year ago, we promised that we would come up with an API for Carbon scripting that was just as easy to use as Cocoa, just as powerful, did all the same things for you, leveraged all of that existing power. And we thought and thought and thought of how to do this, and it was really hard, and we were going to end up having to rewrite almost everything that Cocoa scripting did.
And it occurred to us, well, maybe we could just use Cocoa scripting. So we conducted an experiment and found out that, yes, as a matter of fact, it pretty much just works. You can use Cocoa scripting in an otherwise Carbon application. This is really a special case of the third scenario. You've got this big impenetrable framework that's hard to make scriptable. You want to build a Cocoa scripting implementation alongside of it and tie it into the existing implementation.
You can actually do that with any existing Carbon application. It's not hard, but it's not trivial. There's a few tricks that you need to know, a few modifications you need to make to your application's startup code. But once you've done that, Cocoa Scripting just works. There is a price, though.
You don't get this for free. It can have a performance impact on your application, and the only way for you to find out what that is is to just measure it yourself. And the two things that you're going to need to measure are launch time and memory usage. So you want to get a baseline for those before you start your work. You don't have to do your entire scripting implementation to find out what this impact is going to be, but you do have to make the changes to your startup code.
Once you've changed your startup code, you can relaunch your application and you'll get a pretty good idea of what pulling in Cocoa Scripting is going to do to your application. It'll slow down the launch, it'll increase the footprint. Now, you haven't written your code yet, but you haven't written any Carbon Scriptability code yet either, and the presumption is that they will be about equivalent. That the hit that you're taking is the overhead for Cocoa Scripting, and you can measure that without having to write all the code first.
If you really want to do this, contact developer technical support and put "scripting hybrid" in the subject of your email, and you will get a response from John Monprion about how to go about doing this. Like I said, it's not hard, but you've got to know a few things.
There's lots and lots of documentation to help you make your application scriptable. The most important part of it is the design of your dictionary, and these places here are good places to go to find out information about that. You also need to implement Objective-C or other objects to bring your dictionary to life. There's more detailed information about how to do that at these places.
Um, Cocoa and Carbon are both, uh, are both documented here. And the AppleScript language guide is going to be valuable to anybody that's writing scriptable applications or writing AppleScripts. Anybody along the line, end users, testers, engineers, marketers, everybody should be looking at the AppleScript language guide. It teaches you how AppleScript itself works and teaches you a lot about how your object model needs to interface with that.
Each time we release new software, we keep you up to date on the latest changes. Those are in these release notes, which should be on your CDs. And the Interapplication Communication Tech Note is kind of an umbrella where you'll find almost all the rest of this stuff that we mentioned, plus a whole bunch of other stuff about Apple Events and AppleScript.
If you're afraid you don't want to ask any newbie questions, you can go to the AppleScript Q&A and see if maybe the answer's already there. But don't be afraid to ask newbie questions. They're real easy for us to answer, and we feel like we've done a good deed. And you can get sample code also.
And you can get information from outside. The AppleScript website has a wealth of information, including dozens and dozens of sample scripts for Apple applications and other applications, creating synergy between them. You should definitely visit that site. And there's books and websites published by third parties that also have very valuable information.
And the AppleScript website is a good place to start to find most of those. There's a section in there that's just all about third-party websites that you can go to to learn even more. These are all the sessions that you've missed. But if you get the DVDs, you can watch them there, or maybe some of you guys were at them.
This is today's session and the rest of the ones that we're going to have. I mentioned tomorrow we're going to talk about using AppleScript to test your applications. And also there's going to be a session where Sal Sigournian is going to talk about AppleScript for system administrators. AppleScript is a tremendous tool for system administrators. It saves them a lot of time. If you want your system administrator to love you, you should write a good scriptable app.
And if you need to get to us directly, Todd Fernandez is my boss, Sal Segoyan is our marketing manager, Jason is our technology manager, and John Monprion is our DTS contact. You should always go through John Monprion first. He's going to have the quickest answer for you if it's already been asked, and he's going to direct it to the right person if it hasn't already been asked.