Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Nutter

macrumors 6502
Original poster
Mar 31, 2005
432
0
London, England
Hello,

I'm a beginner Objective-C programmer, and I've just run into a problem when developing my first major project.

My program consists of several objects, arranged in a heirarchy (although not inheriting from one another).

Class A --> Class B --> Class C

Objects of Class A own several objects of Class B, which in turn own several objects of Class C.

However, Class B objects also contain a pointer to the Class A object that owns them, and Class C objects contain a pointer to the Class B object that owns them.

I had put the following lines in ClassA.h:
#import "ClassB.h"

ClassB.h:
#import "ClassA.h"
#import "ClassC.h"

and ClassC.h:
#import "ClassB.h"

However, Xcode was giving me parse errors for my definition of a ClassB pointer in ClassC.h.

I eventually realised that, because I'm chaining together #import statements and the compiler basically sticks it all together, ClassA.h actually contains ClassC, then ClassB, then ClassA, in that order, which of course means that ClassC is trying to reference ClassB before it has been defined.

The only way I could find to get around this was to add an "@class ClassB" line to ClassC.h, and a "@class ClassA" line to "ClassB.h", instead of the #import "ClassB.h" and #import "ClassA.h" statements.

My question (after such a long-winded run up) is whether this is the correct procedure? Am I missing something? Steve Kochan's Objective-C book doesn't mention this issue, so I'm not sure what I should do in the future. Surely if at some point my Class C objects needed to reference actual instance methods from Class B, rather than just defining pointers, the @class directive wouldn't be enough?

Any advice would be appreciated!

Thanks.
 

caveman_uk

Guest
Feb 17, 2003
2,390
1
Hitchin, Herts, UK
I'm a beginner too so this maybe completely wrong but could you use type 'id' for the parent pointer? Objective-C doesn't really care at compile time if the object responds to a message or not. It might give you a warning though. So my understanding is you can send a message to an id even if 'id' doesn't know about it at compile time.

Someone please correct me if I'm talking out of my bottom!
 

robbieduncan

Moderator emeritus
Jul 24, 2002
25,611
893
Harrogate
Reread what you said!

Do you need the circular pointers? If so do you need them to be strongly typed?

As mentioned above id will get round all this, although may generate compiler warnings (it will all be OK at runtime).

Otherwise the @class solution is acceptable.

If you use the id solution you can probably get round the compiler warnings by using protocol so your variable in Class B would look like id<ClassASelectors> owner; and you would have a ClassASelectors.h file that contains the selectors you want callable from Class B and Class A would be delcared something like @class ClassA <ClassASelectors>
 

Nutter

macrumors 6502
Original poster
Mar 31, 2005
432
0
London, England
robbieduncan said:
If you use the id solution you can probably get round the compiler warnings by using protocol so your variable in Class B would look like id<ClassASelectors> owner; and you would have a ClassASelectors.h file that contains the selectors you want callable from Class B and Class A would be delcared something like @class ClassA <ClassASelectors>

That went over my head!

Thanks for the id suggestion. I don't need these pointers to be statically typed, but I don't need them to be dynamically typed either, so isn't it better practice to statically type them?

What would happen if I wanted to call a ClassB method from ClassC.m? Would I need to add #import "ClassB" to ClassC.h?
 

robbieduncan

Moderator emeritus
Jul 24, 2002
25,611
893
Harrogate
Nutter said:
That went over my head!

Thanks for the id suggestion. I don't need these pointers to be statically typed, but I don't need them to be dynamically typed either, so isn't it better practice to statically type them?

What would happen if I wanted to call a ClassB method from ClassC.m? Would I need to add #import "ClassB" to ClassC.h?

Unless you are looking to call a Class method (this is confusing as all you classes are called ClassX!), i.e. one declared as + selectorName (note in Obj-C we should be talking about selectors, methods are Java terminology) you can only call against an instance of that class. You can call pass any message you like to any class in Obj-C, they may not respond to it though!

This is where you get your compiler warnings. The compiler will say something like "Class may not respond to selector ....". It will still compile and will work at run-time. What you are loosing is the compile time check that you have not made a spelling error. This is what my protocol solution was going to get around. I am at work right no, but in a few hours I can knock up some simple examples and post them up somewhere...
 

caveman_uk

Guest
Feb 17, 2003
2,390
1
Hitchin, Herts, UK
Nutter said:
Thanks for the id suggestion. I don't need these pointers to be statically typed, but I don't need them to be dynamically typed either, so isn't it better practice to statically type them?
I was always taught to give as strict typing as you could but in this case it's making life difficult for you when it needn't be. I came from a Pascal background so I didn't have much choice though.
What would happen if I wanted to call a ClassB method from ClassC.m? Would I need to add #import "ClassB" to ClassC.h?
No. The runtime will try to send the message to the object and it's up to it then if it does anything. You can send a message to a 'nil' pointer and it's not an error. You can pass 'self' from the parent to the child class when it's initialized and assign the pointer in the child then. At that point the child can refer to the instance methods/selectors in the parent. At least that's what I think would happen. ;)
 

