Subclassing UIWindow - Need Preprocessor Help - objective-c

Yes, I know subclassing UIWindow is frowned upon, but my subclassed UIWindow is for debugging purposes only (it takes a screenshot of the current-page once a specific motion event is detected).
Anyway, I made a custom precompiler flag called DEBUG in my project's Build Settings, but I'm having a problem getting it to load/function properly. Right now, it's not taking the screenshot, but it is registering the occurrence of the motion event.
Here's the code I have in the AppDelegate's didFinishLaunchingWithOptions:
#if DEBUG
DebugWindow *debugWindow = [[DebugWindow alloc] init];
self.window = debugWindow; //'window' is declared in the AppDelegate's #interface file and synthesized as window=_window in the #implementation file
#else
self.window = _window;
#endif
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];

Here is how to use debug flag
#if DEBUG == 1
#define CMLog(format, ...) NSLog(#"%s:%#", __PRETTY_FUNCTION__,[NSString stringWithFormat:format, ## __VA_ARGS__]);
#define MARK CMLog(#"%s", __PRETTY_FUNCTION__);
#define START_TIMER NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
#define END_TIMER(msg) NSTimeInterval stop = [NSDate timeIntervalSinceReferenceDate]; CMLog([NSString stringWithFormat:#"%# Time = %f", msg, stop-start]);
#else
#define CMLog(format, ...)
#define MARK
#define START_TIMER
#define END_TIMER(msg)
#endif
And here is the screenshot
Also in the release setting put the flag to 0
Like this -DDEBUG=0
This way you can achieve what you want to achieve.Let me know if it helps or not.

Related

RetainCycle Memory leak with AppleScriptObject in NSBlockOperation

