Access what property of the sender was passed into a method - ios

This may be a ridiculous question, but I have a method like this in my view controller:
[self registerProperty:self.currentUser];
and in the implementation of registerProperty: I would like to get the string "currentUser".
I'm doing this because I want to observe the property of the view controller "currentUser", not the actual user object, so I can intercept the setter.
At the moment I'm checking the Objective-C runtime for a list of all properties of the view controller and checking if the value of the property equals the currentUser object:
-(void)registerProperty:(id)property
{
for (NSString *propertyName in [self allPropertiesOfClass:[property class]])
if ([property isEqual:[self valueForKey:propertyName]])
NSLog(#"The property passed into the method is %#", propertyName);
}
The problem with this is that I may have two properties that both contain the same user object, in which case either of them would pass that test. How could I fix this?

Pass in the object whose property you want to observe and, separately, the property name as a string. That is, mirror (a subset of) the arguments of the KVO -addObserver:... method.

Related

Objective-C NSArray can't be changed when passed to method in different class

I have an NSArray and I need to change the order of the items within it. I have written a method that will determine the new order:
+(NSArray*)sortProxyForms:(NSArray*)arrayOfForms
{
NSArray* sortedForms = [arrayOfForms sortedArrayUsingComparator:^(HPSModelFormProxy* a, HPSModelFormProxy* b) {
return [#(a.ordinal) compare:#(b.ordinal)]; // #(a.ordinal) aka Boxing turns int into NSNumber
}];
arrayOfForms = [sortedForms copy]; // DOES NOT WORK
return sortedOfForms; // WORKS IF ASSIGNED IN THE CALLER
}
So, I can pass the NSArray to be sorted into the method. I call the method like this:
[HPSModelUtilities sortProxyForms:_formProxies];
If I actually try setting arrayOfForms (a reference to _formProxies) within the method then once I have returned from the method then the array is unchanged.
However, if I return the sorted array from the method and assign it to the NSArray in the calling method then the assignment works:
_formProxies = [HPSModelUtilities sortProxyForms:_formProxies]; // _formProxies NSArray is changed
_formProxies is declared in the calling class, and "HPSModelUtilities" is a different class.
How come the NSArray can be changed in the caller, but not changed in the called method, even though it is passed by reference?
When you pass a value into a method it is copied. This is called "pass by value". The arrayOfForms you are passing in is a pointer to an NSArray. This means that the pointer is copied when passed in. Redirecting this pointer to another instance of an NSArray does not change where the original pointer is pointing.
I would rename your method to (NSArray*)sortedArrayFromProxyForms:(NSArray*)proxyForms
If you really want to change where your NSArray reference is pointing in the method. Do it like this.
+ (void)sortProxyForms:(NSArray**)proxyForms {
*proxyForms = sortedForms;
}
You are passing a copy of the array reference (subtly different than passing by reference), but then you are changing where that reference points with this line:
arrayOfForms = [sortedForms copy];
arrayOfForms no longer points to the array instance you passed, but to a different array. You could pass a pointer of pointer, and change where the caller's pointer is pointing, but for what you are doing, I think the reassignment is fine.
If you'd really like here's what your function would look like with pointer of pointer:
+(void)sortProxyForms:(NSArray**)arrayOfForms {
NSArray* sortedForms = [arrayOfForms sortedArrayUsingComparator:^(HPSModelFormProxy* a, HPSModelFormProxy* b) {
return [#(a.ordinal) compare:#(b.ordinal)]; // #(a.ordinal) aka Boxing turns int into NSNumber
}];
*arrayOfForms = [sortedForms copy];
}
but I'll add the caveat that this isn't a pattern you see often in objective-c, so I'd avoid it when there are other alternatives available.
Also note when calling this function you need to add the & to get the extra level of indirection:
[HPSModelUtilities sortProxyForms:&_formProxies];

NSManagedObject setValue forKey looping in thread

I'm trying to save some data into database using CoreData so I created Entity named 'Client' with some attributes. Two of them are 'city' and 'post_code', both of String type. I also created Client class extending NSManagedObjects and I wrote some methods there.
-(void) setCity: (NSString*) city
{
[self setValue:city forKey:#"city"];
}
-(NSString*) getCity
{
return [self valueForKey:#"city"];
}
-(void) setPostCode: (NSString*) postCode
{
[self setValue:postCode forKey:#"post_code"];
}
-(NSString*) getPostCode
{
return [self valueForKey:#"post_code"];
}
getPostCode and setPostCode work as I expected but calling setCity or getCity is causing problems. Appication stops and method is looping in thread as you can see on screenshot.
Full size image
This is how I call those methods
if([databaseResult count] > 0)
c = [databaseResult objectAtIndex:0];
else
c = [NSEntityDescription insertNewObjectForEntityForName:#"Client" inManagedObjectContext:context];
[c setPostCode:[jsonData valueForKey:#"post_code_invoice"]];
[c setClientType:[jsonData valueForKey:#"company_type"]];
[c setCity:[jsonData valueForKey:#"city_invoice"]];
it always stops on setCity no matter what data I pass there, even that call doesn't work
[c setCity:#"aaa"];
Did anyone had similar problem?
The setValueForKey method is calling back into the same function because it has the same name.
From the KVC documentation
Default Search Pattern for setValue:forKey:
When the default implementation of setValue:forKey: is invoked for a property the following search pattern is used:
The receiver’s class is searched for an accessor method whose name matches the pattern set:.
If no accessor is found, and the receiver’s class method accessInstanceVariablesDirectly returns YES, the receiver is searched for an instance variable whose name matches the pattern _, _is, , or is, in that order.
If a matching accessor or instance variable is located, it is used to set the value. If necessary, the value is extracted from the object as described in “Representing Non-Object Values.”
If no appropriate accessor or instance variable is found, setValue:forUndefinedKey: is invoked for the receiver.
So when you call setValue:forKey: with the key city, the implementation calls setCity: and your implementation calls setValue:forKey:, and round and round you go.
Why are you even doing it this way rather than setting the value directly?
Or better still use properties and you don't even need to write setters or getters.
The other two methods work because the key names are different. (they have underscores
)

NSManagedObject Class and creating a Setter method

So in a regular Class, you can override the setter method for a class property:
-(void)setSortBy:(NSString *)sortBy {
// Do other stuff
_sortBy = sortBy;
}
using the _ prevents an infinite loop of the method calling its self.
I am trying to do something similar with a NSManagedObject class, but it does not give the option to use the underscore (_):
-(void)setHasNewData:(NSNumber *)hasNewData {
// update self.modifiyDate
_hasNewData = hasNewData;
}
Gives me an error and suggests I replace _hasNewData to hasNewData.
Is this how it should be done or will it give me an infinite loop?
I want it to update the NSManagedObject's property modifyDate anytime I set hasNewData.
Your first example for a "regular class" works if _sortBy is the instance variable
backing up the sortBy property (e.g. the default synthesized instance variable for
that property).
But Core Data properties are not backed up by instance variables.
When overriding Core Data accessors, you have to use the "primitive accessors",
and also trigger the Key-Value Observing notifications:
-(void)setHasNewData:(NSNumber *)hasNewData {
[self willChangeValueForKey:#"hasNewData"];
[self setPrimitiveValue:hasNewData forKey:#"hasNewData"];
[self didChangeValueForKey:#"hasNewData"];
// do other things, e.g.
self.modifyDate = ...;
}
More examples can be found in the "Core Data Programming Guide".

Getting a property from parent class

I've added a view controller as child like this:
UIViewController *sendViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"send"];
[self addChildViewController:sendViewController];
In the sendViewController I got this property:
#property (strong, nonatomic) IBOutlet StringInputTableViewCell *nameCell;
and in that class I can do something like self.nameCell.textField.text = #"test"; to set the textField to test
But how can I get this text thats filled in the textField in the parent class?
You are mixing model and view, thus breaking the MVC design pattern.
You should not try to read what is the content of a UI element. Rather you should have all the data (i.e. model) and the view (i.e. the UI, such as text fields) managed by a controller.
There are (easy) ways to get to this information, but I strongly advise you not to go down that road!
Basic inheritance between the parent and child class should allow you to pass the property forward.
You'll need to create a child object of the class say obj. Then to get the text value of the field you'll use (in the parent class)
id obj = [[ChildClassName alloc] init];
NSString *myChildsText = obj.nameCell.textField.text; // will get the value #"test" as assigned in the childclass.
Or of course, you can create a getter and setter in the Child Class for your #property. For example ::
- (IBOutlet)nameCell {
// returns the value
}
- (IBOutlet)setNameCell :(StringInputTableViewCell)newValue {
//set the value to the #synth value here…
}
then you can call the child objects getters/setters as below ::
NSString *text = [obj nameCell]; //etc etc
You can use 4 approaches in here
a) keep reference to you childviewcontroller -> get the value directly
b) use delegate method ( best for observing changes in your textfield )
c) send NSNotification -> this comes with overhead, well at certain points it can help you a lot
d) KVO

iOS -- updating data structures in new app version

I am not using core data. Just to keep it simple, let's say I have data which were formerly of type NSString, and now they are supposed to be objects of a custom class Person whose only ivar is a "name" ivar of type NSString. In the updated version of the app, I want my Person objects to have their "name" set to whatever the NSString was in the saved data. But suppose my people appear in lots of different places in the app, so telling it how to handle each one individually would be a pain.
What is the best way to handle this? In particular, is there some trick I can do to catch it in the un-archiving process? Or do I have to go through every un-archived object and turn the appropriate NSStrings into Person objects?
You can create a utility class that check the value that come back from your unarchiver, run it through this method. If the value is an NSString, then you can construct a new Person object, if not, then just returns that object back.
+ (Person *)personFromStringOrPersonObject:(id)object {
if ([object isKindOfClass:[NSString class]]) {
// Construct your Person object
Person person = [Person new];
person.name = object;
return person;
} else {
return object;
}
}

Resources