Ruby-style string interpolation with iOS NSRegularExpression - objective-c

I'm attempting to generate an HTML-output for a model in my app. This will effectively go through and fill in various spots inside an HTML file with the relevant values within the model. I was originally just going to take the HTML template as a formatted string, but if anything changes later on the layout or anything, this will just get really messy and tedious to match up the order of the values to the order they appear in the template.
Instead, what I'm trying to do is run a sort of Ruby-style string interpolation of the file. Anywhere I want a value from the model, I put the name of the model attribute I want like so: #{key.path}.
Then, I'm attempting to process this with the following Regex:
#"#{([^}]+)}".
To process this, I'm using the following setup:
NSString *processedTemplate = [regex stringByReplacingMatchesInString:template
options:0
range:NSMakeRange(0, template.length)
withTemplate:[self valueForKeyPath:#"$1"]];
However, when I run this, I get the error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[<Plan 0x78349d0> valueForUndefinedKey:]: this class is not
key value coding-compliant for the key $1.'
What I expect is that I can use the regex match and use it to grab the key-value coding compliant value in my model. However, this clearly doesn't work the way I'm using it.
On a side note, I think I'm using this right, but when I just run this to replace withTemplate:#"$1" I get NULL. So, I tried it using:
NSString *processedTemplate = [template stringByReplacingOccurrencesOfString:#"#{([^}]+)}"
withString:#"$1"
options:NSRegularExpressionSearch
range:NSMakeRange(0, template.length)];
However, when I run this it doesn't replace with the section in (). So one way or another, I'm not doing something right. Anyone have any pointers/solutions?
Update
So it looks like the withString: parameter will interpret #"$1" as whatever the regex match data has found. Is there another way to retrieve match data so it can be passed into such methods as valueForKeyPath:?
Update
On my side note, I don't know why, but my regex #{([^}]+)} does not match as I expect it to. Any other regex simulator I put it up against seems to match it just fine, but it doesn't match in obj-c on iOS. Is there something I'm missing with escapes on character set #{}?

What kind of object is self in your first example? Have you overridden valueForKeyPath:?
The method -valueForKeyPath: is defined for NSObject to return a value based on KVC key paths. The code
obj = [anotherObj valueForKeyPath: #"foo.bar.baz"];
will first send foo to anotherObj, then send bar to the result, then send baz to the result of that and return the final result.
Essentially, the runtime is complaining that you don't have a method called -$1.
And by the way, in Objective-C parameters to methods are evaluated before the method itself, so
[self valueForKeyPath:#"$1"]
is evaluated before stringByReplacingMatchesInString: and $1 means nothing special to valueForKeyPath:.

Related

Why NSLog outputs garbles when logging Chinese characters?

NSLog displays unknown garble with the following code:
NSString *sampleStr = #"你好";
NSLog(sampleStr);
But it outputs:
As I send the above characters to backend, backend shows
?????
How do I fix this problem?
What the problem specifically? NSLogging or the backend one?
If the former, first of all, you should use string formatting to output strings instead of passing the NSString object directly. This must give you desired output:
NSLog(#"%#", sampleStr);
Though, I tried your code without changes in Xcode 13.1 and it gave me the desired output nevertheless. So, probably the problem is somewhere else, not in your NSLog:
NSString *sampleStr = #"你好";
// "Format string is not a string literal (potentially insecure)"
NSLog(sampleStr); // 你好
NSLog(#"%#", sampleStr); // 你好
If you mean backend as your problem, it depends on how you "send" it to backend (in a body as data, as an HTTP parameter as text, with/without escaping, etc.) and whether your backend code supports the input you provide. To answer that more precisely we should have more information.

NSString isEqual strange bug

I have a web server which I used to fetch some data in my iOS application. The data include a field as the itemId let say '48501' (with no quotation). I read item JSON data into my itemObject in which itemId is defined as a NSString and not a NSInteger.
Everything works until this point but I have problems where I want to compare itemObject.itemId using isEqual: function with another NSString filled with 48501.
In other words both string are exactly the same and include 48501 when I print them. No space and hidden things is there. All isEqual: and isEqualToString: and == report false on comparison.
On the hand when I convert NSStrings to NSIntegers and compare them it works but not always! sometime TRUE sometime CRASH with no error to catch and just pointing to the line! I see them printed exactly the same but the if statement does not go through.
I showed the code to someone with far more experience than me and he was like this could be a bug! Anyone has ever exposed to this?
If your itemId is 48501 without any quotation in the JSON, then it's deserialized as NSNumber. Probably that's the problem in the first place. Try logging the type of your itemId and use appropriately -isEqualToString: for NSString and -isEqualToNumber: for NSNumber.

How can I name an obj-c function to call in xml data

Newbie question here. I'd like to be able to specify through data (i.e. an XML file), the appropriate Objective-C message to send. Any advice on if this is possible or how I can do this?
The next best thing, if I can't do this, would be some way to create a map object that would correlate a key (an int) with a function (I guess also a selector). Is that possible if the above isn't?
If someone could point me to some tutorial or example code as reference, that'd be great. Right now I'm doing things with a big switch statement, and I don't like it. (I'm switching on the id and in each case, explicitly calling the method relevant to the particular id.)
I love that you asked this question; too often, I see Satan's Swollen Switch Statement. It's nice to see someone wanting to using a function-table instead.
If you're OK with using a property list file (which is usually encoded in XML), this is really easy.
Just make a property list where the root element is a dictionary, which maps from some keys to some selectors.
Key Type Value
----------------------------------------------
Root Dictionary
firstKey String someSelector
secondKey String anotherSelector
Load the contents of your property list into an NSDictionary:
id path = [[NSBundle mainBundle] pathForResource:#"filename" ofType:#"plist"];
id dict = [NSDictionary dictionaryWithContentsOfFile:path];
SEL selector = NSSelectorFromString([dict objectForKey:#"firstKey"]);
if ([someObject respondsToSelector:selector]) {
[someObject performSelector:selector];
}
Of course, you'll want to refactor this logic into an appropriate method, and probably cache the property list as an instance variable.
Note: I personally think it's better to just put this function table inline; property lists are cool, but I'm not sure that it is very helpful in this case. Also, if you are cool with using Objective-C++, std::map will allow you to get away with not wrapping and unwrapping the selectors in NSString objects, etc.

Sorting NSArray igonring Umlauts

I want to sort an array I have so that the Umlauts are treated as normal versions of the characters (ä == a, etc.). I thought localizedCaseInsensitiveCompare: should do the trick. But it doesn't. Anyone?
Best
–f
localizedCaseInsensitiveCompare: compares using the standard rules in the current language chosen by the user, ignoring the case. In your case you want to disregard the diacritics, not the case. This means you need to do something else.
You need to use compare:options: and pass NSDiacriticInsensitiveSearch as an option. see here.
To sort an array using it, you need to either use a block using sortedArrayUsingComparator:, or implement a category method in NSString and pass that selector to sortedArrayUsingSelector:. Don't forget to prefix the name of the category method so that it doesn't overlap with a private method in the framework. So, do something like
#interface NSString (myaddition)
-(NSComparisonResult)mySecretDiacriticsInsensitveCompare:(NSString*)string;
#end

Selectors in Objective-C?

First, I'm not sure I really understand what a selector is. From my understanding, it's the name of a method, and you can assign it to a class of type 'SEL' and then run methods such as respondToSelector to see if the receiver implements that method. Can someone offer up a better explanation?
Secondly, to this point, I have the following code:
NSString *thing = #"Hello, this is Craig";
SEL sel = #selector(lowercaseString:);
NSString *lower = (([thing respondsToSelector:sel]) ? #"YES" : #"NO");
NSLog (#"Responds to lowercaseString: %#", lower);
if ([thing respondsToSelector:sel]) //(lower == #"YES")
NSLog(#"lowercaseString is: %#", [thing lowercaseString]);
However, even though thing is clearly a kind of NSString, and should respond to lowercaseString, I cannot get the 'respondsToSelector' conditional to return "YES"...
You have to be very careful about the method names. In this case, the method name is just "lowercaseString", not "lowercaseString:" (note the absence of the colon). That's why you're getting NO returned, because NSString objects respond to the lowercaseString message but not the lowercaseString: message.
How do you know when to add a colon? You add a colon to the message name if you would add a colon when calling it, which happens if it takes one argument. If it takes zero arguments (as is the case with lowercaseString), then there is no colon. If it takes more than one argument, you have to add the extra argument names along with their colons, as in compare:options:range:locale:.
You can also look at the documentation and note the presence or absence of a trailing colon.
Selectors are an efficient way to reference methods directly in compiled code - the compiler is what actually assigns the value to a SEL.
Other have already covered the second part of your q, the ':' at the end matches a different signature than what you're looking for (in this case that signature doesn't exist).
That's because you want #selector(lowercaseString), not #selector(lowercaseString:). There's a subtle difference: the second one implies a parameter (note the colon at the end), but - [NSString lowercaseString] does not take a parameter.
In this case, the name of the selector is wrong. The colon here is part of the method signature; it means that the method takes one argument. I believe that you want
SEL sel = #selector(lowercaseString);
NSString's method is lowercaseString (0 arguments), not lowercaseString: (1 argument).
Don't think of the colon as part of the function name, think of it as a separator, if you don't have anything to separate (no value to go with the function) then you don't need it.
I'm not sure why but all this OO stuff seems to be foreign to Apple developers. I would strongly suggest grabbing Visual Studio Express and playing around with that too. Not because one is better than the other, just it's a good way to look at the design issues and ways of thinking.
Like
introspection = reflection
+ before functions/properties = static
- = instance level
It's always good to look at a problem in different ways and programming is the ultimate puzzle.
From my understanding of the Apple documentation, a selector represents the name of the method that you want to call. The nice thing about selectors is you can use them in cases where the exact method to be called varies. As a simple example, you can do something like:
SEL selec;
if (a == b) {
selec = #selector(method1)
}
else
{
selec = #selector(method2)
};
[self performSelector:selec];
As per apple docs:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Selector.html
A selector is the name used to select a method to execute for an object, or the unique identifier that replaces the name when the source code is compiled. A selector by itself doesn’t do anything. It simply identifies a method. The only thing that makes the selector method name different from a plain string is that the compiler makes sure that selectors are unique. What makes a selector useful is that (in conjunction with the runtime) it acts like a dynamic function pointer that, for a given name, automatically points to the implementation of a method appropriate for whichever class it’s used with. Suppose you had a selector for the method run, and classes Dog, Athlete, and ComputerSimulation (each of which implemented a method run). The selector could be used with an instance of each of the classes to invoke its run method—even though the implementation might be different for each.
Example:
(lldb) breakpoint --set selector viewDidLoad
This will set a breakpoint on all viewDidLoad implementations in your app.
So selector is kind of a global identifier for a method.