I'm fairly new to Objective-C and wondering if it's possible to type objects as their supertype without receiving compiler warnings when assigning them, or if there is a recognised way of achieving the same thing?
I realise that this is what type id is for but I have a base class with synthesized properties and if I try to use id I get a build error "request for member 'x' in something not a structure or union", presumably because dynamic typing is fine for sending messages to an object but not for accessing synthesized properties.
For example in Java I might have:
public abstract class A {
public function doSomething() {
//some func
}
}
public class B extends A {
public function doSomething() {
//override some func
}
}
public class C extends A {
public function doSomething() {
//override some func
}
}
//and in my main class:
A objB = new B();
A objC = new C();
//the purpose of all of this is so I can then do:
A objHolder;
objHolder = objB;
objHolder.doSomething();
objHolder = objC;
objHolder.doSomething();
I currently have the above working in Objective-C but with a compiler warning: "assignment from distinct Objective-C type"
OK, here is the Objective-C interfaces, I can add the implementations if you want. It's a composite pattern:
//AbstractLeafNode
#import <Foundation/Foundation.h>
#interface AbstractLeafNode : NSObject {
NSString* title;
AbstractLeafNode* parent;
}
#property (nonatomic, retain) NSString* title;
#property (nonatomic, retain) AbstractLeafNode* parent;
#end
//Page
#import "AbstractLeafNode.h"
#interface Page : AbstractLeafNode {
//there will be stuff here later!
}
#end
//Menu
#import "AbstractLeafNode.h"
#interface Menu : AbstractLeafNode {
NSMutableArray* aChildren;
}
- (void)addChild:(AbstractLeafNode *)node;
- (void)removeChild:(AbstractLeafNode *)node;
- (AbstractLeafNode *)getChildAtIndex:(NSUInteger)index;
- (AbstractLeafNode *)getLastChild;
- (NSMutableArray *)getTitles;
#end
// I'd then like to do something like (It works with a warning):
AbstractLeafNode* node;
Menu* menu = [[Menu alloc] init];
Page* page = [[Page alloc] init];
node = menu;
[node someMethod];
node = page;
[node someMethod];
// Because of the synthesized properties I can't do this:
id node;
// I can do this, but I suspect that if I wanted synthesized properties on the page or menu it would fail:
node = (AbstractLeafNode*)menu;
node = (AbstractLeadNode*)page;
Sorry, as I was editing the question I realised that I was trying to do it the wrong way round and assign an AbstractLeafNode to a Menu, so the compiler warning completely makes sense. No errors when assigning a Menu to an AbstractLeafNode.
I've been staring at this for too long!
Related
Consider the following code which tries to work with the non-NSObject class object __NSMessageBuilder:
- (Class)getTestClass {
Class class = objc_getClass("__NSMessageBuilder");
return class;
}
- (void)testNonNSObjectClass {
// case 1
Class class1 = objc_getClass("__NSMessageBuilder");
const char *name1 = class_getName(class1);
// case 2
Class class2 = [self getTestClass];
const char *name2 = class_getName(class2);
}
The first case works as expected and name1 contains the correct name of the class. However, the execution crashes on [self getTestClass] in case 2 with the following seemingly unrelated log output:
NSForwarding: warning: object 0x7fff76f18dc0 of class '__NSMessageBuilder' does not
implement doesNotRecognizeSelector: -- abort
Message from debugger: Terminated due to signal 9
This does not make any sense to me. What is going on here, and how can I fix it? The problem is not limited to __NSMessageBuilder, this also happens with other special classes like __ARCLite__ etc.
Most probably because you are using ARC. The compiler is adding retain/release messages to your code, and because your root class does not inherit from NSObject, the retain and release methods have not been implemented you get the error "class '__NSMessageBuilder' does not implement doesNotRecognizeSelector: -- abort".
I just made a test with the following code:
#import <Foundation/Foundation.h>
// RootClass.h
NS_ROOT_CLASS
#interface RootClass
#end
// RootClass.m
#import "RootClass.h"
#implementation RootClass
#end
// AppDelegate.m
#import "RootClass.h"
#import <objc/runtime.h>
#interface AppDelegate ()
#property (retain) IBOutlet NSWindow *window; // retain is important here because you will not use ARC in one of the tests
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self testNonNSObjectClass];
}
- (Class)getTestClass {
Class class = objc_getClass("RootClass");
return class;
}
- (void)testNonNSObjectClass {
// case 1
Class class1 = objc_getClass("RootClass");
const char *name1 = class_getName(class1);
// case 2
Class class2 = [self getTestClass];
const char *name2 = class_getName(class2);
}
If I run it with ARC it bombs like you found, but if you run this code without ARC (go to build phases and add the compiler flag -fno-objc-arc to AppDelegate.m) and you will not bomb.
// Marketplace.h
#import <Foundation/Foundation.h>
#import "Item.h"
#interface Marketplace : NSObject
+ (void)addItemToMarketplace:(Item *)newItem; // METHOD IN QUESTION
#end
// Marketplace.m
#import "Marketplace.h"
#interface Marketplace()
#property (strong, nonatomic) NSMutableArray *listOfItems;
#end
#implementation Marketplace
+ (void)addItemToMarketplace:(Item *)newItem // METHOD IN QUESTION
{
[self.listOfItems addObject:newItem]; // Raises 3 errors
}
#end
I have declared a class method addItemToMarketplace that takes in an object of type Item and adds this Item to the listOfItems property that I have declared in the interface of the implementation file (I am not sure that I want other classes to fiddle with this property). I have used this method in another class as such [Marketplace addItemToMarketplace:newItem]. I am not sure how to handle the three errors that are raised when I write [self.listOfItems addObject:newItem].
The 3 errors are as follows:
1. Member reference type 'struct objc_class *' is a pointer; maybe you meant to use '->'?
2. Definition of 'struct objc_class' must be imported from module 'ObjectiveC.runtime' before it is required
3. No member named 'listOfItems' in 'struct objc_class'
Making the change proposed in #1, which changes self.listOfItems to self->listOfItems, raises the error "Member reference base type 'Class' is not a structure or union"
Any help would be appreciated.
// NEW CHANGES!
After making some changes and following some suggestions I found on other sites, here's what I have so far:
// Marketplace.h
#import <Foundation/Foundation.h>
#import "Item.h"
#interface Marketplace : NSObject {}
+ (Marketplace *)sharedMarket;
- (void)addItemToMarketplace:(Item *)newItem;
#end
// Marketplace.m
#import "Marketplace.h"
#interface Marketplace()
#property (strong, nonatomic)NSMutableArray *listOfItems;
#end
static Marketplace *sharedMarketplace = nil;
#implementation Marketplace
+ (Marketplace *)sharedMarket
{
if (sharedMarketplace == nil) {
sharedMarketplace = [[super allocWithZone:NULL] init];
}
return sharedMarketplace;
}
- (void)addItemToMarketplace:(Item *)newItem
{
[self.listOfItems addObject:newItem];
}
- (id)init
{
if ( (self = [super init]) ) {
}
return self;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedMarket];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#end
Questions, comments, concerns?
Only the instance methods can operate on instance data. So you need either an instance or change the data to static too:
#interface Marketplace()
#end
static NSMutableArray *listOfItems; // static is implied, so not really necessary
#implementation Marketplace
+ (void)addItemToMarketplace:(Item *)newItem {
if (!listOfItems) listOfItems = [NSMutableArray array];
[listOfItems addObject:newItem];
}
#end
Note that there's no thread safety here.
The + prefix defines a class method. Within that method self refers to the class itself which is almost certainly not what you intend. The class does not have a listOfItems as that is a property available on each instance of the class.
It is not clear what you are attempting to do here. If you are coming from a C/C++ background perhaps you have assumed a different behavior for this method. Are you trying to add an item to a list managed by a particular instance of this class or to a list shared across all instances?
If you just have one instance of marketplace in your app consider using a singleton..
You then might add items to the single marketplace like this:
[[Marketplace theMarketplace] addItem:<*item*>]
Here's one way to set this up:
#interface Marketplace : NSObject
#property ( nonatomic ) NSArray * items ;
#end
#implementation Marketplace
static Marketplace * __marketplace ;
+(void)load
{
__marketplace = [ Marketplace new ] ;
}
+(instancetype)theMarketplace
{
return __marketplace ;
}
-(void)addItem:(Item*)item
{
self.items = [ ( self.items ?: #[] ) arrayByAddingObject:item ] ;
}
#end
(or in Swift)
class Marketplace
{
struct Static
{
static let marketplace = Marketplace()
}
var items:Array<Item> = [] ;
class func get() -> Marketplace { return Static.marketplace }
func addItem( item: Item ) { self.items += item }
}
I'm trying to get a better grip on the Factory Pattern as illustrated here:
http://www.oodesign.com/factory-pattern.html
The examples are in Java, and I'm not a very strong Java programmer. I mostly don't understand the Constructor product ... = cClass... String.class line. I think I've got the "concept," but are these two code blocks analogous?
Furthermore, is there an example in Cocoa Foundation that uses this pattern? The only one I can think of is in UIKit registering cell classes against a UITableView.
Java:
class ProductFactory
{
private HashMap m_RegisteredProducts = new HashMap();
public void registerProduct (String productID, Class productClass)
{
m_RegisteredProducts.put(productID, productClass);
}
public Product createProduct(String productID)
{
Class productClass = (Class)m_RegisteredProducts.get(productID);
Constructor productConstructor = cClass.getDeclaredConstructor(new Class[] { String.class });
return (Product)productConstructor.newInstance(new Object[] { });
}
}
Objective-C:
#interface ProductFactory : NSObject
- (void)registerProduct:(Class)productClass withIdentifier:(NSString *)identifier;
- (id)newProductForIdentifier:(NSString *)identifier;
#end
#interface ProductFactory();
#property (strong, nonatomic) NSMutableDictionary *registeredProducts;
#end
#implementation ProductFactory
- (id)init
{
self = [super init];
if (self) {
_registeredProducts = [NSMutableDictionary dictionary];
}
return self;
}
- (void)registerProduct:(Class)productClass withIdentifier:(NSString *)identifier
{
self.registeredProducts[identifier] = NSStringFromClass(productClass);
}
- (id)newProductForIdentifier:(NSString *)identifier
{
NSString *classString = self.registeredProducts[identifier];
Class productClass = NSClassFromString(classString);
return [[productClass alloc] init];
}
#end
Yes, that is generally analogous. I haven't done java for a little while so I can't explicitly explain the Constructor line but it's kind of like the definition of a designated initialiser and how to find it.
You could do a little work with #protocols to allow a range of init methods to be available for the instantiation and interrogate the class to see which protocol it conforms to (using conformsToProtocol:).
Coming from a Java background, I'm having trouble figuring out ways to program defensively in Objective-C.
Assuming SomeClass is mutable and provides a copy method, this is a typical piece of code I'd write in Java:
public MyClass
{
private SomeClass customerList;
...
public SomeClass getCustomerList() {
return this.customerList.copy();
}
public void setCustomerList(SomeClass list) {
this.customerList = list.copy();
}
}
I took me some time to figure out that
#property (nonatomic, copy) SomeClass *customerList;
would make a copy of the setter's argument before assigning it to the customerList property.
What confuses me is writing an appropriate getter. So far it looks like this:
(SomeClass *)customerList {
if(!_customerList) {
_customerList = [[SomeClass alloc] init];
}
return _customerList;
}
which works for all internal method calls like self.customerList = ..., but would pass a direct pointer to any external call creating a security breach. I was considering providing a different public getter that would return a copy, but would like to avoid it as it would need to have an unconventional name. How would you go about this situation?
Thank you.
You can override the -customerList implementation to be: return [_customerList copy];. Be aware that's not usually how others expect accessors to work so make sure to document this.
If you want to return a copy backed by a property and its getter, it's pretty easy to use this form:
#interface MyClass : NSObject
- (SomeClass *)copyCustomerList;
#end
#interface MyClass ()
#property (nonatomic, copy) SomeClass * customerList; // hide what you can
#end
#implementation MyClass
- (SomeClass *)copyCustomerList { return self.customerList.copy; }
#end
although you could implement your own getter instead -- it is unconventional in ObjC, as Carl mentions.
Another approach you could take is to use a different name for the actual property:
#interface MyClass : NSObject
- (SomeClass *)customerList;
#end
#interface MyClass ()
#property (nonatomic, copy) SomeClass * privCustomerList;
#end
#implementation MyClass
- (SomeClass *)customerList
{
// -autorelease if MRC
return self.privCustomerList.copy;
}
#end
I found this related question: How do I use composition with inheritance?
I would like to do the same with Objective-C, that is to say that a GenericView knows that its property obj is a GenericObject, and that a SpecializedView knows that the very same obj property is a SpecializedObject.
Here is an example that will be clearer:
//
// Example.m
#import <UIKit/UIKit.h>
/* HEADER */
// Electrical Machine
#interface ElectricalMachine : NSObject {
}
- (void)plugIn;
#end
// Toaster
#interface Toaster : ElectricalMachine {
}
- (float)getThermostat;
#end
// GenericView
#interface GenericView : NSObject {
ElectricalMachine *machine;
}
- (void)doSomethingGeneric;
#property (nonatomic, retain) ElectricalMachine *machine;
#end
//SpecializedView
#interface SpecializedView : GenericView {
}
- (void)doSomethingSpecialized;
#end
/* IMPLEMENTATION */
// GenericView
#implementation GenericView
#synthesize machine;
- (void)doSomethingGeneric {
Toaster *toaster = [[Toaster alloc] init];
[toaster plugIn];
self.machine = toaster;
[toaster release];
}
#end
// SpecializedView
#implementation SpecializedView
- (void)doSomethingSpecialized {
/* ERROR HERE
* Incompatible types in initialization
* 'ElectricalMachine' may not respond to '-getThermostat'
*/
float r = [machine getThermostat];
r = r;
// ...
}
#end
As you see, I get an error at the end, because for SpecializedView the machine property is an ElectricalMachine, not a Toaster.
Thank you very much for your help!
Old Question
Here is the first version of my question, which was maybe too cryptic:
I have the following generic view:
#interface GenericView {
GenericObject obj;
}
- (id)doSomething;
I also have the following specialized view:
#interface SpecializedView : GenericView {
}
- (id)doSomethingElse;
I have the following object:
#interface GenericObject {
}
- (id)plugIn;
and the following specialized object:
#interface SpecializedObject : GenericObject {
}
- (float)toastTime;
Let's say I want GenericView to handle GenericObject, and SpecializedView to handle the same object, knowing that it is SpecializedObject.
Let me explain by showing implementations:
GenericView doSomething
- (id)doSomething {
[obj plugIn];
}
SpecializedView doSomethingElse
- (id)doSomethingElse {
// ERROR here
float time = [obj toastTime];
}
I will get the following warning:
'GenericObject' may not respond to '-toastBread'
and the following error:
Incompatible types in assignement
Which is logical, since I have defined the type of obj as GenericObject. I want to be able to use methods from GenericObject in GenericView, and methods from SpecializedObject in SpecializedView. Is there a way to precise that obj has to be a GenericObject in GenericView to be handled, and has to be a SpecializedObject to be dealt with in SpecializedView, without adding a property? How would you do that?
Objective-C is a dynamically-typed language and methods are resolved at runtime, not compile time. If in SpecializedView, obj is in fact of an object of type SpecializedObject (even though it's declared as GenericObject), it will in fact respond to a toastBread message. The compiler will generate a warning but you can ignore it.
If SpecializedView may have both GenericObjects and SpecializedObjects, you can make sure that obj responds to toastBread using the respondsToSelector: message (inherited from NSObject):
if ([obj respondsToSelector:#selector(toastBread)]) {
[obj toastBread];
}