Skip to content

Instantly share code, notes, and snippets.

@beccadax
Created July 27, 2013 05:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beccadax/6093944 to your computer and use it in GitHub Desktop.
Save beccadax/6093944 to your computer and use it in GitHub Desktop.
NSApplication subclass with additional delegate methods for various useful things.
//
// GSTApplication.h
// Ingist
//
// Created by Brent Royal-Gordon on 7/12/13.
// Copyright (c) 2013 Architechies. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "GSTServicesProvider.h"
@protocol GSTApplicationDelegate;
@interface GSTApplication : NSApplication
@property (strong) id <GSTApplicationDelegate> delegate;
@end
@protocol GSTApplicationDelegate <NSApplicationDelegate>
@optional
- (void)gst_application:(GSTApplication *)app openURL:(NSURL*)URL;
- (GSTServiceHandler)gst_application:(GSTApplication *)app handlerForServiceNamed:(NSString*)name;
- (void)gst_applicationShowHelp:(GSTApplication *)app;
@end
//
// GSTApplication.m
// Ingist
//
// Created by Brent Royal-Gordon on 7/12/13.
// Copyright (c) 2013 Architechies. All rights reserved.
//
#import "GSTApplication.h"
@interface GSTApplication () <GSTServicesProviderDelegate>
@end
@implementation GSTApplication
@dynamic delegate;
- (void)didReceiveGetURLEvent:(NSAppleEventDescriptor*)URLEvent replyEvent:(NSAppleEventDescriptor*)replyEvent {
NSString * URLString = [[URLEvent paramDescriptorForKeyword:keyDirectObject] stringValue];
NSURL * URL = [NSURL URLWithString:URLString];
if([self.delegate respondsToSelector:@selector(gst_application:openURL:)]) {
[self.delegate gst_application:self openURL:URL];
}
}
- (void)finishLaunching {
[NSAppleEventManager.sharedAppleEventManager setEventHandler:self andSelector:@selector(didReceiveGetURLEvent:replyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
GSTServicesProvider.sharedServicesProvider.delegate = self;
self.servicesProvider = GSTServicesProvider.sharedServicesProvider;
[super finishLaunching];
}
- (GSTServiceHandler)servicesProvider:(GSTServicesProvider *)serviceProvider handlerForServiceNamed:(NSString *)name {
if(![self.delegate respondsToSelector:@selector(gst_application:handlerForServiceNamed:)]) {
return nil;
}
return [self.delegate gst_application:self handlerForServiceNamed:name];
}
- (void)showHelp:(id)sender {
if([self.delegate respondsToSelector:@selector(gst_applicationShowHelp:)]) {
[self.delegate gst_applicationShowHelp:self];
return;
}
[super showHelp:sender];
}
@end
//
// GSTServiceProvider.h
// Ingist
//
// Created by Brent Royal-Gordon on 7/25/13.
// Copyright (c) 2013 Architechies. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef void (^GSTServiceHandler)(NSPasteboard * pboard, NSString * userData, NSError ** error);
@protocol GSTServicesProviderDelegate;
@interface GSTServicesProvider : NSObject
@property (weak) id <GSTServicesProviderDelegate> delegate;
+ (instancetype)sharedServicesProvider;
@end
@protocol GSTServicesProviderDelegate <NSObject>
- (GSTServiceHandler)servicesProvider:(GSTServicesProvider*)serviceProvider handlerForServiceNamed:(NSString*)name;
@end
//
// GSTServiceProvider.m
// Ingist
//
// Created by Brent Royal-Gordon on 7/25/13.
// Copyright (c) 2013 Architechies. All rights reserved.
//
#import "GSTServicesProvider.h"
#import <objc/runtime.h>
@interface GSTServicesProvider ()
@property (strong, nonatomic) NSMutableDictionary * serviceHandlers;
@end
@implementation GSTServicesProvider
+ (NSString *)serviceNameForSelector:(SEL)aSelector {
NSString * selectorName = NSStringFromSelector(aSelector);
NSRange range = [selectorName rangeOfString:@":userData:error:" options:NSAnchoredSearch|NSBackwardsSearch];
if(range.location == NSNotFound) {
return nil;
}
return [selectorName substringToIndex:range.location];
}
- (GSTServiceHandler)handlerForServiceNamed:(NSString*)name {
GSTServiceHandler handler = self.serviceHandlers[name];
if(!handler) {
handler = [[self.delegate servicesProvider:self handlerForServiceNamed:name] copy];
self.serviceHandlers[name] = handler;
}
return handler;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString * serviceName = [self.class serviceNameForSelector:sel];
if(!serviceName) {
return [super resolveInstanceMethod:sel];
}
IMP imp = imp_implementationWithBlock(^(GSTServicesProvider * self, NSPasteboard * pboard, NSString * userData, NSError ** error) {
GSTServiceHandler handler = [self handlerForServiceNamed:serviceName];
if(!handler) {
[self doesNotRecognizeSelector:sel];
return;
}
handler(pboard, userData, error);
});
NSString * str = [NSString stringWithFormat:@"%s%s%s%s%s%s", @encode(void), @encode(id), @encode(SEL), @encode(id), @encode(id), @encode(__autoreleasing id *)];
class_addMethod(self, sel, imp, str.UTF8String);
return YES;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
NSString * serviceName = [self.class serviceNameForSelector:aSelector];
if(serviceName && ![self handlerForServiceNamed:serviceName]) {
return NO;
}
return [super respondsToSelector:aSelector];
}
- (id)initSingleton {
if((self = [super init])) {
_serviceHandlers = [NSMutableDictionary new];
}
return self;
}
+ (id)allocWithZone:(NSZone *)zone {
static GSTServicesProvider * singleton;
static dispatch_once_t once;
dispatch_once(&once, ^{
singleton = [[super allocWithZone:zone] initSingleton];
});
return singleton;
}
- (id)init {
return self;
}
+ (instancetype)sharedServicesProvider {
return [self new];
}
@end
@lolgear
Copy link

lolgear commented Sep 16, 2019

@brentdax
Hi!
Could you explain is this example about .servicesProvider property of NSApplication?
I still can't figure out how to implement "servicesProvider" method. See

As I understand, ServicesProvider uses dynamic dispatching by filtering methods with 3 parameters ( pasteboard, userData and error ).
Is it correct?

Thanks!

P.S. It would be nice if you replied with simple ServicesProvider which do nothing but print to console its parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment