IOS: performSelector:withObject: acts weird - ios

my app turns out to be unable to load some content. I was surprised, and I found out that the bug was that
[contentItem performSelector:#selector(setIsContainer:) withObject:[NSNumber numberWithBool:true]];
was passing "False" to the method instead of "True".
Is there any explanation to this? Or I should, as a good practice, avoid using performSelector?
(I asked coworkers for their phones, and I took same iPhone5s with same iOS versions, in all 3 of them everything worked fine, except the boss' phone)

You are passing an NSNumber object to a method which expects a BOOL primitive. It should fail on every version of iOS.
Change the implementation to:
- (void)setIsContainer:(NSNumber *)isContainer {
_isContainer = [isContainer boolValue];
}
(if you require the original semantics then you'll have to provide an alternative version; one for NSNumber and one for BOOL).

Related

NSString Subclass or Wrapper Class or Category

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.

Typedef NSArray of type in Objective C

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?

Clearing the underlying byte data from an NSString

I have a need to ensure the user-inputed string in a UITextField is cleared from memory immediately after it has been used. If I set field.text = #"" or nil and let it go out of scope it should ge gone but I am not sure if it is guaranteed to be released immediately and even if it is, I dont think the OS actually clears the data, it seems likely it is just marked as unused and the data in it is still there until overwritten by something else.
The .text property of the field is a normal NSString and I have looked around for ways to get a useable pointer to its contents using the various CFString / CString methods but none of them work. For example CFStringGetCharactersPtr and CFStringGetCStringPtr both either return const-pointers which cant be written to, or often NULL (the docs say it may return null "if the internal storage of the String does not allow this to be returned efficiently") and from my testing it always returns NULL.
Is there anyway to do this?
answering my own question to be clear:
I got a reply from an apple employee at the devforums and he confirms it is pretty much impossible. His suggestion was to do everything yourself based on UITextInput and not use NSString or UITextField at all.

"Direct comparision of a string literal" warnings since last Xcode update

Since the last update of Xcode (to v4.6) I've got a bunch of the following warnings:
"Direct comparison of string a literal has undefined behavior"
This is when a NSString property is compared with another string by this way:
if ([self.myString isEqualToString:#"Compare Me"]) { ...
Originally I compared with self.myString == #"Compare Me" but there are the same warnings. Xcode suggests to use isEqual: instead.
I wonder why I should do this. Other comparisons still work, e.g.:
if ([segue.identifier isEqualToString:#"nextScreen"]) { // => NO WARNING HERE
Edit: OK, for anyone who doesn't believe/downvoters, see this screenshot:
Sometimes Xcode gets confused and shows old warnings. I suspect it is showing an old warning for use of ==. Clean the build, go to the organiser window and delete the derived data for this project, then restart Xcode.
Basically (according to this other stackoverflow answer, you should switch any of your == string comparisons to use isEqual or isEqualToString.
To make it easier, I found this post on bignerdranch's blog that said there is no difference (in the observable human world) between isEqual and isEqualToString, so I personally use isEqual.
When you use "==", it should, in theory (like in C/C++) compare the pointers, not the value of the objects.
Fact is obj-c (and compiler I guess) will understand what you really want and transform this in isEqualToString. But to be "safe", it's better to use the good method to compare two strings, two numbers etc.

KVO differentiating between willChangeValueForKey and didChangeValueForKey - are both necessary?

In line with Apple's own recommendations, when setting KVC/KVO compliant accessors manually, one should include BOTH KVO methods willChange and didChange. This is what I have done in all my manual accessor methods.
However, observeValueForKeyPath:ofObject:change:context gets called for each half of the KVC methods (will and did) with exactly the same dictionary contents.
When registering an observer using the option: NSKeyValueObservingOptionPrior the observer still gets called twice - once for each half - and, again, with identically the same dictionary contents, save only the difference that the key 'notificationIsPrior' is included in the dictionary.
Now, when KVO is used to alter 'CPU-expensive' attributes - like changing a colour or redrawing a large and elaborate design, it makes sense only to act on the 'didChange' and ignore (or at least separate out) the 'willChange'. In the past, I have achieved this by converting the key string into an enum list element that returns a left-shifted '1' and used this digit to set a flag in a 32 or 64 bit integer on receipt of the first call and when the flag is reset on the second, I execute the CPU-intensive operation(s).
However, it strikes me that this is a non-trivial overhead to implement for every case. Does anyone have any other 'preferred' way of differentiating between the callback for 'willChange' and that for 'didChange' without allowing the same processing to be done twice?
I have scoured Apple's own documentation and this help group copiously for alteranatives but Apple's own doc doesn't actually go in to much detail on the subject and several people in this group have also wrestled with a similiar concern. In neither instance has a definitive solution been offered. If anyone knows of a better way - other than dodging the 'willChange' using alternating flags - I'd be very grateful. (Why couldn't Apple just include a 'phase' key in the change dictionary???)
I think this is what you were getting at in the comments, but for the benefit of future visitors:
If you want to tell whether a callback is "before" or "after" you can look for the NSKeyValueChangeNotificationIsPriorKey key in the change dictionary. If it's a prior notification, this key will be equal to [NSNumber numberWithBool: YES] (incidentally the dictionary will also not contain a value for the NSKeyValueChangeNewKey) The presence/value of NSKeyValueChangeNotificationIsPriorKey is authoritative, so if you're seeing it when you're not expecting to, you might be getting double callbacks.
If you're getting double callbacks it may be, as it sounds like it was in VectorVictors case, that the runtime is firing them AND you're firing them. If you plan to call will/didChangeValueForKey: to manage your KVO notifications manually, (and you don't want double notifications,) you should implement the following class method:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:#"propertyYourePlanningToManageYourself"]) {
automatic = NO;
} else {
automatic=[super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
This is described in detail in Apple's Key-Value Observing Programming Guide.

Resources