I am aware of how normal NSArray concatenation works in Objective-C. This is not that question.
I have data that is being incrementally updated from a web service. My object has the following class definition (with a lot removed):
// NoteTemplate.h
#import <UIKit/UIKit.h>
#interface NoteTemplate
#property (copy, nonatomic) NSString *objectId;
I am caching a list of these on-device and checking at launch to see if there are any new or updated NoteTemplate objects in my database to load. So, I end up with two arrays:
NSArray <NoteTemplate *> *oldArray
NSArray <NoteTemplate *> *newArray
If there are no updates, then all I need to do is simply concatenate the two arrays together and that's that.
If there are updates, however, I want to combine the two arrays, but whenever there is a common objectId, the item in newArray should take precedence over the item in oldArray.
Thus far, I am brute-forcing it like this:
- (void)updateNoteTemplatesWithArray:(NSArray *)newTemplates {
NSArray *oldTemplates = [self getNoteTemplates];
NSMutableArray *combined = [NSMutableArray arrayWithArray:newTemplates];
for (NoteTemplate *noteTemplate in oldTemplates) {
NSArray *matches = [combined filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id blockTemplate, NSDictionary<NSString *,id> *bindings) {
return [((NoteTemplate *)blockTemplate).objectId isEqualToString:noteTemplate.objectId];
}]];
if (matches.count == 0) {
[combined addObject:noteTemplate];
}
}
[self setNoteTemplates:[combined copy]];
}
Is there a more optimized way to do this? I can't see that this will affect performance at all, so perhaps an optimization is unnecessary. Still, this approach feels hacky and way over-engineered.
To extend #Larme's suggestion with Set usage you can try the following approach:
#interface NoteTemplate: NSObject
#property (copy, nonatomic) NSString *objectId;
#property (copy, nonatomic) NSString *text;
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text;
#end
#implementation NoteTemplate
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text {
self = [super init];
if (self != nil) {
_objectId = objectId;
_text = text;
}
return self;
}
- (BOOL)isEqual:(id)object {
return [self.objectId isEqualToString:[object objectId]];
}
#end
And the usage code:
NoteTemplate *nt1 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"old set"];
NoteTemplate *nt2 = [[NoteTemplate alloc] initWithObjectId:#"2" text:#"old set"];
NoteTemplate *nt3 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"new set"];
NoteTemplate *nt4 = [[NoteTemplate alloc] initWithObjectId:#"3" text:#"new set"];
NSSet <NoteTemplate *> *oldSet = [NSSet setWithObjects:nt1, nt2, nil];
NSSet <NoteTemplate *> *newSet = [NSSet setWithObjects:nt3, nt4, nil];
NSMutableSet <NoteTemplate *> *mergedSet = [newSet mutableCopy];
[mergedSet unionSet:oldSet];
for (NoteTemplate *note in mergedSet) {
NSLog(#"Set item %# %#", note.objectId, note.text);
}
After executing this code you'll see in the log:
Set item 3 new set
Set item 1 new set
Set item 2 old set
I assume that's what you were looking for.
I don't know if I'd call this elegant but it's a less brutish approach. Instead of filtering combined at every pass through the loop, get all the new IDs in advance and check the ID list in the loop.
NSMutableArray <NoteTemplate *> *combined = [NSMutableArray arrayWithArray:newTemplates];
NSArray <NSString *> *newTemplateIds = [newTemplates valueForKey:#"objectId"];
for (NoteTemplate *oldTemplate in oldTemplates) {
if (![newTemplateIds containsObject:oldTemplate.objectId]) {
[combined addObject:oldTemplate];
}
}
Related
I was stuck on writing NSDictionary into Object process, I am sure that problem is simple as I imagine but would be great to get assistant. Here is my code:
my custom object:
#interface User : NSObject
#property (nonatomic, retain) NSString *cId;
#property (nonatomic, retain) NSString *firstName;
#property (nonatomic, retain) NSString *lastName;
....
-(instancetype) initWithParameters:(NSDictionary*) parameters;
#end
#import "User.h"
#implementation User
-(instancetype) initWithParameters:(NSDictionary*) parameters
{
self = [super init];
if (self) {
[self setParameters:parameters];
}
return self;
}
- (void) setParameters:(NSDictionary*) parameters{
_cId = parameters[#"cId"];
_firstName = parameters[#"first_name"];
_lastName = parameters[#"last_name"];
....
}
and writing process:
id userObjects = [resultData objectForKey:#"data"];
NSMutableArray* mUsers = [[NSMutableArray alloc] init];
for (NSDictionary* userParameters in userObjects) {
User *user = [[User alloc] initWithParameters:userParameters];
[mUsers addObject:user];
}
userObjects - NSArray got from JSON object from server data.
The problem is : nothing happening and user object still empty after initialization, then I have tried - setValuesForKeysWithDictionary after I called variables same as keys in dictionary and nothing changed.
after adding in mUsers:
Could anybody tell me what I am doing wrong? Thank you!
I believe you think those objects are uninitialized because you are seeing 0 key/value pairs next to each User object.
Your code looks good and I think things will change once you implement [NSObject description] (or [NSObject debugDescription]) like this:
- (NSString *)description
{
return [NSString stringWithFormat:#"cId=%#, firstName=%#, lastName=%#",
_cId, _firstName, _lastName];
}
I am making an Schedule class and trying to output all events in a loop in a ViewController. I have created an Event class and Schedule class. I am looping thorough NSMutableArray to get the text all of all the events. My problem is that when I am in the addNewEvent method in Schedule.m file I overwrite my NSMutable array with the last object I am adding through addObject. My goal is to add all the event objects one by one into the array and display their text. It would also be nice if I could set separate IDs for the event objects. I understand that I probably need to loop through something in the addNewEvent method when I am adding objects to the mutable array, but I don't know through what. I also thought that maybe I am initializing the NSMutableArray (which is a property of the Schedule class) in the wrong place, but it gives an error if I move the initializer anywhere else.
Could you please help me solve this problem. Any help will be much appreciated!
My Code is below. I have two classes (Event & Schedule) and a ViewController file.
Event.h:
#interface Event : NSObject
#property int eventId;
#property NSString * eventTitle;
#property NSString * eventDescription;
#property NSDate * eventDate;
-(void) logEvent;
-(NSString*) getEventText;
#end
Event.m:
#import "Event.h"
#implementation Event
-(NSString*) getEventText {
NSString * eventText1 = [NSString stringWithFormat: #"\n\nEvent ID: %d.\n", _eventId];
NSString * eventText2 = [eventText1 stringByAppendingFormat: #"Title: %#.\n", _eventTitle];
NSString * eventText3 = [eventText2 stringByAppendingFormat:#"Description: %#.\n", _eventDescription];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterFullStyle;
dateFormatter.timeStyle = NSDateFormatterShortStyle;
NSString * eventText = [eventText3 stringByAppendingFormat:#"Date: %#.\n\n", [dateFormatter stringFromDate:_eventDate]];
return eventText;
}
#end
Schedule.h:
#interface Schedule : NSObject
#property int idIndex;
#property NSMutableArray * scheduledEvents;
-(NSString*) getAllEventText;
-(void)addNewEventWithTitle:(NSString *)Title Description:(NSString *)Description andDate:(NSDate *)Date;
#end
Schedule.m:
#import "Schedule.h"
#import "Event.h"
#implementation Schedule
-(void)addNewEventWithTitle:(NSString *)Title Description:(NSString *)Description andDate:(NSDate *)Date {
Event * event = [[Event alloc] init];
_idIndex = 1;
[event setEventTitle:Title];
[event setEventDescription:Description];
[event setEventDate:Date];
[event setEventId:_idIndex];
NSLog(#"%#", [event getEventText]);
_scheduledEvents = [[NSMutableArray alloc] init];
[_scheduledEvents addObject: event];
NSLog(#"%lu", [_scheduledEvents count]);
}
-(NSString*) getAllEventText {
NSString * allEventText;
// loop through NSMutableArray calling each Event's getEventText method using a for loop
for (int i=0; i<[_scheduledEvents count]; i++) {
allEventText = [NSString stringWithFormat:#"Event %d: %#\n", i+1, [_scheduledEvents[i] getEventText] ];
NSLog(#"%#", [_scheduledEvents[i] getEventText]);
}
// return concatenated string
return allEventText;
}
#end
ViewController.m:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Schedule * mySchedule = [[Schedule alloc] init];
[mySchedule addNewEventWithTitle:#"My Birthday" Description:#"My most cherished birthday" andDate:[NSDate dateWithTimeIntervalSinceReferenceDate:484830000]];
[mySchedule addNewEventWithTitle:#"Meeting with the Client" Description:#"My most important meeting" andDate:[NSDate dateWithTimeIntervalSinceReferenceDate:481302000]];
[mySchedule addNewEventWithTitle:#"Appointment with Family Doctor" Description:#"My most urgent appointment" andDate:[NSDate dateWithTimeIntervalSinceReferenceDate:480270000]];
[_outputTextView setText:[mySchedule getAllEventText]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You are re-initializing scheduledEvents everytime addNewEventWithTitle:Description:andDate is called.
You have to move _scheduledEvents = [[NSMutableArray alloc] init]; in your viewDidLoad method.
The problem is this line:
-(void)addNewEventWithTitle:(NSString *)Title Description:(NSString *)Description andDate:(NSDate *)Date {
// ...
_scheduledEvents = [[NSMutableArray alloc] init]; // <--
// ...
}
By definition, if _scheduledEvents already has any value (an existing array), that line destroys that value and replaces it with a new empty array. So the result of running your addNewEvent... method will always be that _scheduledEvents ends up consisting of just this one event.
SOLVED: the problem was in the getAllEventText method.
The allEventText string was constantly replacing its own content in the for loop in the method previously.
I had to change the NSString to NSMutableString and use the append method to add to the existing string, and not replace its content.
So I changed the method as follows and it works now:
-(NSString*) getAllEventText {
NSMutableString* allEventText = [NSMutableString stringWithCapacity:150];
// loop through NSMutableArray calling each Event's getEventText method using a for loop
for (int i=0; i<[_scheduledEvents count]; i++) {
[allEventText appendFormat:#"Event %d: %#\n", i+1, [_scheduledEvents[i] getEventText]];
NSLog(#"%#", [_scheduledEvents[i] getEventText]);
}
// return concatenated string
return allEventText;
}
Also big thanks to metronic and matt for suggesting that I need to move the MSMutableArray initializer away from the addNewEvent method!!
Using Objective-C, is it possible to go through an array by groups :
exemple :
NSArray *arr = 1, 2, 3, ....100;
Every 10 objects, do something and go on
so :
object 0 to 9 : you do something with each object and after the 10° object you do a last action
then object 10 to 19 : you do something with each object and after the 19° object you do a last action
and so on until the last object
thank you for your help
something like this:
for (int i = 0; i < arr.count; i++)
{
[self doSomethingWithArray];
if (i % 10 == 0)
[self doSomethingElse];
}
No it is not possible in Objective-C with in-built functions which matches your exact description. There are crude ways to do it by loops which matches your exact description.
But if you are aware before hand that you are going to make such type of operations, define your own data-structure. Create an NSObject sub-class, define your items (10 items which you were talking about) in it. Then in array, you can directly take out each instance of it comprising of your defined NSObject.
"enumerating by group"; If you want exactly as stated, you can subclass NSEnumerator.
For example:
In your Application code:
#import "NSArray+SubarrayEnumerator.h"
NSArray *arr = ...;
for(NSArray *grp in [arr subarrayEnumeratorEach:10]) {
// do what you want.
}
NSArray+SubarrayEnumerator.h
#import <Foundation/Foundation.h>
#interface NSArray (SubarrayEnumerator)
- (NSEnumerator *)subarrayEnumeratorEach:(NSUInteger)perPage;
#end
NSArray+SubarrayEnumerator.m
#import "NSArray+SubarrayEnumerator.h"
#interface _NSArraySubarrayEnumeratorEach : NSEnumerator
#property (assign, nonatomic) NSUInteger cursor;
#property (assign, nonatomic) NSUInteger perPage;
#property (strong, nonatomic) NSArray *src;
#end
#implementation NSArray (SubarrayEnumerator)
- (NSEnumerator *)subarrayEnumeratorEach:(NSUInteger)perPage {
_NSArraySubarrayEnumeratorEach *enumerator = [[_NSArraySubarrayEnumeratorEach alloc] init];
enumerator.perPage = perPage;
enumerator.src = self;
return enumerator;
}
#end
#implementation _NSArraySubarrayEnumeratorEach
- (id)nextObject {
NSUInteger start = _cursor;
if(start >= _src.count) {
return nil;
}
NSUInteger count = MIN(_perPage, _src.count - start);
_cursor += _perPage;
return [_src subarrayWithRange:NSMakeRange(start, count)];
}
#end
I have to send a data by post in JSON format. I have my nsdictionary with keys and values.
NSDictionary *params_country=[NSDictionary dictionaryWithObjectsAndKeys:
#"1111",#"#id",
nil];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"dummy3", #"#name",
#"dummy3#example.com", #"#mail",
#"password",#"#password", params_country,#"country",
nil];
When i am doing a log
DLog(#"params %#",[params description]);
I am getting the following
params {
"#mail" = "dummy3#example.com";
"#name" = dummy3;
"#password" = password;
}
The problem is that i have to sent the JSON in the order that i have listed in the above initialisation of my nsdictionary but the keys are being sorted somehow.
Any solution?
EDIT
Sorry i am sending a nsdictionary also in the params. If i remove the country then its fine.
Dictionaries are an unordered collection type. If you need to maintain a certain order, then you should use an ordered collection type like NSArray. But for this, your web service shouldn't care about the order, since it should be looking up the values by the keys provided.
As per some of the comments, this requirement does not match a valid JSON object as the official JSON Specification states:
An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).
Unfortunately we don't live in a perfect world with perfect web services and there are often certain things that are out of our control.
I wrote a subclass of NSMutableDictionary after reading up on the internet that will order the dictionary based on the order you call setValue:forKey:.
I put the class into a gist you can download from here: https://gist.github.com/liamnichols/7869468 or you can just copy it from below:
LNOrderedMutableDictionary.h
#interface LNOrderedMutableDictionary : NSMutableDictionary
///If `anObject` is nil, it will not be added to the dictionary.
- (void)setNothingIfNil:(id)anObject forKey:(id)aKey;
#end
LNOrderedMutableDictionary.m
#import "LNOrderedMutableDictionary.h"
#interface LNOrderedMutableDictionary ()
#property (nonatomic, strong) NSMutableDictionary *dictionary;
#property (nonatomic, strong) NSMutableOrderedSet *array;
#end
#implementation LNOrderedMutableDictionary
- (id)initWithCapacity:(NSUInteger)capacity
{
self = [super init];
if (self != nil)
{
self.dictionary = [[NSMutableDictionary alloc] initWithCapacity:capacity];
self.array = [[NSMutableOrderedSet alloc] initWithCapacity:capacity];
}
return self;
}
- (id)init
{
self = [self initWithCapacity:0];
if (self)
{
}
return self;
}
- (void)setObject:(id)anObject forKey:(id)aKey
{
[self.array removeObject:aKey];
[self.array addObject:aKey];
[self.dictionary setObject:anObject forKey:aKey];
}
- (void)setNothingIfNil:(id)anObject forKey:(id)aKey
{
if (anObject != nil)
[self setObject:anObject forKey:aKey];
}
- (void)removeObjectForKey:(id)aKey
{
[self.dictionary removeObjectForKey:aKey];
[self.array removeObject:aKey];
}
- (NSUInteger)count
{
return [self.dictionary count];
}
- (id)objectForKey:(id)aKey
{
return [self.dictionary objectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator
{
return [self.array objectEnumerator];
}
#end
If possible, your web service shouldn't have to rely on the JSON objects to be formatted in a specific order but if there is nothing you can do to change this then the above solution is what you are looking for.
Source: cocoawithlove
I have the following code:
NSDictionary *dict = #[#{#"Country" : #"Afghanistan", #"Capital" : #"Kabul"},
#{#"Country" : #"Albania", #"Capital" : #"Tirana"}];
I want to list many countries and capitals, and then randomize i.e a country and put it on the screen, then the user should be able to pick the correct capital..
How to I put the Country? Like dict.Country[0] or something like that?
What is wrong with the code? I get the error "Initializer element is not a compile-time constant" and the warning "Incompatible pointer types initializing 'NSDictionary *_strong' with an expression of type 'NSArray *'.
Can I make a third String in the Dictionary, containing a flag file.. for example
#"Flagfile" : #"Albania.png"
and later put it in a image view?
I want like a loop with a random number I (for example) and put like (I know this is not right, but I hope you get the point)
loop..
....
text= dict.Country[I];
button.text= dict.Capital[I];
Imageview=dict.Flagfile[I];
.....
....
Your top level element is an NSArray (#[], with square brackets, makes an array) of two NSDictionary's. To access an attribute in one of the dictionaries, you would do array[index][key], e.g. array[0][#"Country"] would give you #"Afghanistan". If you did NSArray *array = ... instead of NSDictionary *dict = ...
If you want to pick a country at random, you can get a random number, get it mod 2 (someInteger % 2) and use that as your index, e.g. array[randomNumber % 2][#"Country"] will give you a random country name from your array of dictionaries.
If you store an image name in the dictionaries, you can load an image of that name using UIImage's +imageNamed: method.
Here's more complete instruction on mbuc91's correct idea.
1) create a country
// Country.h
#interface Country : NSObject
#property(strong,nonatomic) NSString *name;
#property(strong,nonatomic) NSString *capital;
#property(strong,nonatomic) NSString *flagUrl;
#property(strong,nonatomic) UIImage *flag;
// this is the only interesting part of this class, so try it out...
// asynchronously fetch the flag from a web url. the url must point to an image
- (void)flagWithCompletion:(void (^)(UIImage *))completion;
#end
// Country.m
#import "Country.h"
#implementation Country
- (id)initWithName:(NSString *)name capital:(NSString *)capital flagUrl:(NSString *)flagUrl {
self = [self init];
if (self) {
_name = name;
_capital = capital;
_flagUrl = flagUrl;
}
return self;
}
- (void)flagWithCompletion:(void (^)(UIImage *))completion {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.flagUrl]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
completion(image);
} else {
completion(nil);
}
}];
}
#end
2) Now, in some other class, use the Country
#import "Country.h"
- (NSArray *)countries {
NSMutableArray *answer = [NSMutableArray array];
[answer addObject:[[Country alloc]
initWithName:#"Afghanistan" capital:#"Kabul" flagUrl:#"http://www.flags.com/afgan.jpg"]];
[answer addObject:[[Country alloc]
initWithName:#"Albania" capital:#"Tirana" flagUrl:#"http://www.flags.com/albania.jpg"]];
return [NSArray arrayWithArray:answer];
}
- (id)randomElementIn:(NSArray *)array {
NSUInteger index = arc4random() % array.count;
return [array objectAtIndex:index];
}
-(void)someMethod {
NSArray *countries = [self countries];
Country *randomCountry = [self randomElementIn:countries];
[randomCountry flagWithCompletion:^(UIImage *flagImage) {
// update UI, like this ...
// self.flagImageView.image = flagImage;
}];
}
You cannot initialize an NSDictionary in that way. An NSDictionary is an unsorted set of key-object pairs - its order is not static, and so you cannot address it as you would an array. In your case, you probably want an NSMutableDictionary since you will be modifying its contents (see Apple's NSMutableDictionary Class Reference for more info).
You could implement your code in a few ways. Using NSDictionaries you would do something similar to the following:
NSMutableDictionary *dict = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:#"Afghanistan", #"Country",
#"Kabul", #"Capital", nil];
You would then have an array of dictionaries, with each dictionary holding the details of one country.
Another option would be to create a simple model class for each country and have an array of those. For example, you could create a Class named Country, with Country.h as:
#import <Foundation/Foundation.h>
#interface Country : NSObject
#property (nonatomic, retain) NSString *Name;
#property (nonatomic, retain) NSString *Capital;
//etc...
#end