I saw this post: How to sort an NSMutableArray of objects by a member of its class, that is an int or float
And had something similar, but I wasn't sure how it all works and was hoping for some guidance. I have an Array of Marker objects that have a Type ivar that takes on values of 0, 1, 2. I want to have a check box that says what to order by, 0, 1, or 2.
I started off by trying to doing something similar in my own method:
- (NSComparisonResult)Type:(id)otherObject {
if ([self Type] > [otherObject Type]) {
return NSOrderedAscending;
}
else if ([self Type] < [otherObject Type]) {
return NSOrderedDescending;
}
else {
return NSOrderedSame;
}
}
In my .h file:
NSInteger _type;
#property NSInteger Type;
I get the two warnings:
Ordered comparison between pointer and integer ('id' and NSInteger')
Method - Type not found (return type defaults to 'id')
I don't think I understand what is happening when you call the sort method. Thanks.
What happens if you're explicit about the input type being a YourObjectType * (or whatever your object is called) rather than id? To be completely safe you should check the type of the incoming object anyway (as it may not respond to 'Type' at all, or may return something else with the same selector), so you'd end up with:
- (NSComparisonResult)Type:(YourObjectType *)otherObject {
if(![otherObject isKindOfClass:[YourObjectType class]]) return NSOrderedSame;
if ([self Type] > [otherObject Type]) {
return NSOrderedAscending;
}
else if ([self Type] < [otherObject Type]) {
return NSOrderedDescending;
}
else {
return NSOrderedSame;
}
}
I'm basically making the same guess as highlycaffeinated, that the compiler is failing to correctly determine the return type of 'Type', but my guess is that it's because of an incorrect compiler prediction about the incoming object.
To explain the whole process:
When you ask NSArray to sortUsingSelector, it'll apply some sorting algorithm that requires it to be able to compare any two objects within it. When it needs to compare two objects, it'll send the selector you supplied, then use the result you return — so with Ascending/Descending you're specifying the order that the two objects would appear in if correctly sorted.
For example, if you had an NSArray filled with objects of type SillyObjectType and you issued a sortUsingSelector:#selector(compare:) then NSArray would apply some unspecified sorting algorithm that involved sending the selector compare: to instances of SillyObjectType. Each SillyObjectType is being asked "should you come before, after, or at an equal place to this other object?". It replies with NSOrderedAscending, Descending or Same.
This is one place where ObjC's dynamic dispatch gives a slightly different way of writing the thing than you'd use in e.g. C++. The selector will be sent to the objects in the array, so you should implement it on those objects. In each case the normal rules apply, so self will be that instance of that class.
It looks like you've defined Type as an ivar and not a property, therefore it won't be visible in otherObject. Expose it as a property and your comparison should work.
Related
I need to check two values and set conditions based on these two values, return a NS_ENUM value.
From ReactiveCocoa github readme, I find this
RAC(self, createEnabled) = [RACSignal
combineLatest:#[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ]
reduce:^(NSString *password, NSString *passwordConfirm) {
return #([passwordConfirm isEqualToString:password]);
}];
It check two value, the password and passwordConfirm together. I tried to modify it a bit to observe two BOOL property, it shows me "Incompatible block pointer types" error..
RAC(self, showButtonOption) = [RACSignal
combineLatest:#[ RACObserve(self, setting), RACObserve(self, billing) ]
reduce:^(NSSet *setting, NSSet *billing) {
if ([billing containsObject:kBillingExpired]) {
return DialerShowButtonPurchase;
} else if ([setting containsObject:kSettingEnableRecord]) {
return DialerShowButtonRecord;
} else {
return DialerShowButtonCall;
}
}];
I don't know what went wrong and what should be the right syntax to serve the purpose?
Well, let's see what the signature of that method is:
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals
reduce:(id ( ^ ) ( ))reduceBlock
You're trying to return an enum value, a primitive, from the reduceBlock -- which must have return type of id.
This is an annoying but sadly unavoidable aspect of ReactiveCocoa: you need to box. A lot. If you return #(DialerShowButtonPurchase) (etc), you'll actually be returning an NSNumber *, which is an id, so it'll compile.
The RAC macro will automatically unbox it so that showButtonOption doesn't need to be declared as an NSNumber *.
I have a mutable array property declared and synthesized:
#property (nonatomic, strong) NSMutableArray *arrayOfTasks;
I am using KVC collection Accessors for the same property and also I have other methods which will internally call this KVC Collection accessor method like this:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
I had to do some modifications and add the object into the array only when a particular condition is satisfied, so to make sure that this check goes into the designated method, I included the following in the -insertObject KVC Collection accessor method:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
if ([inTask isOperatable])
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
Now each time when I trigger -addObjectINArrayOfTasks method and if the -isOperatable condition returns boolean NO, the app crashes with no stack trace at all! (Stack trace is at main() of the application). All it says is "index 0 beyond bounds for empty array error".
I am not understanding the reason for this, I am not trying to access the array yet, so I am not giving a chance for framework to complain me that there is no element at index 0. Moreover, I am doing the count of array items check everywhere before accessing the objects out of array. For, if I was trying to access and element out of the bounds index, the app would crash at the same point and let me know exactly where I was trying to access the index out of bounds. That would have been a simple straightforward fix.
Now, to just cross verify, I made a small change in the code like this, which seems to work:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
if ([inTask isOperatable])
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
}
I can go ahead with this approach which is working and does not crash, but my concerns are the following:
Adding the same check in designated method would be an added advantage in future if some other programmer would want to invoke the designated method from somewhere else.
Why would the app crash in first case when I wont insert the object into the array in KVC collection accessors based on some condition check?
Thanks for any inputs,
Raj
I think the crash you are seeing is more likely related to internal KVC behavior than your array. That might be the reason you don't see a usable stack trace. Have you enabled the exception breakpoint in Xcode?
KVC basically expects that -insertObject:in<Key>AtIndex: will insert a new object at the given index (presumably 0 in your case). Since it assumes that the object was inserted it should now be accessible by queuing the data structure (NSMutableArray) for the object at the given index. When the condition evolves to NO, you fail to insert this object, which means that an index out of bounds exception is possible when KVO tries to query using the provided index.
The second code snipped you posted avoids this error by not calling the KVC collection accessor when an insertion is not needed.
If you want to minimize the chance of someone incorrectly using those methods, expose just -addObjectInArrayOfTasks: in your public header. In addition you can document this. If you want to make it absolutely certain that -insertObject:in<Key>AtIndex: can't be accessed on int's own, you can add an NSAssert, that checks if the method was called from -addObjectInArrayOfTasks:.
I sometimes like to organize IB elements into NSArrays primarily to help me organize my elements. Most often, different classes of objects make it into the same array with each other. While this is a convenient way of organization, I can't seem to wrap my head around why if I have an array like this:
NSArray *array = [NSArray arrayWithObjects:((UITextField *)textField), ((UISegmentedController *)segmentedController), nil];
Why I get "Does not respond to selector" messages when I put a for loop like this:
for (UITextField *text in array) {
[text setText:#""];
}
The for loop seems to be passed objects that are not of class UITextField.
What is the point of declaring the object's class if all objects in the specified array are passed through the loop?
EDIT Just for reference, this is how I'm handling it as of now:
for (id *object in array) {
if ([object isMemberOfClass:[UITextField class]]) {
foo();
} else if ([object isMemberOfClass:[UISegmentedController class]) {
bar();
}
}
When you do
for (UITextField *text in...
the object pointers from the array are cast to UITextField* type - so if the object isn't actually a UITextField, all sorts of weird things may happen if you try to call UITextField methods.
So instead use the id type (no * needed, btw):
for (id obj in array)
Then check the type as you do and call the appropriate methods. Or, filter the array to get only objects of a certain type, then go though that type only:
for (UITextField* text in [array filteredArrayUsingPredicate:...])
Edit: here's how to build class filter predicates:
Is it possible to filter an NSArray by class?
What is the point of declaring the object's class if all objects in the specified array are passed through the loop?
The class name is just there to let the compiler know what it should expect to find. This allows it to try to figure out what methods it should expect you to call and how you might treat the object. It's the same idea as passing in an int to a method that takes float. The method will not ignore ints - it's assuming you're passing the correct type. You're just giving this construct a little more credit than it's due:
for (UITextField *text in array)
It just doesn't have that functionality. How you're handling it now is the correct way.
Are you sure you don't get an error when you run that code? The "does not respond to selector" message is a runtime error, not a compile time error. The compiler has no idea whether the objects in the array implement -setText:, but you should certainly get an error when you actually send that message to an instance of UISegmentedControl.
Another possibility is that you've got a class called UISegmentedController that does have a -setText: method. The name of the class that implements the multi-part bar-graph-looking user interface widget is UISegmentedControl. So either the code you're showing isn't real, tested code, or you've got a class that we don't know about.
I have an NSMutableArray in my class containing Ingredient objects. I want to check whether the name property of any of the ingredients matches a string, but I can't get the syntax quite right.
I'm really missing Linq and predictates.
-(BOOL) hasIngredient:(NSString *)ingredientName{
for (Ingredient *ingredient in ingredients) {
//if([ingredient isKindOfClass:[Ingredient class]]){
if ([ingredient->name isEqualToString:ingredientName]) {
return YES;
}
//}
}
return NO;
}
The foo->bar syntax directly accesses instance variables. You shouldn't do that. The syntax to access a property is:
object.property
or:
[object property]
Accessing a property is always a method call. If you have a property foo and do #synthesize foo;, the compiler generates a method named foo and setFoo: (if the property isn't readonly).
So you should have something like:
#property(nonatomic,readonly) NSString *name;
Replace readonly with copy if you want the name to be changeable (the reason to use copy instead of retain is because you could pass a mutable string and then later modify that mutable string, which would sure yield unexpected results; you avoid that by copying).
Now your method becomes:
-(BOOL) hasIngredient:(NSString *)ingredientName{
for (Ingredient *ingredient in ingredients) {
if ([[ingredient name] isEqual:ingredientName]) {
return YES;
}
}
return NO;
}
Instead of [ingredient name] you can also write ingredient.name here, but I personally like the former better since the later is also used for accessing members of a struct which is "cheap" whereas accessing a property always involves a method call and thus is "more expensive". But that's just a matter of taste.
Change
if ([ingredient->name isEqualToString:ingredientName])
to
if ([ingredient.name isEqualToString:ingredientName])
Is there a return type for "any primitive" similar to the way you can use NSObject as the return type for any object? I tried using id, but the compiler was giving me an error that it was trying to convert a primitive to a reference.
Here's what I'm trying to do. :
-(void)setObject:(NSObject*)obj forKey:(NSString*)key {
[sharedInstance.variables setObject:obj forKey:key];
}
-(NSObject*)getObjectForKey:(NSString*)key {
return [sharedInstance.variables objectForKey:key];
}
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
-(???)getValueForKey:(NSString*)key {
return [sharedInstance.variables valueForKey:key];
}
The alternative that I have though of is to use separate methods (getIntForKey, getFloatForKey, etc.) to access the values.
1) Read Key-Value Coding Article in XCode documentation - all answers are there
2) There's an object NSValue, which resembles your "NSObject". NSValue can store plain-old-data inside itself.
PS
"Scalar and Structure Support
Key-value coding provides support for scalar values and data structures by automatically wrapping, and unwrapping, of NSNumber and NSValue instance values.
Representing Data as Objects
The default implementations of valueForKey: and setValue:forKey: provide support for automatic object wrapping of the non-object data types, both scalars and structs.
Once valueForKey: has determined the specific accessor method or instance variable that is used to supply the value for the specified key, it examines the return type or the data type. If the value to be returned is not an object, an NSNumber or NSValue object is created for that value and returned in its place.
Similarly, setValue:forKey: determines the data type required by the appropriate accessor or instance variable for the specified key. If the data type is not an object, then the value is extracted from the passed object using the appropriate -Value method."
I would have thought id is the perfect candidate here too... could this just be a casting issue you're seeing?
i.e. the id implicitly implies a pointer so in my mind I see id as an objective c equivalent to the c void*
In other words where you have a NSObject* you could replace this with id such as
-(NSObject*)myMethod1
{
}
so can be done for any returned primitive with
-(id)myMethod1
{
}
i.e. not an id*
Also I expect this was just a copy/paste thing but incase it also causes issues
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:num forKey:key];
}
should probably be
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
I eventually worked through this. The ultimate solution was to have separate accessor/mutator methods per type. So now I have setIntForKey, setBoolForKey, getIntForKey, getBoolForKey, etc. The drawback is quite obvious, in that I can't call one method to set values and another to retrieve them. The advantages are numerous, however. Because the compiler knows what object or primitive type the method is expecting at compile time, I gain compile time checking for all of these methods. Additionally, I don't have to worry with casting the retrieved values to their primitive types (obviously the returned NSObjects are a different story).