Accessing shared instance in target application, or sending actions

51 views
Skip to first unread message

Dan Wood

unread,
Jun 18, 2010, 7:39:32 PM6/18/10
to JSTalk Dev
Hi folks, just trying out JSTalk for the first time since seeing Gus's
talk at C4.

I'm trying to create a new document in my target app with a minimum of
code changes in the app. I'm thinking about a couple of possible
approaches:

* [[NSDocumentController sharedDocumentController]
newDocument:nil] ... Well, the problem is that this addresses the
shared document controller for JSTalk editor, so I end up creating a
new JSTalk window!

* Could I send an action to the first responder? Not sure if it's even
possible to refer to a selector in JSTalk. If I could, I could use -
[NSApplication sendAction:to:from:]

* I guess worse-come-to-worse I could make an accessor in my
NSApplication subclass to give me the application's shared document
controller. That doesn't seem like the cleanest solution.

Thoughts?

Gus Mueller

unread,
Jun 19, 2010, 1:17:10 PM6/19/10
to jstal...@googlegroups.com
On Jun 18, 2010, at 4:39 PM, Dan Wood wrote:

> * I guess worse-come-to-worse I could make an accessor in my
> NSApplication subclass to give me the application's shared document
> controller. That doesn't seem like the cleanest solution.

This is actually what I do in my apps. If you've got the JSTalk framework bundled in your app, it includes a category on NSApplication that looks like this:

- (id) sharedDocumentController {
return [NSDocumentController sharedDocumentController];
}

There's a couple more in there, which you can see in JSTExtras.m.

-gus

--

August 'Gus' Mueller
Flying Meat Inc.
http://flyingmeat.com/

Patrick Geiller

unread,
Jun 20, 2010, 9:40:23 AM6/20/10
to jstal...@googlegroups.com
* [[NSDocumentController sharedDocumentController]
newDocument:nil] ... Well, the problem is that this addresses the
shared document controller for JSTalk editor, so I end up creating a
new JSTalk window!

You need to get the distant app's NSDocumentController for this to work. Distant objects don't work with classes - they do send back the class, but serve it in JSTalk's space, producing your bug : creating documents in JSTalk rather than in your app. There's a simple fix : box the distant class in a custom object, call your class methods (sharedDocumentController) on this distant object, and let this distant object forward calls to the distant class.

This will require a box class, some additions to your app's NSApplication, and will let you use distant classes like this : 

var sketch = JSTalk.application_("Sketch");
// Get distant NSDocumentController class
[[[sketch NSDocumentController] sharedDocumentController] newDocument:nil]
// Same, but using a standard method call
[[[sketch classNamed:'NSDocumentController'] sharedDocumentController] newDocument:nil]


First, this object boxes a class and forwards its calls to it :

@implementation ClassBox

+ (id)withClass:(Class)c
{
id o = [[self new] autorelease];
[o setValue:c forKey:@"class"];
return o;
}

// Check boxed class for selector response
- (BOOL)respondsToSelector:(SEL)sel
{
BOOL b = [super respondsToSelector:sel];
if (!b)
b = [class respondsToSelector:sel];
return b;
}
// Query boxed class for method signature
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
id sig = [super methodSignatureForSelector:sel];
if (!sig)
{
Method m = class_getClassMethod(class, sel);
if (!m) return nil;
sig = [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(m)];
}
return sig;
}
// Invoke boxed class
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:class];
[invocation invoke];
}

@end


Then, add these methods to NSApplication to get any distant class (boxed). The first method is enough to work by using [sketch classNamed:'....']. The last three treat an unknown selector as a class request and send the class back (boxed) if they find it, letting you call [sketch NSDocumentController]. 

@implementation NSApplication (ServeClassesToDistantObjects)

// [sketch classNamed:@"NSDocumentController"]
- (id)classNamed:(NSString*)name
{
id class = objc_getClass([name UTF8String]);
if (!class) return nil;
return [ClassBox withClass:class];
}

// The next three methods handle getting a distant class by using its name as selector, eg [sketch NSDocumentController]
// These might need to be swizzled
- (BOOL)respondsToSelector:(SEL)sel
{
BOOL b = [super respondsToSelector:sel];
if (!b)
{
if (objc_getClass([NSStringFromSelector(sel) UTF8String]))
return YES;
}
return b;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
id sig = [super methodSignatureForSelector:sel];
if (!sig)
{
// We're calling a method that doesn't exist, takes no parameters, and returns an object (the class)
// use the signature of -(id)self which does the same thing
if (objc_getClass([NSStringFromSelector(sel) UTF8String]))
return [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(class_getInstanceMethod([self class], @selector(self)))];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
id class = [self classNamed:NSStringFromSelector([invocation selector])];
[invocation setReturnValue:&class];
}

@end

You might need to swizzle the last three methods as they could interfere with NSApplication's implementations (if they exist).

-Patrick


Reply all
Reply to author
Forward
0 new messages