How to do deep copying Objective-C - objective-c

There is a class Patient with properties:
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) Symptoms *symptoms;
#property (assign, nonatomic) Status status;
#property (weak, nonatomic) id <PatientDelegate> delegate;
There is a class Symptoms with properties:
#property (assign, nonatomic) CGFloat temperature;
#property (assign, nonatomic) BOOL headache;
#property (assign, nonatomic) BOOL stomach_ache;
Both classes implement protocol NSCopying:
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Patient *newPatient = [[[self class] allocWithZone:zone] init];
[newPatient setName:self.name];
[newPatient setSymptoms:self.symptoms];
[newPatient setStatus:self.status];
[newPatient setDelegate:self.delegate];
return newPatient;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Symptoms *newSymptoms = [[[self class] allocWithZone:zone] init];
[newSymptoms setTemperature:self.temperature];
[newSymptoms setHeadache:self.headache];
[newSymptoms setStomach_ache:self.stomach_ache];
return newSymptoms;
}
Also there is a class Doctor:
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSMutableArray *history;
- (void)makeNoteIn:(Patient *)patient card:(NSMutableArray *)history;
- (void)report;
When patient gets well, doctor calls method makeNoteIn:
- (void)makeNoteIn:(Patient *)patient card:(NSMutableArray *)history {
Patient *newRecord = [patient copy];
[history addObject:newRecord];
}
After the record is made, all property of the patient return to the original values. While we are in the method makeNoteIn and the current patient is proceeded, in history there is link to this object which has correct property values. As soon as we exit the method or start to proceed another patient, all the property values reset to the initial value.
I tried to realise copying, but something still wrong.

When you want to deep-copy an object, you have to implement copy on all the substructures:
[newPatient setName:[self.name copy]];
[newPatient setSymptoms:[self.symptoms copy]];
Otherwise they will still reference the same object and changing one will affect all.
Note that you can do that automatically by declaring the properties as copy:
#property (copy, nonatomic) NSString *name;
#property (copy, nonatomic) Symptoms *symptoms;
It's common to use copy with NSString and NSArray to prevent assigning NSMutableString and NSMutableArray which could be changed externally by mistake. Make sure you implement NSCopying on Symptoms.

Related

Assigned to Readonly Property Objective-C

