I understand how to store a custom object in NSUser Defaults but when i tried to implement saving an object with an object of different type inside it (it is called composition if i'm not mistaken) following the same steps for inner object as i did for the first one i got runtime error. Could you please minutely describe steps that i have to undertake in order to save and retrieve everything correctly
All your objects should implement NSCoding protocol. NSCoding works recursively for objects that would be saved. For example, you have 2 custom classes
#interface MyClass : NSObject {
}
#property(nonatomic, retain) NSString *myString;
#property(nonatomic, retain) MyAnotherClass *myAnotherClass;
#interface MyAnotherClass : NSObject {
}
#property(nonatomic, retain) NSNumber *myNumber;
For saving MyClass object to NSUserDefaults you need to implement NSCoding protocol to both these classes:
For first class:
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.myString forKey:#"myString"];
[encoder encodeObject:self.myAnotherClass forKey:#"myAnotherClass"];
}
-(id)initWithCoder:(NSCoder *)decoder{
self = [super init];
if ( self != nil ) {
self.myString = [decoder decodeObjectForKey:#"myString"];
self.myAnotherClass = [decoder decodeObjectForKey:#"myAnotherClass"];
}
return self;
}
For second class:
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.myNumber forKey:#"myNumber"];
}
-(id)initWithCoder:(NSCoder *)decoder{
self = [super init];
if ( self != nil ) {
self.myNumber = [decoder decodeObjectForKey:#"myNumber"];
}
return self;
}
Note, if your another class (MyAnotherClass above) has also custom object then that custom object should implement NSCoding as well. Even you have NSArray which implicity contains custom objects you should implement NSCoding for these objects.
Related
I am trying to save an array of custom classes in Core Data as a transformable attribute, but keep getting the following error when trying to load the saved data:
NSSecureUnarchiveFromData transformer> threw while decoding a value. ({
NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=4864 \"value for key 'NS.objects' was of unexpected class 'MyCustomClass'
In the Core Data schema I have set the transformer to "NSSecureUnarchiveFromData" and the Custom Class to "NSArray" (since I want to save an array of "MyCustomClass")
MyCustomClass.h
#interface MyCustomClass : NSObject <NSSecureCoding>
#property (nonatomic, assign) NSString *identifier;
MyCustomClass.m
#implementation MyCustomClass
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:self.identifier forKey:#"Identifier"];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
if (self = [super init]) {
self.identifier = [coder decodeObjectOfClass:[NSString class] forKey:#"Identifier"];
}
return self;
}
I even tried to change the property declaration in "MyCustomClass+CoreDataProperties" to NSArray<MyCustomClass *> but got the same error.
What step am I missing or doing wrong please?
I was missing a few steps. In addition to the code in the question, the following had to be done to get this to work:
• Change the attribute's Custom Class in the model editor to NSArray
• Create a new class called MyCustomClassTransformer:
#interface MyCustomClassTransformer: NSSecureUnarchiveFromDataTransformer {}
#end
#implementation MyCustomClassTransformer
+ (Class)transformedValueClass {
return [MyCustomClassTransformer class];
}
+ (BOOL)allowsReverseTransformation {
return YES;
}
+ (NSArray<Class> *)allowedTopLevelClasses {
return #[[MyCustomClass class], [NSArray class]];
}
#end
• Register new transformer in AppDelegate's "didFinishLaunchingWithOptions":
MyCustomClassTransformer *transformer = [[MyCustomClassTransformer new];
[NSValueTransformer setValueTransformer:transformer forName: #"MyCustomClassTransformer"];
I need to save an NSMutableArray to disk in order to store my application data. I know there are a lot of similiar questions out there but none of them I found covers my question.
I do not want to integrate Core Data just for saving one NSMutableArray. Normally I would go for implementing the NSCoding protocol and using NSKeyedUnarchiver. Unfortunately, my data model class has some foreign classes from a library which do not implement the NSCoding protocol.
So what is the best way for me to store my array?
This is what I tried but because of the given reasons it won't work:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <ForeignFramework/ForeignFramework.h>
#interface DEModelClass : NSObject <NSCoding>
#property (nonatomic,strong) ForeignFramework *foreignFramework;
#property (nonatomic,strong) UIImage *image;
#property (nonatomic,copy) NSNumber *number1;
#property (nonatomic,copy) NSNumber *number2;
#end
#define kEncodeKeyForeign #"kEncodeKeyForeign"
#define kEncodeKeyImage #"kEncodeKeyImage"
#define kEncodeKeyNumber1 #"kEncodeKeyNumber1"
#define kEncodeKeyNumber2 #"kEncodeKeyNumber2"
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.foreignFramework forKey:kEncodeKey];
[aCoder encodeObject:self.image forKey:kEncodeKeyImage];
[aCoder encodeObject:self.number1 forKey:kEncodeKeyNumber1];
[aCoder encodeObject:self.number2 forKey:kEncodeKeyNumber2];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super init]))
{
self.foreignFramework = [aDecoder decodeObjectForKey:kEncodeKeyForeign];
self.image = [aDecoder decodeObjectForKey:kEncodeKeyImage];
self.number1 = [aDecoder decodeObjectForKey:kEncodeKeyNumber1];
self.number2 = [aDecoder decodeObjectForKey:kEncodeKeyNumber2];
}
return self;
}
You should use the NSCoding protocol and you can use it.
A. I assume that you know, which properties of the foreign classes to store. (Probably that data that let you re-instantiate instance objects at loading.) If not, there is no way to store them. And of course, Cocoa, NSArray, $whatever cannot know it. These are generic.
B. When you are done with selecting the properties to store, simply add a category to the foreign classes that do the job for you:
#interface ForeignClass (MyCodingAddition)
- (void)encodeWithCoder:(NSCoder*)encoder;
- (id)initWithCoder:(NSCoder*)decoder;
#end
#implementation ForeignClass (MyCodingAddition)
- (void)encodeWithCoder:(NSCoder*)encoder
{
[coder encodeObject:self.property withKey:…]
…
}
- (id)initWithCoder:(NSCoder*)decoder
{
self = [super init];
if (self)
{
self.property = [decode objectForKey:…];
…
}
return self;
}
#end
ClassA.h
...
#property (weak, nonatomic) NSString *myVariable;
- (id) setMyVariable:(NSString *)string;
- (id) getMyVariable;
ClassA.m
...
#synthezise myVariable = _myVariable;
... some inits
- (id) setMyVariable:(NSString *)string {
_myVariable = string;
NSLog(#"here nslog success return new value: ", _myVariable);
return _myVariable;
}
- (id) getMyVariable {
NSLog(#"here nslog return nil", _myVariable);
return _myVariable;
}
ClassB.m
#import ClassA.h
...
ClassA *classA = [[ClassA alloc] init];
[classA setMyVariable:#"some"];
ClassC.m
#import ClassA.h
...
ClassA *classA = [[ClassA alloc] init];
NSLog(#"here nslog returns nil: #%", [classA getMyVariable]);
Why does [ClassC getMyVariable] return nil? Same result when I try to set value directly without setter and getter. I already read other topics on StackOverflow and Google, but have not idea why it doesn't work.
Your whole code is a bit of a mess really. Why are you using a weak property? Why are you using a #synthezise since this is is automatically done by xcode for you along with the getters and setters so you don't need to create them ever.
The reason why your [classA getMyVariable]; is nil in ClassC is because you create a new instance of it on the line above. By the looks of what you are trying to do is you want to set the variable for instance of a class in one class and access that variable on the same instance in a different class. So one method of doing this is to use a singleton, these are sometimes not liked but I think they work well and don't see a reason why some (not all) developers don't like them.
So lets do some cleaning up and try implementing a singleton
ClassA.h
#interface ClassA : NSObject
#property (nonatomic, strong) NSString *myVariable;
// No need for you to create any getters or setters.
// This is the method we will call to get the shared instance of the class.
+ (id)sharedInstance;
#end
ClassA.m
#import "ClassA.h"
#implementation ClassA
// No need to add a #synthezise as this is automatically done by xcode for you.
+ (id)sharedInstance
{
static ClassA *sharedClassA = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// If there isn't already an instance created then alloc init one.
sharedClassA = [[self alloc] init];
});
// Return the sharedInstance of our class.
return sharedClassA;
}
#end
Right so we have cleaned our ClassA code up and added a method for getting a shared instance of ClassA so now to ClassB
ClassB.m
// Other code in ClassB
// Get the shared instance
ClassA *classA = [ClassA sharedInstance];
// Set the value to the property on our instance.
[classA setMyVariable:#"Some String Value"];
//........
Now that ClassB has set the variable we can go to ClassC now and look at it.
// Other code in ClassC
// We still need to have an instance of classA but we are getting the sharedInstance
// and not creating a new one.
ClassA *classA = [ClassA sharedInstance];
NSLog(#"My variable on my shared instance = %#", [classA myVariable]);
//........
Might help if you read this and this for help on understanding different design patterns
because you don't set a value after creating an object. i should be like this:
ClassA *classA = [ClassA alloc] init];
[classA setMyVariable:#"some"];
NSLog(#"not nil anymore: #%", [classA getMyVariable]);
BTW: the #property tag provides two keywords to set getter and setter methods.
#property (weak, nonatomic, getter=myVariable, setter=setMyVariable:) NSString *myVariable;
and apple avoids the word "get" in getter-methods...
Is there a standard pattern for implementing a mutable/immutable object class pair in Objective-C?
I currently have something like the following, which I wrote based off this link
Immutable Class:
#interface MyObject : NSObject <NSMutableCopying> {
NSString *_value;
}
#property (nonatomic, readonly, strong) NSString *value;
- (instancetype)initWithValue:(NSString *)value;
#end
#implementation MyObject
#synthesize value = _value;
- (instancetype)initWithValue:(NSString *)value {
self = [self init];
if (self) {
_value = value;
}
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [[MyMutableObject allocWithZone:zone] initWithValue:self.value];
}
#end
Mutable Class:
#interface MyMutableObject : MyObject
#property (nonatomic, readwrite, strong) NSString *value;
#end
#implementation MyMutableObject
#dynamic value;
- (void)setValue:(NSString *)value {
_value = value;
}
#end
This works, but it exposes the iVar. Is there a better implementation that remedies this situation?
Your solution follows a very good pattern: the mutable class does not duplicate anything from its base, and exposes an additional functionality without storing any additional state.
This works, but it exposes the iVar.
Due to the fact that instance variables are #protected by default, the exposed _value is visible only to the classes inheriting MyObject. This is a good tradeoff, because it helps you avoid data duplication without publicly exposing the data member used for storing the state of the object.
Is there a better implementation that remedies this situation?
Declare the value property in a class extension. An extension is like a category without a name, but must be part of the class implementation. In your MyMutableObject.m file, do this:
#interface MyMutableObject ()
#property(nonatomic, readwrite, strong) value
#end
Now you've declared your property, but it's only visible inside your implementation.
The answer from dasblinkenlight is correct. The pattern provided in the question is fine. I provide an alternative that differs in two ways. First, at the expense of an unused iVar in the mutable class, the property is atomic. Second, as with many foundation classes, a copy of an immutable instance simply returns self.
MyObject.h:
#interface MyObject : NSObject <NSCopying, NSMutableCopying>
#property (atomic, readonly, copy) NSString *value;
- (instancetype)initWithValue:(NSString *)value NS_DESIGNATED_INITIALIZER;
#end
MyObject.m
#import "MyObject.h"
#import "MyMutableObject.h"
#implementation MyObject
- (instancetype)init {
return [self initWithValue:nil];
}
- (instancetype)initWithValue:(NSString *)value {
self = [super init];
if (self) {
_value = [value copy];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
// Do not use the iVar here or anywhere else.
// This pattern requires always using self.value instead of _value (except in the initializer).
return [[MyMutableObject allocWithZone:zone] initWithValue:self.value];
}
#end
MyMutableObject.h:
#import "MyObject.h"
#interface MyMutableObject : MyObject
#property (atomic, copy) NSString *value;
#end
MyMutableObject.m:
#import "MyMutableObject.h"
#implementation MyMutableObject
#synthesize value = _value; // This is not the same iVar as in the superclass.
- (instancetype)initWithValue:(NSString *)value {
// Pass nil in order to not use the iVar in the parent.
// This is reasonably safe because this method has been declared with NS_DESIGNATED_INITIALIZER.
self = [super initWithValue:nil];
if (self) {
_value = [value copy];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
// The mutable class really does need to copy, unlike super.
return [[MyObject allocWithZone:zone] initWithValue:self.value];
}
#end
A fragment of test code:
NSMutableString *string = [NSMutableString stringWithString:#"one"];
MyObject *object = [[MyObject alloc] initWithValue:string];
[string appendString:#" two"];
NSLog(#"object: %#", object.value);
MyObject *other = [object copy];
NSAssert(object == other, #"These should be identical.");
MyMutableObject *mutable1 = [object mutableCopy];
mutable1.value = string;
[string appendString:#" three"];
NSLog(#"object: %#", object.value);
NSLog(#"mutable: %#", mutable1.value);
Some debugging right after the last line above:
2017-12-15 21:51:20.800641-0500 MyApp[6855:2709614] object: one
2017-12-15 21:51:20.801423-0500 MyApp[6855:2709614] object: one
2017-12-15 21:51:20.801515-0500 MyApp[6855:2709614] mutable: one two
(lldb) po mutable1->_value
one two
(lldb) po ((MyObject *)mutable1)->_value
nil
As mentioned in the comments this requires discipline in the base class to use the getter instead of the iVar. Many would consider that a good thing, but that debate is off-topic here.
A minor difference you might notice is that I have used the copy attribute for the property. This could be made strong instead with very little change to the code.
There are many questions concerning the category-properties problem.
I know some possibilities to address this:
use a singleton registry
objc_setAssociatedObject and objc_getAssociatedObject
From my point of view both is not clean since the memory allocated is never cleared when the object that created such properties is deallocated.
Categories are a good way to keep code clean and dynamically add functionality to already existing classes. They help to group functionality and to distributed implementation work among more developers.
The bad about categories is the missing storage.
I came across this problem several times now and I'm wondering whether the following would address this problem in an clean way that also takes care about the memory and if there are any problems that I can't see right now.
There is one restriction, that I can ignore since I'm working as a framework developer: I'm able to create my own root class that all my other classes can inherit from.
First of all declare the new root object:
#interface RootObject : NSObject
- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key;
- (id)runtimePropertyForKey:(id)key;
#end
With the corresponding implementation:
#import "RootObject.h"
#interface RootObject ()
#property (readwrite) NSMutableDictionary *runtimeProperties;
#end
#implementation RootObject
#synthesize runtimeProperties = _runtimeProperties;
- (id)init {
self = [super init];
if (self)
{
_runtimeProperties = [[NSMutableDictionary alloc] initWithCapacity:1];
}
return self;
}
- (void)dealloc {
[_runtimeProperties release];
_runtimeProperties = nil;
[super dealloc];
}
- (id)runtimePropertyForKey:(id)key {
return [self.runtimeProperties objectForKey:key];
}
- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key {
if (key)
{
if (runtimeProperty)
{
[self.runtimeProperties setObject:runtimeProperty forKey:key];
}
else
{
[self.runtimeProperties removeObjectForKey:key];
}
}
}
#end
By using this RootObject instead of NSObject it should be very easy to add a "property" to a category on a class. Consider having some class MyClass
#interface MyClass : RootObject
// some interface here
#end
When implementing a special behavior on top of this class you are now able to add a property like this:
#interface MyClass (specialBehavior)
#property (nonatomic, retain) NSString *name;
#property (nonatomic, copy) NSDate *birthday;
#end
With corresponding implementation:
#implementation MyClass (specialBehavior)
#dynamic name;
- (NSString *)name {
return [self runtimePropertyForKey:#"name"];
}
- (void)setName:(NSString *)name {
[self setRuntimeProperty:name forKey:#"name"];
}
#dynamic birthday;
- (NSDate *)birthday {
return [self runtimePropertyForKey:#"birthday"];
}
- (void)setBirthday:(NSDate *)birthday {
[self setRuntimeProperty:[birthday copy] forKey:#"birthday"];
}
#end
Such an implementation could KVO compatible as well by just adding the necessary calls in the setter method.
Very straight forward, but I'm wondering whether I missed something important? (E.g. very very bad runtime performance having many such declared properties or using many of these objects)
This is effectively the same as objc_setAssociatedObject and objc_getAssociatedObject, which do release memory when the object is deallocated (depending on the association type). I would guess they also have much lower overhead than your suggested code.