Pregunta

It looks to me like sending setString: to a NSMutableString that hasn't had init called on it yet does not call init on it's own. For example:

NSMutableString *string;   // Declare, but do not init yet
[string setString:@"foo"];
NSLog (@"%@",string);      // Prints "(null)"

I'd like to overwrite this behavior, so that essentially

- (void) setString:(NSString *)aString
{
    if (!self)
    {
        self = [self initWithString:aString];
    }
    else
    {
        [super setString:aString];
    }
}

I could do so with a subclass, but I would have to go through my project and replace all my NSMutableStrings with my subclass, which is a pain. I was looking at the Apple Docs and it looks like what I want to do is create a Category for NSMutableString. I haven't used categories before, so I got a couple questions:

First, it looks like categories only allow me to add new methods, it doesn't allow me to overwrite existing methods. I suppose it is easy enough to just create a setStringWithInit: method that does what I want, so this first issue isn't really an issue after all (although I still have to do a find-replace through my project for setString, but oh well).

Second, and more importantly, how do I check if the sender of my new method is nil? If setString: returned something other than void, I think I could get it to work, but that's not the case here. How do I get the if (!self) check from my code above to work with a category?

Or are categories not the way to go for this kind of thing, and I'd just be better off sub-classing after all?

EDIT: So the strings I'm using this on will actually be @propertys of a custom NSObject subclass. In my code, I'll actually be calling [myObject.someProperty setString:@"foo"];, and the debugger is showing me that someProperty is nil when I try to do this. Also, in other parts of my app I'm checking if (!myObject.someProperty) to see if that property has been used yet, so I don't want to just automatically self.someProperty = [[NSMutableString alloc] init]; in the init method of myObject's class.

Although now that I think about it, I think I can get away with replacing if (!myObject.someProperty) with if ([myObject.someProperty length] == 0), which would allow me to go through and alloc-init things right away. But if I'm initializing everything right away, that will create some memory space for it, correct? It's probably negligible though. Hm, perhaps this is what I should be doing instead.

¿Fue útil?

Solución 5

macros FTW!

#define setString(X,Y) if(!X){X=[[NSMutableString alloc] initWithString:Y];}else{[X setString:Y];}

When I try to assign a value with this:

  • It will always be initialized first
  • It won't be initialized until I try to give it a value
  • It doesn't clutter up my code
  • It still gives a warning if X isn't an NSMutableString, or if Y isn't an NSString or NSMutableString
  • I haven't tested for if Y is nil, but I expect it will cause a crash, which is what I want.

Drawbacks:

  • I still have to remember to always use my setString() instead of the stock setString:
  • I'll have to do something similar for any other setters I call (the only one that I'm worried about off hand is setValue:forKey:, which I use extensively - one step at a time I guess) - a one size fits all solution would have been nice - maybe a topic for another question.
  • Whatever I pass in has to be a NSString before I pass it, I cannot convert it to a string in line - but at least I get a build error if I try to do so, so it isn't up to me to remember to do so (still adds clutter though)

    NSMutableString *X;

    int y = 0;

    setString(X, [NSString stringWithFormat:@"%d",y]) // <--- Doesn't work

    NSString *Y = [NSStirng stringWithFormat:@"%d",y];

    setString(X,Y) // <--- Does work

Otros consejos

The proper code would simply be:

NSMutableString *string = [NSMutableString string];
[string setString:@"foo"];
NSLog (@"%@",string);

Why would you not initialize the variable? There is no need to override setString: or any other method. Don't try to treat NSMutableString any differently than any other class.

Besides, overriding setString: still won't solve anything. As long as the pointer is nil you can't call a method on it.

You are marching down a path to madness. Abandon hope, all ye who enter here!

Do not try to change the language semantics so that sending a message to a nil object somehow magically creates an instance of the object. That is not how the language works.

What you are trying to do is likely impossible, and if you were able to succeed, you would create programs that are fundamentally incompatible with standard Objective-C. You might as well found a new language, Objective-D

It is legal to send a message to a nil object in Objective C. The result is that the message gets silently dropped, and nothing happens. In many other object-oriented other languages, sending a message to a nil object/zero pointer causes a crash.

The semantics of of Objective C object creation are:

First allocate memory for the object using the class method alloc:

NSMutableString* aString = [NSMutableString alloc];

Then send the newly created object an init method to set it to its initial state:

aString = [aString init];

These 2 steps are just about always combined into a single line:

NSMutableString* aString = [[NSMutableString alloc] init];

Classes sometimes include shortcut "convenience" methods that do the 2 step alloc/init for you, and return an object in one call, e.g.:

NSMutableString *aString = [NSMutableString stringWithCapacity: 50];

Do not try to fight this convention. Learn to follow it. If you cannot tolerate this convention, program in a different language. Really.

You can reimplement a method without subclassing by using method swizzling. Here's a tutorial. There are 2 reasons not to do it here though.

  1. it would be against the good Objective-C practices, since your setter will also be an init method. Not good.
  2. As @rmaddy correctly points out, calling setString: on a nil object will do nothing. Even if you do override the method.

So I recommend creating a category on NSMutableString, and implementing [NSMutableString initWithString:] there. It is a much cleaner solution.

You cannot really do that - you have a method which can be called only on instance of this object, so you will have to create it first anyways to use it.

In your code it will be "nil" anyways - it won't create itself.

Why are you doing it instead of just:

NSMutableString *string = @foo";

I cannot imagine a reason to avoid allocating an object

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top