Script Objects in Objective-C Classes?

Hi All,
Lets say I’ve got an ASOC project with both a script object MyScript and an OBJ-C class MyClass. I can alloc init MyClass to create and use an instance of it in MyScript. How do I, or is it even possible to do the reverse - create and use an instance of MyScript in MyClass. Thanks in advance for any help.

Where do you get this script object from?

Let me try to clarify. I don’t think I asked the question very well. I have a ASOC project like this:

script MyMainScript
	tell current application's MyScript1 to set script1Instance to alloc()'s init()
	tell current application's MyScript2 to set script2Instance to alloc()'s init()
	tell current application's MyScript3 to set script3Instance to alloc()'s init()
	tell script2Instance to script2Method_(script3Instance)
end script

script MyScript1
	-- some properties
	-- some methods
end script

script MyScript2
	on script2Method_(anObject)
		-- do some stuff using anObject's properties and methods
	end script2Method
end script

script MyScript3
	-- some properties
	-- some methods
end script

I can take the script MyScript1 and replace it with an obj-c class MyScript1 and the project runs great. What I’m having difficulty with is converting MyScript2 in the same way. The problem is the script2Method is passed a MyScript3 object, and I don’t see how to declare that in obj-c (other than first converting MyScript3 to obj-c which it turns out is not possible because of similar limitations). It’s not like MyScript3 has a header file that can be imported.

I’m still not sure of what you’re after. But those “script objects” will actually be loaded as classes; ASObjC treats “script” objects differently.

Or are you asking how to call an AS method from Objective-C?

Hello mekronk,

First of all you need to create protocol files because it is a dynamic object. Then you need NSClassfromString to get the Objective-C class created from the AppleScript class file by the appleScriptObjC framework at startup of your application. The protocol is needed because the compiler like to know how the class looks like at compile time.

So when you have a script like:

script finderClass
	property parent : class "NSObject"
	
	on getVersion()
		return version of application "Finder"
	end getVersion
	
	on firstSelection()
		if (count of (selection of application "Finder" as list)) < 1 then return "<Nothing selected>"
		return POSIX path of (item 1 of (selection of application "Finder" as list) as string)
	end firstSelection
end script

The protocol have to define if the class is instantiated later or not. We are going to use an instatiation of the object so we need instance methods and not class methods. The protocol file would look like:

Now to load the ‘AppleScript’ class you can use

I’m not being very clear am I. The problem I run into with the above example, is when I try to convert the script MyScript2 into an obj-c class called MyScript2, it needs to know about the applescript class MyScript3. For example, in the MyScript2 interface file I’ll need to have something like:

- (void) script2Method: (MyScript3 *) anObject;

but the compiler knows nothing about MyScript3 since it’s not an obj-c class.

Thanks DJBazzie Wazzie. I’m just getting going with obj-c, so it might take a while to sort through your answer. I haven’t worked with protocol files before so it’s time to hit the books again, but I might be back with more questions.

You can declare there class in your .h file:

@class MyScript3;

I guess I have a more basic question about your app – why use script objects? This is something I’ve never done before, so what advantage does it have over having separate scripts? I’ve had apps with multiple scripts and Objective-C classes that all played well together, but I’m not sure whether I’ve run into the problem you’re having. Have you tried using the @class directive in the header file of your Objective-C class (@class myScript3;). I have tried that with applescript scripts, and that did eliminate the compiler errors.

Ric

I have to laugh at myself. I tried this already, but I kept getting warnings about “method not found” when I tried to access MyScript3 properties using getters (dot syntax wouldn’t work) so I gave up without trying to build and run. So on your suggestion I figured I should try again. This time I ran the project and it worked fine even with the warnings. I guess that’s why they’re warnings and not errors. I can live with warnings as long as it works. Thanks!

You get rid of the method warnings using a protocol, as DJ outlined. Or you can be lazy like me and use an informal protocol – just put something like this in your .m file before the @implementation:

// informal protocol for AS class methods
@interface MyClassName

// method sigs go here
-(NSString *)relinkLinkID:(NSString *)path toPath:(NSString *)newPath;

@end

Thanks Shane.

I’m probably not being very clear with my nomenclature. My large ASOC projects consist of multiple, separate scripts, but each script, at least from my understanding is a separate script object/class, so I think that we’re talking about the same thing. I’m working on converting these projects over to obj-c, and I thought I’d do that by taking one script object/class at a time and convert over to obj-c classes. That’s the only reason I’m mixing the two like this.

