Using RSpec for iPhone Controllers - ruby

I'm finally getting the hang of RSpec after spending a couple of hours over the weekend. Now I'm stuck trying to figure out how to assert that parameters are indeed passed into the controller. I'm following the Bowled over by Ruby/Cocoa example and adapting it for the iPhone SDK. I've done a more detailed writeup of my progress on my blog so I'll defer there for the entire story. In short I've followed the tutorial all the way up to where you need to pass the pin value from the text field into the Bowling object. RSpec keeps complaining that, "Spec::Mocks::MockExpectationError in ‘OSX::BowlingController should send the pin value to the bowling object’
Mock ‘Bowling’ expected :roll with (10) but received it with (no args)
./test/bowling_controller_spec.rb:38:” Even as I'm certain that I'm passing a value in. Here's my code. Can someone tell me where I'm going wrong?
bowling_controller_spec.rb
require File.dirname(__FILE__) + '/test_helper'
require "BowlingController.bundle"
OSX::ns_import :BowlingController
include OSX
describe BowlingController do
before(:each) do
#controller = BowlingController.new
#bowling = mock('Bowling')
#text_field = mock('Pins')
#text_field.stub!(:intValue).and_return(10)
#controller.pins = #text_field
end
it "should roll a ball" do
#controller.roll
end
it "should roll a ball and get the value from the pins outlet" do
#text_field.should_receive(:intValue).and_return(0)
#controller.roll
end
it "should be an OSX::NSObject" do
#controller.is_a?(OSX::NSObject).should == true
end
it "should have an outlet to a bowling object" do
#controller.bowling = #bowling
end
it "should send the pin value to the bowling object" do
#controller.bowling = #bowling
#bowling.should_receive(:roll).with(10)
#controller.roll
end
end
BowlingController.h
#import <Foundation/Foundation.h>
#class UITextField;
#class Bowling;
#interface BowlingController : NSObject {
UITextField* pins;
Bowling* bowling;
}
#property (nonatomic, retain) UITextField* pins;
#property (nonatomic, retain) Bowling* bowling;
-(void) roll;
#end
BowlingController.m
#import "BowlingController.h"
#import "Bowling.h"
#implementation BowlingController
#synthesize pins;
#synthesize bowling;
-(void) roll{
[self.bowling roll:[self.pins intValue]];
}
#end
// This initialization function gets called when we import the Ruby module.
// It doesn't need to do anything because the RubyCocoa bridge will do
// all the initialization work.
// The rbiphonetest test framework automatically generates bundles for
// each objective-c class containing the following line. These
// can be used by your tests.
void Init_BowlingController() { }
Bowling.h
#import <Foundation/Foundation.h>
#interface Bowling : NSObject {
}
- (void) roll:(int) pins;
#end
Bowling.m
#import "Bowling.h"
#implementation Bowling
- (void) roll:(int) pins{
}
#end
// This initialization function gets called when we import the Ruby module.
// It doesn't need to do anything because the RubyCocoa bridge will do
// all the initialization work.
// The rbiphonetest test framework automatically generates bundles for
// each objective-c class containing the following line. These
// can be used by your tests.
void Init_Bowling() { }

RubyCocoa is not supported at all on the iPhone. There is no bridge support library, and I do not believe there is any ruby interpreter on the phone.
You might be able to get it working in the simulator, it will not stop you from using OS X only libraries if you really try, but that still will not make it work on the iPhone.
If you really want to use RubyCocoa on the iPhone you will need to build ruby as a static library and port the bridge to the phone, which is doable, but would probably be very difficult.

Howdy! Though I'm not familiar with how Ruby/Cocoa wraps foreign method calls- or with Objective C for that matter- the first place that would seem likely to disconnect under test is passing in a Ruby mock to the natively implemented controller. In the bowling tutorial, the ruby controller proxy is exposes its interface to the Cocoa bridge while in this implementation the proxy wraps an exposed Cocoa interface. There might be an issue, then, when substituting a ruby mock for a native field versus a ruby mock for a ruby field.
The roll() test for the pins succeeds, though, so it's possible that messages are being passed correctly but arguments are mangled or dropped.
This probably isn't much help, but it's an interesting problem. Good luck with the project!

Related

Hook in CLLocationManagerDelegate protocol

