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
Related
This is my first day of Objective C so I apologise for the lack of knowledge.
I need to import an existing SKD into an App and I done it successfully. Now I need to create the delegate methods and I don't understand how can I do it.
This is the structure of the header file included from the SDK (SDKManager.h):
#protocol SDKManagerDelegate;
#interface SDKManager : NSObject
#property (nonatomic, weak) id<SDKDelegate> delegate;
+(void)initialize:(NSString*)appId withKEY:(NSString*)key;
+(void)setHandler:(id)delegate;
#end
#protocol SDKManagerDelegate <NSObject>
#required
-(void)appDidReceiveTokens:(NSDictionary*)items withResponse:(NSDictionary*)response;
#end
So, from my FirstViewController.m I was able to import the header and call two methods:
#import "FirstViewController.h"
#import "SDKManager.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[SDKManager setHandler:[UIApplication sharedApplication].delegate];
[SDKManager initialize:#"AppId"withKEY:#"1234"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
but I have noticed that I am not able to call the other methods (i.e. appDidReceiveTokens).
Actually the instructions require to create those methods but I have no idea where.
Any help is really appreciated.
Thank you
You do not call delegate methods directly in the files in which you are implementing the delegate methods. Review Apples documentation on the concept of Delegation.
To implement this properly you would adopt the delegate in your class, then implement the delegate methods that are #required and/or #optional.
You've correctly created the delegate protocol and a property to store the SDKManager's delegate.
Your setHandler: and initialize:withKEY: methods are class methods, whereas the delegate property belongs to each instance of SDKManager. Without seeing your implementation file (.m) for SDKManager, it's hard to know why you've set it up this way. You may be attempting to follow a singleton pattern - if so, read up on it, e.g. here.
The reason for that is you have class methods which sets the calls setHandler method and the delegate is property, so where do you assign the delegate and when and how do you call the delegate. I hope you understand what a class and instance is. So, you cannot call the delegate until you create instance of your object.
You have two different class methods which is used to set some attributes to the class, would it make sense to have them as property.
More generic and better way to do this would be like this,
#protocol SDKManagerDelegate <NSObject>
#required
-(void)appDidReceiveTokens:(NSDictionary*)items
withResponse:(NSDictionary*)response;
#end
#protocol SDKManagerDelegate;
#interface SDKManager : NSObject
- (instancetype)initWithAppId:(NSString *)appId
key:(NSString *)key
delegate:(id<SDKManagerDelegate>)delegate;
#end
#interface SDKManager ()
#property (nonatomic, copy, readonly) NSString *appId;
#property (nonatomic, copy, readonly) NSString *key;
#property (nonatomic, weak, readonly) id<SDKManagerDelegate> delegate;
#end
#implementation SDKManager
- (instancetype)initWithAppId:(NSString *)appId
key:(NSString *)key
delegate:(id<SDKManagerDelegate>)delegate
{
if (self = [super init]) {
_appId = [appId copy];
_key = [key copy];
_delegate = delegate;
}
return self;
}
- (void)doSomeNetworkRequestHere
{
[self fetchTokenFromServer:^(NSDictionary *tokens, NSDictionary *response){
[self.delegate appDidReceiveTokens:tokens
withResponse:response];
}];
}
- (void)fetchTokenFromServer:(void(^)(NSDictionary *tokens, NSDictionary *response))completion
{
}
#end
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.
I've created a class that contains three properties that are Core Audio types:
#interface AudioFilePlayer : NSObject <NSCoding>
#property (assign) AudioUnit mAudioUnit;
#property (assign) AUNode mNode;
#property (assign) AudioStreamBasicDescription mStreamFormat;
#end
My app holds an array of objects of type AudioFilePlayer and I want to archive and unarchive them using NSCoding. I've written the encodeWithCoder: and initWithCoder: methods as follows:
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBytes:(uint8_t*)&_mAudioUnit length:sizeof(AudioUnit) forKey:#"mAudioUnit"];
[aCoder encodeBytes:(uint8_t*)&_mNode length:sizeof(AUNode) forKey:#"mNode"];
[aCoder encodeBytes:(uint8_t*)&_mStreamFormat length:sizeof(AudioStreamBasicDescription) forKey:#"mStreamFormat"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
[self setMAudioUnit:(AudioUnit)[aDecoder decodeBytesForKey:#"mAudioUnit" returnedLength:sizeof(AudioUnit)]];
[self setMNode:(AUNode)[aDecoder decodeBytesForKey:#"mNode" returnedLength:sizeof(AUNode)]];
[self setMStreamFormat:*(AudioStreamBasicDescription*)[aDecoder decodeBytesForKey:#"mStreamFormat" returnedLength:sizeof(AudioStreamBasicDescription)]];
}
return self;
}
I'm able to encode/archive successfully (that is to say a file is written and no errors are returned...I'm not sure it's actually working) but when I launch the app and try to decode/unarchive the object, the app crashes with:
Thread 1: EXC_BAD_ACCESS (code=2,address=0x4)
on this line in my initWithCoder method:
[self setMAudioUnit:(AudioUnit)[aDecoder decodeBytesForKey:#"mAudioUnit" returnedLength:sizeof(AudioUnit)]];
This is my first time using NSCoding so I'm not at all confident I'm doing this even remotely correctly.
These three Core Audio data types are structs so using the "bytes" version of the encode/init NSCoder methods seems like the right way to go.
Any ideas on where I might be going wrong?
If you have a look at decodeBytesForKey:returnedLength: you would see that returnedLength is a NSUInteger* and you're passing an integer. Besides the method returns const uint8_t*, so you need to dereference it to get back your data.
Instead of
[self setMAudioUnit:(AudioUnit)[aDecoder decodeBytesForKey:#"mAudioUnit" returnedLength:sizeof(AudioUnit)]];
it should be
NSUInteger szAudioUnit;
const uint8_t* audioUnitBytes = [aDecoder decodeBytesForKey:#"mAudioUnit" returnedLength:&szAudioUnit];
AudioUnit* pAudioUnit = (AudioUnit*)audioUnitBytes;
self.mAudioUnit = *pAudioUnit;
Actually I don't know how you code even compiled !
On a side note, maybe just my opinion, it's not a convention in Objective-C to name properties with an m prefix.
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.