Bad excess code for an array setter - ios

I have an array of type NSArray as below:
#property (nonatomic, strong) NSArray *myArray;
In the setter of this array, I want to check if the array count is 0, then return nil else return the array as it is. I am doing it as below:
- (NSArray *)myArray
{
return ([self.myArray count] == 0) ? nil : self.myArray;
}
I am not getting any build error if I return nil for an NSArray, but its giving me run time error of bad access code. What mistake am I making here?

Your getter method is recursively calling itself, because
self.myArray
is translated by the compiler to
[self myArray]
This causes a stack overflow (!) eventually.
Inside a setter or getter method of a property, you have to access
the associated instance variable instead, e.g.
- (NSArray *)myArray
{
return ([_myArray count] == 0) ? nil : _myArray;
}

Do you instantiate the array anywhere else prior to calling this method? It will have to exist before you can access it.
_myArray = [NSArray arrayWithObjects:#"one",#"two",nil];
or one of the many other techniques to create an Array.

Related

overriden isEqual method not getting called every time during comparisons

I have a class called
Contact;
In Contact I have (simple version to test, no hash yet)
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return NO;
}
I do:
NSMutableArray *arr = [[NSMutableArray alloc] init];
Contact *contact = [Contact new];
[arr addObject:contact]
// I expect my isEqual to be called here but it does not get called
[arr containsObject:contact] // this evaluates to true somehow!!!
However if I add a another object to type NSString, then it gets called for comparing String object but not for the contact object. Which means
[arr addObject:#""] // so now arr has two elements
// here I expect two calls to isEqual but only one gets there
// when comparing string object against Contact
[arr containsObject:contact]
Why is isEqual not getting called in cases I mentioned above??
Please read the discussion about isEqual: in the NSObject Protocol in the Reference Library.
You'll find that for objects which are inside a collection (such as an NSArray), hash might be used to determine whether two objects are actually the same. If two pointers are actually pointing to the same object, there is no need to check for equality - hence isEqual: never gets called.
The solution as suggested by the reference library is to implement hash in your subclass as well.

iOS: pointer reference to an array

I have a table view with a search, and search scope buttons with two possible scopes. The table is empty until a search is executed. Each scope has it's own mutable array for the table's data source, we'll say scopeA_array and scopeB_array. To simplify some methods, I'm trying to create a generic pointer reference to whichever array is the currently active scope. So I tried this:
#property (nonatomic, assign) NSMutableArray *tableDataArray;
In viewDidLoad, I assign it to the default selected scope.
_tableDataArray = _scopeA_array;
I can log the memory address of each array, they're both the same.
However, if I execute a search, _scopeA_array gets populated. Then in my numberOfRowsInSection method, I take the count of _tableDataArray but it's empty. I log the addresses again, both are different.
How do I create an array property that just references an array, and always points to the same object in memory even if it changes?
EDIT: A simplified way to test this, with the following lines of code, would like a way for tableDataArray to have the contents of testArray, even though the contents of testArray are assigned after:
NSArray *testArray = [NSArray new];
NSArray *tableDataArray = [testArray copy];
testArray = [NSArray arrayWithObjects:#"my", #"test", #"array", nil];
NSLog(#"table data array: %#", tableDataArray);
// logs empty array
I think the best approach is use a method to return conditionally the array for the current scope. So you just always use this method to populate your UITableView
- (NSMutableArray*) tableArray
{
return [self isScopeA] ? _scopeA_array : _scopeB_array;
}
How do I create an array property that just references an array, and always points to the same object in memory even if it changes?
If you want to track changes to a variable then you use a pointer to the variable rather than a pointer to a single array instance. E.g.:
#implementation MyController
{
__strong NSArray* *_currentDataPtr;
NSArray* _dataA;
NSArray* _dataB;
}
- (id)init
{
if (self = [super init])
{
_currentDataPtr = &_dataA; // Ensure _currentDataPtr is never NULL
}
return self;
}
- (void)setSearchScope:(NSInteger)searchScope
{
switch (searchScope)
{
default :
NSAssert(NO, #"");
case 0 :
_currentDataPtr = &_dataA;
break;
case 1 :
_currentDataPtr = &_dataB;
break;
}
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [*_currentDataPtr count];
}
If you want it to be a property then implement a property getter that dereferences the pointer:
#property (nonatomic, readonly) NSArray* currentData;
- (NSArray*)currentData { return *_currentDataPtr; }

iOS How do I compare if Objects are Equal? [duplicate]

This question already has answers here:
How do I compare objects in Objective-C?
(4 answers)
Closed 8 years ago.
I have a list of Objects that I pull from a web service. When I update my UITableView, I retrieve the objects again from the web service, and compare them to each other for equality. I then remove the ones that are not present, and insert the new objects, then update my UITableView. How can I test to see if the new object equals the old object? I've created a test for clarity..
requestA should equal requestC, but fails.
Is this possible to do without looking at each property value as the objects have many values?
I was originally comparing the ID only, but this doesn't work as sometimes other property values change and the ID stays the same.
Request *requestA = [[Request alloc] init];
Request *requestB = [[Request alloc] init];
Request *requestC = [[Request alloc] init];
requestA.requestID = #"1";
requestA.productName = #"Clutch";
requestB.requestID = #"2";
requestB.productName = #"Wiper";
requestC.requestID = #"1";
requestC.productName = #"Clutch";
if (requestA == requestB)
NSLog(#"A == B");
if (requestA == requestC)
NSLog(#"A == C");
if ([requestA isEqual:requestB])
NSLog(#"A isEqual B");
if ([requestA isEqual:requestC])
NSLog(#"A isEqual C");
// Look at the pointers:
NSLog(#"%p", requestA);
NSLog(#"%p", requestB);
NSLog(#"%p", requestC);
isEqual: is a method declared in NSObject Protocol. From official docs of isEqual:
This method defines what it means for instances to be equal. For
example, a container object might define two containers as equal if
their corresponding objects all respond YES to an isEqual: request.
See the NSData, NSDictionary, NSArray, and NSString class
specifications for examples of the use of this method.
If two objects are equal, they must have the same hash value. This
last point is particularly important if you define isEqual: in a
subclass and intend to put instances of that subclass into a
collection. Make sure you also define hash in your subclass.
Thus, as Salavat Khanov pointed out in his answer:
You need to implement -isEqual: and -hash methods for your Request class.
You want to do something like this:
// TestRequest.h
#interface TestRequest : NSObject
#property (nonatomic) NSString *requestID;
#property (nonatomic) NSString *productName;
#end
// TestRequest.m
#import "TestRequest.h"
#implementation TestRequest
- (BOOL)isEqual:(TestRequest *)object {
if (self == object) {
return YES;
}
if (![self.requestID isEqual:object.requestID]) {
return NO;
}
if (![self.productName isEqual:object.productName]) {
return NO;
}
return YES;
}
- (NSUInteger)hash {
// this is a very simple hash function
return [self.requestID hash] ^ [self.productName hash];
}
#end
or you can use a custom method:
- (BOOL)isEqualToRequest:(TestRequest *)otherRequest {
return [self.requestID isEqualToString:otherRequest.requestID] &&
[self.productName isEqualToString:otherRequest.productName];
}
Check this answer: How do I compare objects in Objective-C?
You need to implement -isEqual: and -hash methods for your Request class.
You need to overwrite isEqual: of your Request object to specify the properties to compare.
Write sth. like this:
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
if (!other || ![other isKindOfClass:[self class]]) return NO;
if (![(id)[self name] isEqual:[other name]]) return NO;
// add other checks if needed
return YES;
}
First off. == is a check for "are these two objects actually the SAME OBJECT". I.e. They are just two pointers to the same but of memory.
You need to be using the isEqual method. However, in order to do this properly you need to override the method in the class.
Something like...
- (BOOL)isEqual:(Request *)otherObject
{
return [self.productName isEqual:otherObject.productName]
&& [self.requestID isEqual:otherObject.requestID];
}

NSMutableArray addObject is nil?

I am new to iOS dev,here is my first app-calculator,
But the NSMuteableArray "_numberArrayWaitingForCalculate" always be "nil",I don't know what to do???
Here is the interface
#interface demoViewController ()
#property (strong,nonatomic)NSString *valueString;
#property (strong,nonatomic)NSMutableArray *numberArrayWaitingForCalculate;
#end
here is the implement 1
#implementation demoViewController
#synthesize numberArrayWaitingForCalculate=_numberArrayWaitingForCalculate;
- (NSMutableArray *)numberWaitingForCalculate
{
if(!_numberArrayWaitingForCalculate)
_numberArrayWaitingForCalculate=[[NSMutableArray alloc]init];
return _numberArrayWaitingForCalculate;
}
here is the tapNumber method
- (IBAction)tapNumber:(UIButton *)numberButton {
if(LastButtonWasMode)
{
_valueString=#"";
LastButtonWasMode=NO;
}
NSString *numberAsString = numberButton.currentTitle;
_valueString=[_valueString stringByAppendingString:numberAsString];
result.text=[NSString stringWithFormat:#"%#",_valueString];
}
here is tapPlus method
- (IBAction)tapPlus:(id)sender {
[_numberArrayWaitingForCalculate addObject:[NSNumber numberWithInt:[_valueString intValue]]];
resultOfAllNumberInputBefore +=[_valueString intValue];
[self setMode:1];
}
The following line should be using the property and not the instance variable. i.e. you're not actually calling the getter that allocates the array.
Change this line:
[_numberArrayWaitingForCalculate addObject:[NSNumber numberWithInt:[_valueString intValue]]];
to:
[self.numberArrayWaitingForCalculate addObject:[NSNumber numberWithInt:[_valueString intValue]]];
You created a getter that "lazy loads" the mutable array (meaning that you create it if it doesn't exist already. That's a valid approach.
However, if you do that, you need to ALWAYS use the getter. You're using the iVar directly (_numberArrayWaitingForCalculate). Don't do that. Replace all instances of "_numberArrayWaitingForCalculate" with [self numberArrayWaitingForCalculate] except in the implementation of your getters/setters and probably your dealloc method.
So your tapPlus method should read:
- (IBAction)tapPlus:(id)sender
{
[[self numberArrayWaitingForCalculate] addObject:[NSNumber numberWithInt:[_valueString intValue]]];
resultOfAllNumberInputBefore +=[_valueString intValue];
[self setMode:1];
}
EDIT:
By the way, for something as lightweight as an empty mutable array, I think I would take a different approach. Rather than lazy-loading the array in a getter, I would create an init method for my class that created an empty mutable array and installed it in the iVar.
Objects like view controllers can be initialized more than one way. It might get initialized with initWithNibName:bundle: or with initWithCoder:
What I do in that case is to create a method doInitSetup, and call it from both places.

NSDictionary extend valueForKey method

Can I create category that will extend - (id)valueForKey:(NSString *)key method and if the value of this key will be equal [NSNull null] it will return me empty string #"".
It is connected to nil values for some keys that I get from the backend server in JSON.
So if I need to save some value to the property it can be nil.
Suppose I use this code with value for key nil
artist.name = ad[#"artist"][#"name"]; // nil value on the right.
so if I will have extension it will check value for key and return me #"" intend of nil. Does it make sense.
Or do I need extend NSManagedObject for checking attribute type and then return corresponded value #"", 0 or nil, or 0.0f if the attribute has another not string type.
Rather than defining a category on NSDictionary, you could define a category on NSObject, and do the conversion there:
#interface NSObject (DBNullHandling)
-(NSString*)nullToEmpty;
#end
#implementation NSObject (DBNullHandling)
-(id)nullToEmpty {
return (self == [NSNull null]) ? #"" : self;
}
#end
Now you can do this:
artist.name = [ad[#"artist"][#"name"] nullToEmpty];
If ad[#"artist"][#"name"] returns [NSNull null], artist.name gets assigned #""; otherwise, it gets assigned the value returned by ad[#"artist"][#"name"].
What I do is check before performing the assignment. The tripartite operator is a good shorthand way to do this. You have:
artist.name = ad[#"artist"][#"name"];
Instead, I would write
id val = ad[#"artist"][#"name"];
artist.name = val ? val : #""; // or val ?: #""
(I have not completely understood the question, so it might be necessary to break that check down into more stages or modify it in some way to check for [NSNull null] instead of nil. But you get the idea.)
Instead of subclassing NSDictionary to change the implementation of valueForKey:, I suggest you check if the key exists. So you would instead do this:
BOOL keyExistsInDictionary = [[myDict allKeys] containsObject:key];
id objectForKey = [myDict objectForKey:key];
if (nil == objectForKey && keyExistsInDictionary) objectForKey = #"";
If objectForKey: returns nil, you can check keyExistsInDictionary to see if the key is there and simply contains [NSNull null]. This lets you differentiate between a key that exists and a key that doesn't without modifying NSDictionary.
Can I create category that will extend - (id)valueForKey:(NSString *)key method
The short answer here is no, don't do that. Categories let you extend classes by adding method, but you shouldn't use them to override existing methods. The main reason for this is that it's unreliable -- the order in which the methods in categories are added to a class isn't defined, which means that if you have two categories that override the same method, you'd have no idea which version of the method the class would ultimately end up with.
Another reason is that you have no way to refer to the original method from the overriding method, so you'd have to re-implement the behavior that you want to keep.
Finally, a category changes the class, so every instance of that class will have the new behavior. That's not usually a problem when you're adding methods -- code that doesn't use the new methods remains unaffected by the new functionality. But when you change an existing method, particularly for a class that's as widely used as NSDictionary, it's very likely that you'll break a lot of existing code.
if the value of this key will be equal [NSNull null] it will return me empty string #""
You've gotten some good answers already, but since you asked about categories, here's an approach that solves the problem using a category appropriately. Instead of trying to change -valueForKey:, use a category to add a new method called -stringForKey: that returns a string. It sounds like you're working exclusively with strings, so this seems like a reasonable solution. The implementation can call -valueForKey: and transform non-string objects into strings:
#interface NSDictionary (StringValue)
- (NSString *)stringForKey:(NSString*)key;
#end
#implementation NSDictionary (StringValue)
- (NSString *)stringForKey:(NSString*)key
{
id value = [self valueForKey:key];
NSString *string;
if ([value isKindOfClass:[NSString class]]) {
string = (NSString*)value;
}
else if ([value isKindOfClass:[NSNull class]]) {
string = #"";
}
else {
string = [value description];
}
return string;
}
#end

Resources