What's happening when you synthesize a subclass' variable? - objective-c

I have a superclass and a subclass. I can access the variable some_property (declared in the superclass) via self.some_property in the subclass.
However if I try to access the instance variable directly with _some_property, I'll get the error 'Use of undeclared identifier _some_property...'.
Using #synthesize some_property = _some_property silences this warning.
Whats going on when I re-synthesize the property?

You are creating another ivar named _some_property — and also overriding the getter method to return the value of this new ivar. The compiler gives you an error about this if the base class's #implementation (i.e. the implicit declaration of its _some_property ivar) is visible at the site of the #synthesize in the subclass.
(By the way, don't do this!)
You can demonstrate to yourself by inspecting the Obj-C runtime:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface Base : NSObject
#property id foo;
#end
#interface Derived : Base
#end
#implementation Derived
#synthesize foo=_foo; // the compiler doesn't know about Base's _foo yet, so this is OK...
- (instancetype)init {
if ((self = [super init])) {
_foo = #"I'm derived";
}
return self;
}
#end
#implementation Base // after Derived to avoid the error
- (instancetype)init {
if ((self = [super init])) {
_foo = #"I'm base";
}
return self;
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
Derived *obj = [Derived new];
NSLog(#"getter returns %#", obj.foo);
unsigned int count = 0;
// Examine Base ivars
NSLog(#"Base ivars:");
Ivar *ivars = class_copyIvarList([Base class], &count);
for (unsigned int i = 0; i < count; i++) {
NSLog(#" %s = %#", ivar_getName(ivars[i]), object_getIvar(obj, ivars[i]));
}
// Examine Derived ivars
NSLog(#"Derived ivars:");
ivars = class_copyIvarList([Derived class], &count);
for (unsigned int i = 0; i < count; i++) {
NSLog(#" %s = %#", ivar_getName(ivars[i]), object_getIvar(obj, ivars[i]));
}
}
return 0;
}
Output:
getter returns I'm derived
Base ivars:
_foo = I'm base
Derived ivars:
_foo = I'm derived

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.

Objective-C Runtime - Run Code at Deallocation of Any Object

I was reading this article by Jeff Kelley and trying to do the same. However the code was written before ARC was adopted and now fails to compile.
http://blog.slaunchaman.com/2011/04/11/fun-with-the-objective-c-runtime-run-code-at-deallocation-of-any-object/
The main problem is in this part of the printout, some casting errors and then blocked release messages. I found it to be a very interesting example but I can't seem to get it to work.
The problems are:
0. Autosynthesized property 'block' will use synthesized instance variable '_block', not existing instance variable 'block' on the #implementation JKBlockExecutor
1. Cast of block pointer type 'voidBlock' (aka 'void (^)(void)') to C pointer type 'const void *' requires a bridged cast and Cast of C pointer type 'void *' to block pointer type 'typeof (aBlock)' (aka 'void (^__strong)(void)') requires a bridged cast" on the block = Block_copy(aBlock); line
2. Cast of block pointer type 'voidBlock' (aka 'void (^)(void)') to C pointer type 'const void *' requires a bridged cast on Block_release(block);
typedef void (^voidBlock)(void);
#interface JKBlockExecutor : NSObject {
voidBlock block;
}
#property (nonatomic, readwrite, copy) voidBlock block;
- (id)initWithBlock:(voidBlock)block;
#end
#implementation JKBlockExecutor
#synthesize block;
- (id)initWithBlock:(voidBlock)aBlock
{
self = [super init];
if (self) {
block = Block_copy(aBlock);
}
return self;
}
- (void)dealloc
{
if (block != nil) {
block();
Block_release(block);
}
[super dealloc];
}
#end
This is where he creates a category on NSObject.
const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;
#interface NSObject (JK_RunAtDealloc)
- (void)runAtDealloc:(voidBlock)block;
#end
#implementation NSObject (JK_RunAtDealloc)
- (void)runAtDealloc:(voidBlock)block
{
if (block) {
JKBlockExecutor *executor = [[JKBlockExecutor alloc] initWithBlock:block];
objc_setAssociatedObject(self,
runAtDeallocBlockKey,
executor,
OBJC_ASSOCIATION_RETAIN);
[executor release];
}
}
#end
This is how you execute the example.
NSObject *foo = [[NSObject alloc] init];
[foo runAtDealloc:^{
NSLog(#"Deallocating foo!");
}];
[foo release];
Or another way to get other information.
NSObject *foo = [[NSObject alloc] init];
__block id objectRef = foo;
[foo runAtDealloc:^{
NSLog(#"Deallocating foo at address %p!", objectRef);
}];
[foo release];
Can this code be fixed somehow? I took out all the release messages to no avail.
Code below builds and works (or at least seems so), and prints "Deallocating foo!" when I expect it to print it. Part 1:
typedef void (^voidBlock)(void);
#interface JKBlockExecutor : NSObject {
voidBlock block;
}
#property (nonatomic, readwrite, copy) voidBlock block;
- (id)initWithBlock:(voidBlock)block;
#end
#implementation JKBlockExecutor
#synthesize block = block;
- (id)initWithBlock:(voidBlock)aBlock
{
self = [super init];
if (self) {
block = [aBlock copy];
}
return self;
}
- (void)dealloc
{
if (block != nil) {
block();
block = nil;
}
}
#end
Part 2:
const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;
#interface NSObject (JK_RunAtDealloc)
- (void)runAtDealloc:(voidBlock)block;
#end
#implementation NSObject (JK_RunAtDealloc)
- (void)runAtDealloc:(voidBlock)block
{
if (block) {
JKBlockExecutor *executor = [[JKBlockExecutor alloc] initWithBlock:block];
objc_setAssociatedObject(self,
runAtDeallocBlockKey,
executor,
OBJC_ASSOCIATION_RETAIN);
}
}
#end
Testing if it works:
#autoreleasepool {
NSObject *foo = [[NSObject alloc] init];
[foo runAtDealloc:^{
NSLog(#"Deallocating foo!");
}];
}
EDIT
Changed Block_release(block); to block = nil;
If you want to know more about the code below ,please go to Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object
Part 1:create one class: the object We Want To Be Released When That Happens--- this class is like an event:when the target obj dealloc,it happens。use block to execute the event 。
// .m file
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// the object We Want To Be Released When That Happens--- this class is like an event:when the target obj dealloc,it happens。use block to execute the event 。
typedef void (^voidBlock)(void);
#interface CYLBlockExecutor : NSObject
- (id)initWithBlock:(voidBlock)block;
#end
// .m file
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// the object We Want To Be Released When That Happens--- this class is like an event:when the target obj dealloc,it happens。use block to execute the event 。
#import "CYLBlockExecutor.h"
#interface CYLBlockExecutor() {
voidBlock _block;
}
#implementation CYLBlockExecutor
- (id)initWithBlock:(voidBlock)aBlock
{
self = [super init];
if (self) {
_block = [aBlock copy];
}
return self;
}
- (void)dealloc
{
_block ? _block() : nil;
}
#end
Part 2:core code:use runtime to realize cyl_runAtDealloc method
// CYLNSObject+RunAtDealloc.h file
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// use runtime to realize cyl_runAtDealloc method
#import "CYLBlockExecutor.h"
const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;
#interface NSObject (CYLRunAtDealloc)
- (void)cyl_runAtDealloc:(voidBlock)block;
#end
// CYLNSObject+RunAtDealloc.m file
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// use runtime to realize cyl_runAtDealloc method
#import "CYLNSObject+RunAtDealloc.h"
#import "CYLBlockExecutor.h"
#implementation NSObject (CYLRunAtDealloc)
- (void)cyl_runAtDealloc:(voidBlock)block
{
if (block) {
CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];
objc_setAssociatedObject(self,
runAtDeallocBlockKey,
executor,
OBJC_ASSOCIATION_RETAIN);
}
}
#end
How to use :
#import "CYLNSObject+RunAtDealloc.h"
then
NSObject *foo = [[NSObject alloc] init];
[foo cyl_runAtDealloc:^{
NSLog(#"Deallocating foo!");
}];

Objective C arc -- keeping a reference to a Class

I want to have an ivar of type Class, and to keep the pointer around after it is passed in. But no matter what I do, arc will not let me do that. For instance, if I declare
#property (nonatomic, strong) Class myClass;
the compiler decides that myClass should be unsafe_unretained. And if I try this:
-(id) initWithClass: (Class) passedInClass {
if ((self = [super init])) {
self.myClass = passedInClass;
}
return self;
}
what happens is that even if the class is non-nil in the calling code, it is nil within the init method.
Short of turning off arc, is there any way to get around this?
EDIT: This question is just wrong. It does work. See the accepted answer.
Works as advertised with Xcode 4.3.2 targeting 10.7 and 5.1:
#interface MOYNObject : NSObject
#property (nonatomic, strong, readwrite) Class myClass;
#end
#implementation MOYNObject
#synthesize myClass;
- (id)initWithClass:(id)pClass
{
self = [super init];
if (self != nil)
self.myClass = pClass;
assert(self.myClass);
CFShow((__bridge const void*)self.myClass);
return self;
}
#end
int main(int argc, const char* argv[]) {
#autoreleasepool {
MOYNObject * o = [[MOYNObject alloc] initWithClass:[NSString class]];
// ...
}
return 0;
}
Are you ahead of or behind 4.3.2?

Potential problems in objective-c code

Here is a small piece of code. Posted by Russian company Yandex as a part of their interview. What are potential problems here? It looks very simple, should be hidden problems I can not see.
First header
//Foo.h
#import <Cocoa/Cocoa.h>
#interface Foo : NSObject
{
NSString* str;
static int i = 0;
}
- (NSString*) str;
#end
Another file
//Foo.m
#import "Foo.h"
#implementation
- (id) init
{
return [self initWithStr:"number:" someInt:6];
}
- (id) initWithStr:(NSString*)theStr someInt:(int)value
{
self = [super init];
str = [NSString stringWithFormat:#"%#%d", theStr, value];
return self;
}
- (NSString*) str
{
return str;
}
- (void) setStr:(NSString*)theStr
{
str = theStr;
}
#end
And the last file
//main.m
#import <Cocoa/Cocoa.h>
#import "Foo.h"
int main(int argc, char *argv[])
{
Foo objA;
NSLog([objA str]);
[objA setStr:#"hello world!"];
NSLog([objA str]);
Foo* objB = [[Foo alloc] init];
Foo* objC = [[Foo alloc] initWithStr:#"My magic number:" value:265];
objB = objC;
NSLog([objB str]);
[objA release];
[objB release];
[objC release];
return 0;
}
In another file:
#implementation
implementation of what? must specify.
In the last file:
Foo objA;
NSLog([objA str]);
[objA setStr:#"hello world!"];
NSLog([objA str]);
This will crash, local variable Foo objA is not initialized, it would be fine it was set to nil, since messages to nil are ok in objective c but it is not.
Here:
[objA setStr:#"hello world!"];
That method will give a compile warning since that method is not declared in the interface, but it will still call the method.
Here:
- (id) init
{
return [self initWithStr:"number:" someInt:6];
}
Missing # for the string #"number:"
Here:
objB = objC;
You just leaked objB, since there is now no valid reference to release the previous allocation.
[objA release];
This was never allocated!
[objB release];
[objC release];
The second one will crash since they both refer to the same object, and the retain count is only 1.
The first file also has some potential issues such as declaring a method that appears to be a getter without declaring a property for the ivar, same with the setter, would be better to just declare a property.
#interface Foo : NSObject
{
NSString* str;
static int i = 0;
}
You cann't define static int i = 0; here. Type name does not allow storage class to be specified Foo.h
Also, the setter needs to release the previous string and retain the new one.
- (void) setStr:(NSString*)theStr
{
if(str) {
[str release];
}
str = [theStr retain];
}

Request for member 'pData' with BOOL value TRUE is not a structure or union-Objective C

I could not use the pData[4096] to pass it to the other function from main.
data.m
------
#implementation data
static int msgID;
static char pData[4096]="\0";
+ (void)initialize
{
//some initialisations
msgID =123;
}
-(void)SwapEndian:(uint8_t*)pData withBOOLValue:(BOOL)bIsAlreadyLittleEndian
{
NSLog("%s %s",pData,bIsAlreadyLittleEndian);
}
#end
main.m
-------
[dat SwapEndian:dat.pData withBOOLValue:TRUE];
I am getting pData undeclared. As pData is declared as static inside the Data
implementation i tried with dat.pData to pass it from main.But when i do it i am getting
Request for member 'pData' with BOOL value TRUE is not a structure or union.
It is difficult to determine what the code is supposed to do, but here is how to create an Objective-C object that holds an integer identifier and a 4096-character array. Please note that this sort of thing is usually discouraged. Unless you have a really specific reason for using int and char[], the identifier should be NSInteger and the data should be an NSData or NSString object.
I have also used some of the "standard" naming conventions. If you are writing Cocoa code, it helps to drink a lot of the Kool-Aid.
Message.h:
#interface Message : NSObject
{
int identifier;
char data[4096];
}
#property (nonatomic, assign) int indentifier;
#property (nonatomic, readonly) char * data;
- (void)swapEndian:(BOOL)flag;
#end
Message.m:
#implementation Message
#synthesize identifier;
#synthesize data;
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
identifier = 0;
data[0] = '\0';
return self;
}
- (void)swapEndian:(BOOL)flag
{
NSLog(#"%s %d", data, flag);
}
#end
main.m:
#import "Message.h"
...
Message * message = [[[Message alloc] init] autorelease];
message.identifier = 123;
[message swapEndian:YES];