Double pointers with Objective-C. Change where a pointer points to. - ios

I have a UITableView where I have two data sources.
#property(strong)NSArray* mediaItems;
#property(strong)NSArray* likesItems;
In a lot of cases I need to swap both of the sources out. As a result there is a lot of if statements where I check
if (self.activePage == kDrawingsPage) self.mediaItems = responseObject;
else self.likesItems = responseObject;
What I would like to do is not need to keep checking like this. Using a double pointer. Could I not use a double pointer to do something like. (this does not work)
NSArray** activeItems = &self.mediaItems;
Then in places where I need to access the data I can simply do
- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {
NSArray* arr = *self.activeItems;
return [arr count];
}
Instead of
- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {
if (self.activePage == kDrawingsPage) [self.mediaItems count];
else [self.likesItems count];
}
Shouldn't a double pointer or something similar work in this case? If it cannot work what other way can I minimize these if statements.

You can use computed property
in #interface
#property (readonly) NSArray* activeItems;
in #implementation
- (NSArray *)activeItems {
return self.activePage == kDrawingsPage ? self.mediaItems : self.likesItems
}
so you can do
- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.activeItems count];
}
&self.mediaItems won't give you any meaningful value, because it is same as &[self mediaItems], which give you the address of a returned temporary object.
It should be &_mediaItems i.e. the address of the underlying ivar.

Related

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];
}

accessing NSDictionary property of class in init function gives error "use of undeclared identifier". Need help understanding why

Here is a class for a deck of cards that could have several different configurations that I define using a NSDictionary with string keys and array values of how the cards are to be added. I haven't completed the init function yet, but it gives me the error above on trying to access my NSDictionary property. Fairly new to objective-c sorry if this is trivial question.
Here is my .m class file:
#interface MarioCardDeck()
#property (strong, nonatomic)NSDictionary *cardConfigurations;
#end
#implementation MarioCardDeck
- (instancetype)init {
self = [super init];
if(self) {
unsigned index = arc4random() % [[cardConfigurations allKeys] count]; ** error line
}
return self;
}
- (NSDictionary *)cardConfigurations
{
if(!_cardConfigurations)
{
_cardConfigurations = #{
#"1" :
#[#"flower",#"coin20",#"mushroom",#"star",#"oneUp",#"flower",#"oneUp",#"flower",#"coin10",#"mushroom",#"coin20",#"star",#"mushroom",#"coin10",#"star",#"mushroom",#"flower",#"star"],
#"2" :
#[#"flower",#"coin10",#"oneUp",#"flower",#"oneUp",#"mushroom",#"star",#"mushroom",#"coin20",#"star",#"mushroom",#"coin10",#"star",#"flower",#"coin20",#"mushroom",#"flower",#"star"]
};
}
return _cardConfigurations;
}
#end
You need:
unsigned index = arc4random() % [[self.cardConfigurations allKeys] count];
You need to access the property by using self.
FYI - you should use:
unsigned index = arc4random_uniform([[self.cardConfigurations allKeys] count]);
You need to refer to it by self.cardConfigurations.
You need to change
unsigned index = arc4random() % [[cardConfigurations allKeys] count];
to
unsigned index = arc4random() % [[self.cardConfigurations allKeys] count];
Accessing properties in the init method is, however, dangerous thing to do in Objective-C. If the getter is overridden in a subclass you might get a nasty surprise. I would make another property for the index, assign the instance variable to NSNotFound, and do the calculation the first the time the getter method is called. Also, you should use NSUInteger as the type for storing the index.

NSMutableArray thread safety

