NSString: Return value or Empty string - objective-c

I've been looking at a several Google search results and have found various ways to test an NSString for nil, etc.
So I decided to write some category methods for NSString to help. However it's not working as expected and am wondering if someone can help me figure out why and possible help with a solution.
So here is what I came up with:
NSString+Extenstions.h
#import
#interface NSString (Extensions)
- (NSString *)stringOrNil;
- (NSString *)stringOrEmpty;
#end
NSString+Extenstions.m
#import "NSString+Extensions.h"
#implementation NSString (Extensions)
- (NSString *)stringOrNil {
return self == nil || [self length] == 0 ? nil : self;
}
- (NSString *)stringOrEmpty {
return [self stringOrNil] == nil ? #"" : self;
}
#end
So the idea behind stringOrEmpty is to force the result as an empty NSString (not nil) if the object is nil or the length is 0.
Testing:
NSString *name = nil;
NSLog(#"%#", [name stringOrEmpty]); // result: (null)
I expected, the result above to be a blank, but the console logged it as (null). S I changed the stringOrEmpty method to return [[self stringOrNil] length] == 0 ? #"yes" : #"no"; and expected to see yes, but again got (null). It seems like stringOrEmpty is not being called.
Lastly I tested the following: NSLog(#"%#", [name class]); and again the result was (null).
Am I misunderstanding something? How can I write a category that will return a blank NSString value if the value is nil or truly empty?
Thanks!

nil isn't an instance of NSString or any object, so you can't deal with nil using instance methods.
A message to nil does nothing, it doesn't even get called. It just simply returns nil. You need to define them as class methods:
#interface NSString (Extensions)
+ (NSString *)stringOrNil:(NSString *)string;
+ (NSString *)stringOrEmpty:(NSString *)string;
#end
--
#implementation NSString (Extensions)
+ (NSString *)stringOrNil:(NSString *)string {
return string.length ? string : nil;
}
+ (NSString *)stringOrEmpty:(NSString *)string {
return string ? string : #"";
}
#end
Here is how you can call them:
NSString *name = nil;
NSLog(#"%#", [NSString stringOrEmpty:name]); // result: ("")

Related

Duplicated custom object in NSSet

I have some problems about the NSMutableSet in Objective-C.
I learnt that the NSSet will compare the two objects' hash code to decide whether they are identical or not.
The problems is, I implemented a class that is subclass of NSObject myself. There is a property NSString *name in that class. What I want to do is when instances of this custom class has the same variable value of "name" , they should be identical, and such identical class should not be duplicated when adding to an NSMutableSet.
So I override the - (NSUInteger)hash function, and the debug shows it returns the same hash for my two instances obj1, obj2 (obj1.name == obj2.name). But when I added obj1, obj2 to an NSMutableSet, the NSMutableSet still contained both obj1, obj2 in it.
I tried two NSString which has the same value, then added them to NSMutableSet, the set will only be one NSString there.
What could be the solution? Thank you for any help!
The custom Class:
Object.h:
#import <Foundation/Foundation.h>
#interface Object : NSObject
#property (retain) NSString *name;
#end
Object.m
#implementation Object
#synthesize name;
-(BOOL)isEqualTo:(id)obj {
return [self.name isEqualToString:[(Object *)obj name]] ? true : false;
}
- (NSUInteger)hash {
return [[self name] hash];
}
#end
and main:
#import <Foundation/Foundation.h>
#import "Object.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
Object *obj1 = [[Object alloc]init];
Object *obj2 = [[Object alloc]init];
obj1.name = #"test";
obj2.name = #"test";
NSMutableSet *set = [[NSMutableSet alloc] initWithObjects:obj1, obj2, nil];
NSLog(#"%d", [obj1 isEqualTo:obj2]);
NSLog(#"%ld", [set count]);
}
return 0;
}
Instead of implementing isEqualTo: you have to implement isEqual:
- (BOOL)isEqual:(id)object {
return [object isKindOfClass:[MyObject class]] &&
[self.name isEqual:[(MyObject *)object name]];
}
This will (probably falsely) return NO if both self.name and object.name are nil. If you want to return YES if both properties are nil you should use
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[MyObject class]]) {
return (!self.name && ![(MyObject *)object name]) ||
[self.name isEqual:[(MyObject *)object name]];
}
return NO;
}

