ARC lead to crash CoreFoundation object - ios

I have a part of code which sometimes crashes on OSX system on client side but I can't reproduce the crash. I have only few assumptions but I don't want to make blind fix. I just want to be convinced with the fix:
+ (NSArray *)loginItems:(NSError *__autoreleasing *)error {
LSSharedFileListRef list = [self loginItemsFileListRef];
NSMutableArray *result = [NSMutableArray array];
NSArray *items = [self loginItemsForFileListRef:list];
CFRelease(list);
for (id item in items) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
[result addObject:[self loginItemFromItemRef:itemRef]];
}
return result;
}
+ (NSDictionary *)loginItemFromItemRef:(LSSharedFileListItemRef)itemRef {
NSMutableDictionary *itemDict = [NSMutableDictionary dictionary];
// Name
CFStringRef nameRef = LSSharedFileListItemCopyDisplayName(itemRef);
if (nameRef) {
NSString *name = (__bridge NSString *)nameRef;
itemDict[RESLoginItemsNameKey] = name.stringByDeletingPathExtension;
CFRelease(nameRef);
}
// Path
CFURLRef URLRef = NULL;
if (LSSharedFileListItemResolve(itemRef, 0, &URLRef, NULL) == noErr) {
if (URLRef) {
NSURL *URL = (__bridge NSURL *)URLRef;
NSString *path = URL.path;
if (path) {
itemDict[RESLoginItemsPathKey] = path;
}
CFRelease(URLRef);
}
}
// Hidden
CFBooleanRef hiddenRef = LSSharedFileListItemCopyProperty(itemRef,
kLSSharedFileListLoginItemHidden);
if (hiddenRef) {
if (hiddenRef == kCFBooleanTrue) {
itemDict[RESLoginItemsHiddenKey] = #YES;
}
CFRelease(hiddenRef);
}
return itemDict;
}
The crash inside loginItems: namely SharedFileListItemDeallocate cause the crash. When NSArray's items auto-released. I suppose that:
SSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
breaks ARC behavior and when loop finished it lead to release concrete item of the array. And second release will be called on function return which will lead to the crash.
Has anyone can prove or disprove my idea? Thanks.

I think the problem is that you are releasing list before using the items returned by
[self loginItemsForFileListRef:list]
If the items returned depend on list being valid, because the release of list might also trigger the release of the items, then you might crash when you try to iterate the returned items. Try to move the release of list after the for-loop that iterates the items. Sometimes you get bitten if you try to be overzealous about memory usage.

Related

Not receiving notification dispatch_group_notify

