Access annotated property in Objective-C - objective-c

I’ve searched around for this but found no information at all. Say I create a property like so:
#property (nonatomic, strong, readwrite) NSString *someString __attribute((annotate(“Try to access this text?)));
How could I access the text in the annotation part of the property? I’m guessing I’d have to utilize Objective-c runtime but I found no information there either.

First off, sales pitch - swift would make this easily transparent with optionals. Since you are using objective-c here's how I would do it:
#interface SomeModel {
static NSString * const _requiredProperties[] = {
[0] = #"first",
[1] = #"second"
};
}
#property(nonatomic, strong) NSString *first;
#property(nonatomic, strong) NSString *second;
#end
-(BOOL) doLoad:(...) {
//map properties here
Bool success = YES;
foreach(NSString property in _requiredProperties {
success &= ([self valueForKey: property] != nil);
}
return success;
}
The objective-C language doesn't contain the types of constructs you need. Keep it simple, handle missing properties elegantly.
Also if it's JSON, there's plenty of frameworks available to help. Unsure of your mapping here.
Alternatively you can query the protocol, and use the fact it is in a protocol to determine if required.
See this hello world like example that prints out the protocols in the required protocol - sadly the third param which is "YES" for required seems to include optionals for me on OSX. So I made a separate protocol for optionals. This might help you.
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#protocol TestProtocolRequired
#required
#property(nonatomic, strong) NSString *firstName;
#property(nonatomic, strong) NSString *lastName;
#end
#protocol TestProtocolExtended
#property(nonatomic, strong) NSString *address;
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
unsigned int outCount, i;
objc_property_t *properties = protocol_copyPropertyList2(#protocol(TestProtocolRequired), &outCount, YES, YES);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
}
return 0;
}
It prints out
firstName T#"NSString",&,N
lastName T#"NSString",&,N

Related

ObjectC-Why can't I get the properties correctly using the class_copyPropertyList function?

