Automatically copy property values from one object to another of a different type but the same protocol (Objective-C) - objective-c

I have two classes with the same set of properties, declared using the #property directive in a protocol, they both implement. Now I was wondering if it is possible to automatically populate an instance of the first class with the values from an instance of the second class (and vice-versa).
I would like this approach to be robust, so that if I change the of properties declared in the protocol there will be no need to add extra code in the copying methods.

Yes, given the exact context there could be various approaches to this problem.
One I can think of at the moment is to first get all the properties of source object then use setValue:value forKey:key to set the values on the target object.
Code to retrieve all custom properties:
-(NSSet *)propertyNames {
NSMutableSet *propNames = [NSMutableSet set];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [[[NSString alloc]
initWithCString:property_getName(property)] autorelease];
[propNames addObject:propertyName];
}
free(properties);
return propNames;
}
You may want to checkout the Key-Value Coding Programming Guide for more information.

Related

Objective-C Reflection - Getting generic type of NSArray

I'm currently trying to read the properties of a class with reflection in Objective-C.
Its working all well expect with my NSArray.
This is my property:
#property(nonatomic) NSArray<CustomObject*>* tabs;
I can successfully access my properties of my class with:
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
for (unsigned int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
const char *attrs = property_getAttributes(property);
}
So my property name is tabs. All good. But the attributes are "T#"NSArray",&,N,V_tabs"
How do I get the information about the generic type of my NSArray?
Thx in advance
unless nothing is stored with the indexedSubscript (aka [index]) of your NSArray it is empty and only Xcode knows about your intention what type you want to store there.
so
#property(nonatomic) NSArray<CustomObject*>* tabs;
is equivalent to
#property(nonatomic) NSArray<NSObject*>* tabs;
is equivalent to
#property(nonatomic) NSArray<id>* tabs;
is equivalent to
#property(nonatomic) NSArray* tabs;
The list above is not 100% correct because Xcode could throw an error when you try to mix up datatypes and try to compile and so it should not get to the point where you store wrong datatypes.
Unless no object is allocated it does not take memory and so it can not be asked at runtime.
If it is important in your design pattern you could create your own datatype that can be accessed via indexedSubscripts and also keep informations of your intention of what datatype you keep there even if empty. But it is worth thinking about if this is then still a design pattern that fits what you wanted to achieve.

Identifying properties in the header file vs. implementation file

I have been interested in using something along the the lines of the following code to
automate the building of my objects (since there are many of them with quite a few properties):
MyObject *myObject = [[myObject alloc] init];
unsigned int numberOfProperties = 0;
objc_property_t *propertyArray = class_copyPropertyList([MyObject class], &numberOfProperties);
for (NSUInteger i = 0; i < numberOfProperties; i++)
{
objc_property_t property = propertyArray[i];
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
if (propertyName)
{
id valueForProperty = [myObject valueForKey:propertyName];
[myObject setValue:valueForProperty forKey:propertyName];
}
}
free(propertyArray);
However, what I've noticed is that this code will try to run not just on the properties in my header file, but also all of my implementation properties as well, which I do not want.
Since Objective-C doesn't actually distinguish public vs private properties, I am not sure how to do this. Any thoughts on how to indicate that I'm only interested in the properties in the header file to simulate the same thing effectively?
In short, you don't. This information is not available in the compiled program. You'd need to write a custom preprocessor to do this if you really wanted to.

Getting an array of properties for an object in Objective-C

Is it possible to get an array of all of an object's properties in Objective C? Basically, what I want to do is something like this:
- (void)save {
NSArray *propertyArray = [self propertyNames];
for (NSString *propertyName in propertyArray) {
[self doSomethingCoolWithValue:[self valueForKey:propertyName]];
}
}
Is this possible? It seems like it should be, but I can't figure out what method my propertyNames up there should be.
I did some more digging, and found what I wanted in the Objective-C Runtime Programming Guide. Here's how I've implemented the what I wanted to do in my original question, drawing heavily from Apple's sample code:
#import <Foundation/NSObjCRuntime.h>
#import <objc/runtime.h>
- (void)save {
id currentClass = [self class];
NSString *propertyName;
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(currentClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
propertyName = [NSString stringWithCString:property_getName(property)];
[self doSomethingCoolWithValue:[self valueForKey:propertyName]];
}
}
I hope this will help someone else looking for a way to access the names of an object's properties programatically.
dont forget
free(properties);
after the loop or you will get a leak. The apple documentation is clear:
An array of pointers of type objc_property_t describing the properties
declared by the class. Any properties declared by superclasses are not
included. The array contains *outCount pointers followed by a NULL
terminator. You must free the array with free().