Nutter

macrumors 6502
Original poster
Mar 31, 2005
432
0
London, England
Thanks for the replies, guys.

At the moment, using @class and statically typing works fine.

My classes aren't actually called ClassA, ClassB, and ClassC, I was just calling them that for the sake of generalisation!

I'm actually programming a Sudoku Solver. Heard of Sudoku?
http://www.timesonline.co.uk/sudoku
 

robbieduncan

Moderator emeritus
Jul 24, 2002
25,611
893
Harrogate
OK I'm home :) I've attached a really simple XCode project. It does nothing! It has ClassA and ClassB. ClassA contains an instance variable of type ClassB* and ClassB has an instance variable of type id (which would be pointed back at ClassA). It also has a protocol declaration showing how you can get the effect of static typing without the @Class declarations and a protocol instead.

Any questions?
 

Attachments

  • UseProtocols.zip
    16.8 KB · Views: 394

Nutter

macrumors 6502
Original poster
Mar 31, 2005
432
0
London, England
Thanks for that! Sorry it's taken me so long to reply.

So, using protocols like that allows you to use the id type, while still having the compiler check that the receiver can respond to the message.

My only question is: what is the benefit of doing it that way, rather than using the @class directive?

As a beginner, I find the categories and protocols a bit daunting. I understand what they can do, in theory, I'm just not sure when I should be using them. Obviously for simpler programs it's not necessary to bother using them, but I would like to get into good habits before it is too late...!
 

robbieduncan

Moderator emeritus
Jul 24, 2002
25,611
893
Harrogate
Nutter said:
Thanks for that! Sorry it's taken me so long to reply.

So, using protocols like that allows you to use the id type, while still having the compiler check that the receiver can respond to the message.

My only question is: what is the benefit of doing it that way, rather than using the @class directive?

As a beginner, I find the categories and protocols a bit daunting. I understand what they can do, in theory, I'm just not sure when I should be using them. Obviously for simpler programs it's not necessary to bother using them, but I would like to get into good habits before it is too late...!

No problem! I don't expect people to read code instantly :)

In this case there is no clear advantage in using a protocol over a class directive. I personally prefer the protocol method as it declares the messages you are going to use as opposed to the class declaration which, unless I am mistaken, simply tells the compiler that the class is declared elsewhere and not to complain!* Also by using a protocol you can replace the owner class (Class A) without needing to change the child class (Class B). As long as the new owner/container class implements the protocol this will be fine.

Protocols are really cool when you start dynamically loading code (think plugins) as you can ensure that the dynamically loaded class responds to some messages without knowing anything else about it.

