Trying to automatically view a computer in Apple Remote Desktop via Scripting Bridge in Objective-C with this:
#try {
SBApplication *RD = [SBApplication applicationWithBundleIdentifier:#"com.apple.RemoteDesktop"];
// (code to check for ARD running and installed omitted here)
[RD activate]; // works just fine
RemoteDesktopComputer *computer = [[[RD classForScriptingClass:#"computer"] alloc] initWithProperties:
[NSDictionary dictionaryWithObjectsAndKeys:
ipAddress,#"InternetAddress", // looked up from header
nil
]
];
// attempt to add it to a container first:
[(SBElementArray*)[(RemoteDesktopApplication*)RD computers] addObject:computer];
// this is what raises the exception:
[computer observeZooming:Nil];
}
#catch (NSException *e) {
NSLog(#"Exception: %#", [e description]);
}
Running this yields the following exception in the log:
Exception: *** -[SBProxyByClass observeZooming:]: object has not been added to a container yet; selector not recognized [self = 0x6050004819b3]
I've done as much research as there is available on this subject and have learned that SB isn't the easiest to deal with because of how it's wired under the hood, but any experts or veterans of native Scripting Bridge (no third party frameworks or languages other than obj-c, please) is much appreciated.
All prerequisites like linking to the ScriptingBridge.framework and importing Remote Desktop.h are performed - the typecasts are to avoid what appear to be unavoidable link-time errors when building...
Edit 1: Reading the documentation on SBObject (parent of RemoteDesktopComputer) says that it's a reference rather than an actual instance, which you can fetch by calling SBObject's get method (returns id). So I tried running this as well but unfortunately received the same results:
[[computer get] observeZooming:Nil];
Here's the documentation on SBObject: https://developer.apple.com/library/mac/documentation/cocoa/Reference/SBObject_Class/SBObject/SBObject.html#//apple_ref/occ/instm/SBObject/get
Still trying...
(FWIW, I already had the following How To written up, so I'm leaving it here for future reference.)
How to use AppleScript-ObjC in place of Scripting Bridge
Scripting Bridge is, at best, an 80/20/80 "solution" (i.e. 80% of the time it works, 20% of the time it fails, and 80% of the time you've no idea why). There's little point trying to argue with SB when it breaks on stuff that works perfectly well in AppleScript - the Apple engineers responsible designed it that way on purpose and simply refuse to accept they broke spec [1] and screwed up. As a result, the AppleScript language, for all its other deficiencies, remains the only supported solution that is guaranteed to speak Apple events correctly [2].
Fortunately, since OS X 10.6 there has been another option available: use ObjC for all your general programming stuff, and only call into AppleScript via the AppleScript-ObjC bridge for the IPC stuff.
From the POV of your ObjC code, your AppleScript-based ASOC 'classes' are more or less indistinguishable from regular ObjC classes. It requires a bit of fiddling to set up, and you'll pay a bit of a toll when crossing the bridge, but given the crippled, unreliable nature of the alternatives, it's the least horrid of the supported options for anything non-trivial.
Assuming you've already got an existing ObjC-based project, here's how to add an ASOC-based class to it:
In Targets > APPNAME > Build Phases > Link Binary With Libraries, add AppleScriptObjC.framework.
In Supporting Files > main.m, add the import and load lines as shown:
#import <Cocoa/Cocoa.h>
#import <AppleScriptObjC/AppleScriptObjC.h>
int main(int argc, const char * argv[]) {
[[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
return NSApplicationMain(argc, argv);
}
To define an ASOC-based class named MyStuff that's callable from ObjC, create a MyStuff.h interface file that declares its public methods:
// MyStuff.h
#import <Cocoa/Cocoa.h>
#interface MyStuff : NSObject
// (note: C primitives are only automatically bridged when calling from AS into ObjC;
// AS-based methods with boolean/integer/real parameters or results use NSNumber*)
-(NSNumber *)square:(NSNumber *)aNumber;
#end
along with a MyStuff.applescript file containing its implementation:
-- MyStuff.applescript
script MyStuff
property parent : class "NSObject"
on square_(aNumber)
return aNumber ^ 2
end square_
end script
Because the MyStuff class doesn't have an ObjC implementation, the linker can't link your ObjC code to it at build-time. Instead, use NSClassFromString() to look up the class object at run-time:
#import "MyClass.h"
...
MyStuff *stuff = [[NSClassFromString(#"MyStuff") alloc] init];
Otherwise it's pretty much indistinguishable from a native ObjC class in normal use:
NSNumber *result = [stuff square: #3];
NSLog(#"Result: %#", result);
HTH
--
[1] Apple management broke up the original AppleScript team shortly after its initial release, causing its designers to quit in response, so a lot of knowledge of precisely how this stuff should work was lost. In particular, a full, formal specification was never produced for application developers to follow when designing their scripting support, so all they could do was use personal judgement and best guesses, then test against AppleScript to check it worked as hoped. Thus, AppleScript's own Apple event bridge is the de facto specification that every single scriptable app has been implemented against in the last twenty years, so the only way that other AE bridges can ever work correctly is if they mimic AS's own bridge down to every last query and quirk - a lesson, unfortunately, that the current AS team have repeatedly failed to understand [2].
[2] JavaScript for Automation's Apple event supported is equally crappy and busted, incidentally.
Scripting Bridge is a defective, obfuscated mess, so when an application command fails to work you've no idea if the problem is SB being defective or the application itself being buggy or simply requiring you to phrase it in a different way.
Therefore, the first step is to write a test script in AS to see it works there. If it does, it's SB that's crap; if not, try fiddling with your AS code (e.g. try phrasing the reference for the at parameter in different ways, or omitting it entirely) till it does.
You should also ask on Apple's AppleScript Users and ARD mailing lists and anywhere else that ARD scripters are likely to hang out, as most apps' scripting documentation is grossly inadequate, so a lot of knowledge of how to do things is word of mouth. (The guy you really want to talk to is John C Welch, aka #bynkii, as he's the guru of ARD scripting.)
Related
Is it at all possible to have Xcode create a .playground file for Objective-C instead of Swift? Are there any available Xcode plugins that allow that?
You can quickly test code snippets using a test case in a new project. Just create a new project and go to the Navigator in the left pane and hit the Test Navigator button. Then follow this guide
The setup code will look a little different than a swift playground, but it still allows you to prototype and play around.
There is a very good library developed by Krzysztof Zabłocki in Github entitled KZPlayground that support both code in Playgrounds for Objective-C and Swift and a lot of cool features.
I hope this can help you.
If the only purpose is to test out Objective-C snippets, i would really recommend you an OS X command line Tool project.
There are enough moving parts in a playground, and all of those would have to be reimplemented for Objective-C. Reliable playgrounds also depend on definite initialization which Objective-C does not have.
For instance consider:
var d: NSData // this is not initialized, so I can't use it
vs.
NSData *d; // this is also not initialized, but now I can use it
If I am the person storing the description of your NSData for the sidebar, now I know that I am not supposed to do
describe(d)
in the Swift case, but for the Objective-C case, I don't have equal knowledge and I run the risk of saying
[d description]; // even though d is a random pointer now.. oops, I just crashed!
In short, I don't think any such thing exists, and making one work would also involve some trickery
I'm new to objective C and there's something odd that I don't understand.
How can I even call a NSString method on a NSDate object? For example:
NSString* ptr = [[NSString alloc] init];
[ptr uppercaseString];
NSDate* dPtr = [[NSDate alloc] init];
[dPtr uppercaseString];
id temp;
[temp uppercaseString];
Well, I do get that id can point to anything but how does it even know of the existence of the uppercaseString method without casting or something?
I'm have a C++ and Java background where I didn't notice anything like this before.
I'd love to get an explanation.
Unlike Java and C++, Objective-C has weak typing and late binding, which explains that you don't have to do a cast.
This is one of big dividing lines in object-oriented programming: Whether the language uses strong typing, so a variable can only hold references (or pointers) to objects of a given class and its subclasses, or if it can hold anything. If a variable can hold any object, the exact method implementation then has to be resolved at runtime when a message is received.
Objective-C got the philosophy of late binding from Smalltalk (see smalltalk), but is moving towards a more and more strictly typed language (formal protocols, use of the id type discouraged, etc.). The basics remain the same, however.
This is also one of the reasons, contrary to C++, Objective-C needs a runtime in order to run on your machine. Something has to take care of those method lookups.
Because the check for the existence of the method is not made right before the call but while trying to find the method. What actually happens (simplified a lot is)
[obj methodCall];
=> replaced => objc_send(obj, #"methodCall")
Inside the C function objc_send The call itself is resolved and made
If(obj.respondsTo(methodCall) Then obj.methodCall();
Objective-C methods are not the same as Java or C++ methods. They are messages, and they exist independently of any class or object. When you write (taken from CocoaDevCentral) in Photo.h:
#import <Cocoa/Cocoa.h>
#interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- caption;
- photographer;
#end
you are saying that the Photo class has a caption and a photographer object, and that it will respond to the messages caption and photographer. That was the old pre-properties way of writing code for those two items.
You will write code in Photo.m giving the implementation of the two messages, so that a Photo can respond to them. But nothing stops you from sending caption to any object. It's like the old Far Side cartoon about what we say to dogs and what they hear. Any errors occur at runtime.
So, what happens when you send a message to an object that it does not know how to respond to? If you have not done anything special,
The runtime system packages the message into a thing of type SEL.
It sends the doesNotRecognizeSelector: message to the object with that selector.
The object inherits from NSObject an implementation that raises a NSInvalidArgumentException.
However, there are a few opportunities before that to intervene by overriding a method:
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
This lets you install an implementation at runtime.
- (id)forwardingTargetForSelector:(SEL)aSelector
This lets you nominate another object to accept the message.
- (void)forwardInvocation:(NSInvocation *)anInvocation
This lets you handle the message any way you want.
Before Objective-C gained blocks, there were a number of libraries that used forwarding for functional programming. Suppose you have an NSArray of Accounts that all understand the balance message. Suppose then you want to collect the balances of all the accounts in another NSArray. Instead of looping, the library provided a category for NSArray with a collect message, and you would write:
NSArray *accounts = ...;
NSArray *balances = [[accounts collect] balance];
The result of [accounts collect] does not have an implementation for the balance message; how could it? collect is provided by the library. Instead, it has a forwardInvocation: implementation that sends the balance message to all the members of accounts, and creates a new NSArray from them. One might use blocks and enumerateObjectsUsingBlock: these days, but that was a quite succinct and powerful technique.
Others have provided the answer - late binding, the method is looked up on the object at runtime without concern for the type of the object - if it has an appropriate method it is called.
However your call above [dPtr uppercaseString] should produce an error from Xcode. While the compiler will perform a lot of checks and refuse to compiler some programs (such as the above) that is really all the type-checking you get and it can be easily by-passed (e.g. [(id)dPtr uppercaseString] will remove the error and let you code run - when it will promptly fault due to no such method on NSDate).
Essentially the types are comments, if you use them properly you code should be type-correct, but there is no requirement for type-correctness for your code to compile.
It may seem implied that everyone knows what a "Non Fragile ABI" is - considering the frequency and matter-of-fact-nature to which it is referred to - within Xcode. For example...
Subscript requires size of interface node which is not constant in non-fragile ABI
or
Select the Objective-C ABI version to use. Available versions are 1 (legacy "fragile" ABI), 2, (non-fragile ABI 1), and 3 (non-fragile ABI 2).
That said... What is a non-fragile ABI? (and why isn't it called something less-abstract / explained more clearly?)
The non-fragile ABI refers to the ability to add instance variables to a class without requiring recompilation of all subclasses.
I.e. in v1 (there really aren't true versions of ObjC), if Apple were to add an instance variable to, say, NSView (on Cocoa, 32 bit), then every subclass of NSView (or subclass of subclass) would have to be recompiled or they would blow up. v2 and v3 fix this.
It is explained in detail in this weblog post.
The documentation you are referring to is in the llvm/clang man page. Pretty rare place to be for most developers most of the time; unless you are writing a Makefile that is driving the compiler directly, there isn't much reason to read that page (unless spelunking -- which is quite educational, of course).
It is written in the style of a Unix man page and, no surprise, is a bit... obtuse. For almost all tasks, it is best to stick to the higher level documentation. I.e. the Xcode build settings documentation is general quite a bit less obtuse.
After some poking around, one of the best summaries / pieces of advice on the subject is the following…
The non-fragile ABI allows for things like changing the ivars of a superclass without breaking already compiled subclasses (among other things). It's only supported on 64-bit on the Mac though, because of backwards compatibility concerns which didn't allow them to support it on existing 32-bit architectures.
It goes on to say, basically.. that if Xcode, which often is configured to build for the "Active Architecture Only", aka 64-bit only.. one may run into issues when switching to a "Release" scheme, which is usually set to build for both (63bit/32bit) architectures, aka "Universal"..
You may you want to use ARC on the Mac, I'm pretty sure you'll have to drop 32-bit support to do so. You can change the targeted architectures in the build settings for your target in Xcode.
In my own experience, I believe that what the non-fragile ABI benefits us with is an abbreviated syntax, and patterns such as…
// source.h - readonly public properties.
#interface SuperClassy : NSObject
#property (readonly) NSArray *cantTouchThis;
#end
// source.m set readonly properties, internally.
#implementation SuperClassy
// look, no #synthesize… just use _ivarName.
-(void) touchIt:(NSArray*)a { _cantTouchThis = a; }
#end
int main(int argc, const char * argv[]) {
SuperClassy *it = [SuperClassy new];
// you cannot set it.cantTouchThis = #[anArray].
[it touchIt:#[#"cats"]];
// but you can via a method, etc.
NSLog(#"%#", it.cantTouchThis);
}
NSLOG ➜ ( cats )
Assume that:
New Protocol is declared
Method in this protocol is marked #required
Class conforms to Protocol
Class does not implement the method mentioned in Protocol
At compile time, information about this method is known: i.e. that it is required and that this class and any other classes this class may may extend do not implement it.
Why in this case the compiler issues a warning and not an error?
Errors are only issued when the compiler cannot continue because something went terribly wrong.
When calling a method in Objective-C, the method lookup is done during runtime and not during compilation, which C++ does. In Objective-C a "message" is simply sent to the object, something like obj.executeCommand("Hey, can you execute function <name> for me?"). In C++ the object will be called directly, in a way like obj.<name>(). In the case of Objective-C the executeCommand() method is called, which exists. In C++'s case the function is called but it does not exist. These are methods that are linked on the compiler level, which means they both become memory addresses rather than names. executeCommand becomes 0x12345678 but it still uses the same message ("execute function <name>").
This is probably very confusing, but it's related to the way methods are implemented in different languages.
If you feel strongly about it, why not turn on -Werror?
I don't know the real answer but here is a use case that would go against it.
What if you implemented all of the protocol methods in a category???
Main interface declaration adopts the protocol however the protocol method implementation is in a category.
This is valid code but will show compile error if compiler was that strict !!
Objective-C is a dynamic language. The idea of what an implementation is, is different to a static language.
For the most part, it's in code that most of us implement inside the #implementation ... #end block.
But what if a method is not found? Then an object has a chance deal with it dynamically.
Imagine you have an interface for a sound effect player:
#protocol FX
- (void)playBeep;
- (void)playSiren;
- (void)playHonk;
#end
An implementation could have the files Beep.mp3, Siren.mp3, Honk.mp3 to play, but instead of implementing each of the methods, it could override -forwardInvocation: and parse the selector string, something like this pseudocode:
NSString *selName = NSStringFromSelector([invocation selector]);
if ([selName startsWith:#"play"]) {
NSString filename = fileNameFromSelector(selName);
[self playSoundFileNamed:filename];
}
This may seem contrived, but once you start using the dynamic features of the language, you will start finding more and more places where it makes sense. And by sense I mean, does this effort help in the long run?
In the above case, just add a -sound* method name to the interface, and drop in a appropriately named sound file. It just works.
Another example from personal experiments: how to deal with Core Data entities in a more natural way. I want to do this:
NSArray *people = [Person findAllWithNameLike:#"B%"];
instead of mucking about with predicates, fetch requests etc.
But I don't want to define every permutation of method in code.
How about if I wanted to build an XML builder? I would look at a dynamic approach. It has served Groovy Builders well (look at Groovy/Grails for examples).
One last example: I have a traits system where I can define behaviours in the form of groups of methods and have my objects assimilate this behaviour. So, while the compiler doesn't see an implementation for the interface my object conforms to, the implementation is injected into it from a trait class, using the Objective-C runtime. Why would I do this? I find many delegate methods are boiler plate, but at the same time, a single base class for each situation is not flexible enough. Instead of cut and paste from code samples, my 'samples' compile and run :) and any changes are reflected across all projects using the trait.
To really understand why all this is available to you, it is worth playing around with a Smalltalk environment (search Pharo or Squeak). This is where Objective-C has its roots.
And finally, to stop these warnings:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprotocol"
#implementation ... #end
#pragma clang diagnostic pop
Because there are times when there are bogus "required" methods in a poorly designed protocol.
They should have been optional but someone insisted they are "required".
Thusly making this a run time issue rather than a compile bug is very very wise.
On a past project (pre-iOS 4.0), I wrote the following category method on NSSortDescriptor:
+ (id)sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending;
When Apple released the iOS SDK 4.0, it included the exact same method (which presumably does exactly the same thing). Is it possible to write a category that is only added to the runtime either if you're running a particular OS version, or probably more to the point, if there isn't already a method declared with the same signature?
In this case, it's probably safe to override the sortDescriptorWithKey:ascending: method with a category, which will give both iOS 3 and iOS 4 support, since my version will almost certainly do the same thing. I'd still prefer not to mess with system defined methods if possible, due to the (unlikely) possibility of breaking things in edge cases.
Joshua’s answer will work well, but if you want to get really fancy, you can use the dynamic nature of Objective-C to modify the NSSortDescriptor class to your liking:
#import <objc/runtime.h>
SEL theSelector = #selector(sortDescriptorWithKey:ascending:);
if ( ! [NSSortDescriptor instancesRespondToSelector:theSelector]) {
class_addMethod([NSSortDescriptor class],
theSelector,
(IMP)mySortDescriptorWithKey,
"##:#B");
}
Of course, that depends on a C function:
id mySortDescriptorWithKeyAscending(id self, SEL _cmd, NSString *key, BOOL ascending) {
// Put your code here.
}
DISCLAIMER: I have not tried to compile any of this.
DISCLAIMER II: This may be frowned upon by Apple in terms of App Store submission.
Not directly, no.
The way I'd recommend doing this would be to have a my_sortDescriptorWithKey: which can then check if the class responds to sortDescriptorWithKey: and uses that if it does, otherwise use your own implementation.