I am writing a unit test to test a method that updates a checklist. The checklist has these properties:
typedef NS_ENUM (NSUInteger, ChecklistStatus) { Pending, Completed };
#protocol IChecklistItem <NSObject>
#property (nonatomic, assign, readonly) NSInteger Id;
#property (nonatomic, copy, readonly) NSString *Description;
#property (nonatomic, assign, readonly)BOOL IsCompleted;
#property (nonatomic, assign, readwrite) ChecklistStatus Status;
#property (nonatomic, strong, readwrite) NSDate *CompletedDate;
#property (nonatomic, copy, readwrite) NSString *CompletedByUserId;
#property (nonatomic, assign, readonly) NSInteger RoleId;
#property (nonatomic, assign, readonly) NSInteger GroupId;
#property (nonatomic, strong, readonly) NSArray<IChecklistNote> *Notes;
- (void)sortNotes;
#end
However, in my unit test, as I am trying to validate,
checklistItem.Description = #"hello";, I get the error"Assignment to readonly property"
Why is this so?
heres the rest of my test method:
- (void)testUpdateChecklist {
NSString *testChecklistId = #"1";
NSString *testPatientDescription = #"Descriptive Description";
// What other properties do I need here?
XCTAssertNotNil(_service);
__block CCChecklistItem *checklistItem = nil;
SignalBlocker *blocker = [[SignalBlocker alloc] initWithExpectedSignalCount:1];
id delegate = OCMProtocolMock(#protocol(ChecklistServiceDelegate));
OCMExpect([delegate didCompleteUpdateChecklistItem:[OCMArg checkWithBlock:^BOOL(id obj) {
checklistItem = obj;
XCTAssertNotNil(checklistItem);
[blocker signal];
return true;
}]]);
[_service updateChecklistItem:checklistItem delegate:delegate];
[blocker waitWithTimeout:5.0f];
OCMVerifyAll(delegate);
NSString *originalDescription = checklistItem.Description;
checklistItem.Description = #"hello";
}
EDITED QUESTION:
So when I change the property from above to ReadWrite, I get this error in CChecklistItem
#interface CCChecklistItem ()
#property (nonatomic, assign, readwrite) NSInteger Id;
#property (nonatomic, copy, readwrite) NSString *Description;
#property (nonatomic, assign, readwrite) NSInteger RoleId;
#property (nonatomic, assign, readwrite) NSInteger GroupId;
#property (nonatomic, strong, readwrite) NSMutableArray<IChecklistNote> *Notes;
#end
`Illegal redeclaration of readwrite property in class extension 'CChecklistItem'
Your property is set to readonly as seen here:
#property (nonatomic, copy, readonly) NSString *Description;
Change it to:
#property (nonatomic, copy) NSString *Description;
or if you want to be consistent with the other properties (though overly explicit, IMO):
#property (nonatomic, copy, readwrite) NSString *Description;
Changing scope visibility only to satisfy tests is not encouraged. The easiest solution in your case would be to take advantage of wonderful KVO which Objective-C gives you.
Translated to your original question it would be something like:
[checklistItem setValue:#"hello" forKey:#"Description"]
No need to change access modifiers and your tests will be fine.
Your property is declared readonly in the protocol that the class CChecklistItem conforms. When that property is then synthersized it will create the backing variable and a getter method -(NSString *)description; but no setter method, since it is readonly. So redeclaring it as readwright in your anonymous category, that i'm guessing is declared in your test file to expose private methods to the test case, won't work since there still is no setter method for the property. Further more, even if you decide to try to make your own setter in the implementation of a category on your class you can't since there is no way to access the variable _description that is only exposed in the CChecklistItem.m file.
Depending on what you need to do with your test it might work to stub the getter - (NSString *)description; and return your #"hello" string when that method is called instead of trying to set the actual value to the backing variable.

How can I cast my NSURLSessionDownloadTask to my custom NSURLSessionDownloadTask (inheritance)?

I have created a custom NSURLSessionDownloadTask named VJSessionTask and I have just added some custom things like a type (enum) and a custom object (id):
#interface VJSessionTask : NSURLSessionDownloadTask
typedef enum types
{
LS, LSH, DL, UL, RM, TH
} type;
#property enum types type;
#property (strong, nonatomic) id customObject;
#property (strong, nonatomic) NSString *progressNotif;
#property (strong, nonatomic) NSString *doneNotif;
#property (strong, nonatomic) NSURL *tmpFile;
#end
And when I do this:
VJSessionTask *taskSession = (VJSessionTask *)[self.prioritySession downloadTaskWithRequest:listFileRequest];
// init taskSession with its type
taskSession.type = LS;
I get this error:
-[__NSCFLocalDownloadTask setType:]: unrecognized selector sent to instance 0x1556198f0
Then I come to you as I don't understand or I don't know how to do that...
Thank you in advance ;)
NSURLSessionTasks are not strictly speaking subclass-able unfortunately. This is evident in that the system can queue a data task and return a NSCFLocalDownloadTask (presumably meaning that the task will return its content from the cache).
The best way to go about doing this is to borrow on from the architectural decision of AFNetworking and have individual taskDelegates that monitor all the responses an individual task works on. Then when you want to find the data relating to a task you can query your dictionary of taskDelegates. Each task has a unique identifier that you can use to key your dictionary with.
In AFNetworking you can see the taskDelegate is defined as follows:
#interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
#property (nonatomic, weak) AFURLSessionManager *manager;
#property (nonatomic, strong) NSMutableData *mutableData;
#property (nonatomic, strong) NSProgress *progress;
#property (nonatomic, copy) NSURL *downloadFileURL;
#property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
#property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
#end
#implementation AFURLSessionManagerTaskDelegate
and subsequently retrieved as follows:
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[#(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
See this post for more info

CoreData How to connect with relationship entity to another entity with already preloaded data?

This is my first application in CoreData so I definitely missing something here, but I spend over a week trying to find solution and apparently nobody on Internet had the same problem ;)
I'm creating something like deckbuilder app.:
My model looks like this: coredatamodel
The relationship is to-one and inverse to-many ( every card can be in one set, and in every set can be many cards) from CARD to SET.
For sake of simplicity I want to focus on CARD and SET entities.
I preloaded data into SET from CSV file into 5 atributes - didnt fill relationship "karty" because I didn't know how to do this. There are about 25 records in this entity.
And now, problem is I am trying to fill CARDS Entity which will have over 500 records.
Trying few options I found on SO I end with connection that created 500 records in SET too:/ so it looked like sql "JOIN" command.
What I want is load data to CARDS and connect them somehow to SET not changing number of records in SET.
If i have property (nonatomic, retain) NSSet *karty; what exactly NSSet means? It is s et but set of what? Set of single rows from Card entity? Set of Card objects? Set of NSStrings? Set of NSManagedObjects?
In normal SQL primary key to connect them would be "nazwaszort" so Card.nazwaszort=Set.nazwaszort.
My Set.h
#class Card;
#interface Set : NSManagedObject
#property (nonatomic, retain) NSNumber * cykl;
#property (nonatomic, retain) NSNumber * czymajor;
#property (nonatomic, retain) NSString * nazwa;
#property (nonatomic, retain) NSString * nazwashort;
#property (nonatomic, retain) NSNumber * nrwcyklu;
#property (nonatomic, retain) NSSet *karty;
#end
#interface Set (CoreDataGeneratedAccessors)
- (void)addKartyObject:(Card *)value;
- (void)removeKartyObject:(Card *)value;
- (void)addKarty:(NSSet *)values;
- (void)removeKarty:(NSSet *)values;
#end
My Card.h
#class Frakcja, Kolekcja, Set;
#interface Card : NSManagedObject
#property (nonatomic, retain) NSNumber * czylimit;
#property (nonatomic, retain) NSString * frakcja;
#property (nonatomic, retain) NSString * icesila;
#property (nonatomic, retain) NSNumber * iloscwsecie;
#property (nonatomic, retain) NSNumber * influence;
#property (nonatomic, retain) NSString * kodkarty;
#property (nonatomic, retain) NSNumber * koszt;
#property (nonatomic, retain) NSNumber * minimumdecksize;
#property (nonatomic, retain) NSString * nazwa;
#property (nonatomic, retain) NSString * nazwasetu;
#property (nonatomic, retain) NSString * nazwaszort;
#property (nonatomic, retain) NSNumber * nrcyklu;
#property (nonatomic, retain) NSString * podtyp;
#property (nonatomic, retain) NSString * strona;
#property (nonatomic, retain) NSNumber * trashkoszt;
#property (nonatomic, retain) NSString * typ;
#property (nonatomic, retain) Kolekcja *ilewkolekcja;
#property (nonatomic, retain) Frakcja *nazwafrakcji;
#property (nonatomic, retain) Set *wjakimsecie;
#end
I preloaded cards to SET using method
-(void) preloadDataPackInfoToDatabase {
NSError *error=nil;
NSString *sciezka = [[NSBundle mainBundle]pathForResource:#"nrsets" ofType:#"csv"];
NSArray *rows = [NSArray arrayWithContentsOfCSVFile:sciezka];
for (int i=1; i <=([rows count]-1); i++) {
Set *nowyDataPack = [NSEntityDescription insertNewObjectForEntityForName:[entityset name] inManagedObjectContext:_contextdp];
NSLog(#"tablica wierszy %#",rows[i][2]);
NSString *koddodatku = rows[i][2];
NSLog(#"kod dodatku:%#",koddodatku);
NSString *nrwcyklu = rows[i][4];
NSString *nrcyklu = rows[i][3];
NSString *nazwadatapack =rows[i][3];
NSString *czymajor =rows[i][0];
[nowyDataPack setValue:nazwadatapack forKey:#"nazwa"];
NSNumberFormatter *f = [[NSNumberFormatter alloc]init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *nrwcykluint = [f numberFromString:nrwcyklu];
NSNumber *nrcykluint = [f numberFromString:nrcyklu];
NSNumber *czymajorbool = [f numberFromString:czymajor];
[nowyDataPack setValue:nrwcykluint forKey:#"nrwcyklu"];
[nowyDataPack setValue:nrcykluint forKey:#"cykl"];
[nowyDataPack setValue:koddodatku forKey:#"nazwashort"];
[nowyDataPack setValue:czymajorbool forKey:#"czymajor"];
}
if (![ self.contextdp save:&error]) {
NSLog(#"Nieznany błąd %#,%#",error,[error userInfo]);
}
}
And another method (on pastebin to not flood your screens --> preloadAllCardsToDatabase
Thanks in advance for any help.
OK I finally did it. Had to move some CoreData stuff outside loop.
Still dont understand this whole magic but at least it works.
for those interested this is my preloading function --> here

NSDictionary: Comparing NSDictionaries

There are two of files. Lets call them fileOne and fileTwo
Each has several NSMutableDictionary properties with identical names. To list a few:
#property (retain, nonatomic) NSMutableDictionary * lunchStartTimeObject;
#property (retain, nonatomic) NSMutableDictionary * lunchLocationNameObject;
#property (retain, nonatomic) NSMutableDictionary * lunchLocationAddressObject;
#property (retain, nonatomic) NSMutableDictionary * activity1NameObject;
#property (retain, nonatomic) NSMutableDictionary * activity1StartTimeObject;
#property (retain, nonatomic) NSMutableDictionary * activity1LocationNameObject;
#property (retain, nonatomic) NSMutableDictionary * activity1CommentsFieldObject;
#property (retain, nonatomic) NSMutableDictionary * activity1LocationAddressObject;
#property (retain, nonatomic) NSMutableDictionary * activity2NameObject;
#property (retain, nonatomic) NSMutableDictionary * activity2StartTimeObject;
#property (retain, nonatomic) NSMutableDictionary * activity2LocationNameObject;
#property (retain, nonatomic) NSMutableDictionary * activity2CommentsFieldObject;
#property (retain, nonatomic) NSMutableDictionary * activity2LocationAddressObject;
I would like to compare the dictionaries with the same name in the two files by calling the method below (or something similar):
-(NSMutableDictionary *)cellColorForChanges:(NSMutableDictionary *)newdictionary :(NSMutableDictionary *)oldDictionary;
{
if(![newdictionary isEqualToDictionary:oldDictionary])
{
[newdictionary setValue:#"UIColor yellowColor" forKey:#"cellColor"];
}
return newdictionary;
}
I'm trying avoid writing code for each NSMutableDictionary manually. Is there a way to avoid the following:
if(![fileOne.lunchStartTimeObject isEqualToDictionary:fileTwo.lunchStartTimeObject])
{
fileOne.lunchStartTimeObject setValue:#"UIColor yellowColor" forKey:#"cellColor"];
}
I'm having trouble figuring out the most efficient way to accomplish the above. Is it somehow possible to send each dictionary to a method and get back the dictionary (updated with another key, if it's not equal)? Or what I'm trying to avoid is unavoidable?
You are running into this problem because you are not abstracting your objects sufficiently. It seems to me that your property list above is ridiculously redundant. Even the simple code code you provided is hardly readable.
Try to think of your problem in a more conceptional way. Try to think of objects that could encapsulate the functionality you are looking for.
Looking at your properties you probably want a class like this
#interface Activity : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSDate *startTime;
#property (nonatomic, strong) Location *location;
-(BOOL)isEqualToActivity:(Activity*)activity;
#end
Maybe you need a location class that stores more information about a location; perhaps you could use even more fields like firstName, lastName; maybe you need a type (enum or string) property that tells what kind of activity it is, etc. -- you get the idea.
As indicated, you could write your own comparison method where you could tweak for allowing more or less strict capital or small letters, number formats etc.

Unable to refresh UITableView with modified data

I have a iPad app, using XCode 4.5, Storyboards, Core Data and iOS 6. I select a row, make a change to the contents of the record (which is successful), but the row doesn't change. I have tried to refresh the UITableView, but cellForRowAtIndexPath is never called. I have searched SO and Google to no avail; I don't see what's wrong. Can someone please tell me how to fix this? (with an explanation of what I'm doing wrong for the next time?)
Here is the pertinent code:
- (IBAction)btnModify:(UIButton *)sender {
//NSLog(#"btnModify clicked");
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
// find client by primary telephone number
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"aClientPrimaryPhone ==[c] %#", cvPrimaryPhone.text];
ClientInfo *clientDataFound = [ClientInfo MR_findFirstWithPredicate:predicate inContext:localContext];
if(clientDataFound) {
clientDataFound.aClientName = cvCustName.text; // now start moving the data
clientDataFound.aClientAddr1 = cvAddress1.text;
clientDataFound.aClientAddr2 = cvAddress2.text;
clientDataFound.aClientCity = cvContactCity.text;
clientDataFound.aClientPostalCode = cvPostalCode.text;
clientDataFound.aClientCellPhone = cvCellPhone.text;
clientDataFound.aClientPrimaryPhone = cvPrimaryPhone.text;
clientDataFound.aClientEMail = cvPersonalEmail.text;
clientDataFound.aClientNotes = cvNotes.text;
[localContext MR_saveNestedContexts];
[self reloadClientList];
}
}
-(void) reloadClientList {
//Init Array to hold TableView Data
tableDataArray = [NSMutableArray new];
[tableDataArray addObjectsFromArray:[ClientInfo findAll]]; // Load
[self.clientList reloadData];
}
and this is ClientInfo.m
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface ClientInfo : NSManagedObject
#property (nonatomic, retain) NSString * aClientAddr1;
#property (nonatomic, retain) NSString * aClientAddr2;
#property (nonatomic, retain) NSString * aClientCellPhone;
#property (nonatomic, retain) NSString * aClientCity;
#property (nonatomic, retain) NSString * aClientEMail;
#property (nonatomic, retain) NSData * aClientImage;
#property (nonatomic, retain) NSString * aClientName;
#property (nonatomic, retain) NSString * aClientNotes;
#property (nonatomic, retain) NSString * aClientPostalCode;
#property (nonatomic, retain) NSString * aClientPrimaryPhone;
#end
I found it... my "clientList" was NOT connected to the object... don't know how I missed that one!
There are a few reasons I can think of:
Your table view reference clientList is nil. (not connected)
Your table view's DataSource & Delegate is not set (Actually I'm not sure if it compiles when DataSource is not set)
Your table view is a subclass of UITableView and in that subclass reloadData method is overridden.