A plug-in is a software bundle that contains code and resources. Plug-ins use a well-documented interface to extend and expand the functionality of an application in a modular fashion. They can be added or omitted without affecting the operation of the core application.
WeatherSnoop 3's plug-in architecture allows it to be extended in new and interesting ways using this “bolt-on” approach. Developers can add new functionality quite easily to WeatherSnoop 3 with plug-ins that range from simple, single-view add-ons to elaborate, multi-windowed extensions.
We’ve made the development of a plug-in as easy as possible by providing several sample Xcode projects of working plug-ins that you can use to both study and to start from. In order to create a WeatherSnoop plug-in, you need to be familiar with Apple’s Xcode development environment. You will also need to have experience with the Objective-C programming language and the Cocoa framework.
WeatherSnoop 3 looks for plug-ins under your home directory in the
Library/Application Support/com.tee-boy.WeatherSnoop3/Library/PlugIns folder.
You can look at our example plug-ins by visiting our GitHub page. Full source is available and comments in the code will help you to understand how a plug-in is architected.
All code related to the WeatherSnoop Plug-in is located inside of a Cocoa framework named WS3Kit. Any plug-in created for WeatherSnoop must link against this framework. The framework includes a number of protocols and classes that are documented below.
The simplest plug-in consists of a single Objective-C class, subclassed from WSPlugIn. Subclassing is not optional – your plug-in must be subclassed from WSPlugIn. Elaborate plug-in bundles can go beyond this simple scheme and contain other classes. Those classes can be managed and orchestrated by your plug-in class.
WSPlugIn itself is subclassed from NSViewController, so all plug-ins are essentially view controllers that contain a view. This allows your plug-in to provide a user interface component.
Let's go over the methods in the WSPlugIn class that your plug-in subclass should be aware of.
These common initialization methods are called by the WeatherSnoop Plug-in Manager when your plug-in is loaded and unloaded, respectively. You can use these methods to initialize, setup and tear-down any objects that your plug-in has allocated.
The initWithController: method is called when your plug-in is created by the plug-in manager. A reference to the WSSiteWindowController object is passed. The WSPlugIn class keeps a reference to this object, so do not be concerned with storing it.
- (id)initWithController:(WSSiteWindowController *)controller; - (void)dealloc;
Also, the WSPlugIn class adopts the NSCoding protocol. This allows your plug-in to be serialized and unserialized between unloads and loads. You can adopt the following methods to serialize any objects that you wish to persist between loads of your plug-in:
- (id)initWithCoder:(NSCoder *)aDecoder; - (void)encodeWithCoder:(NSCoder *)aCoder;
These methods are intended to provide information about your plug-in. The first method should return an identifying proper name for your plug-in. The second method is for information purposes, and can be any amount of text necessary to describe your plug-in.
- (NSString *)name; - (NSString *)description;
The following two methods are called at important points in the plug-in creation/destruction process: right after the plug-in has been loaded, and just before it will be unloaded. Depending on your plug-in, you may want to add code here to deal with certain tasks like loading or unloading resources, etc.
- (void)didLoad; - (void)willUnload;
This method returns an NSMenu which will be inserted into the Plug-ins menu on the WeatherSnoop menu bar. If your plug-in is designed to not require any user interactivity, or you do not wish to have it appear in the menu, return nil.
- (NSMenu *)menu;
By default, the plug-in manager will create a menu with a single menu item that allows users to see an “About” window for your plug-in.
The WSSiteWindowController object is central to a plug-in's interaction with WeatherSnoop 3. As noted earlier, a reference to it is passed into the
initWithController: method. The WSPlugIn class keeps a reference to it as a property, so you can access it in your plug-in at any point.
The WSSiteWindowController holds references to a number of interesting objects, including other plug-ins, as well as the WSAgent object.
Finally, your windows will want to be part of the Site Document's set of window controllers. Therefore, you must use this method to register the window controller with the Site Document:
- (void)registerWindowController:(NSWindowController *)windowController;
This object can be referenced from the WSSiteWindowController, and holds information about the station or data source, including all of its weather properties.
WSAgent *agent = self.controller.agent;
There are several useful methods in the WSAgent object that your plug-in can call.
This method returns the name of the agent. The name of each supported agent can be found in the WSPlugIn.h header file. Your plug-in can use this method to discern which agents it will support.
- (NSString *)agentName;
This method returns the WeatherSnoop license tier as a string (currently supported return values are “Lite” or “Pro”). Your plug-in may want to allow or disallow certain features based on the license tier. If you don't implement this method, then it is assumed that your plug-in will work with the lowest tier license.
- (NSString *)licenseTier;
If your plug-in contains one or more windows, they should be represented by window controllers. The Cocoa class is NSWindowController; however, WSPlugIn provides a subclass named WSWindowController. Any window controllers for your plug-in's windows should be subclassed from WSWindowController. Doing so allows the plug-in architecture to automatically handle persistence of location and window size.
There are also several useful properties that can be accessed from the WSAgent object:
NSString *siteName; NSString *siteLocation; NSString *siteLongitude; NSString *siteLatitude; NSArray *properties; BOOL isRunning; DBManager *db;
siteLatitude properties return information from the Site tab of the Agent Window.
properties contains an array of objects which represents weather properties for the agent. This is where your plug-in can obtain weather data (temperatures, humidities, wind, etc). Each weather property is represented by an Objective-C object of the class WSProperty.
isRunning property returns the agent's running status. Your plug-in can use key-value observing (KVO) to determine when the user starts or stops the agent.
db property allows your plug-in to execute SQL queries on the SQLite database that is in the Site Document. You can consult the DBManager.h header file in the WSKit framework for more details.
Plug-ins can utilize WeatherSnoop’s logging facility by using the TBLog class. The class returns a singleton object when the sharedLog message is sent to it. That object then becomes the receiver of messages sent to it. The class provides a simple method which sends a string to the WeatherSnoop log window:
- (void)log:(NSString *)message level:(TBLogLevel)level;
The log level, defined in TBLog.h, corresponds to the log level slider found on the agent window. You can determine at what level your message will appear in the log by passing the level to this method. A handy macro, TBLogFormat(), can be used to pass a format string and a variable number of arguments, like so:
[[TBLog sharedLog] log:TBLogFormat(@”Grabbing image from %@”, server) level:TBLogLevelInfo];
To get you started, we have several plug-ins on GitHub (https://github.com/weathersnoop). Feel free to use these plug-ins as a template for your own plug-in idea!