Extend iTunesApplication class with Categories - objective-c

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];
}

Related

Trouble with calling a method in Objective C (Apple Documentation example)

I'm following along with Apple's "Programming with Objective C" document, the link being: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithObjects/WorkingwithObjects.html#//apple_ref/doc/uid/TP40011210-CH4-SW1
Anyways, I've gotten to the point where it ask for calling the sayHello method.
"Create a new XYZPerson instance using alloc and init, and then call the sayHello method."
#import <Foundation/Foundation.h>
#import "XYZPerson.h"
int main(int argc, const char * argv[]);
XYZPerson *firstPerson = [[XYZPerson alloc] init]; //Initializer element is not a lime-time constant
[firstPerson sayHello]; //No Visible #interface for 'XYZPerson' delcares the selector 'sayHello'
#implementation XYZPerson
- (void)sayHello {
[self saySomething:#"Hello, World"];
}
- (void)saySomething: (NSString *)greeting {
NSLog(#"%#", greeting);
}
#end
I believe I'm having a misunderstanding with how apple is explaining the work or just have no clue.
Wishing apple had these examples done for us to review over.
You need to put the code inside the main function. Right now you have the code just sitting in your file, outside of any function. It should be:
int main(int argc, const char * argv[]) {
XYZPerson *firstPerson = [[XYZPerson alloc] init];
[firstPerson sayHello];
}
Also, according to the docs you should have a separate main.m file that has your main function inside of it.
As you can only access public functions which are declared in .h file with the class object.
Kindly declare that function in .h file and it will solve your problem

Invoking function getting stuck in a recursive loop and never comes

I am learning Objective-C inheritance and my program is getting lost in a recursive loop and won't come out. It gets hung up when calling a getter function.
I am using XCode version: Version 6.2 (6C101)
My program is given below
Vehicle.h
#ifndef exercise_2_Vehicle_h
#define exercise_2_Vehicle_h
#import <Foundation/Foundation.h>
#interface Vehicle : NSObject
#property float speed;
-(void) start;
-(void) stop;
-(void) park;
#end
#endif
Vehicle.m
#import "Vehicle.h"
#implementation Vehicle
-(void) setSpeed:(float)speed {
self.speed = speed;
}
-(float) speed {
return self.speed;
}
-(void) start {
NSLog(#"Starting the vehicle");
}
-(void) stop {
NSLog(#"Stopping the vehicle");
}
-(void) park {
NSLog(#"Parking the vehicle");
}
#end
Car.h
#ifndef exercise_2_Car_h
#define exercise_2_Car_h
#import "Vehicle.h"
#interface Car : Vehicle
#property (nonatomic) NSString* make;
-(Car*) initMake: (NSString*) make;
-(NSString*) make;
#end
#endif
Car.m
#import "Car.h"
#implementation Car
-(Car*) initMake:(NSString *)make {
self = [super init];
if (self) {
self.make = make;
}
return self;
}
-(NSString*) make {
return self.make;
}
#end
main.m
#import <Foundation/Foundation.h>
#import "Car.h"
#import "Vehicle.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
// insert code here...
Car* car = [[[Car alloc] init] initMake: #"Camry"];
//[car setSpeed:45];
NSLog(#"The model initialized is ");
[car make];
// [car speed];
}
return 0;
}
The issue you have is caused by creating the property for speed:
#property float speed;
and overriding setSpeed: method.
When you create #property compiler adds two methods for you, in your example setSpeed and speed.
This command:
self.speed = speed;
is equal to:
[self setSpeed: speed];
and inside setSpeed you have this command again which cause the loop. In your example you can remove both methods (setSpeed and speed) because compiler will add it for you. If you need it because you want to do some customisation you should use _speed instead self.speed.
_speed is backed variable added by compiler when using #property.
Change your method to:
-(void) setSpeed:(float)speed {
_speed = speed;
}
to remove the infinite loop.
In the
- (NSString*)make;
use
return _make
instead. The same with the speed.
If you return "self.x" in a getter method, then it's going to try and call the method again because you're requesting it on self. XCode will automatically convert the properties into variables that can be accessed with an '_' character, so you don't need to do any extra work.
You could also ignore our advice and remove both the "speed" and "make" getter methods you have made, because XCode automagically creates them for you.

Cocoa Console Application - property not found on object of type

So I am quite new on OC programming, I come from Front-end background (i.e. HTML/CSS/JavaScript ...), so I understand basic concepts of programming :)
Basically I created a console application, with a simple FooClass.
FooClass.h
#import <Foundation/Foundation.h>
#interface FooClass : NSObject
#property (strong, nonatomic) NSString *username;
- (NSString *) username;
- (void) setUsername:(NSString *)username;
#end
FooClass.m
#import "FooClass.h"
#implementation FooClass
#synthesize username = _username;
- (instancetype) init
{
self = [super init];
if (self) {
}
return self;
}
- (NSString *) username
{
return _username;
}
- (void) setUsername:(NSString *)username
{
_username = username;
}
#end
And in the main.m file, where the app bootstraps.
#import <Foundation/Foundation.h>
#include "FooClass.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
// insert code here...
NSLog(#"Hello, World!");
FooClass *foo = [[FooClass alloc] init];
foo.username = #"a";
}
return 0;
}
XCode tells me that it cannot find property username on object of type FooClass. And I don't really have idea about it. Any one could help?
I am a bit late in posting the answer. Here are few things that you should consider.
Since you have a property username. You are not required to create methods for setters and getters. The compiler will create them for you. Simply remove the two statements.
No need to synthesize in .m as well.
Instead of #include use #import. import takes only one copy even if you try to add the file(s) directly or indirectly from other files as compared to include.

Objective-C - Having Trouble Following "Initializer Chain"

Okay. Tough to find the best starting point, here. The error XCode (4.3.2) in Lion is kicking back to me is:
Redefinition of 'a' with a different type
The author says when we declare this line (near the bottom of this page, in main)...
OwnedAppliance *a = [[OwnedAppliance alloc] init];
...that it should run fine. It doesn't. It kicks back the error above. I understand that, because OwnedAppliance has no init method in its implementation, the compiler will go up the hierarchy to OwnedAppliance's superclass, which is Appliance, and search for an init method there. It finds the overridden init, which contains only the following line...
[self initWithProductName:#"Unknown"];
...and runs that. Understood.
Ugh. Sorry, guys. I just tried to explain what I think might be happening. It took a dozen lines and I'd just scratched the surface. Rather than bore you with what I think is happening, I'll just ask:
What's going on with this code? Where does the initialization "path", for lack of a better term, end? Where does the redefinition (the error) occur?
/******************** Appliance.h ********************/
#import <Foundation/Foundation.h>
#interface Appliance : NSObject
{
NSString *productName;
int voltage;
}
#property (copy) NSString *productName;
#property int voltage;
-(id)init;
// Designated initializer
-(id)initWithProductName:(NSString *)pn;
...
#end
/******************** Appliance.m ********************/
#import "Appliance.h"
#implementation Appliance
#synthesize productName, voltage;
-(id)init
{
return [self initWithProductName:#"Unknown"];
}
-(id)initWithProductName:(NSString *)pn
{
self = [super init];
if (self) {
[self setProductName: pn];
[self setVoltage: 120];
}
return self;
...
#end
/******************** OwnedAppliance.h ********************/
#import "Appliance.h"
#interface OwnedAppliance : Appliance
{
NSMutableSet *ownerNames;
}
// Designated initializer
-(id)initWithProductName:(NSString *)pn
firstOwnerName:(NSString *)n;
...
#end
/******************** OwnedAppliance.m ********************/
#import "OwnedAppliance.h"
#implementation OwnedAppliance
-(id)initWithProductName:(NSString *)pn
firstOwnerName:(NSString *)n
{
self = [super initWithProductName:pn];
if (self) {
ownerNames = [[NSMutableSet alloc] init];
if (n) {
[ownerNames addObject:n];
}
}
return self;
}
-(id)initWithProductName:(NSString *)pn
{
return [self initWithProductName:pn
firstOwnerName:nil];
}
...
#end
/******************** main.m ********************/
#import <Foundation/Foundation.h>
#import "Appliance.h"
#import "OwnedAppliance.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
// Previously omitted problematic code:
Appliance *a = [[Appliance alloc] init];
NSLog(#"a is %#", a);
[a setProductName:#"Washing Machine"];
[a setVoltage:240];
NSLog(#"a is %#", a);
// The following line is where the error occurs:
OwnedAppliance *a = [[OwnedAppliance alloc] init];
...
}
return 0;
}
I've thought a lot about this question and how to ask it. I don't think it's a terribly dumb one. :) But my brain is fried from 9 hours of studying this stuff, so I apologize if this is a totally obvious question. TIA
EDIT: main() now contains the code that was actually causing the error. Thanks to Jacques for being good enough to catch it despite the omission.
The compiler's actually telling you that the variable itself, a, has been declared twice; the error has nothing to do with the assignment. Somehwhere else, in scope, you have another variable named a, which has a different type than OwnedAppliance *. Change the name(s) of one (or both) and the error will go away.

gcc compile errors in an basic example objc program

Hey all, I'm new to programming and going through an objective-c book to learn the language and programing fundamentals. I've looked through the code repeatedly, went back to the book's example, and attempted to understand the gcc comple errors. Here's my code:
#import <stdio.h>
#import <objc/Object.h>
#interface Point: Object
{
int xaxis;
int yaxis;
}
-(void) print;
-(void) setx: (int)x;
-(void) sety: (int)y;
#end
#implementation Point;
-(void) print
{
printf("(%i,%i)", xaxis, yaxis);
}
-(void) setx: (int) x
{
xaxis = x;
}
-(void) sety: (int) y
{
yaxis = y;
}
#end
int main (int argc, char *argv[])
{
Point *myPoint;
myPoint = [Point alloc];
myPoint = [myPoint init];
[myPoint setx: 4];
[myPoint sety: 5];
printf("The coordinates are: ");
[myPoint print];
printf("\n");
[myPoint free];
return 0;
}
Then the compile errors from gcc look like this:
urban:Desktop alex$ gcc point.m -o point -l objc
point.m: In function ‘main’:
point.m:38: warning: ‘Point’ may not respond to ‘+alloc’
point.m:38: warning: (Messages without a matching method signature
point.m:38: warning: will be assumed to return ‘id’ and accept
point.m:38: warning: ‘...’ as arguments.)
point.m:40: error: ‘mypoint’ undeclared (first use in this function)
point.m:40: error: (Each undeclared identifier is reported only once
point.m:40: error: for each function it appears in.)
point.m:49: warning: ‘Point’ may not respond to ‘-free’
Where am I going wrong?
btw I'm going through "Programming in Objective-C" by Stephen Kochan if you wanted to know.
First the base class should be NSObject, not Object
the normal way to do the initialization is to write the alloc and init in the same statement. You would typically have an -(id)init; method in your class:
-(id)init
{
if ( ( self = [super init] ) )
{
; // additional initialization goes here
}
return self;
}
and
int main (int argc, char *argv[])
{
Point *myPoint = [[Point alloc] init];
Properties are better used, then you get the setter and getter automatically generated for you
instead of
#interface Point: Object
{
int xaxis;
int yaxis;
}
write
#interface Point : NSObject
{
}
#property int xaxis;
#property int yaxis;
then when you assign you can either write
[myPoint setXaxis:4]
or
myPoint.xaxis = 4;
when you release the object write release, not free
[myPoint release];
hth
You have warnings and an error. The warnings seem to suggest that Object, which you are subclassing, doesn't implement alloc, init or free. Normally, on an Apple platform, you'd subclass NSObject, which does implement these, but without knowing which platform you're on, it's not possible to advise the correct option.
Secondly, you had a typo, but that now seems to be corrected. This
point.m:40: error: ‘mypoint’ undeclared (first use in this function)
suggests that you had mypoint in your code, rather than myPoint.
You forgot to include the header Foundation.h:
#import <Foundation/Foundation.h>