Related
The documentation on NSSet objectEnumeration says:
When this method is used with mutable subclasses of NSSet, your code shouldn’t modify the set during enumeration. If you intend to modify the set, use the allObjects method to create a “snapshot” of the set’s members. Enumerate the snapshot, but make your modifications to the original set.
Now my question is: Is the allObjects method itself thread safe?
I have implemented an operation set like so:
#interface OperationSet : NSObject
#end
#implementation OperationSet
{
NSMutableSet *_set;
}
- (instancetype)init
{
self = [super init];
if (self)
{
_set = [[NSMutableSet alloc] init];
}
return self;
}
- (void)addOperation:(Operation *)operation
{
if (operation)
{
[_set addObject:operation];
}
}
- (void)removeOperation:(Operation *)operation
{
if (operation)
{
[_set removeObject:operation];
}
}
- (void)removeAllOperations
{
[_set removeAllObjects];
}
- (void)enumerateWithOperationBlock:(OperationBlock)block
{
NSArray *allObjects = [_set allObjects];
[allObjects enumerateObjectsUsingBlock:^(Operation *o, NSUInteger idx, BOOL *stop) {
block(o);
}];
}
- (void)flushCompletedOperations
{
NSArray *allObjects = [_set allObjects];
NSSet *safeSet = [NSSet setWithArray:allObjects];
NSSet *completed = [safeSet objectsPassingTest:^BOOL(Operation *o, BOOL *stop){
return o.completed;
}];
[_set minusSet:completed];
}
- (NSUInteger)count
{
return [_set count];
}
- (BOOL)any:(OperationAnyBlock)block
{
NSArray *allObjects = [_set allObjects];
NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
return block(o);
}];
return (index != NSNotFound);
}
- (Operation *)getOperationWithMatchingData:(NSDictionary *)data
{
NSArray *allObjects = [_set allObjects];
NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
return [o matchesData:data];
}];
return (index == NSNotFound ? nil : allObjects[index]);
}
#end
This all works fine.
But I have got a crash via Crashlytics, which is rare (two out of hundreds), but is there:
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x0000000000000008
Thread : Crashed: com.apple.main-thread
0 CoreFoundation 0x000000018772c438 -[__NSSetM addObject:] + 448
1 CoreFoundation 0x000000018772c430 -[__NSSetM addObject:] + 440
The OperationSet is accessed from multiple threads.
Any help is greatly appreciated.
EDIT
Thanks dasblinkenlight for enlighting the allObjects usage.
I have edited my implementation like so:
#interface OperationSet : NSObject
#end
#implementation OperationSet
{
NSMutableSet *_set;
dispatch_queue_t _queue;
}
- (instancetype)init
{
self = [super init];
if (self)
{
_set = [[NSMutableSet alloc] init];
_queue = dispatch_queue_create("OperationQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)addOperation:(Operation *)operation
{
if (operation)
{
dispatch_async(_queue, ^{
[_set addObject:operation];
});
}
}
- (void)removeOperation:(Operation *)operation
{
if (operation)
{
dispatch_async(_queue, ^{
[_set removeObject:operation];
});
}
}
- (void)removeAllOperations
{
dispatch_async(_queue, ^{
[_set removeAllObjects];
});
}
- (void)enumerateWithOperationBlock:(OperationBlock)block
{
__block NSArray *allObjects;
dispatch_sync(_queue, ^{
allObjects = [_set allObjects];
});
[allObjects enumerateObjectsUsingBlock:^(Operation *o, NSUInteger idx, BOOL *stop) {
block(o);
}];
}
- (void)flushCompletedOperations
{
__block NSArray *allObjects;
dispatch_sync(_queue, ^{
allObjects = [_set allObjects];
});
NSSet *safeSet = [NSSet setWithArray:allObjects];
NSSet *completed = [safeSet objectsPassingTest:^BOOL(Operation *o, BOOL *stop){
return o.completed;
}];
[_set minusSet:completed];
}
- (NSUInteger)count
{
return [_set count];
}
- (BOOL)any:(OperationAnyBlock)block
{
__block NSArray *allObjects;
dispatch_sync(_queue, ^{
allObjects = [_set allObjects];
});
NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
return block(o);
}];
return (index != NSNotFound);
}
- (Operation *)getOperationWithMatchingData:(NSDictionary *)data
{
__block NSArray *allObjects;
dispatch_sync(_queue, ^{
allObjects = [_set allObjects];
});
NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
return [o matchesData:data];
}];
return (index == NSNotFound ? nil : allObjects[index]);
}
#end
The code works! Which is a good sign, but can you please review it?
And there is another question: Is there any difference in using allObjects versus making a set copy?
That is using this code:
- (void)enumerateWithOperationBlock:(OperationBlock)block
{
__block NSArray *allObjects;
dispatch_sync(_queue, ^{
allObjects = [_set allObjects];
});
[allObjects enumerateObjectsUsingBlock:^(Operation *o, NSUInteger idx, BOOL *stop) {
block(o);
}];
}
over this code:
- (void)enumerateWithOperationBlock:(OperationBlock)block
{
__block NSSet *safeSet;
dispatch_sync(_queue, ^{
safeSet = [_set copy];
});
[safeSet enumerateObjectsUsingBlock:^(Operation *o, BOOL *stop) {
block(o);
}];
}
Thanks for your help.
NSMutableSet is not thread-safe. If you wish to access one from multiple threads, you must enforce one-at-a-time access yourself.
This is documented in “Thread Safety Summary” in the Threading Programming Guide.
The typical way to enforce one-at-a-time access is by creating one GCD queue (for each set) and accessing the set only from that queue (using dispatch_sync or, if possible, dispatch_async). In your example, you would add a dispatch_queue_t instance variable to your class, initialize it ininit, and use it in each of your other instance methods.
NSMutableSet is listed among the classes that are not thread-safe, so its methods should be considered non-thread safe unless explicitly documented otherwise (none of the NSMutableSet methods are documented as thread-safe at this time).
I think that by
use the allObjects method to create a “snapshot”
they meant creating a snapshot behind a lock, to avoid holding a lock on the entire set during the entire time that it takes you to enumerate its objects, and perform operations on them.
Your other question: [mySet allObjects] returns an NSArray containing all the objects in the set, while [mySet copy] returns an NSSet. If you don't need the properties of a set (very fast test for membership), an NSArray will probably be a bit quicker.
Hello I a class of type NSObject:
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
I want to pass the "details" object to a dictionary.
I did,
NSDictionary *dict = [NSDictionary dictionaryWithObject:details forKey:#"details"];
I am passing this dict to another method which performs a check on JSONSerialization:
if(![NSJSONSerialization isValidJSONObject:dict])
And I am getting a crash on this check. Am I doing anything wrong here? I know that the details I am getting is a JSON object and I am assigning it to the properties in my ProductDetails class.
Please help me. I am a noob in Objective-C.
I now tried:
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:(NSData*)details options:kNilOptions error:&error];
All I need here is an easy way to convert details to NSData.
I noticed that I have an array inside my object may be thats why all the ways I tried is throwing an exception. However since this question is becoming to big, I have started an another question thread for it where I have displayed the data I am getting inside the object - https://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary
This may well be the easiest way to achieve it. Do import #import <objc/runtime.h> in your class file.
#import <objc/runtime.h>
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
NSDictionary *dict = [self dictionaryWithPropertiesOfObject: details];
NSLog(#"%#", dict);
//Add this utility method in your class.
- (NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
NSDictionary *details = {#"name":product.name,#"color":product.color,#"quantity":#(product.quantity)};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:details
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
Second part's source: Generate JSON string from NSDictionary in iOS
As mmackh said, you want to define a custom method for your ProductDetails object that will return a simple NSDictionary of values, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity)};
}
...
Let's assume that we added manufacturer property to our ProductDetails, which referenced a ManufacturerDetails class. We'd just write a jsonObject for that class, too:
#implementation ManufacturerDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"address1" : self.address1,
#"address2" : self.address2,
#"city" : self.city,
...
#"phone" : self.phone};
}
...
And then change the jsonObject for ProductDetails to employ that, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity),
#"manufacturer" : [self.manufacturer jsonObject]};
}
...
If you have potentially nested collection objects (arrays and/or dictionaries) with custom objects that you want to encode, you could write a jsonObject method for each of those, too:
#interface NSDictionary (JsonObject)
- (id)jsonObject;
#end
#implementation NSDictionary (JsonObject)
- (id)jsonObject
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[dictionary setObject:[obj jsonObject] forKey:key];
else
[dictionary setObject:obj forKey:key];
}];
return [NSDictionary dictionaryWithDictionary:dictionary];
}
#end
#interface NSArray (JsonObject)
- (id)jsonObject;
#end
#implementation NSArray (JsonObject)
- (id)jsonObject
{
NSMutableArray *array = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[array addObject:[obj jsonObject]];
else
[array addObject:obj];
}];
return [NSArray arrayWithArray:array];
}
#end
If you do something like that, you can now convert arrays or dictionaries of your custom objects object into something that can be used for generating JSON:
NSArray *products = #[[[Product alloc] initWithName:#"Prius" color:#"Green" quantity:3],
[[Product alloc] initWithName:#"Accord" color:#"Black" quantity:1],
[[Product alloc] initWithName:#"Civic" color:#"Blue" quantity:2]];
id productsJsonObject = [products jsonObject];
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:productsJsonObject options:0 error:&error];
If you're simply trying to save these objects in a file, I'd suggest NSKeyedArchiver and NSKeyedUnarchiver. But if you need to generate JSON objects for your own private classes, you can do something like the above might work.
In .h File
#import <Foundation/Foundation.h>
#interface ContactDetail : NSObject
#property (nonatomic) NSString *firstName;
#property (nonatomic) NSString *lastName;
#property (nonatomic) NSString *fullName;
#property (nonatomic) NSMutableArray *mobileNumbers;
#property (nonatomic) NSMutableArray *Emails;
#property (assign) bool Isopen;
#property (assign) bool IsChecked;
-(NSDictionary *)dictionary;
#end
in .m file
#import "ContactDetail.h"
#import <objc/runtime.h>
#implementation ContactDetail
#synthesize firstName;
#synthesize lastName;
#synthesize fullName;
#synthesize mobileNumbers;
#synthesize Emails;
#synthesize IsChecked,Isopen;
//-(NSDictionary *)dictionary {
// return [NSDictionary dictionaryWithObjectsAndKeys:self.fullName,#"fullname",self.mobileNumbers,#"mobileNumbers",self.Emails,#"emails", nil];
//}
- (NSDictionary *)dictionary {
unsigned int count = 0;
NSMutableDictionary *dictionary = [NSMutableDictionary new];
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id value = [self valueForKey:key];
if (value == nil) {
// nothing todo
}
else if ([value isKindOfClass:[NSNumber class]]
|| [value isKindOfClass:[NSString class]]
|| [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSMutableArray class]]) {
// TODO: extend to other types
[dictionary setObject:value forKey:key];
}
else if ([value isKindOfClass:[NSObject class]]) {
[dictionary setObject:[value dictionary] forKey:key];
}
else {
NSLog(#"Invalid type for %# (%#)", NSStringFromClass([self class]), key);
}
}
free(properties);
return dictionary;
}
#end
if any crash ,You check the property (NSMutableArray,NSString,etc ) in else if condition inside of for.
In Your Controller, in any func...
-(void)addItemViewController:(ConatctViewController *)controller didFinishEnteringItem:(NSMutableArray *)SelectedContact
{
NSLog(#"%#",SelectedContact);
NSMutableArray *myData = [[NSMutableArray alloc] init];
for (ContactDetail *cont in SelectedContact) {
[myData addObject:[cont dictionary]];
}
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myData options:NSJSONWritingPrettyPrinted error:&error];
if ([jsonData length] > 0 &&
error == nil){
// NSLog(#"Successfully serialized the dictionary into data = %#", jsonData);
NSString *jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSLog(#"JSON String = %#", jsonString);
}
else if ([jsonData length] == 0 &&
error == nil){
NSLog(#"No data was returned after serialization.");
}
else if (error != nil){
NSLog(#"An error happened = %#", error);
}
}
Try this:
#import <objc/runtime.h>
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] ? [obj valueForKey:key] : #"" forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
The perfect way to do this is by using a library for serialization/deserialization
many libraries are available but one i like is
JagPropertyConverter
https://github.com/jagill/JAGPropertyConverter
it can convert your Custom object into NSDictionary and vice versa
even it support to convert dictionary or array or any custom object within your object (i.e Composition)
JAGPropertyConverter *converter = [[JAGPropertyConverter alloc]init];
converter.classesToConvert = [NSSet setWithObjects:[ProductDetails class], nil];
//For Object to Dictionary
NSDictionary *dictDetail = [converter convertToDictionary:detail];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:dictDetail options:NSJSONWritingPrettyPrinted error:&error];
You can convert object (say modelObject) to dictionary at runtime with the help of objc/runtime.h class but that has certain limitations and is not recommended.
Considering MVC, mapping logic should be implemented in Model class.
#interface ModelObject : NSObject
#property (nonatomic) NSString *p1;
#property (nonatomic) NSString *p2;
-(NSDictionary *)dictionary;
#end
#import "ModelObject.h"
#implementation ModelObject
-(NSDictionary *)dictionary
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:self.p1 forKey:#"p1"];// you can give different key name here if you want
[dict setValue:self.p2 forKey:#"p2" ];
return dict;
}
#end
Uses:
NSDictionary *modelObjDict = [modelObj dictionary];
Try using
NSDictionary *dict = [details valuesForAttributes:#[#"name", #"color"]];
And compare what the dictionary contains. Then try to convert it to JSON. And look at the JSON spec - what data types can go into a JSON encoded file?
You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping
It's a quit easy. Just describe the mapping rules in your class:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
#interface MyCustomClass : NSObject
#property (nonatomic, strong) NSNumber * someNumber;
#property (nonatomic, strong) NSString * someString;
#end
#implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = #{ #"someNumber": #"some_number",
#"someString": #"some_string" };
}
return mapping
}
#end
And then you can easily map your object to dictionary:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = #1;
myObj.someString = #"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Also you can parse your object from dictionary:
NSDictionary * myDict = #{ #"some_number": #123,
#"some_string": #"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];
Swift
Now the swift is very popular and most of the SDK's are written in Objective C, we need to convert NSObject to NSDictionary, With the Help of #thatzprem Answer, I wrote an extension for Swift which will convert our NSObject into NSDictionary, then we can use that NSDictionary to simple Dictionary or JSON Object or other purpose. I hope so this will help out the Swift User.
extension NSObject {
func convertNSObjectToNSDictionary() -> [AnyHashable : Any]? {
var dict: [AnyHashable : Any] = [:]
var count: UInt32 = 0
let properties = class_copyPropertyList(type(of: self), UnsafeMutablePointer<UInt32>(mutating: &count)) //as? objc_property_t
for i in 0..<Int(count) {
var key: String? = nil
if let property = properties?[i] as? objc_property_t {
key = String(utf8String: property_getName(property))
}
//dict[key] = (obj as? NSObject)?.value(forKey: key ?? "")
dict[key] = (self).value(forKey: key ?? "")
}
free(properties)
return dict
}
}
I'd like to get all tracks from an album on iTunes using iTunes.h.
Right now I get the data of the current track by:
NSInteger trackID = iTunes.currentTrack.databaseID;
NSString *name = iTunes.currentTrack.name;
And the name of the album by:
NSString *trackAlbum = iTunes.currentTrack.album;
But know I don't know how to get all the tracks that are in the same album as the current track.
Any ideas? Thanks
The iTunes API is poorly written.
You have to filter the array with a predicate.
NSArray *allSongs = [self allSongs];
NSArray *songsOfAlbum = [allSongs filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"album == %# && artist == %#", albumString, artistString]];
You can get all the songs like this:
// Get all Songs
- (NSArray *)allSongs {
if (_allSongs == nil) {
NSArray *tracksToPlay = [(SBElementArray *)[self.library tracks] get];
// Sort by artist
_allSongs = tracksToPlay;
}
return _allSongs;
}
- (iTunesLibraryPlaylist *)library {
if (_library == nil) {
// Whole Library
iTunesSource *source = [[[[self.iTunes sources] get] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"kind == %i", iTunesESrcLibrary]] objectAtIndex:0];
// Only the Music
_library = [[[[source playlists] get] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"specialKind == %i", iTunesESpKMusic]] objectAtIndex:0];
}
return _library;
}
You can use iTunes Library Framework (10.9 osx) for iTunes 11.
#import <iTunesLibrary/ITLibrary.h>
#import <iTunesLibrary/ITLibMediaItem.h>
#import <iTunesLibrary/ITLibAlbum.h>
NSError *error = nil;
ITLibrary *library = [ITLibrary libraryWithAPIVersion:#"1.0" error:&error];
if (library)
{
NSArray *tracks = library.allMediaItems; // <- NSArray of ITLibMediaItem
}
for (ITLibMediaItem *item in tracks) {
NSLog(#"tracks %#",item.album.title);
}
You can fetch album information from ITLibMediaItem
As an example to retrieve the artworks I have submitted a github repository:
It is an objective c code which uses the above code changed:
#import <Foundation/Foundation.h>
#import <iTunesLibrary/ITLibrary.h>
#import <iTunesLibrary/ITLibMediaItem.h>
#import <iTunesLibrary/ITLibArtwork.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSError *error = nil;
ITLibrary *library = [ITLibrary libraryWithAPIVersion:#"1.0" error:&error];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (library)
{
//NSArray *playlists = library.allPlaylists; // <- NSArray of ITLibPlaylist
NSArray *tracks = library.allMediaItems; // <- NSArray of ITLibMediaItem
[tracks enumerateObjectsUsingBlock:^(ITLibMediaItem* _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.artworkAvailable) {
ITLibArtwork* artWork=obj.artwork;
//NSData* d=artWork.imageData;
switch (artWork.imageDataFormat) {
case ITLibArtworkFormatPNG:{
NSURL* fileURL=obj.location;
NSString* fs=fileURL.lastPathComponent;
NSString* path=[fileURL.path stringByReplacingOccurrencesOfString:fs withString:#"Folder.png"];
if (![fileManager fileExistsAtPath:path]){
[artWork.imageData writeToFile:path atomically:YES];
}
}
break;
case ITLibArtworkFormatJPEG:{
NSURL* fileURL=obj.location;
NSString* fs=fileURL.lastPathComponent;
NSString* path=[fileURL.path stringByReplacingOccurrencesOfString:fs withString:#"Folder.jpg"];
if (![fileManager fileExistsAtPath:path]){
[artWork.imageData writeToFile:path atomically:YES];
}
}
break;
default:
break;
}
}
}];
NSLog(#"End reached");
}
}
return 0;
}
https://github.com/ENees/iTunesGetImages
Thanks to a lot of help I've received here on SO, I've gotten an algorithm to check a list of around 15,000 8-letter words for any partial anagrams, against a list of around 50,000 total words (so I suppose a total of 108 million iterations). I call this method once for each comparison (so 750 million times). I'm getting the following error, always somewhere in the midst of the 119th iteration through the 1,350 there should be:
AnagramFINAL(2960,0xac8c7a28) malloc: *** mmap(size=2097152) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
I've narrowed the memory issue down to being a huge number of allocated CFStrings (immutable). Any idea what I can do to remedy the issue? I'm using ARC and an #autoreleasepool, not sure what else I could do, it seems something isn't being released when it should be.
AnagramDetector.h
#import <Foundation/Foundation.h>
#interface AnagramDetector : NSObject {
NSDictionary *allEightLetterWords;
NSDictionary *allWords;
NSFileManager *fileManager;
NSArray *paths;
NSString *documentsDirectory;
NSString *filePath;
}
- (BOOL) does: (NSString *) longWord contain: (NSString *) shortWord;
- (NSDictionary *) setupAllWordList;
- (NSDictionary *) setupEightLetterWordList;
- (void) saveDictionary: (NSMutableDictionary *)currentArray;
#end
AnagramDetector.m
#implementation AnagramDetector
- (id) init {
self = [super init];
if (self) {
fileManager = [NSFileManager defaultManager];
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
}
return self;
}
- (BOOL) does: (NSString *) longWord contain: (NSString *) shortWord {
#autoreleasepool {
NSMutableString *longerWord = [longWord mutableCopy];
for (int i = 0; i < [shortWord length]; i++) {
NSString *letter = [shortWord substringWithRange: NSMakeRange(i, 1)];
NSRange letterRange = [longerWord rangeOfString: letter];
if (letterRange.location != NSNotFound) {
[longerWord deleteCharactersInRange: letterRange];
} else {
return NO;
}
}
return YES;
}
}
- (NSDictionary *) setupAllWordList {
#autoreleasepool {
NSString *fileWithAllWords = [[NSBundle mainBundle] pathForResource:#"AllDefinedWords" ofType:#"plist"];
allWords = [[NSDictionary alloc] initWithContentsOfFile: fileWithAllWords];
NSLog(#"Total number of words: %d.", [allWords count]);
}
return allWords;
}
- (NSDictionary *) setupEightLetterWordList {
#autoreleasepool {
NSString *fileWithEightWords = [[NSBundle mainBundle] pathForResource:#"AllDefinedEights" ofType:#"plist"];
allEightLetterWords = [[NSDictionary alloc] initWithContentsOfFile: fileWithEightWords];
NSLog(#"Total number of words: %d.", [allEightLetterWords count]);
}
return allEightLetterWords;
}
- (void) saveDictionary: (NSMutableDictionary *)currentArray {
#autoreleasepool {
filePath = [documentsDirectory stringByAppendingPathComponent: #"A.plist"];
[fileManager createFileAtPath:filePath contents: nil attributes: nil];
[currentArray writeToFile: filePath atomically:YES];
[currentArray removeAllObjects];
}
}
#end
Code running on launch (inside AppDelegate for now, since no VC):
#autoreleasepool {
AnagramDetector *detector = [[AnagramDetector alloc] init];
NSDictionary *allWords = [[NSDictionary alloc] initWithDictionary:[detector setupAllWordList]];
NSDictionary *eightWords = [[NSDictionary alloc] initWithDictionary:[detector setupEightLetterWordList]];
int remaining = [eightWords count];
for (NSString *currentEightWord in eightWords) {
if (remaining % 10 == 0) NSLog(#"%d ::: REMAINING :::", remaining);
for (NSString *currentAllWord in allWords) {
if ([detector does: [eightWords objectForKey: currentEightWord] contain: [allWords objectForKey: currentAllWord]]) {
// NSLog(#"%# ::: CONTAINS ::: %#", [eightWords objectForKey: currentEightWord], [allWords objectForKey: currentAllWord]);
}
}
remaining--;
}
}
The problem seems to be that a lot of autoreleased objects fill up the memory waiting to be released. So a solution is to add your own autorelease pool scope to collect autoreleased objects and release them sooner.
I suggest that you do something like this:
for (NSString *currentEightLetterWord in [eightLetterWordsDictionary allKeys]) {
#autoreleasepool {
for (NSString *currentWord in [allWordsDictionary allKeys]) {
}
}
}
Now all autoreleased objects inside #autoreleasepool { .. } will be released for each iteration of the outer loop.
As you see ARC might save you from thinking about most reference counting and memory management issues but objects can still end up in autorelease pools with ARC when using methods that directly or indirectly create autoreleased objects.
An alternative solution that I don't really recommend is to try to avoid using method that will use autorelease. Then does:contain: could awkwardly be rewritten to something like this:
- (BOOL) does: (NSString* ) longWord contain: (NSString *) shortWord {
NSMutableString *haystack = [longWord mutableCopy];
NSMutableString *needle = [shortWord mutableCopy];
while([haystack length] > 0 && [needle length] > 0) {
NSMutableCharacterSet *set = [[NSMutableCharacterSet alloc] init];
[set addCharactersInRange:NSMakeRange([needle characterAtIndex:0], 1)];
if ([haystack rangeOfCharacterFromSet:set].location == NSNotFound) return NO;
haystack = [haystack mutableCopy];
[haystack deleteCharactersInRange:NSMakeRange(0, [haystack rangeOfCharacterFromSet: set].location)];
needle = [needle mutableCopy];
[needle deleteCharactersInRange:NSMakeRange(0, 1)];
}
return YES;
}
I need to merge two NSDictionarys into one provided that if there are dictionaries within the dictionaries, they are also merged.
More or less like jQuery's extend function.
NSDictionary+Merge.h
#import <Foundation/Foundation.h>
#interface NSDictionary (Merge)
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2;
- (NSDictionary *) dictionaryByMergingWith: (NSDictionary *) dict;
#end
NSDictionary+Merge.m
#import "NSDictionary+Merge.h"
#implementation NSDictionary (Merge)
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2 {
NSMutableDictionary * result = [NSMutableDictionary dictionaryWithDictionary:dict1];
[dict2 enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
if (![dict1 objectForKey:key]) {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict1 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
} else {
[result setObject: obj forKey: key];
}
}
}];
return (NSDictionary *) [[result mutableCopy] autorelease];
}
- (NSDictionary *) dictionaryByMergingWith: (NSDictionary *) dict {
return [[self class] dictionaryByMerging: self with: dict];
}
#end
I think this is what you're looking for:
First, you need to make a deep mutable copy, so you can create a category on NSDictionary to do this:
#implementation NSDictionary (DeepCopy)
- (id)deepMutableCopy
{
id copy(id obj) {
id temp = [obj mutableCopy];
if ([temp isKindOfClass:[NSArray class]]) {
for (int i = 0 ; i < [temp count]; i++) {
id copied = [copy([temp objectAtIndex:i]) autorelease];
[temp replaceObjectAtIndex:i withObject:copied];
}
} else if ([temp isKindOfClass:[NSDictionary class]]) {
NSEnumerator *enumerator = [temp keyEnumerator];
NSString *nextKey;
while (nextKey = [enumerator nextObject])
[temp setObject:[copy([temp objectForKey:nextKey]) autorelease]
forKey:nextKey];
}
return temp;
}
return (copy(self));
}
#end
Then, you can call deepMutableCopy like this:
NSMutableDictionary *someDictionary = [someDict deepMutableCopy];
[someDictionary addEntriesFromDictionary:otherDictionary];
I added this to the code mentioned above. It may not be fully correct, but it handles the case where 2 dict has an element that 1 dict does not.
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2 {
NSMutableDictionary * result = [NSMutableDictionary dictionaryWithDictionary:dict1];
NSMutableDictionary * resultTemp = [NSMutableDictionary dictionaryWithDictionary:dict1];
[resultTemp addEntriesFromDictionary:dict2];
[resultTemp enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
if ([dict1 objectForKey:key]) {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict1 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
} else {
[result setObject: obj forKey: key];
}
}
else if([dict2 objectForKey:key])
{
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict2 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
} else {
[result setObject: obj forKey: key];
}
}
}];
return (NSDictionary *) [[result mutableCopy] autorelease];
}
I came here looking for a copy of jQuery's extend but I ended up writing my own implementation. It's a super simple implementation, I did it so I'd understand a way to do it.
+(NSDictionary*) dictionaryByExtending:(NSDictionary*)baseDictionary WithDictionary:(NSDictionary*)extensionDictionary {
NSMutableDictionary * resultDictionary = [NSMutableDictionary dictionaryWithDictionary:baseDictionary];
[extensionDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
BOOL isDict = [obj isKindOfClass:[NSDictionary class]];
BOOL hasValue = [baseDictionary hasObjectForKey:key] != nil;
id setObj = obj;
if( hasValue && isDict ) {
BOOL hasDict = [[baseDictionary objectForKey:key] isKindOfClass:[NSDictionary class]];
if( hasDict ) {
NSDictionary * extendedChildDictionary = [NSDictionary dictionaryByExtending:[baseDictionary objectForKey:key] WithDictionary:obj];
setObj = extendedChildDictionary;
}
}
[resultDictionary setObject:setObj forKey:key];
}];
return resultDictionary;
}
-(NSDictionary*) dictionaryByExtendingWithDictionary:(NSDictionary*)extensionDictionary {
return [NSDictionary dictionaryByExtending:self WithDictionary:extensionDictionary];
}
Hopefully someone will find this helpful, it worked in my tests with deep-recursion. I'm using it to extend deep JSON files full of text.
Alexsander Akers works for me except the case where dict2 contains a dictionary that's missing from dict1 - it crashes. I changed the logic to this:
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2 {
NSMutableDictionary * result = [NSMutableDictionary dictionaryWithDictionary:dict1];
[dict2 enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
if (![dict1 objectForKey:key]) {
[result setObject: obj forKey: key];
} else if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict1 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
}
}];
return (NSDictionary *) [result mutableCopy];
}
I know this is an old question, but I need to do the same thing: recursively merge two dictionary objects. I need to go a step further and merge any objects that can be merged recursively (the end goal is merging two dictionaries created from plists). I am hosting my solution at https://github.com/bumboarder6/NSDictionary-merge
I am still working on the project, but as of this writing it already works (in limited testing) for recursive dictionary merging. Arrays and Sets are coming soon.
I noticed a few logic errors in some other solutions I have seen for this problem and I hopefully avoided those pitfalls, but critiques are welcome.
Usage is simple:
#import "NSMutableDictionary-merge.h"
NSMutableDictionary* dict1 = [NSMutableDictionary ...];
NSDictionary* dict2 = [NSDictionary ...];
[dict1 mergeWithDictionary:dict2];
#import "NSDictionary+Merge.h"
#implementation NSDictionary (Merge)
+ (NSDictionary *)dictionaryByMerging:(NSDictionary *)src with:(NSDictionary *)new
{
NSMutableDictionary *result = [src mutableCopy];
[new enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSDictionary class]]
&& [src[key] isKindOfClass:[NSDictionary class]]) {
result[key] = [src[key] dictionaryByMergingWith:obj];
} else {
result[key] = obj;
}
}];
return [NSDictionary dictionaryWithDictionary:result];
}
- (NSDictionary *)dictionaryByMergingWith:(NSDictionary *)dict {
return [[self class] dictionaryByMerging:self with:dict];
}
#end
I needed a way to recursively merge (append) objects within two JSON objects, focusing on the NSDictionaries within, but also considering NSArrays, and gracefully handling when types don't match along the way. The other answers here didn't go that far, and so I needed to write it myself. The following handles all those cases. Because the validation is at the top rather than in the middle it is usable starting with mixed nonnull and nullable objects. It could be expanded in the future to support additional types where appending may apply. To use, rename the xxx_ prefix to your own three digit prefix in lowercase. This is appropriate since this is an extension to a foundation class:
NSObject+Append.h
#interface NSObject (Append)
+ (nullable id)xxx_objectAppendingObject1:(nullable id)object1 object2:(nullable id)object2 NS_SWIFT_NAME(kva_objectAppending(object1:object2:));
#end
NSObject+Append.m
#implementation NSObject (Append)
+ (nullable id)xxx_objectAppendingObject1:(nullable id)object1 object2:(nullable id)object2
{
// VALIDATE ELSE RETURN
if (object1 == nil)
{
return object2;
}
if (object2 == nil)
{
return object1;
}
// MAIN
// dictionary1
NSDictionary *dictionary1 = [object1 isKindOfClass:NSDictionary.class] ? (NSDictionary *)object1 : nil;
// dictionary2
NSDictionary *dictionary2 = [object2 isKindOfClass:NSDictionary.class] ? (NSDictionary *)object2 : nil;
// array1
NSArray *array1 = [object1 isKindOfClass:NSArray.class] ? (NSArray *)object1 : nil;
// array2
NSArray *array2 = [object2 isKindOfClass:NSArray.class] ? (NSArray *)object2 : nil;
// A. NSDICTIONARY TO NSDICTIONARY
if ((dictionary1 != nil) && (dictionary2 != nil))
{
NSMutableDictionary *returnDictionary = dictionary1.mutableCopy;
[dictionary2 enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
{
returnDictionary[key] = [self.class kva_objectAppendingObject1:dictionary1[key] object2:obj];
}];
return returnDictionary;
}
// B. NSARRAY TO NSARRAY
if ((array1 != nil) && (array2 != nil))
{
return [array1.mutableCopy arrayByAddingObjectsFromArray:array2];
}
// DEFAULT
return object2;
}
#end