So I'm new to iOS, but I'm a bit baffled by the complexity of a simple task. I'm trying to store my custom NSObject class called 'Vehicle' in NSUserDefaults. Obviously, this can't be done, so I'll need to encode it to NSDATA first. Fine.
But that means that I need to encode each property of the class as well in the decode...
Inside my Vehicle class...
- (void) encodeWithCoder: (NSCoder *) coder
{
[coder encodeInt: x forKey: #"x"];
[coder encodeInt: y forKey: #"y"];
[coder encodeInt: direction forKey: #"direction"];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) coder
{
if (self = [super init]) {
x = [coder decodeIntForKey: #"x"];
y = [coder decodeIntForKey: #"y"];
direction = [coder decodeIntForKey: #"direction"];
}
return (self);
} // initWithCoder
If I end up adding a new property to the vehicle class, I've got to add the encode and decode logic too. This is the same for creating a copy of a class using CopyWithZone. This leaves 3 or 4 areas where adding a new property to a class can go wrong.
I currently program mostly in LabVIEW, and we have the ability to take a class, and feed it to an encoder, which will do all the versioning and property manipulation automatically.
So I guess my question is:
Is this not heard of in iOS?
If it's not possible, is there a way to enumerate through all properties in a class and write a function to do this automatically.
You can use the objective-c runtime to find all the properties of an object and decode them, but I wouldn't recommend it. If you'd ilke, I can create a simple example for you.
EDIT: Here's an example:
#import <objc/runtime.h>
void decodePropertiesOfObjectFromCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try
{
[obj setValue:[coder decodeObjectForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't decode value for key %#.", propName);
}
}
free(properties);
}
void encodePropertiesOfObjectToCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try {
[coder encodeObject:[obj valueForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't encode value for key %#.", propName);
}
}
free(properties);
}
__attribute__((constructor))
static void setDefaultNSCodingHandler()
{
class_addMethod([NSObject class], #selector(encodeWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
encodePropertiesOfObjectToCoder(self, coder);
} copy]), "v#:#");
class_addMethod([NSObject class], #selector(initWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
if ((self = [NSObject instanceMethodForSelector:#selector(init)](self, #selector(init))))
{
decodePropertiesOfObjectFromCoder(self, coder);
}
return self;
} copy]), "v#:#");
}
This allows you to encode any object that exposes enough properties to reconstruct itself.
Related
Is there possibility to create custom annotation which will check if parameter of method is empty array or empty string? Something like #NotEmpty in Java. I already use _Nonnull and check parameter with NSParameterAssert but I am curious does we can write custom annotation?
Thanks.
You can use macros to define an inline function.
#define isNil(x) nil ==x
Objective-C has no customizable annotations of this sort, but one of its major strengths is the versatility of its runtime.
And so, if we really wanted to, we could implement this with a wrapper class type:
#interface NotEmpty<Object> : NSProxy
#property(readonly,copy) Object object;
+ (instancetype)notEmpty:(Object)object;
- (instancetype)initWithObject:(Object)object;
#end
#implementation NotEmpty {
id _object;
}
- (id)object {
return _object;
}
+ (instancetype)notEmpty:(id)object {
return [[self alloc] initWithObject:object];
}
- (instancetype)initWithObject:(id)object {
if ([object respondsToSelector:#selector(length)]) {
NSParameterAssert([object length] != 0);
} else {
NSParameterAssert([object count] != 0);
}
_object = [object copy];
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
if (selector == #selector(object)) {
return [NSMethodSignature signatureWithObjCTypes:"#:"];
} else {
return [_object methodSignatureForSelector:selector];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation {
invocation.target = _object;
[invocation invoke];
}
#end
#interface SomeClass : NSObject
#end
#implementation SomeClass
- (void)method:(NotEmpty<NSString*> *)nonEmptyString {
// Call NSString methods, option 1
unsigned long length1 = [(id)nonEmptyString length];
// Call NSString methods, option 2
unsigned long length2 = nonEmptyString.object.length;
// Note that just printing `nonEmptyString` (not nonEmptyString.object)
// will print an opaque value. If this is a concern, then also forward
// #selector(description) from -methodSignatureForSelector:.
NSLog(#"Received: %# (length1: %lu length2: %lu)", nonEmptyString.object, length1, length2);
}
#end
int main() {
SomeClass *sc = [SomeClass new];
[sc method:[NotEmpty notEmpty:#"Not an empty string"]];
// [sc method:[NotEmpty notEmpty:#""]]; // Raises error
}
Do note that this will cause some small performance penalties.
I need to realize an interface that mimics MKMapView in some sense. Specifically, I need to support methods
-(void)addAnnotation:(id<MyAnnotation>)annotation;
-(UIView *)viewForAnnotation:(id<MyAnnotation>)annotation;
Internally there is a mapping which maps an annotation to an UIView object (which may be null).
So how should I store these annotations and corresponding views? The natural choice would be NSMutableDictionary:
#property(nonatomic) NSMutableDictionary *m_dict;
-(void)addAnnotation:(id<MyAnnotation>)annotation {
UIView *view = ....;
if (view) {
[_m_dict setObject:view forKey:annotation];
}
else {
[_m_dict setObject:[NSNull null] forKey:annotation];
}
}
-(UIView *)viewForAnnotation:(id<MyAnnotation>)annotation {
id ret = [_m_dict objectForKey:annotation];
if ([ret isKindOfClass:[UIView class]]) {
return ret;
}
return nil;
}
But it does not work, because there is no guarantee that id<MyAnnotation> conforms NSCopying.
I thought about saving id<MyAnnotation> to a raw pointer and then storing the pointer into NSNumber (which conforms NSCopying). But I am not sure is this a good idea and how to implement it safely.
You can make a unique string as keypath, and save it in your annotation.
#property(nonatomic) NSMutableDictionary *m_dict;
-(void)addAnnotation:(id<MyAnnotation>)annotation {
UIView *view = ....;
NSString *unique_id = ....;
[annotation setValue:unique_id forKey:#"_unique_id"];
if (view) {
[_m_dict setObject:view forKey:unique_id];
}
else {
[_m_dict setObject:[NSNull null] forKey:unique_id];
}
}
-(UIView *)viewForAnnotation:(id<MyAnnotation>)annotation {
id ret = [_m_dict objectForKey:annotation valueForKey:#"_unique_id"];
if ([ret isKindOfClass:[UIView class]]) {
return ret;
}
return nil;
}
I have two arrays. First contains custom objects. Now I want to copy all object of first array in another array. For that I am using below code.
Arays.
arr_post=[[NSMutableArray alloc]init];
copy_arr_user_post=[[NSMutableArray alloc]init];
am adding the objects into them like this.
for(i=0;i<[arr_main count];i++)
{
Post *obj=[[Post alloc]init];
obj.name=#"abc";
obj.category=#"social";
[arr_post addObject:obj];
}
Now I am copying to another array like this
[arr_post addObject:user_post];
Post *objectCopy = [user_post copy]; //create a copy of our object
[copy_arr_user_post addObject: objectCopy]; //insert copy into other array
In Post.h
#interface Post : NSObject<NSCopying>
In Post.m
- (id)copyWithZone:(NSZone *)zone
{
// Copying code here.
Post *another =[[[self class] allocWithZone:zone] init];
another.id=self.id;
another.category=self.category;
return another;
}
But it does not copy objects I get null value. Why?
One method that I find faster than NSCopyng
-Create an NSObject category with this two method
#import <objc/runtime.h>
-(id)deepCopy
{
NSArray *tmpArray = #[self];
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject:tmpArray];
return [NSKeyedUnarchiver unarchiveObjectWithData:buffer][0];
}
- (NSMutableArray *)allProperties
{
NSMutableArray *props = [NSMutableArray array];
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];
//Excluding all readOnly properties
unsigned int numOfAttributes;
objc_property_attribute_t *propertyAttributes = property_copyAttributeList(property, &numOfAttributes);
BOOL foundReadonly = NO;
for ( unsigned int ai = 0; ai < numOfAttributes; ai++ )
{
switch (propertyAttributes[ai].name[0]) {
case 'T': // type
break;
case 'R': // readonly
foundReadonly = YES;
break;
case 'C': // copy
break;
case '&': // retain
break;
case 'N': // nonatomic
break;
case 'G': // custom getter
break;
case 'S': // custom setter
break;
case 'D': // dynamic
break;
default:
break;
}
}
free(propertyAttributes);
if (!foundReadonly)
{
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSASCIIStringEncoding];
[props addObject:propertyName];
}
}
free(properties);
return props;
}
-Make your object conforms to NSCoding
#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self)
{
NSArray *keys = [self allProperties];
for (NSString *key in keys)
{
[self setValue:[decoder decodeObjectForKey:key] forKey:key] ;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
NSArray *keys = [self allProperties];
for (NSString *key in keys)
{
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
-Import the category
Now you are able to copy any kinds of object
MYObject *copy = [originalObject deepCopy];
NSArray *arrayWithCopiedObjects = [originalArray deepCopy];
etc....
Try
NSMutableArray * arr_post=[[NSMutableArray alloc]init];
NSMutableArray * copy_arr_user_post=[[NSMutableArray alloc]init];
for(int i=0;i<3;i++)
{
Post *obj=[[Post alloc]init];
obj.name=#"abc";
obj.category=#"social";
[arr_post addObject:obj];
}
[arr_post enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[copy_arr_user_post addObject:[obj copy]];
}];
Post * temp = [arr_post objectAtIndex:0];
temp.name = #"123";
NSLog(#"%#",arr_post);
NSLog(#"%#",copy_arr_user_post);
And log
2015-10-23 13:25:31.994 OCTest[1784:130931] (
"123 social",
"abc social",
"abc social"
)
2015-10-23 13:25:31.995 OCTest[1784:130931] (
"abc social",
"abc social",
"abc social"
)
I add description for debugging
-(NSString *)description{
return [NSString stringWithFormat:#"%# %#",self.name,self.category];
}
I'm working on cline-server app. I'm getting JSON objects as response from the server,
then I'm converting JSON to NSDictionary. Now I need to map NSDictionary to custom data object.
So I have created BasicDataObject class that has:
#pragma mark - Inits
- (id)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dictionary];
}
return self;
}
#pragma mark - Service
- (id)valueForUndefinedKey:(NSString *)key {
NSArray *allKeys = [self allKeys];
id returnObject = nil;
BOOL keyFound = NO;
for (NSString *propertyName in allKeys) {
if ([propertyName isEqualToString:key]) {
id object = [self performSelector:NSSelectorFromString(key)];
returnObject = object ? object : [NSNull null];
keyFound = YES;
break;
}
}
if (!keyFound) {
#throw [NSException exceptionWithName:NSUndefinedKeyException reason:[NSString stringWithFormat:#"key '%#' not found", key] userInfo:nil];
}
return returnObject;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSString *capitalizedString = [key stringByReplacingCharactersInRange:NSMakeRange(0,1)
withString:[[key substringToIndex:1] capitalizedString]];
NSString *setterString = [NSString stringWithFormat:#"set%#:", capitalizedString];
[self performSelector:NSSelectorFromString(setterString) withObject:value];
}
- (void)setNilValueForKey:(NSString *)key {
object_setInstanceVariable(self, key.UTF8String, 0);
}
- (NSArray *)allKeys {
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(self.class, &propertyCount);
NSMutableArray *propertyNames = [NSMutableArray array];
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
free(properties);
return propertyNames;
}
Each data object is a subclass of this class, so it could be initialised from NSDictionary.
If some data object subclass needs some custom initialisation, I'm overriding it's:
- (id)initWithDictionary:(NSDictionary *)dictionary
Is it correct/good approach, or do I need to add something more?
This is an OK way to do this. Ive found that rarely are the json keys the most appropriate names for my object's properties, so most people's code that I've seen manually sets each property on their object so they can 1) name it whatever they want 2) convert to primitive values from the json dictionary, which will always contain objects. (Eg NSNumber -> float)
While implementing a subclass of NSArray (a class cluster), I was surprised to see that my overridden description method was not called. Can somebody explain what is happening here?
#interface MyArrayClassCluster : NSArray
#end
#implementation MyArrayClassCluster
{
NSArray *_realArray;
}
// Implement the class cluser stuff here
- (NSUInteger)count
{
return [_realArray count];
}
- (id)objectAtIndex:(NSUInteger)index
{
return [_realArray objectAtIndex:index];
}
// lifeCycle
- (id)initWithItems:(NSArray *)items
{
self = [super init];
_realArray = [items retain];
return self;
}
- (void)dealloc
{
[_realArray release];
[super dealloc];
}
- (NSString *)description
{
return [NSString stringWithFormat:#"My Custom Array: %p, objs:%#", self, _realArray];
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *a = #[#1, #2, #3];
NSLog(#"a: %#", a);
MyArrayClassCluster *clzCluster = [[MyArrayClassCluster alloc] initWithItems:a];
NSLog(#"clzCluster: %#", clzCluster);
}
return 0;
}
Output
2013-01-29 18:52:38.704 ClassClusterTester[31649:303] a: (
1,
2,
3
)
2013-01-29 18:52:38.707 ClassClusterTester[31649:303] clzCluster: (
1,
2,
3
)
The link #Rob pointed to had the correct answer at the bottom. An obscure fact: if something implements descriptionWithLocale:, NSLog will call that instead. Since my class is a subclass of NSArray, and NSArray implements that, my version of description was not called.