macOS 11.5.2
Xcode 13.2.1
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <iostream>
int main(int argc, const char * argv[]) {
#autoreleasepool {
Class clazz = NSClassFromString(#"NSString");
uint32_t count = 0;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
for (uint32_t i = 0; i < count; i++){
const char* name = property_getName(properties[i]);
std::cout << name << std::endl;
}
free(properties);
}
return 0;
}
I will take some snippets of the output:
hash
superclass
description
debugDescription
hash
superclass
description
debugDescription
vertexID
sha224
NS_isSourceOver
hash
superclass
description
debugDescription
...
From the output, we can find that properties such as hash, description, superclass, etc. will appear repeatedly several times, while some properties (such as UTF8String) do not appear in the result list.
How should I get the list of properties correctly?
I would appreciate it.
The reason you're not seeing UTF8String come up as a property is that it's not declared as a property in the main declaration of NSString, but rather in a category. On macOS 12.2.1/Xcode 13.2.1, the declaration of NSString boils down to this:
#interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
#property (readonly) NSUInteger length;
- (unichar)characterAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
#end
All other properties and methods on NSString are declared in categories immediately afterwards:
#interface NSString (NSStringExtensionMethods)
#pragma mark *** Substrings ***
/* To avoid breaking up character sequences such as Emoji, you can do:
[str substringFromIndex:[str rangeOfComposedCharacterSequenceAtIndex:index].location]
[str substringToIndex:NSMaxRange([str rangeOfComposedCharacterSequenceAtIndex:index])]
[str substringWithRange:[str rangeOfComposedCharacterSequencesForRange:range]
*/
- (NSString *)substringFromIndex:(NSUInteger)from;
- (NSString *)substringToIndex:(NSUInteger)to;
// ...
#property (nullable, readonly) const char *UTF8String NS_RETURNS_INNER_POINTER; // Convenience to return null-terminated UTF8 representation
// ...
#end
When a property is declared in a category on a type like this, it doesn't get emitted as an actual Obj-C property because categories can only add methods to classes, and not instance variables. When a category declares a property on a type, it must be backed by a method and not a traditional property.
You can see this with a custom class, too — on my machine,
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface MyClass: NSObject
#property (nullable, readonly) const char *direct_UTF8String NS_RETURNS_INNER_POINTER;
#end
#interface MyClass (Extensions)
#property (nullable, readonly) const char *category_UTF8String NS_RETURNS_INNER_POINTER;
#end
#implementation MyClass
- (const char *)direct_UTF8String {
return "Hello, world!";
}
- (const char *)category_UTF8String {
return "Hi there!";
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
Class clazz = NSClassFromString(#"MyClass");
printf("%s properties:\n", class_getName(clazz));
uint32_t count = 0;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
for (uint32_t i = 0; i < count; i++){
printf("%s\n", property_getName(properties[i]));
}
free(properties);
puts("-----------------------------------------------");
printf("%s methods:\n", class_getName(clazz));
Method *methods = class_copyMethodList(clazz, &count);
for (uint32_t i = 0; i < count; i++) {
SEL name = method_getName(methods[i]);
printf("%s\n", sel_getName(name));
}
free(methods);
}
return 0;
}
outputs
MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String
category_UTF8String
If you remove the actual implementations of the *UTF8String methods from the class, the property remains declared, but the category method disappears (because it doesn't actually have a synthesized implementation because of how categories work):
MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String
As for how to adjust to this: it depends on what purpose you're trying to fetch properties for, and why you might need UTF8String specifically.
NSString declares in its interface it implements methods, but it does not actually implement them, that is why when you print at runtime a list of the its methods it does not print what you expect.
The methods are implemented by other private classes, and when you initialize a new instance of NSString, instead of getting an instance of NSString you get an instance of that private class that have the actual implementation.
You can see that by printing the class type of a string, the following prints NSCFString or NSTaggedPointerString, not NSString:
NSString* aString = [NSString stringWithFormat: #"something"];
NSLog(#"%#", [aString class]);
And this prints __NSCFConstantString:
NSLog(#"%#", [#"a constant string" class]);
This pattern is called a class cluster pattern.
If you modify to dump the methods of the NSCFString you will get a "redactedDescription", it seems you are prevented to query these classes.

Having trouble with class extension in Objective C, mac OS. Getting error 'NSInvalidArgumentException', no visible #interface declares the selector

I'm working on exercise 3 on page 76 in Apple's developer pdf in the class categories and extensions section, "Programming with Objective C", found here: (https://developer.apple.com/library/mac/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html)
my XYZPerson header looks like this:
#import <Foundation/Foundation.h>
#interface XYZPerson : NSObject
#property (readonly) NSNumber *height;
#property (readonly) NSNumber *weight;
-(NSNumber *)measureHeight;
-(NSNumber *)measureWeight;
#end
My implementation file looks like this:
#import "XYZPerson.h"
#property (readwrite) NSNumber *height;
#property (readwrite) NSNumber *weight;
#end
/////////////////////
#implementation XYZPerson
-(NSNumber *)measureHeight{
if(!_height){
_height = [[NSNumber alloc] init];
}
return _height;
}
-(NSNumber *)measureWeight{
if(!_weight){
_weight = [[NSNumber alloc] init];
}
return _weight;
}
#end
And then in my main file I have:
#import <Foundation/Foundation.h>
#import "XYZPerson.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
XYZPerson *aPerson = [[XYZPerson alloc] init];
[aPerson setHeight: [NSNumber numberWithInt:72] ];
[aPerson setWeight: [NSNumber numberWithInt:190] ];
NSLog(#"%# %#",[aPerson measureHeight],[aPerson measureWeight]);
}
return 0;
}
There might be more than one error unrelated to the issue I brought up, I'm a huge novice at Objective C right now. The exact compiler error I am getting is on the line that says,
[aPerson setHeight: [NSNumber numberWithInt:72] ];
The compiler error reads, "ARC Semantic Issue. No visible #interface for 'XYZperson' declares the selector 'setWeight:'.
Oh doh.
You will not be able to call that method from OUTSIDE of your class implementation.
Overriding properties in class extensions in the .m file doesn't expose that to the world.
At least not to the compiler.
That's the whole point.
It's for when you want properties that are readonly to the world outside of that object, but internally to the object you still want to be able to conveniently do things.
You declared the property read-only in the header file, so to everything outside of the class, the properly is read-only, and thus a setHeight: method doesn't exist.
You have made he properties height and weight read only. Then in the main function you are using setWeight and setHeight.
You can't do that as that would be writing the weights but you explicitly set them to read only.
Place your properties in .m with class extension:
#interface XYZPerson ()
#property (readwrite) NSNumber *height;
#property (readwrite) NSNumber *weight;
#end
See Apple docs about class extension:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

'AClass' may not response to 'FunctionA' (Objective-C)

Something about inheritance, I have two class here: female, the subclass of human, and human, it can run but showing issues.
Two issues here:
main.m:29:10: 'human' may not respond to 'setSexy:'
main.m:30:10: 'human' may not respond to 'isSexy'
main.m
#import <Foundation/Foundation.h>
#import "female.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
human *sexyGirl = [[female alloc] init];
[sexyGirl setName:#"SexyGirl"];
[sexyGirl setGender:0];
[sexyGirl setSexy:1];
[sexyGirl isSexy];
}
return 0;
}
female.h
#import "human.h"
#interface female : human {
BOOL sexy;
}
#property BOOL sexy;
-(void)isSexy;
#end
human.h
#import <Foundation/Foundation.h>
#interface human : NSObject {
NSInteger *hp;
NSString *name;
BOOL gender;
}
#property (assign, nonatomic) NSInteger *hp;
#property (assign, nonatomic) NSString *name;
#property (assign, nonatomic) BOOL gender;
-(void) walk;
#end
There is no method name isSexy and setSexy in human class
You should change this human *sexyGirl = [[female alloc] init];
to this female *sexyGirl = [[female alloc] init];
when you do : human *sexyGirl = [[female alloc] init];
you upcast the female to human. all the extra information (extra methods) a female has are still there but no longer visible
you can only work with what a human has from then on OR you downcast the variable again.
[(female*)sexyGirl setSexy:1];
[(female*)sexyGirl isSexy]
alternativly never downcast it and declare it as female * all the way

How to avoid temp objects when returning NSString under ARC

I've got a class with two properties:
#interface Contact : NSObject {
NSString *lastname;
NSString *lastNameUpper;
}
I've declared lastname as a property (and synthesize it in the .m-file):
#property (nonatomic, retain) NSString *lastname;
However, I want to write my own method to access the lastNameUpper, so I declared a method:
- (NSString *) lastNameUpper;
and implemented it like this:
- (NSString *) lastNameUpper {
if (!lastNameUpper) {
lastNameUpper = [lastname uppercaseString];
}
return lastNameUpper;
}
This works all right, but as this is called quite often, a lot of temporary objects are called. Interestingly, the Instruments show a lot of "Malloc (4k)", and the number increase each time lastNameUpper is accessed. I can also see that the memory is allocated in objc_retailAutoreleaseReturnValue.
As this was working fine before I converted my project to ARC, I'm assuming that I have to make some ARC specific additions to the method signature, but I can't seem to be able to make it work.
Any suggestions?
0: you should copy your NSString properties:
#property (nonatomic, copy) NSString * lastname;
I'm guessing that returning the string is implemented by copying it.
nope. copy of an immutable string is a retain operation. just run it in the profiler to see how much this costs in time and memory. also, there's no implicit copy in this case.
Update
I tested this on Lion-64. uppercaseString may return a mutable string.
To be safe, you may consider assigning a copy of the result of uppercaseString: lastNameUpper = [[lastname uppercaseString] copy];. that may result in more or less allocations, depending on how you used the string in your implementation. if your properties copy, then a copy will be made each time you assign it. the easy generalization is to assign a copy, and the rest usually takes care of itself.
Test Program
// ARC enabled
#import <Foundation/Foundation.h>
#interface Contact : NSObject
{
NSString * lastname;
NSString * lastNameUpper;
}
#property (nonatomic, copy) NSString *lastname;
#end
#implementation Contact
#synthesize lastname;
- (NSString *) lastNameUpper {
if (!lastNameUpper) {
lastNameUpper = [lastname uppercaseString];
}
return lastNameUpper;
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
int n = 0;
while (n++ < 100000) {
Contact * c = [Contact new];
c.lastname = #"skjdhskjdhaksjhadi";
NSString * lastNameUpper = c.lastNameUpper;
}
}
return 0;
}
Override the - (void)setLastname:(NSString*)aLastname method (created automatically by #synthesize lastname, and set lastNameUpper as in the existing method.
Now create a lastNameUpper property (and synthesize it):
#property (nonatomic, readonly) NSString *lastNameUpper;
Since this will return the pointer of the lastNameUpper instance variable, no copies should be made whenever this is accessed.

Objective-C dot syntax or property value?

I keep reading that dot syntax is possible but I keep getting errors that the struct does not contain members I am referencing. Perhaps its not the dot syntax so I have included details of what I am doing in hopes of a solution:
// MobRec.h - used as the objects in the MobInfo array
#import <Foundation/Foundation.h>
#interface MobRec : NSObject {
#public NSString *mName;
#public int mSpeed;
}
#property (nonatomic, retain) NSString *mName;
#property (nonatomic) int mSpeed;
// MobDefs.h - array of MobRecords
#interface Mobdefs : NSObject {
#public NSMutableArray *mobInfo;
}
#property(assign) NSMutableArray *mobInfo; // is this the right property?
-(void) initMobTable;
#end
// MobDefs.m
#import "Mobdefs.h"
#import "Mobrec.h"
#implementation Mobdefs
#synthesize mobInfo;
-(void) initMobTable
{
// if I use traditional method I get may not respond
[mobInfo objectAtIndex:0 setmName: #"doug"];
// if I use dot syntax I get struct has no member named mName
mobInfo[1].MName = #"eric";
}
// main.h
MobDefs *mobdef;
// main.m
mobdef = [[Mobdefs alloc] init];
[mobdef initMobTable];
although both methods should work I get erros on both. What am I doing wrong? My best thoughts have been that I am using the wrong #property but I think I have tried all. I am performing alloc in main. Ideally I would like to for this use dot syntax and cant see why its not allowing it.
A couple of things: (edit: original point #1 removed due to error)
Although the dot syntax is supported, the array index syntax for NSArray is not. Thus, your call to mobInfo[1] will not be the same as [mobInfo objectAtIndex:1]; Instead, mobInfo will be treated as a simple C-style array, and that call would be almost guaranteed to result in a crash.
You should not define variables in your header file as you do in main.h. The line MobDefs *mobdef; belongs somewhere in main.m.
edit: Here is how it should look:
MobRec.h
#interface MobRec : NSObject {
NSString *mName;
int mSpeed;
}
#property (nonatomic, retain) NSString *mName;
#property (nonatomic) int mSpeed;
MobRec.m
#implementation MobRec
#synthesize mName;
#synthesize mSpeed;
#end
MobDefs.h
#interface MobDefs : NSObject {
NSMutableArray *mobInfo;
}
#property(assign) NSMutableArray *mobInfo;
-(void) initMobTable;
#end
MobDefs.m
#import "MobDefs.h"
#import "MobRec.h"
#implementation MobDefs
#synthesize mobInfo;
-(void) initMobTable
{
// option 1:
[(MobRec*)[mobInfo objectAtIndex:0] setMName:#"doug"];
// option 2:
(MobRec*)[mobInfo objectAtIndex:0].mName = #"eric";
// option 3:
MobRec *mobRec = [mobInfo objectAtIndex:0];
mobRec.mName = #"eric";
}
main.m
MobDef *mobdef = [[MobDefs alloc] init];
[mobdef initMobTable];
...
[mobdef release]; // don't forget!
You need to either cast the object returned by -objectAtIndex:, or use a method call on it:
[[mobInfo objectAtIndex: 0] setMName: #"doug"];
or
((Mobrec *) [mobInfo objectAtIndex: 0]).MName = #"doug";
[mobInfo objectAtIndex:0 setmName: #"doug"];
There is no objectAtIndex:setmName method, so you're going to have to explain what you think this is even supposed to do.
mobInfo[1].MName = #"eric";
Use objectAtIndex to look something up in an NSArray object.