Problem with an Array, a Property and NSLog - objective-c

So i have this block of code it adds players to an NSMutableArray in my ViewController playerList. For some reason i cannot print all the playernames to the log. Am I doing something wrong? I keep getting an error that says member refrence struc objc_object is a pointer. Can anyone see what im doing wrong?
p1,p2,p3,p4 are all NSString Objects that just have the players names.
the addPlayer method creates a new player object with a property named playerName.
- (IBAction)addPlayerButton:(id)sender {
[self.playerList addObject:[self addPlayer:p1]];
[self.playerList addObject:[self addPlayer:p2]];
[self.playerList addObject:[self addPlayer:p3]];
[self.playerList addObject:[self addPlayer:p4]];
for (id element in playerList) {
NSLog(element.playerName);
}
}

for (id element in playerList) {
NSLog(element.playerName);
}
The compiler warning/error is because element is of type id and you can't use the dot syntax with object references of type id (a specific design choice when creating that feature, btw).
Fixed code:
for (Player *element in playerList) {
NSLog(#"%#", element.playerName);
}
Two (unrelated) problems fixed:
explicitly type element to be a reference to your player class (I assumed the name). This'll allow the dot syntax to work.
Use a format string with NSLog. If a player's name were ever to contain a formatting sequence -- %#, for example -- then NSLog() would try to expand the next (non-existent) argument to NSLog and your app would crash or print garbage (say, if the player's name were "Bob %f %f %f").
doesnt look like they are getting
added to the array properly
Make sure you allocate an array and assign it to playerList somewhere:
self.playerList = [NSMutableArray array];