In my app I'm accessing and changing a mutable array from multiple threads. At the beginning it was crashing when I was trying to access an object with objectAtIndex, because index was out of bounds (object at that index has already been removed from array in another thread). I searched on the internet how to solve this problem, and I decided to try this solution .I made a class with NSMutableArray property, see the following code:
#interface SynchronizedArray()
#property (retain, atomic) NSMutableArray *array;
#end
#implementation SynchronizedArray
- (id)init
{
self = [super init];
if (self)
{
_array = [[NSMutableArray alloc] init];
}
return self;
}
-(id)objectAtIndex:(NSUInteger)index
{
#synchronized(_array)
{
return [_array objectAtIndex:index];
}
}
-(void)removeObject:(id)object
{
#synchronized(_array)
{
[_array removeObject:object];
}
}
-(void)removeObjectAtIndex:(NSUInteger)index
{
#synchronized(_array)
{
[_array removeObjectAtIndex:index];
}
}
-(void)addObject:(id)object
{
#synchronized(_array)
{
[_array addObject:object];
}
}
- (NSUInteger)count
{
#synchronized(_array)
{
return [_array count];
}
}
-(void)removeAllObjects
{
#synchronized(_array)
{
[_array removeAllObjects];
}
}
-(id)copy
{
#synchronized(_array)
{
return [_array copy];
}
}
and I use this class instead of old mutable array, but the app is still crashing at this line: return [_array objectAtIndex:index]; I tried also this approach with NSLock, but without a luck. What I'm doing wrong and how to fix this?
I believe this solution is poor. Consider this:
thread #1 calls count and is told there are 4 objects in the array.
array is unsynchronized.
thread #2 calls removeObjectAtIndex:2 on the array.
array is unsynchronized.
thread #1 calls objectAtIndex:3 and the error occurs.
Instead you need a locking mechanism at a higher level where the lock is around the array at both steps 1 and 5 and thread #2 cannot remove an object in between these steps.
You need to protect (with #synchronized) basically all usage of the array. Currently you only prevent multiple threads from concurrently getting objects out of the array. But you have no protection for your described scenario of concurrent modification and mutation.
Ask yourself why you're modifying the array on multiple threads - should you do it that way or just use a single thread? It may be easier to use a different array implementation or to use a wrapper class that always switches to the main thread to make the requested modification.

Looping through an array of dynamic objects

I'm not really sure exactly how to describe what I want to do - the best I can do is provide some code as an example:
- (void) doStuffInLoopForDataArray:(NSArray *)arr forObjectsOfClass:(NSString *)class
{
for ([class class] *obj in arr)
{
// Do stuff
}
}
So I might call this like
NSArray *arr = [NSArray arrayWithObjects:#"foo",#"bar", nil];
[self doStuffInLoopForDataArray:arr forObjectsOfClass:#"NSString"];
and I would expect the code to be executed as if I had wrote
- (void) doStuffInLoopForDataArrayOfStrings:(NSArray *)arr
{
for (NSString *obj in arr)
{
// Do KVC stuff
}
}
Is there a way to get this kind of behavior?
I don't see much point in passing the class to the method. Run your loop as:
for (id obj in arr) {
and check the methods you want to call exist. Passing the class is only really useful if you want to check that the objects in the array are actually of that class, but you couldn't then do much with that information.
Another approach would be to create a single superclass that all the classes I'd like to use this method for inherit from. I can then loop using that superclass.
So if I want to be able to loop for MyObject1 and MyObject2, I could create a BigSuperClass, where MyObject1 and MyObject2 are both subclasses of BigSuperClass.
- (void) doStuffInLoopForDataArray:(NSArray *)arr
{
for (BigSuperClass *obj in arr)
{
// Do stuff
}
}
This loop should work for arrays of MyObject1 objects, arrays of MyObject2 objects, or arrays of BigSuperClass objects.
The more I've been thinking about this, the more I'm leaning towards this being the best approach. Since I can setup my BigSuperClass with all the #propertys and methods I'd be interested in as part of my // Do Stuff, which means I won't have to check respondsToSelector as with the other answers. This way just doesn't feel quite as fragile.
I came up with an idea while I was typing up this question, figured I might as well finish it. I just need to change how I'm doing my loop, and I don't really need to send in the class.
- (void) doStuffInLoopForDataArray:(NSArray *)arr
{
for (int i=0; i < [arr count]; i++)
{
// Do stuff
}
}
I should note that part of my // Do stuff is checking to make sure if ([[arr objectAtIndex:i] respondsToSelector:...]) before I actually try to do anything with it - and from what I understand that should prevent any nasty crashes.

Resources