I'm currently helping a client that needs to change the language in their app due to certain governmental guidelines (medical vs wellness wording). Their app is huge and all the strings are contained in the code i.e. (stringWithFormat/hardcoded), none of it is in an external table. Meaning this would be a huge manual task.
At a undetermined point in the future the client believes they will receive approval to return to their current wording and will want to switch the strings back. Most of the changes will literally be switching a single problematic word to a less problematic word.
I thought that maybe if I could change the strings at run time based on a bool switch that it might eliminate the manual work involved and it would let me switch the language back when needed.
First attempt:
+ (instancetype)stringWithFormat:(NSString *)format, ...
{
va_list args;
va_start(args,format);
//todo check flag if we're changing the language
//todo replace problematic word from 'format'
NSString *result = [NSString stringWithFormat:format,args];
return result;
}
I first quickly coded up a category to override stringWithFormat to replace problematic words. I forgot that I would lose the original implementation of stringWithFormat. This resulted in endless recursion.
Next Attempt (subclass):
I started an attempt to subclass NSString but hit a stackoverflow post saying that if my solution was to subclass a class cluster then I didn't understand my problem because subclassing a class cluster is almost never done.
Final Option (wrapper):
My final attempt would be to write a wrapper class but that kind of defeats the original purpose which was to avoid having to manually seek out each string in the app.
I'm not really sure how to approach this problem anymore. What do I do if I need to add/override functionality to one of the core classes.
There is nothing wrong with the idea of your first attempt, just a little mistake in its implementation. Change:
NSString *result = [NSString stringWithFormat:format,args];
to:
NSString *result = [NSString alloc] initWithFormat:format arguments:args];
which is the expansion of stringWithFormat: and the interception will work.
Thoughts about class clusters are a red herring in this particular situation, the front class for a cluster (NSString) must provide implementations for class methods (+stringWithFormat:), so you can use a simple category to intercept them.
However, having intercepted +stringWithFormat: be careful. A simple test will show you it is used a lot by the frameworks and you do not wish to break them - as my first simple test did by simply changing "d" to "c", which changes "window" to "wincow", which in turn broke the binding setup of Xcode's default app which binds the property "window"...
If you are changing health-related words you might be OK, whole strings would be better.
A better approach might be to simply write a RE to match all the literal strings in the code and replace them by function(string) for some function (not method) you write to do the conversion.
HTH
There is a much simpler solution that seems like a better fit. Use NSLocalizedString, with keys instead of actual strings:
displayString *NSString = NSLocalizedString(#"displayString", nil);
cancelButtonTitle *NSString = NSLocalizedString(#"cancelButtonTitle", nil);
Then create a Localizable.strings file in your app, and define the actual values that should be displayed:
"displayString" = "The string to display in English"
"cancelButtonTitle" = "Cancel"
You put the Localizable.strings file in your app bundle and the app uses it to do string replacements at runtime.
You can also define different versions of Localizable.strings for different languages, so, for example, if the user has set their language to Spanish, the call to NSLocalizedString() gives you the Spanish-language version.
(As you've mentioned, NSString is a class cluster. That means that it is a public interface to a variety of different private subclasses. You can't be sure what private subclass you get when you create an NSString, and that makes it a bad idea to try to subclass it.)
For hardcoded strings you have no other way but to modify those manually by assigning it to a string converter class of some sort. So those for:
yourmom.text = #"Hi Mom";
yourdad.text = [NSString stringWithFormat:#"%# and Dad!",yourmom.text];
You need to change these kind of assignments to something like
yourmom.text = [StringConverter string:#"Hi Mom"];
yourdad.text = [StringConverter string:#"%# and Dad!" placeHolder:yourmom.text];
As for strings in storyboards or xibs, you can change them by iterations loop in viewdidload. Good luck.
Related
It has been a long time since I have worked in Objective C but now I am using it because I need to write something that will remain mostly source compatible for future versions. I want to create an init method that allows me to init my viewController with an array of my custom model object. In Swift I would do it like this:
typealias Stack = [StackBarTabItem]
…
func init(stacks:[Stack])
But how would I typedef an NSArray like that? I am pretty sure I can't do something like typedef NSArray<StackBarTabItem> Stack; so what is the syntax in objective c?
Until iOS 9 and Xcode 7, this isn't officially supported. One way to do this is to subclass NSArray or NSMutableArray and enforce typing in your subclass, but this isn't really recommended. One way to deal with the fact that NSArray can only hold ids is to use respondsToSelector before calling a method on any of the objects in the array.
This solution isn't really a substitute for a good typing system, but it's a common practice to get around this limitation. Thankfully, generic support is getting added soon!
Objective-C is dynamically typed. You simply do not check for it.
Asking the audience on talks and in internet fora, the real danger that code will be shipped with a typing bug is minimal and by far lower than other sources of errors. Simply do not care about this.
Ask yourself: How could that happen without getting a runtime error at the very beginning of your next program run?
I have a question about the parameter that #synchronized take, I have read the Apple document about synchronisation but still I don't have a clear idea.
I have a case that #synchronized will take a string property inside some object like this :
#synchronized(someObject.A)
since A is a NSString object and in some cases will carry the same value but from different someObject is this will guarantee the locking for all objects with same A values?
It's worth noting that NSString has some special cases that are handled magically.
NSString *s1 = #"Test string";
NSString *s2 = #"Test string";
Here, s1 and s2 are actually compiled to access the same memory address, even though they are different variables and could be instantiated in completely different places within the application.
However, if you are loading the data on-the-fly or using one of the construction methods for NSString instead of hard-coding it, strings that match character-for-character will not share the same memory.
You can consider this the difference between comparing with == and isEqualToString:. #synchronized only ever uses the == result.
So, to answer your question: maybe.
If you are using hard-coded values of the form #"some string" within your application, your #synchronized command will link to all objects that share the same textual value for A.
If you are creating NSString objects by any other means, your #synchronized command will only link to objects that point to the exact same NSString object.
The synchronization will be done on whatever object someObject.A is currently referencing. The important piece is the actual object you use #synchronized on.
If you assign the same string to two completely difference properties and you then use #synchronized on those two completely different properties, it will work since both point to the same string.
The following example may help:
// In one method
#synchronized(someObject.A) {
}
// In another method
NSString *foo = someObject.A;
#synchronized(foo) {
}
The above two blocks will be thread safe on the same string object.
I am creating a set of API and some users have suggested that I use id type for a particular method that can accept custom object (defined by the API) or string instead of creating two versions. Is the use of id type in method a good or acceptable practice? Does Apple do it with their any of their API?
That would be very poor practice. If you're creating an API you need to retain full control, and allowing users to pass any object to your method at which point you would have to cast it to that object or string you mentioned could be fatal depending on what's passed. Creating two methods with different parameters is not only okay, but follows the tenets of polymorphism to the T.
Accepting id is not in itself good or bad practice. How much manual procedural if/then/else/if/then/else nonsense will you acquire? If quite a lot then something is wrong.
Put another way: if the conditional logic related to different kinds of object ends up being implicit, via the Objective-C dispatch mechanisms, then the design is good. If you end up impliedly reimplementing dynamic dispatch then you've gone completely wrong.
Apple does it frequently. Just off the top of my head there are:
as per Nikolai's comment, all the collection types: set, dictionary, array, etc.
anything that takes %# as a format specifier: NSLog, certain methods on NSString, etc.
anything that still uses an informal protocol.
anything in or semi-close to the runtime like key-value coding.
archiving and the user defaults.
anywhere that storage is offered for your own use — the hardy userInfo on NSTimer and the rest.
anywhere that target/action is used — all UIControls, the notification centre, etc.
As per my comment, suppose your custom class had this method:
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding
And suppose it were the only method being called by whomever is being passed either a string or your custom object. Then id would be the right choice, since you'd have in effect implemented an informal protocol, and the thing being passed an object genuinely doesn't care whether it's a string or not. The only contractual requirement is the informal protocol and the protocol is informal i.e. has no footprint on the type syntax.
Conversely, suppose your custom class had no methods in common with NSString and your code just looked like:
- (void)myMethod:(id)object
{
if([object isKindOfClass:[NSString class]])
[self myMethodOnString:object];
else
[self myMethodOnCustomClass:object];
}
Then id would be inappropriate. You're just obscuring what the method does and implicitly reproducing work that's built into the runtime anyway.
I have been looking at Swift the last couple of days and it looks good, I am confused on a point though even after review of the guide Apple published.
I understand that memory management in Swift uses ARC and therefore quite safe, however in situations where I'd like more fine grained control of object creation and destruction I'm slightly confused.
Take these two simple examples:
In ObjC, when you'd (for example) like a UIViewController you intend to push to stay around for the lifetime of your app and only be created once (say because it's expensive to create), you'd make it a property and then wrap it's Alloc Init like this:
if (!self.myProperty)
{
self.myProperty = [[MyObj alloc] init];
}
This way it only creates once, keeps state and the same object instance can be reused indefinitely, yielding known object references, and good performance not having to recreate each time the viewController goes out of scope.
Another example of this is with NSMutableString, vs Swifts String.
You can set an NSMutableString's string many times and it will keep the same object reference, however this doesn't seem to be the case with a Swift String when changing it.
This may just be 'deprecated thinking in Objective C' but is there a best practice with regard to keeping around expensive objects or to keep state (same object) for certain objects during the lifetime of their usage.
I think what you're looking for is a "Lazy stored property":
The example in the swift docs is:
class DataManager {
#lazy var importer = DataImporter()
var data = String[]()
// the DataManager class would provide data management functionality here
}
and it says about #lazy:
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the #lazy attribute before its declaration.
The comment about NSMutableString is a little different, you can continue to use NSMutableString in your swift code, but Apple are encouraging developers to use the Swift built-in types whenever possible :)
see the "String Mutability" section in the swift docs
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.
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.
it would be against the good Objective-C practices, since your
setter will also be an init method. Not good.
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
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