Is -allObjects on NSMutableSet thread safe? - objective-c

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.

Related

Principle of Locking -- in Objective C

I got some problems with my codes that i can't tell.
Will u finger it out for me?
And can you help answer some of the following questions?
This class's functions may be called in multi-thread situation.
#import "FileHandler.h"
#import <pthread.h>
#define kJsonFilePath [NSTemporaryDirectory() stringByAppendingString:#"/foo/jsonFile.json"]
static pthread_mutex_t _lock;
#implementation FileHandler
+ (void)initialize
{
if (self == [YWMultiAccountsHandle class]) {
pthread_mutex_init(&_lock, NULL);
}
}
+ (void)writeIntoTheFileWithArray:(NSArray *)arr
{
pthread_mutex_lock(&_lock);
NSMutableArray *muArr = [NSMutableArray array];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (![obj isKindOfClass:[NSDictionary class]]) {
[muArr addObject:[obj get_keyValues]];
}
else
{
[muArr addObject:obj];
}
}];
pthread_mutex_unlock(&_lock);
NSData *data = [NSJSONSerialization dataWithJSONObject:[muArr copy] options:NSJSONWritingPrettyPrinted error:nil];
pthread_mutex_lock(&_lock);
[data writeToFile:kJsonFilePath atomically:YES];
pthread_mutex_unlock(&_lock);
}
1.Can i remove the pthread_mutex_unlock(&_lock); and pthread_mutex_lock(&_lock); in the middle?
2.Is there any problem in this class?
3.when the _lock will release and dealloc?
4.Is it thread safe?

How to access smart folder from gallery using ALAsset

I am making an app where I want to get the list of all albums name from gallery including smart folders(favorites, screenshots. this is an old app where we have used ALAsset in order to access the gallery in our app.
Is there any way through which we can access smart folders as well using ALAssetLibrary?
This code is help.
#import <AssetsLibrary/AssetsLibrary.h>
#property (nonatomic, strong) ALAssetsLibrary *_assetsLibrary;
- (ALAssetsLibrary *)defaultAssetsLibrary {
static dispatch_once_t pred = 0;
static ALAssetsLibrary *library = nil;
dispatch_once(&pred, ^{
library = [[ALAssetsLibrary alloc] init];
});
return library;
}
-(void) getgalleryPic
{
if (self.photos == nil) {
self.photos = [[NSMutableArray alloc] init];
}else
{
[self.photos removeAllObjects];
}
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusAuthorized) {
// Access has been granted.
NSMutableArray *collector = [[NSMutableArray alloc] initWithCapacity:0];
ALAssetsLibrary *library = [self defaultAssetsLibrary];
[library enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
{
if (asset) {
[collector addObject:asset];
}else
{
self.photos = [[[collector reverseObjectEnumerator] allObjects] mutableCopy];
NSLog(#"photo lenght %lu",(unsigned long)[self.photos count]);
[_collectionVW reloadData];
}
}];
}
failureBlock:^(NSError *error) { NSLog(#"Boom!!!");}
];
}
else if (status == PHAuthorizationStatusDenied) {
// Access has been denied.
}
else if (status == PHAuthorizationStatusNotDetermined) {
// Access has not been determined.
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized) {
// Access has been granted.
NSMutableArray *collector = [[NSMutableArray alloc] initWithCapacity:0];
ALAssetsLibrary *library = [self defaultAssetsLibrary];
[library enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
{
if (asset) {
[collector addObject:asset];
}else
{
self.photos = [[[collector reverseObjectEnumerator] allObjects] mutableCopy];
NSLog(#"photo lenght %lu",(unsigned long)[self.photos count]);
[_collectionVW reloadData];
}
}];
}
failureBlock:^(NSError *error) { NSLog(#"Boom!!!");}
];
}
else {
// Access has been denied.
}
}];
} else if (status == PHAuthorizationStatusRestricted) {
// Restricted access - normally won't happen.
}
}

Searching an NSMutableArray of classes