I have a problem from 3 days :( I want to hook in CLLocationManagerDelegate protocol this method:
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
I tried everything but without success. I know how to hook into class or framework but I can't find a solution to hook a Delegate.
Please help me!
Thanks
Hooking requires you to provide objective-C class you would like to hook. This is what Class type is for. One way to get obj-c class is by name via objc_getClass function. But in your case as I understand it correctly you don't have the name. You want to hook every class that conforms to CLLocationManagerDelegate protocol and implements specific method. Here is what you can do.
You can obtain every registered obj-C class and search for those which conform toCLLocationManagerDelegate protocol like this:
static IMP original_didUpdateLocations;
void replaced_didUpdateLocations(id self, SEL _cmd, CLLocationManager* manager, NSArray* locations)
{
NSLog(#"%# did update locations to %#", manager, locations);
original_didUpdateLocations(self, _cmd, manager, locations);
}
...
#import <objc/runtime.h>
int numClasses = objc_getClassList(NULL, 0);
Class* list = (Class*)malloc(sizeof(Class) * numClasses);
objc_getClassList(list, numClasses);
for (int i = 0; i < numClasses; i++)
{
if (class_conformsToProtocol(list[i], #protocol(CLLocationManagerDelegate)) &&
class_getInstanceMethod(list[i], #selector(locationManager:didUpdateLocations:)))
{
MSHookMessageEx(list[i], #selector(locationManager:didUpdateLocations:), (IMP)replaced_didUpdateLocations, (IMP*)&original_didUpdateLocations);
}
}
free(list);
We need to know how many classes there is. objc_getClassList(NULL, 0) returns number of all registered classes.
Allocating memory with malloc(sizeof(Class) * numClasses) and filling it with objects of type Class using objc_getClassList(list, numClasses).
Searching through all these classes for those which conform to CLLocationManagerDelegate protocol and implement locationManager:didUpdateLocations: method. If we found one we are hooking it with our own implementation.
In our own implementation we are printing some debug message and calling original implementation before returning. Of course, you can do whatever you what, this is just an example.
Freeing allocated memory using free(list).

How to send a message to the Master Class of a class?

Quick Question:
I am creating an object from the MainWindowController:
about = [[About alloc]init];
In the Class About I do my Init:
-(id)init{
if(!_viewAbout){
[NSBundle loadNibNamed:#"About" owner:self];
[NSApp beginSheet:self.viewAbout modalForWindow:*?????* modalDelegate:self didEndSelector:NULL contextInfo:NULL];
}
return self;
}
My problem is that the Window is created in the MainWindowController. My question is how to call/send a message to the creator of the class if the class itself doesn't know the master class?
If I understand you correctly, most classes have self.superclass and just super, like
[super someMethod....
or
[self.superclass blegh....
Or are you asking for the class that creates another class ? If that is the case, you need to declare the creator class inside the other one, some (id) variable would do the trick.
But the most popular design pattern on the mac is the delegate pattern, and once you start using that you will love it. Declaring a delegate is usually the way Cocoa and UIKit do things, but other programming languages might not. Obj-C doesn't have any magic variables like python f.ex. has. Either you have a delegate or you have a declared variable which you would set right after the init/alloc stuff.
Also your (init) call doesn't look right. Usually it looks like :
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Here you could declare your setting.
}
return self;
}
But my opinion is that if you are declaring a singular pattern, you would call a specific method in your class, like you do with so many classes on the iOS/Cocoa, like :
[someclass DefaultClass]
This would be your init class where you would do init, unless the class had been declared before and then you would just return the object.

How to manage multiple windows in Cocoa apps with Interface Builder

I have this application with 3 classes: AppController, Profile, ProfileBuilder. I also need 3 windows: one for each class. I tried keeping all 3 as subclasses of NSObject and applying initWithNibName to an NSWindowController class WindowController variable but when I tried outputting some values on each window it wouldn't work, and also the window resulted as null using NSLog. I was wondering what was the best way to manage multiple windows, perhaps all from a same class like an AppWindowsController involving as least as possible specific code in the other classes, and keeping, if possible, the other classes as subclasses of NSObject and not NSWindowController. So if there is, maybe a way to control the behavior of the windows remotely, adding as least as possible code inside the specific classes, just to keep them as clear as possible and uniquely focused on their content. Thanks, hope I made myself clear, I'm actually pretty new to the Cocoa framework.
You should be able to load the nib files with your windows in an init method for your different classes. For example, in Profile, you could do something like this:
-(id)init {
if (self = [super init]) {
NSArray *array;
BOOL success = [[NSBundle mainBundle] loadNibNamed:#"ProfileWindow" owner: self topLevelObjects:&array];
if (success) {
for (id obj in array) {
if ([obj isKindOfClass:[NSWindow class]]) {
self.profileWindow = obj;
}
}
[self.profileWindow makeKeyAndOrderFront:self];
}
}
return self;
}
profileWindow is a property (typed as strong). In the xib file, I set the File's Owner to Profile.
I just like to improve the solution of rdelmar.
You don't need to iterate over the array to find the NSWindow class.
If you define profileWindow as an outlet and connect it in the IB, the call
[[NSBundle mainBundle] loadNibNamed:#"ProfileWindow" owner:self topLevelObjects:&array];
will assign the window object to your outlet, the array stuff is not required.
The key here is the owner object which act as interface. In the IB you can define the class type of the owner and if so, see its outlets.

I have 30 annotations, and growing. Looking for a simpler way to code this?

I am coding multiple annotations into a project. Currently I have 30 annotations, and growing. I'm wondering if there is a simplier way of having to create a annotation.h and annotation.m classes for each single annotation.
Currently in my map view controller, I create the annotation objects and place them in an array, which has been working well for me but as you could imagine, its a lot of code to manage once you have tons of annotations, not to mention tons of classes.
So for example, one of the annotation classes looks like this:
Annotation.h:
//Annotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface Annotation : NSObject {
}
#end
Annotation.m:
//Annotation.m
#import "Annotation.h"
#implementation Annotation
-(CLLocationCoordinate2D)coordinate;
{
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = -45.866416;
theCoordinate.longitude = 170.519931;
return theCoordinate;
}
-(NSString *)title
{
return #"Title";
}
-(NSString *)subtitle
{
return #"Subtitle";
}
-(void)dealloc
{
[super dealloc];
}
#end
I'm thinking of reading in a CSV file with all the annotations would be the best way to go, any option I choose will result in me rewriting a lot of code, which is why I'm asking this question before I do anything. Does anyone have any suggestions?
The MapCallouts sample app unfortunately doesn't give a good example of how to implement a generic annotation class.
Your class that implements the MKAnnotation protocol can provide a settable coordinate property or a custom init method that takes the coordinates.
However, since you're using iOS 4.0, an easier option is to just use the pre-defined MKPointAnnotation class that provides properties that you can set. For example:
MKPointAnnotation *annot = [[MKPointAnnotation alloc] init];
annot.title = #"Title";
annot.subtitle = #"Subtitle";
annot.coordinate = CLLocationCoordinate2DMake(-45.866416, 170.519931);
[mapView addAnnotation:annot];
[annot release];
The annotation data can of course come from anywhere and you can loop through the data to create the annotations on the map.

Methods in super that will be subclassed anyway (Cocoa)

Sorry if this is a repost but I couldn't quite search for it because I can't explain it in a few words. I have a super class with lots of methods but they will always (not all of them) be subclassed. From the super I need to run those methods. I could either leave the methods in super empty or I could just not type them in super but call them anyway like so [self myMethod] and it will call my subclassed method even if it doesn't exist in super. This works but Xcode gives me an error though. 'superclass' may not respond to '-subclassmethod'
What should I do so I won't get the warnings?
I prefer to define the unimplemented methods in the superclass like this:
#interface GLObject : NSObject {}
- (id)someSubclassProvidedMethod;
#end
#implementation GLObject
- (id)someSubclassProvidedMethod {
[self doesNotRecognizeSelector: _cmd];
}
#end
It's almost entirely redundant, because the Objective-C runtime would eventually call -doesNotRecognizeSelector: if I didn't define the method at all. But because I do define it, it's in the class's interface which both keeps the compiler happy and provides me with some documentation.
Rather than the superclass, you could declare the methods in a protocol, what is called a "interface" in other languages.
#protocol MyProtocol
-(id)myMethodWith:(id)arg;
#end
Change the type declaration of the variables to declare that the object conforms to the protocol.
-(id)doStuffWith:(SuperClass <MyProtocol> *)aThing and:(id)another {
return [aThing myMethodWith:another]
}
Note that you won't be able to pass an instance of your SuperClass to doStuffWith:and:, since it won't implement MyProtocol, but it sounds like that's what you want.
My solution was a little weird, but here it is:
#protocol JSDog <NSObject>
- (void)yipe;
#end
#interface JSDog : NSObject
#end
#implementation JSDog
+ (void)initialize {
if ([self isSubclassOfClass:[JSDog class]] && ![self conformsToProtocol:#protocol(JSDog)]) {
NSAssert(false, #"Subclasses of JSDog must conform to <JSDog>.");
}
}
#end
Having a protocol with the same name as a class is precedented in NSObject. Because methods in a formal protocol a by default #required, you will be protected on both ends: in compile-time, if your JSDog subclass purports to conform to <JSDog>, but doesn't implement -yipe, you will receive an error; at runtime, if your subclass does not claim to conform with <JSDog>, you will receive a warning when the subclass is instantiated.
I lately like using NSAssert for this task:
- (BOOL)proceedForVersion:(int)versionInteger
{
NSAssert(false, #"This method needs to be overridden in a subclass of iMBApertureAbstractParser");
return NO;
}

Resources