I use my example a lot as a substitute for Scripting Bridge. AppleScript classes in a project are converted to Objective-C classes before the NSApplication is created. The command that creates it is in the main.m file

This means that the compiler doesn’t know anything about your AppleScript classes (the GCC compiler doesn’t support applescript). These objects are also named dynamic classes or virtual classes in Java and C++. The protocol is needed to inform the compiler how the class, that doesn’t exists at compile time, looks like. When you read how Java and python bridges works I noticed that Apple uses Class and not @Class. @Class is an efficient compiler instruction against #import but @class needs like #import statically classes. The Class is used to define a class at runtime and with NSClassFromString you ask the Objective-C run time for a class. You can access it by it’s name because the the AppleScriptObjC framework creates an Objective-C class with the same name as you have named the script object.

I wanted to make clear that there is no such thing as accessing in AppleScript class in Objective-C. At runtime you just have dynamic and static Objective-C classes, in other words every class is an Objective-C class. The dynamic classes should be the classes that you have written in AppleScript.

I also saw in your example that you want to send an object as an argument. For this the same rules as I descibed applies. As far as I know are more than half of all arguments you use now already a object. When you use a string as an argument you use in instance of an NSString class as a argument. So sending an instance of your own class as an argument should be done exact the same way as you do with instances of cocoa classes.

That’s true, but I think it’s often useful to think in terms of Objective-C classes and AS classes. “AS classes” do have specific behaviour, and anything above bits is just a convenient fiction away :wink:

Maybe this will help understand what happens in passing stuff between the two:

When you call an Objective-C method from AS, text/list/record/boolean/integer/real arguments get converted to their NS equivalents, and other AS objects (dates, whatever) are passed as NSAppleEventDescriptors. What is passed back in a result can be treated as NS objects, although they are actually object pointers stored in typeObjCInstance instances («class ocid»). AppleScriptObjC has coercions that can coerce these to text/list/record/boolean/integer/real objects, provided the pointers are to the equivalent NS class objects. When you pass an typeObjCInstance object as an argument, the bridge extracts and passes the object pointer to the Objective-C method.

Going the other way, you must pass objects and not primitive types to AS methods, and these again are stored in typeObjCInstance objects. As above, AppleScriptObjC can coerce these to text/list/record/boolean/integer/real objects, provided the pointers are to the equivalent NS class objects. What is passed back as a result to Objective-C methods will be the NS equivalent of any text/list/record/boolean/integer/real objects, any typeObjCInstance objects received from Objective-C calls, and NSAppleEventDescriptors for any other AS objects.

Also true and normally I keep AS classes separate from Objective-C classes. I wasn’t sure if mekronk was aware of that so I thought it was useful to mention it for a better understanding how AS classes and objects should be used in Objective-C. From now on I will name AS classes the way it should be :wink:

Many thanks for extra explanation of how it all works. It is sometimes difficult for me to explain it in a foreign language, especially when it gets detailed. It’s always a pleasure when you’re around.

I should add that some of it is my speculation, but I thing’s it’s pretty accurate.

Speaking of AS classes, class_copyMethodList() reveals some interesting stuff. Three instance methods all AS classes have are handlerNames, propertyNames and setterNames. The latter returns a dictionary matching property setter signatures with their properties.

I’d like to experiment with class_copyMethodList(), as well as some others in that list (Objective-C Runtime Reference), but I can’t seem to get it working… I’ve looked up many examples but I’m drawing errors (my lack of old-time Obj-C / C is showing)… Appears that I’m not including a header file that’s needed, drawing “I don’t know what that is” errors… Care to drop a working example, if you have the time?

Thanks.

Thanks for all of the detailed explanations digging into the guts of whats going on between the applscript and obj-c world. I was pretty much clueless about all those details.

You need #import <objc/objc-class.h>, and maybe #import <malloc/malloc.h>. Then it’s roughly:

unsigned int numFields = 0;
Method* methods = class_copyMethodList(NSClassFromString(className), &numFields);

Then loop through getting names with something like:

const char* name = sel_getName(method_getName(methods[index]));

Ok. That looks a lot like the couple samples I was trying… Those headers should probably take care of it.

Thanks.