*I just tried this. The compiler does still check that the messages called exist in the class (i.e. even though you don't import ClassA.h declaring ClassA via @class seems to allow the compiler to check that the messages exist when called from ClassB.m). So it looks like the @protocol thing is a little less useful/important than I thought. The reuse/changing owner class thing still stands.
 

Nutter

macrumors 6502
Original poster
Mar 31, 2005
432
0
London, England
Ah, I see. Using @class doesn't let you statically type, it just lets you call the Class by name. So using a protocol is better.

Is this a very common situation to have, or is there a different way of designing object interactions that would avoid this problem?

I'm beginning to see how protocols can be useful. What about categories? Are they only useful for expanding a class without changing the source code?
 

robbieduncan

Moderator emeritus
Jul 24, 2002
25,611
893
Harrogate
Nutter said:
Ah, I see. Using @class doesn't let you statically type, it just lets you call the Class by name. So using a protocol is better.

Is this a very common situation to have, or is there a different way of designing object interactions that would avoid this problem?

I'm beginning to see how protocols can be useful. What about categories? Are they only useful for expanding a class without changing the source code?

See my correction above. It looks like you get full static typing via @class. Well as full as you get from Obj-C, you can pass messages that the class does not appear to respond to!

I don't encounter this very often and it tends to be slightly bad design. Normally you have one class that sends messages to others and gets responses but the target class does not normally initiate a message. Note that you should be using a MVC (model-view-controller) pattern. If you view ClassA as the controller and ClassB as part of the model then the only time that ClassB would want to send a message to ClassA is if the model data changes. Using bindings (or at least key-view observing) you can have the runtime do this work for you meaning you don't need the backwards pointer.

Catagories are only used to extend other classes. They can be useful even with your own classes as they allow you to split your class into a number of source files which can make managing your code easier. They are really useful for adding methods to API classes.
 

Nutter

macrumors 6502
Original poster
Mar 31, 2005
432
0
London, England
Are you sure? The compiler gives me messages along the lines of "ClassA may not respond to selector:". I think you were right originally when you said that using @class just tells the compiler to shut up and get on with it.

At the moment I'm working through Steve Kochan's Objective-C book, and am just starting to use the Foundation Framework. I'm not using bindings or any of advanced features of Cocoa.

When I designed my program, I intended the heirarchy to work in one direction only, as you describe. But I realised that the objects at the bottom of the heirarchy, having performed a certain change on their own state, would have to indicate this to the object at the top of the heirarchy. I thought it would be a good idea to establish a chain of messages going up the heirarchy, but perhaps I should change my design so that the objects can simply return this information to the object that called them.
 

Nutter

macrumors 6502
Original poster
Mar 31, 2005
432
0
London, England
Hmm, actually, I'm not sure if that will work.

The problem is that objects of ClassC are owned by more than one object of ClassB, which in turn is owned by more than one object of ClassA. When an object of ClassC records a certain change, I need all the objects above it in the heirarchy to record the change as well.

How can I do this except by sending a message to each of its owners? Is this something that Cocoa bindings would allow me to do (when I get to learning about that)?
 

robbieduncan

Moderator emeritus
Jul 24, 2002
25,611
893
Harrogate
I did a test with 2 new classes using @class instead of a protocol and got the expected "/Users/robbie/UseProtocols/ClassD.m:18: warning: `ClassC' may not respond to `-doSometing'". So it looks good to me.

In terms of the informing a group of other objects that the data has changed you have 2 options:

1) Before bindings (which is implemented via key-value observing) you would raise a notification and have all interested classes register for that notification in their init (or awakeFromNib if appropriate) methods.

2) Use key-value observing or bindings and get the runtime to automatically tell your objects when the value is changed. This requires your class to be fully key-value compliant. There are docs in this.

For the moment I would go with notifications as they are easy to set up and understand.
 

robbieduncan

Moderator emeritus
Jul 24, 2002
25,611
893
Harrogate
Nutter said:
Are notifications part of the Foundation Framework of the AppKit?

I'll look into this. Thanks for the tip.

Foundation and the AppKit are different things. The AppKit builds on Foundation for GUI apps. Look at NSNotification (which is in Foundation).

To register for a notification you do something like:

Code:
	// Register for notification.
	[[NSNotificationCenter defaultCenter] 
		addObserver:self 
		   selector:@selector(deleteMessage:) 
			   name:SMS_DELETE_MESSAGE_NOTIFICATION_NAME
			 object:nil];

deleteMessage: is the handler for the notification. It has a signature something like:

Code:
- (void) deleteMessage:(NSNotification *)aNotification
{
NSDictionary *userInfo = [notification userInfo];
...
}

The notification is raised via something like:

Code:
	NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
	[userInfo setObject:path 
				 forKey:SMS_DELETE_MESSAGE_PATH_KEY];
	NSNotification *deleteNotification = [NSNotification notificationWithName:SMS_DELETE_MESSAGE_NOTIFICATION_NAME 
																	   object:self 
																	 userInfo:userInfo];
	[[NSNotificationCenter defaultCenter] postNotification:deleteNotification];

You can use the userInfo dictionary to pass any amount of data between the poster and the receiver(s). Obviously anything in all caps above is a #define constant. I tend to put these in a separate .h file to ensure they are the same everywhere and that I don't make typing errors.
 

GeeYouEye

macrumors 68000
Dec 9, 2001
1,669
10
State of Denial
I think the problem is being made bigger than it actually is; the general thing to do is use @class in the .h files and use #import in the .m files.

So, if I'm understanding this right, the question is based on the following

@interface ClassA : NSObject
{
ClassB *instance1;
ClassB *instance2;
ClassB *instance3;
}
//do stuff
@end

@interface ClassB : NSObject
{
ClassA *owner;
ClassC *instance1;
ClassC *instance2;
ClassC *instance3;
}
//do stuff
@end

@interface ClassC : NSObject
{
ClassB *owner;
}
//do stuff
@end


The solution, and the correct one, is, yes, use the @class directives in the .h files, and the #imports in the .m files.

@class ClassB; in ClassA.h
@class ClassA; and @class ClassC; in ClassB.h
@class ClassB; in ClassC.h

#import "ClassB.h" in ClassA.m
#import "ClassA.h" and #import "ClassC.h" in ClassB.m
#import "ClassB.h" in ClassC.m

Hope that answers your question.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.