Methods in super that will be subclassed anyway (Cocoa) - 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;
}

Related

NSWindowController in Swift. Subclassing and initializing with Nib

In a test Swift project, I am subclassing NSWindowController. My NSWindowController subclass is designed to work with a particular Nib file. It is desirable, then, that when my window controller is initialized, the nib file is automatically loaded by the window controller instance. In Objective-C, this was achieved by doing:
#implementation MyWindowController
- (id)init {
self = [super initWithWindowNibName:"MyWindowNib"]
if (self) {
// whatever
}
return self
}
#end
Now, in Swift this is not possible: init() cannot call super.init(windowNibName:), because the later is declared not as a designated initializer, but as a convenience one by NSWindowController.
How can this be done in Swift? I don't see a strightforward way of doing it.
P.S.: I have seen other questions regarding this topic, but, as long as I've been able to understand, the solutions all point to initialize the Window Controller by calling init(windowNibName:). Please note that this is not the desired beheaviour. The Window Controller should be initialized with init(), and it should be the Window Controller itself who "picks up" its Nib file and loads it.
If you use the init() just to call super.init(windowNibName:), you could instead just override the windowNibName variable.
override var windowNibName: String {
get {
return "MyWindowNib"
}
}
Then there should be no need to mess with the initializers.
You can create your own convenience initializer instead:
override convenience init() {
self.init(windowNibName: "MyWindowNib")
}
You should instead opt in to replacing all designated initializers in your subclass, simply delegating to super where appropriate. Confer https://stackoverflow.com/a/24220904/1460929

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.

Xcode override all methods

I want override all methods of a subclass automatically on xcode, for example I have a class extended of UiViewControler, how I override all methods of UiViewController on xcode to be more or less well:
- (id) init
{
return [super init];
}
My intention with this is to log all methods to see when they are called, then my methods will be more or less well
- (id) init
{
[self log];
return [super init];
}
where log is as follow method:
-(void) log
{
NSLog(#"%#",[(NSString *) (NSArray *) [NSThread callStackSymbols][1] componentsSeparatedByString:#"-["][1]);
}
thanks a lot!
In this case you don't have to do anything. If you don't provide an implementation, then the superclass's implementation will be used.
Edited after the question was edited
If you put the log statement in the superclass's implementation then it doesn't matter what you do with your own initialiser.
Why?
One of the many conventions in Cocoa is that each class has a designated initialiser. All the other designated initialisers then call this initialiser. And when you subclass the class, then you create a new designated initialiser for the new class, and as part of the initialisation - this calls the superclass's designated initialiser.
Which is why you see NSObject subclass initialisers calling [super init], because NSObject's designated initialiser is init.
So, just call your logging method in the designated initialiser of your class, and as long as you follow the above convention, this initialiser will always be called by a subclass, and so your logging method will always be called.

How to add a method to an existing protocol in Cocoa?

I want to extend or add another method to an existing protocol. Although the protocol in particular is not important, this is what I am trying to do.
#protocol NSMatrixDelegate
- (void)myNewMethod:(id)sender;
#end
The compiler warns that I have a duplicate declaration of the same protocol. How would I do this properly?
Thanks.
You can't define categories for protocols. There are 2 ways around this:
use a new formal protocol
use an informal protocol and runtime checking
Formal Protocol
Defining a new formal protocol would look like this:
#protocol MyCustomMatrixDelegate <NSMatrixDelegate>
- (void) myNewMethod:(id)sender;
#end
Then you would make your custom class conform to <MyCustomMatrixDelegate> instead of <NSMatrixDelegate>. If you use this approach, there's something to be aware of: [self delegate] will likely be declared as id<NSMatrixDelegate>. This means that you can't do [[self delegate] myNewMethod:obj], because <NSMatrixDelegate> does not declare the myNewMethod: method.
The way around this is to retype the delegate object via casting. Maybe something like:
- (id<MyCustomMatrixDelegate>) customDelegate {
return (id<MyCustomMatrixDelegate>)[self delegate];
}
(However, you might want to do some type checking first, like:
if ([[self delegate] conformsToProtocol:#protocol(MyCustomMatrixDelegate)]) {
return (id<MyCustomMatrixDelegate>)[self delegate];
}
return nil;
)
And then you'd do:
[[self customDelegate] myNewMethod:obj];
Informal Protocol
This is really a fancy name for a category on NSObject:
#interface NSObject (MyCustomMatrixDelegate)
- (void) myNewMethod:(id)sender;
#end
Then you just don't implement the method. In your class that would send the method, you'd do:
if ([[self delegate] respondsToSelector:#selector(myNewMethod:)]) {
[[self delegate] myNewMethod:someSenderValue];
}

Using RSpec for iPhone Controllers

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!

Resources