What is the most elegant way to encode/decode an Object to Dictionary

The object in question consists of key/value pairs aka #property. Is there an elegant way to encode/decode this object to a dictionary? It seems brute force to manually pull out each attribute and create the dictionary by hand.
Does it absolutely need to be a dictionary? Because NSKeyedArchiver gives you the memento-stored-by-key behaviour without actually being an NSDictionary - and has the added bonus that it can archive many objects which property-list serialization doesn't automatically support. There's a good description of using archivers and unarchivers on the CocoaDev wiki.
Objective-C's "object as dictionary" support comes through Key-Value Coding:
NSArray *myAttributes; // Assume this exists
NSDictionary *dictRepresentation = [object dictionaryWithValuesForKeys:myAttributes];
If the keys you desire are ObjC-2.0 properties of the class in question, you could do something similar to the following:
// Assume MyClass exists
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([myClassInstance class], &count);
NSMutableDictionary *propertiesDict = [NSMutableDictionary dictionary];
unsigned int i;
for(i = 0; i < count; ++i) {
NSString *propertyName = [NSString stringWithCString:property_getName(properties[i]) encoding:NSASCIIStringEncoding];
id propertyValue = [self valueForKey:propertyName];
if(propertyValue)
[propertiesDict setObject:propertyValue forKey:propertyName];
}
free(properties), properties = NULL;
// Do something with propertiesDict
This could also be a simple class extension.

How do I find all the property keys of a KVC compliant Objective-C object?

Is there a method that returns all the keys for an object conforming to the NSKeyValueCoding protocol?
Something along the lines of [object getPropertyKeys] that would return an NSArray of NSString objects. It would work for any KVC-compliant object. Does such a method exist? I haven't found anything in searching the Apple docs so far.
Thanks,
G.
#import "objc/runtime.h"
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for(i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName) {
const char *propType = getPropertyType(property);
NSString *propertyName = [NSString stringWithUTF8String:propName];
NSString *propertyType = [NSString stringWithUTF8String:propType];
}
}
free(properties);
Use class_getPropertyList. That will tell you all the #properties of the object.
It won't necessarily list every KVC-compliant property, because any method that takes no arguments and returns a value is a valid KVC-compliant getter. There's no 100%-reliable way for the runtime to know which ones behave as properties (e.g., -[NSString length]) and which ones behave as commands (e.g., -[NSFileHandle readDataToEndOfFile]).
You should be declaring your KVC-compliant properties as #properties anyway, so this shouldn't be too big of a problem.
There is no such method as the KVO system does not require objects/classes to register with it which properties they support KVO for. Any key could potentially support KVO, the only way to know is from the author's documentation.
And of course, there is no guarantee that an #property will support KVO; it's quite possible to write a property that doesn't (and may be necessary sometimes). So, getting a list of a class's #propertys and then assuming they're KVO-compliant would be a dangerous choice in my opinion.
You need a getPropertyType function. See this post: Get an object attributes list in Objective-C
For Swift onlookers, you can get this functionality by utilising the Encodable functionality. I will explain how:
Conform your object to Encodable protocol
class ExampleObj: NSObject, Encodable {
var prop1: String = ""
var prop2: String = ""
}
Create extension for Encodable to provide toDictionary functionality
public func toDictionary() -> [String: AnyObject]? {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let data = try? encoder.encode(self),
let json = try? JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0)), let jsonDict = json as? [String: AnyObject] else {
return nil
}
return jsonDict
}
Call toDictionary on your object instance and access keys property.
let exampleObj = ExampleObj()
exampleObj.toDictionary()?.keys
Voila! Access your properties like so:
for k in exampleObj!.keys {
print(k)
}
// Prints "prop1"
// Prints "prop2"