I have my own class defined as below.
#interface PersonList : NSObject
#property(nonatomic, strong)NSNumber *ID;
#property(nonatomic, strong)NSString *FirstName;
#property(nonatomic, strong)NSString *SecondName;
#end
I use it like the following method:
PersonList *P = [[PersonList alloc]init];
[P setID: ...];
[P setFirstname:...];
[P setSecondname:...];
then add it to an array.
[PersonListArray addObject:P];
What I'm trying to do is search this array for the class where ID = x.
Is it the best way?
for(int i = 0; i < PersonListArray.count; i++)
{
PersonListArray *aPersonListArray = [PersonListArray objectAtIndex:i];
if(aPersonListArray.ID == x)
{
//Do what i want here
//break;
}
}
Thanks
You can use this NSArray method that makes things a lot easier and is also very optimized:
- (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
Your code should then look like that:
NSInteger personIndex = [PersonListArray indexOfObjectPassingTest:^BOOL(PersonList person, NSUInteger idx, BOOL *stop) {
return [person.ID isEqualToNumber:x];
}];
PersonList personList = PersonListArray[personIndex]
Two more things:
you might consider not capitalizing your variables, to follow conventions.
If you want to compare values of objects in ObjC, use the equalTo methods, not the == sign which is for comparing pointers
Hope this helps,
There is another way, a little bit more simple:
for(PersonList *AnyPerson in PersonListArray)
{
if([AnyPerson.ID isEqualToNumber:x])
{
//do what you want
}
}
You could do this like this:
for(PersonList *person in PersonListArray){
if([person.ID isEqualToNumber: x]){
// do your job, it you want to do it for the first case only
// use break here or return depends on the case
}
}
Take a look at the way of comparing values (if you want sth more than equality consider usage of compare: method)
BTW It might be valuable for you to take a look on the possibilities of sorting and searching arrays in case of possibilities and performance, take a look at this.
Try this
#interface PersonList ()
#property (nonatomic, strong) NSMutableArray *persons;
#end
#implementation PersonList
-(NSMutableArray *)persons{
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
_persons=[[NSMutableArray alloc] init];
});
return _persons;
}
-(instancetype)initWithIDs:(NSArray *)personIDs FirstNames:(NSArray *)firstNames SecondNames:(NSArray *)secondNames{
if(self=[super init]){
[personIDs enumerateObjectsUsingBlock:^(id personID, NSUInteger idx, BOOL *stop) {
NSMutableDictionary *person=[[NSMutableDictionary alloc] init];
[person setObject:personID forKey:#"ID"];
[person setObject:[firstNames objectAtIndex:idx] forKey:#"FIRSTNAME"];
[person setObject:[secondNames objectAtIndex:idx] forKey:#"SECONDNAME"];
[self.persons addObject:person];
}];
}
return self;
}
-(NSDictionary *)findPersonByID:(NSString *)personID{
__block NSDictionary *dictionary=[[NSDictionary alloc] init];
[self.persons enumerateObjectsUsingBlock:^(id person, NSUInteger idx, BOOL *stop) {
if ([[person objectForKey:#"ID"] isEqualToString:personID]) {
dictionary=person;
*stop=YES;
}
}];
return dictionary;
}
#end

NSManagedObject to NSDictionary

Trying to serialise NSManagedObject to NSDictionary including related data.
I found some code for that here:
http://vladimir.zardina.org/2010/03/serializing-archivingunarchiving-an-nsmanagedobject-graph/
Unfortunately, there is no support for NSOrderedSet. Tried to implement it myself, but have a crash with message doesn't recognise selector on line if (!relatedObject.traversed) {.
- (NSDictionary*) toDictionary
{
self.traversed = YES;
NSArray* attributes = [[[self entity] attributesByName] allKeys];
NSArray* relationships = [[[self entity] relationshipsByName] allKeys];
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:
[attributes count] + [relationships count] + 1];
[dict setObject:[[self class] description] forKey:#"class"];
for (NSString* attr in attributes) {
NSObject* value = [self valueForKey:attr];
if (value != nil) {
[dict setObject:value forKey:attr];
}
}
for (NSString* relationship in relationships) {
NSObject* value = [self valueForKey:relationship];
if ([value isKindOfClass:[NSSet class]]) {
// To-many relationship
// The core data set holds a collection of managed objects
NSSet* relatedObjects = (NSSet*) value;
// Our set holds a collection of dictionaries
NSMutableSet* dictSet = [NSMutableSet setWithCapacity:[relatedObjects count]];
for (ExtendedManagedObject* relatedObject in relatedObjects) {
if (!relatedObject.traversed) {
[dictSet addObject:[relatedObject toDictionary]];
}
}
[dict setObject:dictSet forKey:relationship];
}
else if ([value isKindOfClass:[NSOrderedSet class]]) {
// To-many relationship
// The core data set holds a collection of managed objects
NSOrderedSet* relatedObjects = (NSOrderedSet *)value;
// Our set holds a collection of dictionaries
NSMutableSet* dictSet = [NSMutableSet setWithCapacity:[relatedObjects count]];
for (ExtendedManagedObject* relatedObject in relatedObjects) {
if (!relatedObject.traversed) {
[dictSet addObject:[relatedObject toDictionary]];
}
}
[dict setObject:dictSet forKey:relationship];
}
else if ([value isKindOfClass:[ExtendedManagedObject class]]) {
// To-one relationship
ExtendedManagedObject* relatedObject = (ExtendedManagedObject*) value;
if (!relatedObject.traversed) {
// Call toDictionary on the referenced object and put the result back into our dictionary.
[dict setObject:[relatedObject toDictionary] forKey:relationship];
}
}
}
return dict;
}
- (void) populateFromDictionary:(NSDictionary*)dict
{
NSManagedObjectContext* context = [self managedObjectContext];
for (NSString* key in dict) {
if ([key isEqualToString:#"class"]) {
continue;
}
NSObject* value = [dict objectForKey:key];
if ([value isKindOfClass:[NSDictionary class]]) {
// This is a to-one relationship
ExtendedManagedObject* relatedObject =
[ExtendedManagedObject createManagedObjectFromDictionary:(NSDictionary*)value
inContext:context];
[self setValue:relatedObject forKey:key];
}
else if ([value isKindOfClass:[NSSet class]]) {
// This is a to-many relationship
NSSet* relatedObjectDictionaries = (NSSet*) value;
// Get a proxy set that represents the relationship, and add related objects to it.
// (Note: this is provided by Core Data)
NSMutableSet* relatedObjects = [self mutableSetValueForKey:key];
for (NSDictionary* relatedObjectDict in relatedObjectDictionaries) {
ExtendedManagedObject* relatedObject =
[ExtendedManagedObject createManagedObjectFromDictionary:relatedObjectDict
inContext:context];
[relatedObjects addObject:relatedObject];
}
}
else if (value != nil) {
// This is an attribute
[self setValue:value forKey:key];
}
}
}
it is fast and easy way
NSMutableArray *array = [NSMutableArray arrayWithCapacity:ManagedObjectItems.count];
[[ManagedObjectItems allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Diary_item_food *food = obj;
NSArray *keys = [[[food entity] attributesByName] allKeys];
NSDictionary *dict = [obj dictionaryWithValuesForKeys:keys];
[array addObject:dict];
}];
I found the ready gist on Gihub: https://gist.github.com/nuthatch/5607405
Even easier way, query for the objectID and use NSDictionaryResultType on the fetch request.
Update: Only if you don't need related data.

Remove object from an array stored in a singleton

Im working with a singleton to store some data, her's the implementation
static ApplicationData *sharedData = nil;
#implementation ApplicationData
#synthesize list;
+ (id)sharedData
{
static dispatch_once_t dis;
dispatch_once(&dis, ^{
if (sharedData == nil) sharedData = [[self alloc] init];
});
return sharedData;
}
- (id)init
{
if (self = [super init])
{
list = [[NSMutableArray alloc]init];
}
return self;
}
if list have less than 3 (2<) object i the app crash with "index 0 beyond bounds for empty array"
// NSMutableArray *anArray = [[NSMutableArray alloc]initWithObjects:#"", nil];
while ([[[ApplicationData sharedData]list] lastObject] != nil)
{
File *file = [[[ApplicationData sharedData]list] lastObject];
BOOL isDir;
if (![[NSFileManager defaultManager] fileExistsAtPath:file.filePath isDirectory:&isDir])
{
NSMutableDictionary *tmpDic = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:file.fileName,file.filePath,logEnteryErrorfileNotFoundDisplayName,[formatter stringFromDate:[NSDate date]], nil] forKeys:[NSArray arrayWithObjects:logShredFileName,logShredFilePath,logShredStatue,logShredDate, nil]];
[logArray addObject:tmpDic];
errorOccured = YES;
[[[ApplicationData sharedData]list] removeLastObject];
continue;
}
... other code
}
if i use the anArray that work perfectly.
what is the problem ?
That's totally weird, you've probably did something else to achieve this. Why don't you use - (void)removeAllObjects?
Maybe you remove objects in the while cycle the last line, ie:
while ([[[ApplicationData sharedData]list] count] != 0)
{
// remove object from list
// ...
[[[ApplicationData sharedData]list] removeLastObject];
}
And just a note, you don't need to check if (sharedData == nil) in sharedData as far as it's guaranteed to be executed only once. (unless you do something outside to your static variable, but that's not how it's supposed to be done I believe)