Property not found on object of type error with method declaration in Objective C

Have a weird error on method declaration and call...
MyObject.h --- declarations. has been trimmed down
#import <Foundation/Foundation.h>
#interface MyObject : NSObject
- (void) useFlattenHTML;
- (NSString *) flattenHTML:(NSString *) inHtml;
- (NSString *) justAStringMethod;
#end
And method defined and called like this...
MyObject.m --- method declaration and usage. Trimmed down
#import "MyObject.h"
#implementation MyObject
- (NSString *) flattenHTML:(NSString *) inHtml {
NSScanner *theScanner;
NSString *text = nil;
theScanner = [NSScanner scannerWithString:inHtml];
while ([theScanner isAtEnd] == NO) {
[theScanner scanUpToString:#"<" intoString:NULL] ;
[theScanner scanUpToString:#">" intoString:&text] ;
inHtml = [inHtml stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#"%#>", text] withString:#""];
}
//
inHtml = [inHtml stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
return inHtml;
}
- (NSString *) justAStringMethod
{
// Some calls.
}
- (void) useFlattenHTML
{
NSString* resultStr = [self.flattenHTML #"Some html tagged string"];
NSString* anotherStr = [self.justAStringMethod];
}
#end
I get
Property 'flattenHTML' not found on object of type 'MyObject *'
Maybe because you're using the dot notation in a case where it doesn't make much sense :
in useFlattenHTML method, self.flattenHTML is the same as [self flattenHTML], which doesn't exist, as you only have [self flattenHTML:someString].
On top of that, dot notation is possible, but you should keep it for fields declared as #property only

Simple addObject to NSMutableArray method gives lldb error

I'm adding objects to a NSMutableArray stack in my model. Here's the interface:
#interface calcModel ()
#property (nonatomic, strong) NSMutableArray *operandStack;
#end
And the implementation:
#implementation calcModel
#synthesize operandStack = _operandStack;
- (NSMutableArray *)operandStack;
{
if (_operandStack == nil) _operandStack = [[NSMutableArray alloc]init];
return _operandStack;
}
This addobject method works fine:
- (void)pushValue:(double)number;
{
[self.operandStack addObject:[NSNumber numberWithDouble:number]];
NSLog(#"Array: %#", self.operandStack);
}
but this one crashes the app and just says 'lldb' in the log:
- (void)pushOperator:(NSString *)operator;
{
[self.operandStack addObject:operator];
NSLog(#"Array: %#", self.operandStack);
}
What's causing this error?
The NSString you're adding is probably nil. Do this:
- (void)pushOperator:(NSString *)operator {
if (operator) {
[self.operandStack addObject:operator];
NSLog(#"Array: %#", self.operandStack);
} else {
NSLog(#"Oh no, it's nil.");
}
}
If that's the case, figure out why it's nil and fix that. Or check it before adding it.
The reason why the first method doesn't crash is, because there is no double value that can't be used to init an NSNumber, so it will never be nil.

isEqualToString Combinations

I have multiple strings combinations that I want my isEqualtoString to find automatically.
Right now, I have all combinations manually searched.
if([aString isEqualToString:#"xyz"] || [aString isEqualToString:#"zxy"] || [aString isEqualToString:#"yzx"] || [aString isEqualToString:#"xzy"] etc...){}
If you just want to know if any of them match, you can put all your candidates (xyz, zxy, ...) in an NSArray and call containsObject:aString on the array. Use indexOfObject:aString if you need to know which string was matched.
You can write a NSString category that does the job:
#interface NSString (isEqualToAnyStringAddition)
- (BOOL)isEqualToAnyString:(NSString *)firstString, ... NS_REQUIRES_NIL_TERMINATION;
#end
#implementation NSString (isEqualToAnyStringAddition)
- (BOOL)isEqualToAnyString:(NSString *)firstString, ...
{
if([self isEqualToString:firstString])
return YES;
va_list arguments;
va_start(arguments, firstString);
NSString *string;
while((string = va_arg(arguments, NSString *)))
{
if([self isEqualToString:string])
{
va_end(arguments);
return YES;
}
}
va_end(arguments);
return NO;
}
#end

Objective-C for Dummies: How do I loop through an NSDictionary inside of an NSDictionary?

Alright guys, I'm quite confused. So, I have an NSDictionary which is populated by a JSON string which looks like:
{"Success":true,"Devices":[{"UDId":"...","User":"...","Latitude":0.0,"Longitude":0.0}]}
Now, I know how to check if Success is true, but I need to loop through the array of Devices (JSON object) and create an internal array of Devices (internal app object) and I have no idea how to do that. Can someone please explain how to do it?
Here's my Device.m/h:
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#interface Device : NSObject {
NSString *udId;
NSString *name;
NSNumber *latitude;
NSNumber *longitude;
}
#property (nonatomic, retain) NSString *udId;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#pragma mark -
#pragma mark MKAnnotation Properties
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
----
#import "Device.h"
#implementation Device
#synthesize udId, name, latitude, longitude;
- (CLLocationCoordinate2D)coordinate {
CLLocationCoordinate2D internalCoordinate;
internalCoordinate.latitude = [self.latitude doubleValue];
internalCoordinate.longitude = [self.longitude doubleValue];
return internalCoordinate;
}
- (void)dealloc {
[udId release];
udId = nil;
[name release];
name = nil;
[latitude release];
latitude = nil;
[longitude release];
longitude = nil;
[super dealloc];
}
#end
And here's the methods where I should be reading the response and converting it to objects I can use:
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
I'd really appreciate some help on this. I just can't seem to wrap my head around it...
Thanks in advance!
You are almost there. In your code where you say:
// READ "DEVICES" AND CONVERT TO OBJECTS
do this:
NSArray * devices = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary * deviceInfo in devices) {
Device * d = [[[Device alloc] init] autorelease];
[d setLatitude:[deviceInfo objectForKey:#"Latitude"]];
[d setLongitude:[deviceInfo objectForKey:#"Longitude"]];
[d setName:[deviceInfo objectForKey:#"User"]];
[d setUdId:[deviceInfo objectForKey:#"UDId"]];
// do some stuff with d
}
What's going on here: I didn't see what JSON library you are using to convert, but presuming it works like TouchJSON or SBJSON, the JSON array is automatically turned into an NSArray instance, while the inner hashes of the NSArray are NSDictionary objects. At the point that you have deserialized that JSON string, everything you're dealing with will be instances of NSString, NSNumber, NSArray and NSDictionary (and depending on the library, NSNull to represent null values).
First you need to define your initializer/constructor for your Device class.
Device.h
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon;
Device.m
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon {
if (self = [super init]) {
self.udid = udid;
self.name = name;
self.latitude = lat;
self.longitude = lon;
}
return self;
}
Then you can initialize a new object like:
Device *dev = [[Device alloc] initWithUdid:#"a udid" name:#"the name" latitude:latNum longitude:lonNum];
So, you should be able to iterate the array and build your Device objects like so:
NSArray *devicesArray = [dict objectForKey:#"Devices"];
for (NSDictionary *d in devicesArray) {
Device *dev = [[Device alloc] initWithUdid:[d objectForKey:#"UDId"]
name:[d objectForKey:#"User"]
latitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]
longitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]];
}
You want to access the array of device dictionaries from the top-level dictionary just as you did the Success value. Then iterating over the dictionaries you can use each's -keyEnumerator method to iterate over its keys.
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
NSArray* deviceArray = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary* dict in deviceArray)
{
for(NSString* key in [dict keyEnumerator])
{
NSLog(#"%# -> %#", key, [dict objectForKey:key]);
}
}
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
Sounds like you need to reuse your line:
[jsonDictionary objectForKey:#"Success"]
try having a look at
[jsonDictionary objectForKey:#"Devices"]
You really need to figure out what type it returns.
If you're lucky, it returns an NSDictionary, or alternately something that you can easily turn into an NSDictionary.