I am currently using a dispatch_group to get notify when all concurrent tasks are done. I am offloading some heavy tasks on one concurrent queue within the [TWReaderDocument documentFileURL:url withCompletionBlock:] class method.
I have implemented the following code but never received any notification. I don't see what i am potentially doing wrong in the below code:
dispatch_group_t readingGroup = dispatch_group_create();
NSFileManager* manager = [NSFileManager defaultManager];
NSString *docsDir = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Data"];
NSDirectoryEnumerator *dirEnumerator = [manager enumeratorAtURL:[NSURL fileURLWithPath:docsDir]
includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey,
NSURLIsDirectoryKey,nil]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:nil];
// An array to store the all the enumerated file names in
NSMutableArray *arrayFiles;
// Enumerate the dirEnumerator results, each value is stored in allURLs
for (NSURL *url in dirEnumerator) {
// Retrieve the file name. From NSURLNameKey, cached during the enumeration.
NSString *fileName;
[url getResourceValue:&fileName forKey:NSURLNameKey error:NULL];
// Retrieve whether a directory. From NSURLIsDirectoryKey, also cached during the enumeration.
NSNumber *isDirectory;
[url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if (![isDirectory boolValue]) {
dispatch_group_enter(readingGroup);
TWReaderDocument* doc = [TWReaderDocument documentFileURL:url withCompletionBlock:^(BOOL success) {
dispatch_group_leave(readingGroup);
}];
[arrayFiles addObject:doc];
}
else if ([[[fileName componentsSeparatedByString:#"_" ] objectAtIndex:0] isEqualToString:#"XXXXXX"]) {
TreeItem* treeItem = [[TreeItem alloc] init];
arrayFiles = [NSMutableArray arrayWithCapacity:10];
treeItem.child = arrayFiles;
treeItem.nodeName = [[fileName componentsSeparatedByString:#"_" ] lastObject];
[self addItem:treeItem];
}
}
dispatch_group_notify(readingGroup, dispatch_get_main_queue(), ^{ // 4
NSLog(#"All concurrent tasks completed");
});
Does the dispatch_group_enter and dispatch_group_leave have to be executed on the same thread?
EDIT
The code snippet of my factory method might help aswell:
+ (TWReaderDocument *)documentFileURL:(NSURL *)url withCompletionBlock:(readingCompletionBlock)completionBlock{
TWReaderDocument * twDoc = [[TWReaderDocument alloc] init];
twDoc.status = ReaderDocCreated;
twDoc.doc = [ReaderDocument withDocumentFilePath:[url path] withURL:url withLoadingCompletionBLock:^(BOOL completed) {
twDoc.status = completed ? ReaderDocReady : ReaderDocFailed;
completionBlock(completed);
}];
return twDoc;
}
TWReaderDocument is a wrapper class that call internally the following methods of a third-party library (it is a PDF reader)
+ (ReaderDocument *)withDocumentFilePath:(NSString *)filePath withURL:(NSURL*)url withLoadingCompletionBLock:(readingCompletionBlock)completionBlock{
ReaderDocument *document = [[ReaderDocument alloc] initWithFilePath:filePath withURL:url withLoadingCompletionBLock:[completionBlock copy]];
return document;
}
- (id)initWithFilePath:(NSString *)fullFilePath withURL:(NSURL*)url withLoadingCompletionBLock:(readingCompletionBlock)completionBlock {
id object = nil; // ReaderDocument object;
if ([ReaderDocument isPDF:fullFilePath] == YES) // File must exist
{
if ((self = [super init])) // Initialize superclass object first
{
_fileName = [ReaderDocument relativeApplicationFilePath:fullFilePath]; // File name
dispatch_async([ReaderDocument concurrentLoadingQueue], ^{
self.guid = [ReaderDocument GUID]; // Create a document GUID
self.password = nil; // Keep copy of any document password
self.bookmarks = [NSMutableIndexSet indexSet]; // Bookmarked pages index set
self.pageNumber = [NSNumber numberWithInteger:1]; // Start on page 1
CFURLRef docURLRef = (__bridge CFURLRef)url;// CFURLRef from NSURL
self.fileURL = url;
CGPDFDocumentRef thePDFDocRef = CGPDFDocumentCreateX(docURLRef, self.password);
BOOL success;
if (thePDFDocRef != NULL) // Get the number of pages in the document
{
NSInteger pageCount = CGPDFDocumentGetNumberOfPages(thePDFDocRef);
self.pageCount = [NSNumber numberWithInteger:pageCount];
CGPDFDocumentRelease(thePDFDocRef); // Cleanup
success = YES;
}
else // Cupertino, we have a problem with the document
{
// NSAssert(NO, #"CGPDFDocumentRef == NULL");
success = NO;
}
NSFileManager *fileManager = [NSFileManager new]; // File manager instance
self.lastOpen = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0]; // Last opened
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:fullFilePath error:NULL];
self.fileDate = [fileAttributes objectForKey:NSFileModificationDate]; // File date
self.fileSize = [fileAttributes objectForKey:NSFileSize]; // File size (bytes)
completionBlock(success);
});
//[self saveReaderDocument]; // Save the ReaderDocument object
object = self; // Return initialized ReaderDocument object
}
}
return object;
}
It's hard to say what's going on here without knowing more about TWReaderDocument, but I have a suspicion...
First off, no, dispatch_group_enter and dispatch_group_leave do not have to be executed on the same thread. Definitely not.
My best guess based on the info here would be that for some input, [TWReaderDocument documentFileURL:withCompletionBlock:] is returning nil. You might try this instead:
if (![isDirectory boolValue]) {
dispatch_group_enter(readingGroup);
TWReaderDocument* doc = [TWReaderDocument documentFileURL:url withCompletionBlock:^(BOOL success) {
dispatch_group_leave(readingGroup);
}];
// If the doc wasn't created, leave might never be called.
if (nil == doc) {
dispatch_group_leave(readingGroup);
}
[arrayFiles addObject:doc];
}
Give that a try.
EDIT:
It's exactly as I expected. There are cases in which this factory method will not call the completion. For instance:
if ([ReaderDocument isPDF:fullFilePath] == YES) // File must exist
If -isPDF: returns NO the completionBlock will never be called, and the returned value will be nil.
Incidentally, you should never compare something == YES. (anything non-zero is equivalent to YES, but YES is defined as 1. Just do if ([ReaderDocument isPDF:fullFilePath]). It's equivalent, and safer.

Potential leaks in iOS project

Hello i have this function, that gets dummy data from file in project:
Issues are shown in those lines:
NSString *path = [[NSBundle mainBundle] pathForResource: #"StatisticsDataJSON" ofType: #"TXT"]; - potential leak of an object.
NSMutableDictionary *statisticsResponse = [jsonParser objectWithString:data]; - potential leak of an object stored into 'jsonParser'
for (id key in statisticsResponse) { - potential leak of an object stored into 'statisticArray'
for (int i = 0; i < statsForDate.count; i++) { - potential leak of an object stored into 's'
if (self.statistics==nil)
{
self.statistics = [[NSMutableDictionary alloc]init];
NSString *path = [[NSBundle mainBundle] pathForResource: #"StatisticsDataJSON" ofType: #"TXT"];
NSError *error = nil;
NSString *data = [NSString stringWithContentsOfFile: path
encoding: NSUTF8StringEncoding
error: &error];
//NSLog(#"%#",data);
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *statisticsResponse = [jsonParser objectWithString:data];
for (id key in statisticsResponse) {
NSArray *statsForDate = [statisticsResponse objectForKey:key];
NSMutableArray *statisticArray = [[NSMutableArray alloc]init];
for (int i = 0; i < statsForDate.count; i++) {
Statistic *s = [[Statistic alloc]init];
s.locationId = [[statsForDate objectAtIndex:i] objectForKey:#"locationId"];
int value =[[[statsForDate objectAtIndex:i] objectForKey:#"visits"] integerValue];
s.visits = value;
value =[[[statsForDate objectAtIndex:i] objectForKey:#"totalUsers"] integerValue];
s.totalUsers = value;
value= [[[statsForDate objectAtIndex:i] objectForKey:#"uploads"] integerValue];
s.uploads = value;
value = [[[statsForDate objectAtIndex:i] objectForKey:#"downloads"] integerValue];
s.downloads = value;
value = [[[statsForDate objectAtIndex:i] objectForKey:#"apps"] integerValue];
s.apps = value;
[statisticArray addObject:s];
}
[self.statistics setObject:statisticArray forKey:key];
};
}
I have found that autorelease in ststisticsResponse - resolves the problem:
NSMutableDictionary *statisticsResponse = [[jsonParser objectWithString:data]autorelease];
But then something fails in SBJsonStreamParserAccumulator.m in dealoc function.
What is the problem?
Note that the warnings about potential leaks come on the line after the potential leak, because that's the first point at which the referenced object is technically "leaked". So your current fixes are probably over-releasing and causing crashes.
The first statement in your question actually refers to a leak in this line, immediately before:
self.statistics = [[NSMutableDictionary alloc]init];
You've got no further reference to that allocated dictionary, and it's a retained property, so you have a leak.
self.statistics = [[[NSMutableDictionary alloc]init] autorelease];
Will fix that one. The next one, you have to release the jsonParser when you've finished with it (after the parsing is done):
[jsonParser release];
I'm not going to go through all of them but you should get the idea. Basically you need to read the memory management guide, or update to ARC.
Pay attention to the variable names in the warnings. They tell you where the leak is.

TouchXML CXMLNode.m unrecognized selector when adding to NSMutableArray

I'm new to Objective C and XML, so this is going to look pretty rough.
My code is as follows:
#import "dbCommunicator.h"
#import "BookInfo.h"
#import "TouchXML.h"
#implementation dbCommunicator
-(void)getNextBooks {
if(self.bookListing == nil) {
for(int i = 0; i < 5; i++) {
BookInfo *newBook = [[BookInfo alloc] init];
[self.bookListing addObject:newBook];
}
self.currentPage = 0;
}
if(self.currentPage == 10) {
self.currentPage = 0;
}
NSString *test = #"<Authors><Book.Author><Id>1026</Id><Name>Mark Twain</Name></Book.Author></Authors>";
NSString *bookQueryString = [NSString stringWithFormat:#"http://bookworm.azurewebsites.net/api/book/list/5/%d",_currentPage];
NSURL *bookQueryURL = [NSURL URLWithString: bookQueryString];
self.currentPage++;
NSError *theError = NULL;
NSDictionary *mappings = [NSDictionary dictionaryWithObjectsAndKeys:
#"http://schemas.datacontract.org/2004/07/Bookworm.Models.ResponseModels",
#"datacontract",
nil];
CXMLDocument *xmlReturn = [[CXMLDocument alloc] initWithXMLString:test options:0 error:&theError];
NSArray *returnedBooks = [xmlReturn nodesForXPath:#"//Authors" error:&theError];
for(CXMLElement *resultElement in returnedBooks) {
NSLog(#"%s", "We actually got here");
}
}
There's a lot of junk code in there at the moment. The intention is to pull an XML file from a database and put its information into an array of BookInfo classes. For the moment, I simplified by just using a test XMLstring to ensure it wasn't an issue with what the database was sending me. This makes the dictionary (to deal with the namespace issues TouchXML has) superfluous. Anyways.
It always crashes with a unrecognized selector error on this line:
[theArray addObject:[CXMLNode nodeWithLibXMLNode:theNode freeOnDealloc:NO]];
In this context:
if (xmlXPathNodeSetIsEmpty(theXPathObject->nodesetval))
theResult = [NSArray array]; // TODO better to return NULL?
else {
NSMutableArray *theArray = [NSMutableArray array];
int N;
for (N = 0; N < theXPathObject->nodesetval->nodeNr; N++) {
xmlNodePtr theNode = theXPathObject->nodesetval->nodeTab[N];
[theArray addObject:[CXMLNode nodeWithLibXMLNode:theNode freeOnDealloc:NO]];
}
}
and with that, I'm totally at a loss. I've tried plenty of things, scoured every StackOverflow post even closely related and tried their fixes, nothing's working. Any suggestions?
array is a static method on NSArray a super class of NSMutableArray that returns an NSArray object. NSArray does not respond to the method addObject:
Try instead NSMutableArray *theArray = [NSMutableArray new];
The problem probably is on this selector:
nodeWithLibXMLNode:freeOnDealloc:
if you try to see your crash log in the console, should be written the selector unrecognized and should be this. If so check the class reference of the CXMLNode.
I checked now, and that method doesn't exists. Just a method might be useful for you in some way, that is:
- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error;
Enjoy ;)

iOS - App crash without error during loop process

during a loop process, my App crash without error. The array count is equal to 175260. With profiler I don't have leaks, so I don't know why the App exit, maybe the CPU usage 100% during a lot of time?
Thank you for your help.
Just this code following crash the App :
for(unsigned int i = 0; i <14;i++)
{
if(findSensor[i]==YES)
{
for(unsigned int j = 1; j <[array count];j++)
{
#autoreleasepool {
if([[[[array objectAtIndex:j] componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
//Code here
}
}
}
}
}
The full code is :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = [NSString stringWithFormat:#"%#/%#",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase];
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:fileName];
NSFileHandle *output = [NSFileHandle fileHandleForReadingAtPath:[NSString stringWithFormat:#"%#/%#10",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase]];
if(output == nil)
{
NSManagedObjectContext *context = [self managedObjectContext];
_recordlocal = [NSEntityDescription insertNewObjectForEntityForName:#"RECORD" inManagedObjectContext:context];
_recordlocal.date = [ibNavSettings interfaceSettings].selectedFileToDataBase;
NSData *inputData = [NSData dataWithData:[fh readDataToEndOfFile]];
NSString *inputString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
NSArray *array = [[NSArray alloc] initWithArray:[inputString componentsSeparatedByString:#"\n"]];
for(unsigned int i = 0; i <14;i++)
{
if(findSensor[i]==YES)
{
[[NSFileManager defaultManager] createFileAtPath:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)] contents:nil attributes:nil];
NSMutableString *saveString = [[NSMutableString alloc] init];
int count = 0;
for(unsigned int j = 1; j <[array count];j++)
{
#autoreleasepool {
if([[[[array objectAtIndex:j] componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
[saveString appendString:[array objectAtIndex:j]];
[saveString appendString:#"\n"];
if(i == 0)
count++;
progress++;
pourcent = progress/total;
load = pourcent*100;
if(load%5==0)
[self performSelectorInBackground:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
}
}
}
[saveString writeToFile:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)] atomically:YES encoding:NSUTF8StringEncoding error:nil];
if(i == 0)
_recordlocal.count = [[NSNumber alloc] initWithInt:(count/50)];
}
}
_recordlocal.load = [[NSNumber alloc] initWithBool:YES];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Core data error %#, %#", error, [error userInfo]);
abort();
}
I would guess that your app is crashing without a readable exception because it is running out of available RAM, especially since you indicated that it is running through a large number of iterations.
For a test, I would recommend doing what Rikkles suggests with the autorelease pool. In addition, since the value of i (and as a result the comparison string) rarely changes, I would create that string outside the j loop as well. This would avoid the creation of a lot of extra strings laying around.
Beyond that, since it appears that you are looking for a string at the beginning of a string that is delimited by a semicolon, I would recommend instead of doing componentsSeparatedByString and then examining element zero that you use the NSString method hasPrefix to check for the condition you are looking for.
Here is an example:
for(unsigned int i = 0; i <14;i++)
{
NSString *searchString = [NSString stringWithFormat:#"%d;", 10*(i+1)];
if(findSensor[i]==YES)
{
for(unsigned int j = 1; j <[array count];j++)
{
if([[array objectAtIndex:j] hasPrefix:searchString])
{
//Code here
}
}
}
}
(I hope this compiles and runs, if it doesn't it should require more than minor tweaks. I am away from my Mac right now.)
If this doesn't help, then something going on inside //Code here must be the culprit.
Why are you creating [array count] autoreleasepools? What's the point of creating so many of them? It could crash because of that. Put the #autoreleasepool outside the for loop.
The only reason I could think that you would do that is if you create so many transient objects inside each iteration of the for loop that you'd want to get rid of them as soon as you got out of the iteration. But there are other ways to do that, including reusing those objects within each iteration.
First suggestion
Just use fast enumeration for the inner loop, you aren't actually using the index 'j' for anything
https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/Enumeration.html
Second suggestion
Put some NSLog's in place, it will slow everything down, but you need to figure out what point you are failing at. That will help point everyone in the right direction.
Third suggestion
Actually use NSError objects and output their value if an error is thrown:
NSError *writeError = nil;
[saveString writeToFile:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)]
atomically:YES
encoding:NSUTF8StringEncoding
error:&writeError];
if(error != nil) NSLog(#"error writing file: %#", [[writeError userInfo]description]);
Fourth suggestion
You appear to try to be updating the UI from a background thread. This will not work or will cause a crash. UI code can only be called from a main thread. So dont do this:
[self performSelectorInBackground:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
If you are already on a background thread this will probably crash because you are creating threads on threads on threads. You instead would want to call:
[self performSelectorOnMainThread:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
Fifth suggestion
You may be going over the maximum length for NSString (it's big but I did it once on accident before). You should probably just be appending the file on each iteration of the loop instead, so you don't have an ever growing NSMutableString:
NSString *path = [NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)]
NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:filePath];
NSData *newLine = [#"\n" dataUsingEncoding:NSUTF8StringEncoding];
for(NSString *rowString in array)
{
if([[[rowString componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
NSData *stringData = [rowString dataUsingEncoding:NSUTF8StringEncoding];
[fh truncateFileAtOffset:[fh seekToEndOfFile]];
[fh writeData:stringData];
[fh truncateFileAtOffset:[fh seekToEndOfFile]];
[fh writeData:newLine];
if(i == 0)
count++;
progress++;
pourcent = progress/total;
load = pourcent*100;
if(load%5==0)
[self performSelectorOnMainThread:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
}
}
}
And this has the added benefit of helping you ditch the autoreleasepools
This was invalid
If your array does in fact have 175260 rows, that is probably your issue. You are looping using unsigned int as your index var. Unsigned ints in c only have a max value of 65535. Use an unsigned long int, max 4294967295.

NSMutableDictionary remove object at key path?

I've got a layered NSMutableDictionary object and i'd like to be able to remove dictionaries deeper down in the hierarchy. Is there a quick and easy way to do this, for example, a removeObjectAtKeyPath-like method? Can't seem to find one.
Thanks!
Nothing built in, but your basic category method will do just fine:
#implementation NSMutableDictionary (WSSNestedMutableDictionaries)
- (void)WSSRemoveObjectForKeyPath: (NSString *)keyPath
{
// Separate the key path
NSArray * keyPathElements = [keyPath componentsSeparatedByString:#"."];
// Drop the last element and rejoin the path
NSUInteger numElements = [keyPathElements count];
NSString * keyPathHead = [[keyPathElements subarrayWithRange:(NSRange){0, numElements - 1}] componentsJoinedByString:#"."];
// Get the mutable dictionary represented by the path minus that last element
NSMutableDictionary * tailContainer = [self valueForKeyPath:keyPathHead];
// Remove the object represented by the last element
[tailContainer removeObjectForKey:[keyPathElements lastObject]];
}
#end
N.B. That this requires that the second-to-last element of the path -- the tailContainer be something that responds to removeObjectForKey:, probably another NSMutableDictionary. If it's not, boom!
You can create a category :
This is upto 1 level down:
#import "NSMutableDictionary+RemoveAtKeyPath.h"
#implementation NSMutableDictionary (RemoveAtKeyPath)
-(void)removeObjectAtKeyPath:(NSString *)keyPath{
NSArray *paths=[keyPath componentsSeparatedByString:#"."];
[[self objectForKey:paths[0]] removeObjectForKey:paths[1]];
}
#end
It is called as :
NSMutableDictionary *adict=[[NSMutableDictionary alloc]initWithDictionary:#{#"key1" : #"obj1", #"key11":#"obj11"}];
NSMutableDictionary *bdict=[[NSMutableDictionary alloc]initWithDictionary:#{#"key2" : adict}];
NSLog(#"%#",bdict);
NSLog(#"%#",[bdict valueForKeyPath:#"key2.key1"]);
[bdict removeObjectAtKeyPath:#"key2.key1"];
NSLog(#"After category : %#",bdict);
Minor improvement to Josh's answer, in order to handle keypaths which don't contain a period (i.e. keypaths which are actually keys):
- (void)removeObjectAtKeyPath:(NSString *)keyPath
{
NSArray *keyPathElements = [keyPath componentsSeparatedByString:#"."];
NSUInteger numElements = [keyPathElements count];
if (numElements == 1) {
[self removeObjectForKey:keyPath];
} else {
NSString *keyPathHead = [[keyPathElements subarrayWithRange:(NSRange){0, numElements - 1}] componentsJoinedByString:#"."];
NSMutableDictionary *tailContainer = [self valueForKeyPath:keyPathHead];
[tailContainer removeObjectForKey:[keyPathElements lastObject]];
}
}
I know this is an older post, but I needed to find the same solution in Swift 2.0, unable to find a simple answer I came up with this solution:
public extension NSMutableDictionary {
public func removeObjectAtKeyPath(keyPath:String) {
let elements = keyPath.componentsSeparatedByString(".")
let head = elements.first!
if elements.count > 1 {
let tail = elements[1...elements.count-1].joinWithSeparator(".")
if let d = valueForKeyPath(head) as? NSMutableDictionary {
d.removeObjectAtKeyPath(tail)
}
}else{
removeObjectForKey(keyPath)
}
}
}
I've added an extension to NSMutableDictionary using recursion to step thru the keyPath
I just iterate on jscs's accepted answer with minor improvement - working on the KVC key-path using NSString's built-in pathUtlities category.
#implementation NSMutableDictionary (NestedMutableDictionaries)
- (void)removeObjectForKeyPath: (NSString *)keyPath
{
NSArray *key = [keyPath pathExtension];
NSString *keyPathHead = [keyPath stringByDeletingPathExtension];
[[self valueForKeyPath:keyPathHead] removeObjectForKey:key];
}
#end
Just a little nicer, I think.

Resources