I'm on X-Code 8.2, OSX not iOS, Objective-C
In a custom NSBlockOperation I included an Applescript (gets compiled by XCode) like that:
SHImportLayoutOperation.m file
#import "SHImportLayoutOperation.h"
#import "Helpers.h"
#class SHIndesignImport; // Applescript
// ################################################
#interface SHImportLayoutOperation()
#property SHIndesignImport *indesignImport;
#end
// ################################################
#interface SHIndesignImport:NSObject
// Methods
- (NSMutableDictionary*)scanIndesign:(NSMutableDictionary*)parameters errors:(NSMutableArray*)errors;
#property NSBlockOperation* operation;
#end
// ################################################
#implementation SHImportLayoutOperation
- (id)init
{
self = [super init];
if (self)
{
_helpers = [[Helpers alloc] init];
init];
_indesignImport = [[NSClassFromString(#"SHIndesignImport") alloc] init];
}
return self;
}
Later on in the operation i need a method of the script. Everything just works fine. BUT: i want to pass a reference of the current operation (self) to the script to check for the isCancelled state. This also works but leads to a memory leak/cycle.
[_indesignImport setOperation:self];
// call scanIndesign
NSMutableDictionary* scanIndesignResult = [_indesignImport scanIndesign:parametersINDD errors:errors];
_indesignImport.operation = nil;
This leads to the cycle. So i tried a weak self
__weak typeof(self) weakSelf = self;
[_indesignImport setOperation:weakSelf];
// call scanIndesign
NSMutableDictionary* scanIndesignResult = [_indesignImport scanIndesign:parametersINDD errors:errors];
_indesignImport.operation = nil;
Same error. Then i tried to release it from within the applescript (at the end):
-- Beginning of script
#property operation: missing value
-- Lots of stuff --
-- End of script
set operation to missing value
Same stuff. If i don't pass the reference everything works fine. No cycle. So this has to be the reason. Any help appreciated.
Edit:
Here's a screenshot of instruments. As you can see, the ref count is still 1 (this is with the weakSelf)

Generate gcda-files with Xcode5, iOS7 simulator and XCTest

Being inspired by the solution to this question I tried using the same approach with XCTest.
I've set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES'.
XCode still doesn't produce any gcda files. Anyone have any ideas of how to solve this?
Code:
#import <XCTest/XCTestLog.h>
#interface VATestObserver : XCTestLog
#end
static id mainSuite = nil;
#implementation VATestObserver
+ (void)initialize {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:XCTestObserverClassKey];
[super initialize];
}
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (mainSuite == suite) {
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
}
#end
In AppDelegate.m I have:
extern void __gcov_flush(void);
- (void)applicationWillTerminate:(UIApplication *)application {
__gcov_flush();
}
EDIT: I edited the question to reflect the current status (without the red herrings).
EDIT To make it work I had to add the all the files under test to the test target including VATestObserver.
AppDelegate.m
#ifdef DEBUG
+ (void)initialize {
if([self class] == [AppDelegate class]) {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:#"XCTestObserverClass"];
}
}
#endif
VATestObserver.m
#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>
// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set
#interface VATestObserver : XCTestLog
#end
#ifdef DEBUG
extern void __gcov_flush(void);
#endif
static NSUInteger sTestCounter = 0;
static id mainSuite = nil;
#implementation VATestObserver
+ (void)initialize {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:XCTestObserverClassKey];
[super initialize];
}
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
sTestCounter++;
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
sTestCounter--;
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (sTestCounter == 0) {
__gcov_flush();
}
}
Update 1:
After reading a bit more about this, 2 things have now become clear to me (emphasis added):
Tests and the tested application are compiled separately. Tests are actually injected into the running application, so the __gcov_flush() must be called inside the application not inside the tests.
— Xcode5 Code Coverage (from cmd-line for CI builds) - Stack Overflow
and,
Again: Injection is complex. Your take away should be: Don’t add .m files from your app to your test target. You’ll get unexpected behavior.
— Testing View Controllers – #1 – Lighter View Controllers
The code below was changed to reflect these two insights…
Update 2:
Added information on how to make this work for static libraries, as requested by #MdaG in the comments. The main changes for libraries is that:
We can flush directly from the -stopObserving method because there isn't a separate app where to inject the tests.
We must register the observer in the +load method because by the time the +initialize is called (when the class is first accessed from the test suite) it's already too late for XCTest to pick it up.
Solution
The other answers here have helped me immensely in setting up code coverage in my project. While exploring them, I believe I've managed to simplify the code for the fix quite a bit.
Considering either one of:
ExampleApp.xcodeproj created from scratch as an "Empty Application"
ExampleLibrary.xcodeproj created as an independent "Cocoa Touch Static Library"
These were the steps I took to enable Code Coverage generation in Xcode 5:
Create the GcovTestObserver.m file with the following code, inside the ExampleAppTests group:
#import <XCTest/XCTestObserver.h>
#interface GcovTestObserver : XCTestObserver
#end
#implementation GcovTestObserver
- (void)stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
#end
When doing a library, since there is no app to call, the flush can be invoked directly from the observer. In that case, add the file to the ExampleLibraryTests group with this code instead:
#import <XCTest/XCTestObserver.h>
#interface GcovTestObserver : XCTestObserver
#end
#implementation GcovTestObserver
- (void)stopObserving
{
[super stopObserving];
extern void __gcov_flush(void);
__gcov_flush();
}
#end
To register the test observer class, add the following code to the #implementation section of either one of:
ExampleAppDelegate.m file, inside the ExampleApp group
ExampleLibrary.m file, inside the ExampleLibrary group
#ifdef DEBUG
+ (void)load {
[[NSUserDefaults standardUserDefaults] setValue:#"XCTestLog,GcovTestObserver"
forKey:#"XCTestObserverClass"];
}
#endif
Previously, this answer suggested to use the +initialize method (and you can still do that in case of Apps) but it doesn't work for libraries…
In the case of a library, the +initialize will probably be executed only when the tests invoke the library code for the first time, and by then it's already too late to register the observer. Using the +load method, the observer registration in always done in time, regardless of which scenario.
In the case of Apps, add the following code to the #implementation section of the ExampleAppDelegate.m file, inside the ExampleApp group, to flush the coverage files on exiting the app:
- (void)applicationWillTerminate:(UIApplication *)application
{
#ifdef DEBUG
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
Enable Generate Test Coverage Files and Instrument Program Flow by setting them to YES in the project build settings (for both the "Example" and "Example Tests" targets).
To do this in an easy and consistent way, I've added a Debug.xcconfig file associated with the project's "Debug" configuration, with the following declarations:
GCC_GENERATE_TEST_COVERAGE_FILES = YES
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
Make sure all the project's .m files are also included in the "Compile Sources" build phase of the "Example Tests" target. Don't do this: app code belongs to the app target, test code belongs to the test target!
After running the tests for your project, you'l be able to find the generated coverage files for the Example.xcodeproj in here:
cd ~/Library/Developer/Xcode/DerivedData/
find ./Example-* -name *.gcda
Notes
Step 1
The method declaration inside XCTestObserver.h indicates:
/*! Sent immediately after running tests to inform the observer that it's time
to stop observing test progress. Subclasses can override this method, but
they must invoke super's implementation. */
- (void) stopObserving;
Step 2
2.a)
By creating and registering a separate XCTestObserver subclass, we avoid having to interfere directly with the default XCTestLog class.
The constant key declaration inside XCTestObserver.h suggests just that:
/*! Setting the XCTestObserverClass user default to the name of a subclass of
XCTestObserver indicates that XCTest should use that subclass for reporting
test results rather than the default, XCTestLog. You can specify multiple
subclasses of XCTestObserver by specifying a comma between each one, for
example #"XCTestLog,FooObserver". */
XCT_EXPORT NSString * const XCTestObserverClassKey;
2.b)
Even though it's common practice to use if(self == [ExampleAppDelegate class]) around the code inside +initialize [Note: it's now using +load], I find it easier to omit it in this particular case: no need to adjust to the correct class name when doing copy & paste.
Also, the protection against running the code twice isn't really necessary here: this is not included in the release builds, and even if we subclass ExampleAppDelegate there is no problem in running this code more than one.
2.c)
In the case of libraries, the first hint of the problem came from this code comment in the Google Toolbox for Mac project: GTMCodeCovereageApp.m
+ (void)load {
// Using defines and strings so that we don't have to link in XCTest here.
// Must set defaults here. If we set them in XCTest we are too late
// for the observer registration.
// (...)
And as the NSObject Class Reference indicates:
initialize — Initializes the class before it receives its first message
load — Invoked whenever a class or category is added to the Objective-C runtime
The “EmptyLibrary” project
In case someone tries to replicate this process by creating their own "EmptyLibrary" project, please bear in mind that you need to invoke the library code from the default emtpy tests somehow.
If the main library class is not invoked from the tests, the compiler will try to be smart and it won't add it to the runtime (since it's not being called anywhere), so the +load method doesn't get called.
You can simply invoke some harmless method (as Apple suggests in their Coding Guidelines for Cocoa # Class Initialization). For example:
- (void)testExample
{
[ExampleLibrary self];
}
Because you have to create a new XCTestSuiteRun instance in the testSuiteDidStop method, you are not going to get the proper results on an == check. Instead of depending on instance equality, we used a simple counter and call flush when it hits zero, which it will when the top-level XCTestSuite finishes executing. There are probably more clever ways to do this.
First, we had to set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES' in both the Test and main app targets.
#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>
// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set
#interface GCovrTestObserver : XCTestLog
#end
#ifdef DEBUG
extern void __gcov_flush(void);
#endif
static NSUInteger sTestCounter = 0;
static id mainSuite = nil;
#implementation GCovrTestObserver
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
sTestCounter++;
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
sTestCounter--;
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (sTestCounter == 0) {
__gcov_flush();
}
}
#end
There was an additional step required, because the +initialize call was not being made on the observer when included in the Test target.
In the AppDelegate, add the following:
#ifdef DEBUG
+(void) initialize {
if([self class] == [AppDelegate class]) {
[[NSUserDefaults standardUserDefaults] setValue:#"GCovrTestObserver"
forKey:#"XCTestObserverClass"];
}
}
#endif
Here's another solution that avoids having to edit your AppDelegate
UIApplication+Instrumented.m (put this in your main target):
#implementation UIApplication (Instrumented)
#ifdef DEBUG
+ (void)load
{
NSString* key = #"XCTestObserverClass";
NSString* observers = [[NSUserDefaults standardUserDefaults] stringForKey:key];
observers = [NSString stringWithFormat:#"%#,%#", observers, #"XCTCoverageFlusher"];
[[NSUserDefaults standardUserDefaults] setValue:observers forKey:key];
}
- (void)xtc_gcov_flush
{
extern void __gcov_flush(void);
__gcov_flush();
}
#endif
#end
XCTCoverageFlusher.m (put this in your test target):
#interface XCTCoverageFlusher : XCTestObserver
#end
#implementation XCTCoverageFlusher
- (void) stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
SEL coverageFlusher = #selector(xtc_gcov_flush);
if ([application respondsToSelector:coverageFlusher])
{
objc_msgSend(application, coverageFlusher);
}
[application.delegate applicationWillTerminate:application];
}
#end
- (void)applicationWillTerminate:(UIApplication*)application must be defined in your application delegate, not in the observer class.
I didn't have any library problems. "-lgov" is not needed and you don't have to add any libraries. Coverage is supported directly by LLVM compiler.
The process for this is a little different if you're using Specta, since it does its own swizzling. The following is working for me:
Test Bundle:
#interface MyReporter : SPTNestedReporter // keeps the default reporter style
#end
#implementation MyReporter
- (void) stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
#end
AppDelegate:
- (void)applicationWillTerminate:(UIApplication *)application
{
#ifdef DEBUG
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
You then need to enable your custom reporter subclass by setting the environmental variable SPECTA_REPORTER_CLASS to MyReporter in the Run section of your main scheme.
GCOV Flush in -(void)applicationWillTerminate didn't work for me, I think because my App is running in Background.
I also set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES' but no gcda-Files.
Then I executed "__gcov_flush()" in -(void)tearDown from the TestClass, which gave me gcda-Files for my TestClass ;)
Then I created the following Function in my AppDelegate:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
+(void)gcovFlush;
#end
#implementation AppDelegate
+(void)gcovFlush{
extern void __gcov_flush(void);
__gcov_flush();
NSLog(#"%s - GCOV FLUSH!", __PRETTY_FUNCTION__);
}
#end
I called [AppDelegate gcovFlush] in my -(void)tearDown and voilá, there are my gcda Files ;)
I hope this helps, bye Chris

What are the __gm_weak means in iOS

#interface Demo2ViewController () <GMGridViewDataSource, GMGridViewSortingDelegate, GMGridViewTransformationDelegate>
{
__gm_weak GMGridView *_gmGridView1;
__gm_weak GMGridView *_gmGridView2;
__gm_weak UIButton *_buttonOptionsGrid1;
__gm_weak UIButton *_buttonOptionsGrid2;
UIPopoverController *_popOverController;
UIViewController *_optionsController1;
UIViewController *_optionsController2;
}
Trying out the GMGridview, and saw this __gm_weak GMGridView *_gmGridView1;. Anyone know what __gm_weak means and when do you use __? Thanks in advance.
those declarations are not common, they are defined in the GMGridView sources:
//
// ARC on iOS 4 and 5
//
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_5_0 && !defined (GM_DONT_USE_ARC_WEAK_FEATURE)
#define __gm_weak __weak
#else
#define __gm_weak __unsafe_unretained
#endif
as far as the double underscore usage, i'd advise against it. just consider it reserved for the implementation (e.g. toolchain) and use a normal identifier.

Is there any way to get the neat Objective-C literal indexing feature in Xcode 4.4?

I read all about the new Objective-C literals, and used Xcode to convert my old code, but the indexing code didn't change. I changed it by hand but then it wouldn't compile. I saw a post that said we have to wait until iOS 6, but I want the indexing NOW!
Is there any solution?
Well, there is a way to do it! Add the indexing methods as a category to NSArray and NSDictionary, and you can get the feature for most of the classes you'd want it for. You can read up on ObjectiveC literals here. And thanks to James Webster's solution for #YES and #NO you can use them properly in your projects now too! (the technique)
1) Create the Interface files
// NSArray+Indexing.h
#if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
#interface NSArray (Indexing)
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
#end
#interface NSMutableArray (Indexing)
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
#end
// NSDictionary+Indexing.h
#interface NSDictionary (Indexing)
- (id)objectForKeyedSubscript:(id)key;
#end
#interface NSMutableDictionary (Indexing)
- (void)setObject:(id)obj forKeyedSubscript:(id)key;
#end
#endif
2) Create the Implementation files // see edit below before doing this - you can skip this
// NSArray+Indexing.m
#if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
#import "NSArray+Indexing.h"
#implementation NSArray (Indexing)
- (id)objectAtIndexedSubscript:(NSUInteger)idx
{
return [self objectAtIndex:idx];
}
#end
#implementation NSMutableArray (Indexing)
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
[self replaceObjectAtIndex:idx withObject:obj];
}
#end
// NSMutableDictionary+Indexing.m
#implementation NSDictionary (Indexing)
- (id)objectForKeyedSubscript:(id)key
{
return [self objectForKey:key];
}
#end
#implementation NSMutableDictionary (Indexing)
- (void)setObject:(id)obj forKeyedSubscript:(id)key
{
[self setObject:obj forKey:key];
}
#end
#endif
3) Add the Interface files to your pch file for global use, or add them as needed to .m files
// Add to PCH file
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
...
#if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
// New Indexing
#import "NSDictionary+Indexing.h"
#import "NSArray+Indexing.h"
// Provided by James Webster on StackOverFlow
#if __has_feature(objc_bool)
#undef YES
#undef NO
#define YES __objc_yes
#define NO __objc_no
#endif
#endif
#endif
#endif
4) Rebuild, then add the below files to verify that it all works
// Test Example
{
NSMutableArray *a = [NSMutableArray arrayWithArray:#[ #"a", #"b", #"c" ]];
NSLog(#"%#", a[1]);
a[1] = #"foo";
NSLog(#"a: %#", a);
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:#{ #"key" : #"object" }];
NSLog(#"%#", dict[#"key"]);
dict[#"key"] = #"New Object";
dict[#"newKey"] = #"WOW a new object";
NSLog(#"dict: %#", dict);
NSLog(#" %# %#", #YES, #NO );
}
EDIT: Well, according to a key llvm/clang Apple engineer, there is a library that already gets linked in with the implementations, so you just need the interface file:
Date: Mon, 20 Aug 2012 15:16:43 -0700
From: Greg Parker
To: ...
Subject: Re: How to make Obj-C collection subscripting work on iOS 5?
...
As an experiment I added the category #interface for these methods, but not the #implementation — the app still ran fine (at least in the 5.1 simulator)
The compiler emits the same calls. The magic is in the increasingly inaccurately named libarclite ("It's Not Just For ARC Anymore™"), which adds implementations of the subscripting methods at runtime if they don't already exist.
IIRC there are some subscript-able classes that libarclite does not upgrade (NSOrderedSet, maybe?) so you still need to test thoroughly on older deployment targets.

Extend iTunesApplication class with Categories

I am just learning how to use ScriptingBridges. I made a method that slowly fades the volume on iTunes, and would like to make it a category so I can do the following:
iTunesApplication* iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
[iTunes lowerVolume:50 speed:1];
I made another category for NSSpeechSynthesizer that works, but I can't get this one to. I keep getting the following build error:
"_OBJC_CLASS_$_iTunesApplication", referenced from:
l_OBJC_$_CATEGORY_iTunesApplication_$_iTunesApplicationAdditions in iTunesApplication.o
objc-class-ref-to-iTunesApplication in iTunesApplication.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
Is there something special I can do to make it work since I can't include the symbols?
Thanks,
Ryan Pendleton
UPDATE:
I only found one solution, which is below. It involves MethodSwizzling, so I'm open to better answers, but for now it's all I have.
The solution I found was to use the Objective-C runtime API. I'm sure there's a better way to organize this, but here's how I did it:
Here are my .h and .m files for creating the category. Notice how lowerVolume is not an actual method, but a C function with the arguments id self, and SEL _CMD. You'll also notice a setupCategories function. We'll call that later.
// iTunes+Volume.h
#import <objc/runtime.h>
#import "iTunes.h"
void lowerVolume(id self, SEL _cmd, int dest, float speed);
void setupCategories();
#interface iTunesApplication (Volume)
- (void)lowerVolume:(int)dest speed:(float)speed;
#end
// iTunes+Volume.m
#import "iTunes+Volume.h"
void lowerVolume(id self, SEL _cmd, int dest, float speed)
{
NSLog(#"Lower Volume: %i, %f", dest, speed);
}
void setupCategories()
{
id object = [[SBApplication alloc] initWithBundleIdentifier:#"com.apple.iTunes"];
Class class = [object class];
[object release];
class_addMethod(class, #selector(lowerVolume:speed:), (IMP)lowerVolume, "#:if");
}
Now that I've made the functions, I need to actually add them to the scripting bridge class using the Objective-C runtime API. I'll do this in main.m to make sure that the methods are ready to be used when the run loop starts.
// main.m
#import <Cocoa/Cocoa.h>
#import "iTunes+Volume.h"
int main(int argc, char *argv[])
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
setupCategories();
return NSApplicationMain(argc, (const char **) argv);
[pool drain];
}
Now, I can use my method wherever I want as long as I include the header files:
- (void)mute
{
iTunesApplication* iTunes = [[SBApplication alloc] initWithBundleIdentifier:#"com.apple.iTunes"];
[iTunes lowerVolume:0 speed:1];
[iTunes release];
}
If any of this doesn't make sense, just tell me and I'll try to explain it better.
I think you need to include -framework ScriptingBridge to your gcc arguments. That got it to compile for me!
As noted above, you can't easily do a category on iTunesApplication because it doesn't exist at compile time, and also because the runtime class name is ITunesApplication (capital "I").
The best solution I've found is to do your category on the class that DOES exist, SBApplication. Here's the code I tested that works and does what the original example was trying to do:
// SBApplication+Extensions.h
#import ScriptingBridge;
#interface SBApplication (Extensions)
- (void)lowerVolume:(int)dest speed:(float)speed;
#end
// SBApplication+Extensions.m
#import "iTunes.h"
#import "SBApplication+Extensions.h"
#implementation SBApplication (Extensions)
- (void)lowerVolume:(int)dest speed:(float)speed
{
NSLog(#"Lower Volume: %i, %f", dest, speed);
}
#end
// Caller, say in AppDelegate
#import "SBApplication+Extensions.h"
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
iTunesApplication *iTunesApp =
[SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
[iTunesApp lowerVolume:4 speed:3.3f];
}