Use this instead:
NSLog(#"%#", element.playerName);
NSLog is sort of like printf() and friends, but not exactly. You must provide a first argument that is a string literal with the format you want to use, followed by any variables represented in the format. In Objective-C, the special format %# means "use the obeject's description method to fill in a value (if there is one)." Sometimes you get a debugger-like output for an object that does not have that method, e.g. or some such, which isn't too useful of course.
In your case, assuming playerName is an NSString, you'll see it's name output if you use the format %# in NSLog's first argument.
EDIT:
You should be able to use a for statement like this:
for(Player *p in playerList) {
NSLog(#"%#", p.playerName);
}
Just because you use addObject: to add the objects doesn't mean you have to give up using the objects' type when you look at them from the array.
If in fact the objects in playerList are just NSStrings, then your loop can simply be
for(NSString *name in playerList) {
NSLog(#"%#", name);
}

Related

Printable type string from parameter

Using clang and objective-c I'm wondering if I can get a printable string describing the type of a potentially null parameter (i.e. compile time only type information.)
For example something like:
- (void)myFunc:(NSString *)aNilString {
NSLog(#"%s", __typeof__(aNilString));
}
Obviously this doesn't work because __typeof__ gets me the actual type, not a string. In C++ we have typeid which returns a std::type_info, but that name is mangled, e.g. "P12NSString*" and not "NSString*".
Ideally I'd like something that can be passed into functions like objc_getClass(). Is there a way to get what I want?
Edit: I'd like not to have to compile as C++, so this solution is out:
abi::__cxa_demangle(typeid(*aNilString).name(), 0, 0, 0));
To get a string that can be passed into functions like objc_getClass() just use the description of the class object.
In this example, I changed the type of your argument from NSString * to id since if you already knew that the argument was an NSString * then this question would be kind of moot. Note that I call the class method of the object being passed in and then the description method of the class in order to get an NSString * which describes the class and can be used with the methods that you reference.
Note also that if the object is nil then there is no class and calling description will simply return nil and the NSLog will print (null). You can't determine the type of pointer at runtime because it is simply a pointer to a class object (and that doesn't exist for nil).
- (void)myFunc:(id)aClassObject {
// Get the description of aClassObject's class:
NSString *classString = [[aClassObject class] description];
NSLog(#"%#", classString); // Prints __NSCFConstantString
// Bonus: Get a new object of the same class
if ([classString length] > 0) {
id theClass = objc_getClass([classString UTF8String]);
id aNewObjectOfTheSameType = [[theClass alloc] init];
NSLog(#"%#", [[aNewObjectOfTheSameType class] description]); // Prints __NSCFConstantString
}
}

objectAtIndex returns SIGABRT error

I have this line:
NSString *objectkey=[NSString stringWithFormat:#"%#",[line objectAtIndex:1]];
If objectAtIndex:0, it works fine. But if 1, it produces SIGABRT error at runtime.
However, I have another line to confirm that the array "line" has an object at index 1:
NSLog(#"%d",[line count]);
This returns 2 to the console.
Why would SIGABRT occur even though an index should exist there?
As for line, it is created like this:
for (int i=1;i<=[components count];i++){
NSArray*line=[[components objectAtIndex:(i-1)] componentsSeparatedByString:#"|"];
"line" is recreated during each loop iteration (I assume this is okay? no release is necessary, from what i understand using the "separated by string" method).
the array "components" contains lines such as:
Recipe Books|BOOKS
Recipe Photos|PHOTOS
I have created this little loop to verify that all are strings in "line":
for( NSObject* obj in line )
{
NSLog(#"%#",obj);
if ([obj isKindOfClass:[NSString class]]==YES) { NSLog(#"string"); }
}
Most likely the object contained at [line objectAtIndex:1] is not an NSString*. Why don't you try iterating over the set of objects in line and outputting them with NSLog. My guess is that the second one is going to print an address (of the form <Classname: 0x0>, not a string.
for( NSObject* obj in line )
{
NSLog(#"%#",obj);
}
Add an NSLog to the code where you create line:
for (int i=1;i<=[components count];i++){
NSLog(#"%#",[components objectAtIndex:(i-1)]);
NSArray*line=[[components objectAtIndex:(i-1)] componentsSeparatedByString:#"|"];
I have a strong feeling that your input data is bad, as you've excluded our prior answers as the solution with your responses. Your input data might have a <cr> somewhere it doesn't belong or missing data. How big is the input file for components?
Second answer based on gdb results
Try using [NSString stringWithString:[line objectAtIndex:1]] instead of stringWithFormat. Based on your use of gdb its likely that stringWithFormat is breaking on the unexpected control character (and trying to format it). stringWithString should copy the string character by character. Removing the control character(s) is another problem. :)
Assuming there is a valid object at index 1 of your line NSArray, the likely answer is that it's not a NSString, it's some other class that can't be translated using %# in your stringWithFormat: call. Look at the stack where it aborts and you could also check the type of the object before calling the stringWithFormat: call.

If the type of objects in a collection is known, should I specify the type of the iterating variable when using fast enumeration?

For example, say I have an NSArray that contains a bunch of NSString objects:
NSArray *games = [NSArray arrayWithObjects:#"Space Invaders", #"Dig Dug", #"Galaga", nil];
What are the pros and cons of doing this:
for (id object in games) {
NSLog(#"The name of the game is: %#", object);
NSLog(#"The name is %d characters long.", [object length]);
// Do some other stuff...
}
versus:
for (NSString *name in games) {
NSLog(#"The name of the game is: %#", name);
NSLog(#"The name is %d characters long.", [name length]);
// Do some other stuff...
}
It's not strictly necessary, and will force a cast to the specified type on every iteration, so unless you are calling methods specific to NSString inside of your loop, then I don't see why this could be beneficial.
So, in your case I would do it, because you're invoking -length (an NSString method) on every object. In order to avoid compiler warnings, you'd cast the object to the specific type - which is what NSFastEnumeration does for you when you specify a non-id type. So yeah, use your second example.
You can't make iterator universal by specifying it's type as id if you've already passed a class specific message to it. If you want to make it "almost" universal you can specify as iterator's type the top-most class in hierarchy having all messages you plan to use. For your case this class is NSString

NSMutableArray from filterUsingPredicate error

I am trying to return a subset of my NSMutableArray (MessageArray) with the following code. MessageArray contains an NSDictionary, one of the keys being FriendStatus. I get a strange error which I know is a DUH syntax issue. "error. void value not ignored as it ought to be".
-(NSMutableArray*)FriendMessageArray {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"FriendStatus == 1"];
NSMutableArray *filtered = [MessageArray filterUsingPredicate:predicate];
return filtered;
}
"void value not ignored as it ought to be" means that a method with a signature that starts with (void) is being used to assign a value or object to a variable. What's the signature for filterUsingPredicate? does it start with (void) ?
I'm assuming "MessageArray" is an instance variable (never name instance variables this way; you should have a property called -messages and access it with self.messages). I'l further assume that it is an NSMutableArray or else you'd be getting warnings from the compiler.
NSMutableArray -filterUsingPredicate: modifies the array itself, returning void. The method you want is -filteredArrayUsingPredicate: which returns an array. The fact that the former is a verb and the latter is a noun indicates this fact even without reading the docs. Cocoa naming is extremely consistent, which is why I mention it in the first paragraph. Pay attention to the names and you will have far fewer bugs.

How to use #encode() to get #"NSArray" in Objective-C

I'm using the runtime functions to get the type of a property (thanks to eJames for helping me to figure out this way).
The attribute string of the property looks like this:
T#"NSArray",&,Vstuff
I need to check if the property type is an array, at the moment I'm doing it like this:
- (BOOL)valueForKeyIsArray:(NSString *)key fromTagret:(id)target
{
NSString *lowerCaseKey = [self convertToKVCKey:key];
objc_property_t property = class_getProperty([target class], [lowerCaseKey UTF8String]);
NSString *propertyAttrs = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *encodedType = #"#\"NSArray\"";
NSRange range = [propertyAttrs rangeOfString:encodedType options:NSLiteralSearch];
return range.location != NSNotFound;
}
But since Apple can change the type definition string at any time, I would like to generate this #"NSArray" type string. I tried it with #encode(), but it did not work:
NSString *encodedType = [NSString stringWithUTF8String:#encode(NSArray *)];
So how can I generate this type string? Or is there a better way to check if this property attributes contain the array type?
There is no way to check this. In Objective-C source code the variables being typed as NSArray * is only there for the compiler to issue warnings. It has no meaning, and does not exist at runtime. If you mis-typed an NSArray as an NSString, you would get lots of warnings when compiling, but your code would behave exactly the same when run. At runtime all that is known is that the ivar/property is "an object".
Another way to think of it, is that once Objective-C is compiled, all object references are id references.
Just accept that if the runtime changes, your code will break, and move on. However, I think you might be miscategorizing ivars of type NSMutableArray *, CFArrayRef, or CFMutableArrayRef. You also seem to be assuming all keys correspond directly to a declared property.
The cleanest solution might be to assert that the sample object being used for the test (the target) must have a non-nil value for that key, and just grab the value and test that [[target valueForKey:key] isKindOfClass:[NSArray class]].