I want to create a CSSearchableItem with a attributeSet of ALL the attributes available.
To do so, I am trying to get all of the properties of the CSSearchableItemAttributeSet class in the following way:
NSMutableArray * allPropertyNames(Class c)
{
unsigned count;
objc_property_t *properties = class_copyPropertyList([CSSearchableItemAttributeSet class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++)
{
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
free(properties);
return rv;
}
The problem is I am getting the following result:
HTMLContentDataNoCopy,
textContentNoCopy,
accountType,
textSelected,
subtitle,
userTags,
albumPersistentID,
adamID,
extendedContentRating,
fileIdentifier,
parentFileIdentifier,
filename,
documentIdentifier,
dataOwnerType,
existingThread,
partiallyDownloaded,
queryResultMatchedFields,
uniqueIdentifier,
bundleID,
protectionClass,
expirationDate,
userActivityType,
queryResultRelevance,
applicationName,
contentSnippet,
relatedAppBundleIdentifier,
mailAttachmentNames,
mailAttachmentTypes,
mailAttachmentKinds,
mailDateReceived,
mailDateLastViewed,
mailFlagged,
mailFlagColor,
mailRead,
mailRepliedTo,
mailPriority,
mailGMailLabels,
mailMessageID,
mailCategory,
mailConversationID,
readerView,
textContentDataSource,
fileProviderID,
fileItemID,
parentFileItemID,
ownerName,
ownerIdentifier,
lastEditorName,
lastEditorIdentifier,
fileProviderDomaindentifier,
fileProviderUserInfoKeys,
fileProviderUserInfoValues,
trashed,
shared,
uploaded,
uploading,
uploadError,
downloading,
downloadError,
extraData,
favoriteRank,
subItemCount,
sharedItemCurrentUserRole,
versionIdentifier,
downloadingStatus,
lastApplicationLaunchedDate,
isPlaceholder,
mutableAttributes,
customAttributes,
attributes,
searchableItemFlags,
decoder,
contentDecoder,
codedAttributes,
codedCustomAttributes,
contentObj,
hasCodedCustomAttributes
None of those are the properties I was looking for.
Does anyone know how to get this?
That does appear to be the list of properties declared on the CSSearchableItemAttributeSet class. What is unexpected?
While Objective-C has introspection features, said features are not designed to be used at runtime to deduce or subdivide capabilities of classes in this fashion.
I.e. introspection driven programming is largely discouraged save for some very specific examples like delegation.
In this case, you'll likely find success in either adopting CoreData's formal modeling capabilities (or a similar solution) or creating a class method that contains a list of properties that you want to advertise.
Overall, though, your code is likely to be less bug prone if you generally use relatively static call sites (i.e. [someObj myProperty]) as opposed to trying to abstract away.
Related
I'm looking at a code sample for deserializing a JSON response. The last line return [topics copy]; copies the array before returning. I've looked up the reason for this and it's to return an immutable NSArray.
However, is this standard practice or highly defensive programming? The calling method will assign the return value to something, and if it wants to assign the return value to an immutable NSArray it will do it. If it assigns the return value to an NSMutableArray then it will do that.
So my question is - is there any realistic scenario where this will prevent unwanted consequences?
// Returns array of #c NPTopic objects
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
return nil;
}
NSDictionary *JSONDictionary = [super responseObjectForResponse:response data:data error:error];
if (!JSONDictionary) return nil;
// Note: the expected JSON format of this response is { data: [ { <a topic> }, { <another topic>} ], metadata: { ...} }
NSArray *JSONTopics = JSONDictionary[#"data"];
NSMutableArray *topics = [NSMutableArray array];
for (NSDictionary *JSONTopic in JSONTopics) {
// For each topic in JSON format, we can deserialize it from JSON to our desired model class using Mantle
NPTopic *topic = [MTLJSONAdapter modelOfClass:[NPTopic class] fromJSONDictionary:JSONTopic error:error];
if (!topic) return nil;
[topics addObject:topic];
}
*error = nil;
return [topics copy];
}
The copy is so it returns an NSArray, not an NSMutableAray. The issue is that if an NSMutableAray is returned it can be changed, that can be a problem is there are multiple pointers to it and one makes changes but another assumes that it is immutable and won't change.
It is good practice.
Don't make assumptions about the actual implementation, there are several ways that the "copy" can occur without actually making a copy. Being concerned about performance without a need and proof is called" Premature Optimization" and warned again by many including famously by Donald Knuth.
It really should have it's return typed NSArray *, not id so the compiler can catch type errors.
Based on your comment to #Zaph, let's try for an explanation...
Fundamental to Objective-C, and many other languages, is the concept of subclasses; an instance of class B which derives from a class A can usually be used whenever an instance of class A is required.
Because of this there is a lot of type information loss; pass an instance of B to something expecting an A then the receiver doesn't know unless it chooses to query the actual type - the receiver has less information about the instance's actual type, seing it as an A, while the actual instance still is a B.
The extreme case of type information loss is when an instance is stored in a container, such as NSArray, which justs stores "objects" (id or NSArray *) - when that instance is later extracted little is known about it for certain, though if the programmer has only stored, say, NSString instances then they can safely assume only NSString instances are extracted.
All this usually works fine.
Where the "usually" breaks down is when some fundamental property, such as mutability, changes in the derived class.
Consider the simple class (please no sexism etc. comments!):
#interface Woman : Person
#property NSString *maidenName;
#property NSString *marriedName;
#end
#implementation Woman
// nothing to do
#end
and the code fragment:
Woman *mary = [Woman new];
NSMutableString *name = [NSMutableString stringWithString:#"Mary Jones"];
mary.maidenName = name;
[name replaceOccurencesOfString:#"Jones" with:#"Williams];
mary.marriedName = name;
What is the value of mary.maidenName? Mary Williams... Probably not what was intended.
What might be the result if rather can create your own string as above you obtained it from a method which claimed to return an immutable string and you assigned it to maidenName or marriedName, but in fact the return string was mutable and subsequently changed elsewhere? Poor Mary would find her name changing.
To address this problem two rules are generally advised:
Consumer: If you are storing a reference to an object of an immutable class which has a mutable subclass then copy the object before storing to avoid surprises if the instance is in fact mutable. For the above example this can be done by adding the copy attribute to the properties:
#property (copy) NSString *maidenName;
#property (copy) NSString *marriedName;
Producer: If you are creating and returning an object instance which you declare is immutable, but during creation you use a mutable subclass, then make an immutable copy and return that. I.e. return what you say you are returning (or an immutable subclass of it).
Following these rules should reduce surprises, and that is what the code for responseObjectForResponse does.
You are correct, this is defensive programming. However; due to the prevalence of type information loss, something fundamental to this style of programming, and the issues unexpected mutability can cause; it is not highly defensive programming.
As Monty Python, and others, would advise: always expect the unexpected and defend against it.
HTH
I needs to list variables for NSManagedObject, I know there is a way to do it using "class_copyIvarList" as given in How do I list all fields of an object in Objective-C?
but "class_copyIvarList" isn't working on "NSManagedObject".
here is piece of code Im using, which is working perfectly fine for "NSObject" but not for "NSManagedObject":
unsigned int outCount;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar var = vars[i];
unsigned int idCount;
NSLog(#"%s %s", ivar_getName(var), ivar_getTypeEncoding(var));
}
free(vars);
What is wrong with it?
I'm not sure what you're doing here, but with managed objects it's usually more typical to use Core Data's own introspection rather than ask the Objective-C runtime. In a method on a managed object subclass, you'd use [[self entity] propertiesByName] to get a list of all attributes and relationships defined by the entity type. You could replace that method with attributesByName or relationshipsByName depending on what you need. The objects you get back can be further queried, for example to find out the type of a property or the target entity of a relationship.
I have a generic person object with properties personName, lastName, and age. I am storing the user input into an NSMutableArray and I wanted to find a under by his/her name in the array. I have tried finding a bunch of different solutions but none that quite really work.
This is my main.m
#autoreleasepool {
char answer;
char locatePerson[40];
//Create mutable array to add users for retrieval later
NSMutableArray *people = [[NSMutableArray alloc] init];
do{
Person *newPerson = [[Person alloc]init];
[newPerson enterInfo];
[newPerson printInfo];
[people addObject:newPerson];
NSLog(#"Would you like to enter another name?");
scanf("\n%c", &answer);
}while (answer == 'y');
NSLog(#"Are you looking for a specific person?");
scanf("%c", locatePerson);
//This is where I need help
int idx = [people indexOfObject:]
}
This is very basic but I am new to objective-c and I wanted to try and find the user by name. The solutions I've seen have used the indexesOfObjectsPassingTest method. But I was wondering if I can't just use the indexOfObjectmethod the way I did there to locate a person by its name?
Any help is appreciated.
This is one of those hard problems you should avoid with some up-front design. If you know that you are putting things into a collection class and will need to get them out again based on some attribute (rather than by order of insertion) a dictionary is the most efficient collection class.
You can use a NSDictionary keyed with Person's name attribute. You can still iterate over all the objects but you will avoid having to search the whole collection. It can take a surprisingly long time to find a matching attribute in a NSArray! You wouldn't even have to change your Person object, just do
NSDictionary *peopleDictionary = #{ person1.name : person1, person2.name : person2 };
or add them one by one as they are created into a NSMutableArray.
You can try something like this assuming that 'name' is a property for your Person class.
NSUInteger i = 0;
for(Person *person in people) {
if([person.name isEqualToString:locatePerson]) {
break;
}
i++;
}
I found this answer:
https://stackoverflow.com/a/5163334/1364174
Which presents how for in loop is implemented.
NSFastEnumerationState __enumState = {0};
id __objects[MAX_STACKBUFF_SIZE];
NSUInteger __count;
while ((__count = [myArray countByEnumeratingWithState:&__enumState objects:__objects count:MAX_STACKBUFF_SIZE]) > 0) {
for (NSUInteger i = 0; i < __count; i++) {
id obj = __objects[i];
[obj doSomething];
}
}
The problem is that, I found it wrong.
First of all, when you have Automatic Reference Counting (ARC) turned on, you got an error
Sending '__strong id *' to parameter of type '__unsafe_unretained_id*' changes retain/release properties of pointer
But even when I turn ARC off I found out that I __object array seems to behave strangely :
This is actual Code (I assumed MAX_STACKBUFF_SIZE to be 40):
#autoreleasepool {
NSArray *myArray = #[#"a", #"b", #"c", #"d", #"e", #"f", #"g"];
int MAX_STACKBUFF_SIZE = 40;
NSFastEnumerationState __enumState = {0};
id __objects[MAX_STACKBUFF_SIZE];
NSUInteger __count;
while ((__count = [myArray countByEnumeratingWithState:&__enumState objects:__objects count:MAX_STACKBUFF_SIZE]) > 0) {
for (NSUInteger i = 0; i < __count; i++) {
id obj = __objects[i];
__enumState.itemsPtr
NSLog(#" Object from __objects ! %#", obj); // on screenshot different message
}
}
}
return 0;
I got EXC_BAD_ACESS when I try to get the contents of the __object array.
I also found out that when you try to iterate through __enumState.itemsPtr it actually works.
Could you explain me what is going on here ? Why my __objects seems to be "shrunken down". And why it doesn't contains desired object? And why is that error when ARC is turned on.
Thank you very very much in advance for your time and effort! (I provided screenshot for better understanding what causes an error)
First of all, strong pointers cannot be used in C-structures, as explained in the "Transitioning to ARC Release Notes", therefore the objects array has be be declared
as
__unsafe_unretained id __objects[MAX_STACKBUFF_SIZE];
if you compile with ARC.
Now it is not obvious (to me) from the NSFastEnumeration documentation, but it is
explained in Cocoa With Love:Implementing countByEnumeratingWithState:objects:count:
that the implementation need not fill the supplied objects array, but can just set
__enumState.itemsPtr to an existing array (e.g. some internal storage). In that case, the contents of the
__objects array is undefined, which causes the crash.
Replacing
id obj = __objects[i];
by
id obj = __enumState.itemsPtr[i];
gives the expected result, which is what you observed.
Another reference can be found in the "FastEnumerationSample" sample code:
You have two choices when implementing this method:
1) Use the stack
based array provided by stackbuf. If you do this, then you must
respect the value of 'len'.
2) Return your own array of objects. If
you do this, return the full length of the array returned until you
run out of objects, then return 0. For example, a linked-array
implementation may return each array in order until you iterate
through all arrays.
In either case, state->itemsPtr MUST be a valid
array (non-nil). ...
I need to scroll through several thousands of words to categorize them... to determine which words have the same pattern. (this part works)
For example, a four letter word that has two m's in 2nd & 4th position represent a pattern ("-m-m"). Once I have gone through all the words, I will know how many words there are for any given pattern. I am scrolling through now, but the problem I have is 'remembering' how many words I have in any given pattern.
I was thinking of using NSMutableDictionary and have the key be the pattern ('-m-m-') and the object represent the count of that pattern. This means every time I come across a pattern, I look up that pattern in the dictionary, get the key, increment the key, and put it back in the dictionary.
I need help with both the decision and syntax for performing this task.
Thank You
The answer to your question was this part of your (given) question "I will know how many words there are for any given pattern.". I would use an array of dictionary. You use the dictionary to store key value pair: a known pattern and the count. And you use the array to store those KVP records. So the next time you detect a pattern, search for the array for that record (dictionary), if found, increment the count. If not, create new record and set the count to 1.
Added sample code:
#define kPattern #"Pattern"
#define kPatternCount #"PatternCount"
-(NSMutableDictionary *)createANewDictionaryRecord:(NSString *) newPattern
{
int count = 1;
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
newPattern, kPattern,
[NSString stringWithFormat:#"%i",count], kPatternCount,
nil];
return myDictionary;
}
-(void)addANewPatternToArray:(NSMutableDictionary *)newDictionary
{
// NSMutableArray *myArrayOfDictionary = [[NSMutableArray alloc]init]; // you need to define it somewhere else and use property etc.
[self.myArrayOfDictionary addObject:newDictionary]; //or [self.myArrayOfDictionary addObject:newDictionary]; if you follow the recommendation above.
}
-(BOOL)existingPatternLookup:(NSString *)pattern
{
for (NSMutableDictionary *obj in self.myArrayOfDictionary)
{
if ([[obj objectForKey:kPattern] isEqual:pattern])
{
int count = [[obj objectForKey:kPatternCount] intValue] + 1;
[obj setValue:[NSString stringWithFormat:#"%i",count] forKey:kPatternCount];
return YES;
}
}
[self.myArrayOfDictionary addObject:[self createANewDictionaryRecord:pattern]];
return NO;
}
-(void)testData
{
NSMutableDictionary *newDict = [self createANewDictionaryRecord:#"mmm"];
[self addANewPatternToArray:newDict];
}
-(void) printArray
{
for (NSMutableDictionary * obj in self.myArrayOfDictionary)
{
NSLog(#"mydictionary: %#", obj);
}
}
- (IBAction)buttonPressed:(id)sender
{
if ([self existingPatternLookup:#"abc"])
{
[self printArray];
} else
{
[self printArray];
}
}
Not being an objective C expert but solving this problem in java before, I would say a dictionary(I used a map when doing it in java) is the best way. Check if the key(pattern) already exist if so increment that count else put a new one in the dictionary.
EDIT
If you want to not just get the count of a pattern, but in fact tell which words fall under that pattern, I would use a dictionary of strings to mutable arrays. In the arrays you store the words and the key to the array is the pattern(as a string), similar code as above but instead of just incrementing the count, you have to add the new word to the array.
The only difference in NSDictionary and NSMutableDictionary is that one can have objects added to it. I think your implementation is good, but English is a complex language. It would be more efficient to parse out the string with regex than to set a key for it.
Why don't you use NSCountedSet instead:
NSCountedSet Class Reference
..which is available in iOS 2.0 and later?
Each distinct object inserted into an NSCountedSet object has a counter associated with it. NSCountedSetkeeps track of the number of times objects are inserted [...] Thus, there is only one instance of an object in an NSSet object even if the object has been added to the set multiple times. The count method defined by the superclass NSSet has special significance; it returns the number of distinct objects, not the total number of times objects are represented in the set.
Then use:
- (NSUInteger)countForObject:(id)anObject
Use a dictionary of NSMutableArrays, and check for the existence of each search key as you recommended. If the key doesn't exist, add an NSMutableSet or NSMutableArray (depending on your needs) for the searched key type "-m-m" for example, and then add to the set or array for that key.