Detecting Presence of a Framework Within an NSBundle (Plugin)

×

This post was originally published in 2011
It may contain stale & outdated information. Or it may have grown more awesome with age, like the author.

Introduction
Jump to Tutorial

In light of Cocoa Warning: Implicitly declaring C library function ‘objc_getClass’, this tutorial has been updated.

Recently, it was brought to my attention that my method of including a particular framework (Sparkle) inside a plugin was wrong – the way I was doing it (blindly including the framework as if my plugin was the boss) was causing conflicts within the application in certain situations.

Instead, I should have been ‘sniffing’ for the presence of the framework within my plugin, and only if it wasn’t present, loading the framework dynamically. This is instead of: pretending my plugin was a real application, and including the framework in the traditional manner.

As I’m no Cocoa genius, I didn’t know what set of functions / objects one should use to implement the above process. I Googled, and luckily my searches led me on a path that ended with this: Click to Flash’s GitHub page for a specific commit intended to solve this problem: Plugin implementation.

Tutorial
In an effort to provide aid to anyone who winds up in a similar situation, the following is a short tutorial outlining the exact steps required to integrate this functionality into a plugin.

Required Steps

  • [1, 2, 3] Include an internal version of the Sparkle framework in a way that doesn’t cause it to be automatically loaded – to avoid cases where the framework has already been loaded by the application / another plugin
  • [4] Check for Sparkle at initialisation, and load an internal version if required
  • [4] (Optional) Store the Sparkle updater object internally for use in in the “checkForUpdates” IBAction

1. Download the Sparkle framework
2. Using XCode now, Drop the Sparkle.framework folder into your plugin’s Resources folder
3. Right-click on your plugin’s target & create a new “Copy Files” build phase, set it’s destination to “Resources”. Drag the Sparkle.framework from your “Resources” folder into this new build phase.
4. In the header for whatever class you want to perform the loading, in this case SomeClass.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Sparkle/Sparkle.h>
 
@interface SomeClass { 
	// For convenience, this is set to the SUUpdater object for this plugin (iff Sparkle was loaded)
	id sparkle;
}
 
@property (retain) id sparkle;
 
- (void) loadSparkle;
- (IBAction) checkForUpdates:(id)notifier;
 
@end

And the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@implementation SomeClass
 
@synthesize sparkle;
 
- (id) init{
    if ((self = [super init]) != nil){
 
		@try {
			[self loadSparkle];	// Load the framework
		}
		@catch (NSException * e) {
			SLog(@"SomeClass init: %@", e);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
		}
	}
    return self;
}
 
- (void) awakeFromNib{
	@try {
		if (nil != [self sparkle]) {	// If sparkle was loaded, check for updates
			[[self sparkle] setAutomaticallyChecksForUpdates:YES];
			[[self sparkle] resetUpdateCycle];
		}
	}
	@catch (NSException * e) {
		SLog(@"SomeClass awakeFromNib: %@", e);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
	}
}
 
- (void) loadSparkle {
 
	@try {
		if (!NSClassFromString(@"SUUpdater")) {	// If Sparkle hasn't been loaded, attempt to load it
			NSString *frameworksPath = [[[NSBundle bundleForClass:[self class]] bundlePath] 
										stringByAppendingPathComponent:@"Contents/Resources/Sparkle.framework"];	
			NSBundle *sparkleFramework = [NSBundle bundleWithPath:frameworksPath];
			NSError *error = nil;
 
			BOOL loaded = [sparkleFramework loadAndReturnError:&error];
 
			if (loaded) {
				NSBundle *pluginBundle = [NSBundle bundleWithIdentifier:@"com.youridentifier.yourplugin"];
				NSAssert(pluginBundle, nil);
				Class updaterClass = NSClassFromString(@"SUUpdater");
				NSAssert(updaterClass, nil);
				[self setSparkle:[updaterClass updaterForBundle:pluginBundle]];
				NSAssert([self sparkle], nil);
			}
			if (error) SLog(@"error loading Your Plugin's Sparkle: %@", error);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
		} else {	// Sparkle has been loaded already, use the loaded version
			[self setSparkle:[SUUpdater updaterForBundle:[NSBundle bundleForClass:[self class]]]];
		}
	}
	@catch (NSException * e) {
		SLog(@"SomeClass loadSparkle: %@", e);    // http://pagesofinterest.net/blog/2011/09/more-informative-nslog-alternative/
	}
}
 
- (IBAction) checkForUpdates:(id)notifier{
	if (nil != [self sparkle]) {	// Check for updates!
		[[self sparkle] checkForUpdates:nil];
	}	
}
 
@end

Link your “Check for Updates” button to the “checkForUpdates” IBAction.

Success!

Your plugin should now be able to operate in both environments where Sparkle is missing, and where Sparkle is loaded before your plugin has been initialised.

Comments (1) | Trackback