Swift Compile Error, Subclassing Nsvalue, Using Super.Init(Nonretainedobject:)

Swift compile error, subclassing NSValue, using super.init(nonretainedObject:)

NSValue(nonretainedObject:) isn't a designated initializer. The only initializer listed in the NSValue reference (and hence the designated initializer) is NSValue(value:CConstVoidPointer, withObjCType type:CString)

The other constructors are all convenience constructors derived from class helper methods.

You might try:

init(baseObject: T) {
super.init(bytes:&baseObject, withObjCType:"^v")
}

"^v" is the type string returned by an NSValue created with valueWithNonRetained....

Unfortunately, I'm not coming up with an appropriate way to pass baseObject as a CConstVoidPointer.

Barring that, the best thing I can come up with is to wrap NSValue instead of subclassing it:

class ID<T:AnyObject> {
let wrapped:NSValue

init(baseObject:T) {
wrapped = NSValue(nonretainedObject:baseObject)
}
}

Finally got something to work, but it's a little bit ugly, basically add a convenience constructor that wraps the nonretainedObject constructor and then use that in your subclass:

extension NSValue {
convenience init<T:AnyObject>(unretained:T) {
self.init(nonretainedObject:unretained)
}
}

class ID<T>:NSValue {
convenience init<T:AnyObject>(unretained:T) {
self.init(unretained:unretained)
}
}

Depending on what you're actually trying to do the category alone might be sufficient?

Subclass Objective-C class without linking with the superclass?

This is entirely possible, and not actually very difficult to do. Here's a simple example with NSValue:

@interface MySubclass : NSObject

-(void) someMethod;

@end

@implementation MySubclass

+(void) load {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"

class_setSuperclass(self, NSClassFromString(@"NSValue"));

#pragma clang diagnostic pop
}

-(void) someMethod {
NSLog(@"%@", [self superclass]);
}

-(const char *) objCType {
return @encode(int);
}

-(void) getValue:(void *)value {
if (value) {
*((int *) value) = 10;
}
}

@end

int main() {
MySubclass *theSubclass = [MySubclass new];
[theSubclass someMethod];

NSLog(@"%i", [theSubclass isKindOfClass:[NSValue class]]);
}

class_setSuperclass, while deprecated, still has an implementation as of OS X 10.10, and can be done after the class has been registered. I have not fully explored the effects of changing a class's superclass post the creation of an instance of that class, however, so take caution if you are to do this at any point past +load or +initialize.

This may make it difficult to call methods on super, but this could be gotten around by simply declaring a category on NSObject (or whichever superclass you choose to inherit from in your implementation).

Swizzling +[NSObject initialize] method to log each class instantiation

After Edit Answer

You are correct about use of +[NSObject initialize] method for tracking the first use of a class. I don't know anything more appropriate for that. The swizzling would look like this:

#import <objc/runtime.h>

@implementation NSObject (InitializeLog)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getClassMethod(self, @selector(initialize));
Method swizzledMethod = class_getClassMethod(self, @selector(tdw_initializeLog));

method_exchangeImplementations(originalMethod, swizzledMethod);

});
}

+ (void)tdw_initializeLog {
const char *className = class_getName(self);
printf("Initializing %s\n", className);
[self tdw_initializeLog];
}

@end

There are a few things to be advised about:

  1. initialize doesn't fallback to the NSObject implementation (which is swizzled above) if derived classes have this method implemented AND don't have [super initialize]; called. Thus for any custom class inherited from Cocoa classes either don't implement this method OR call [super initialize]; somewhere in your implementation:
+ (void)initialize {
[super initialize];
...
}

  1. Cocoa classes are rarely as straightforward as they look like. Quite a lot of interfaces and classes are hidden under the same name and sometimes the logs will be somewhat misleading (e.g. in place of NSNumber you will get NSValue class reported). Thus, take any logging out of Foundation classes with a grain of salt and always double-check where it comes from (also be ready that those classes won't be reported at all).

  2. First use of NSLog also triggers some classes to initialise themselves and it make them to call +[NSObject initialize]. In order to avoid an infinite loop or bad_access errors I decided to use printf to log the fact of initialisation in my implementation.



Original Answer

The + (void)initialize method has very little to do with objects instantiation, since it gets called for each Objective-C class shortly before it's first time used in your client code. It might be called multiple times if subclasses of a given class don't have this method implemented and never gets called afterward. Thus it's just a bad choice if you want to track objects instantiation.

However there are still a few options you may want to employ to track occasions of objects instantiation.

Swizzling -[NSObject init]

First, I would consider init method of NSObject:

#import <objc/runtime.h>

@implementation NSObject (InitLog)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(init));
Method swizzledMethod = class_getInstanceMethod(self, @selector(initLog_tdw));

method_exchangeImplementations(originalMethod, swizzledMethod);

});
}

- (instancetype)initLog_tdw {
self = [self initLog_tdw];
if (self) {
const char *className = class_getName([self class]);
NSLog(@"Instantiating %s", className);
}
return self;
}

@end

It will work fine as long as instances falls back to the -[NSObject init] method. Unfortunately quite a lot of Cocoa classes don't do that. Consider the following scenario:

NSObject *obj = [NSObject new]; // NSLog prints "Instantiating NSObject"
NSString *hiddenStr = [[NSMutableString alloc] initWithString:@"Test"]; // NSLog is silent
NSURL *url = [[NSURL alloc] initWithString:@"http://www.google.com"]; // NSLog is silent

-[NSURL initWithString:] and -[NSMutableString initWithString:] somehow avoids NSObject's default constructor being called. It will still work for any custom classes which don't have any fancy initialisation:

@implementation TDWObject

- (instancetype)initWithNum:(int)num {
self = [super init];
if (self) {
_myNum = num;
}
return self;
}

@end

TDWObject *customObj = [TDWObject new]; // NSLog prints "Instantiating TDWObject"
TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Instantiating TDWObject"

Swizzling +[NSObject alloc]

Alternatively you can swizzle the alloc method:

#import <objc/runtime.h>

@implementation NSObject (AllocLog)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getClassMethod(self, @selector(alloc));
Method swizzledMethod = class_getClassMethod(self, @selector(tdw_allocLog));

method_exchangeImplementations(originalMethod, swizzledMethod);

});
}

+ (instancetype)tdw_allocLog {
id allocatedObject = [self tdw_allocLog];
if (allocatedObject) {
const char *className = class_getName([allocatedObject class]);
NSLog(@"Allocating %s", className);
}
return allocatedObject;
}

@end

It will intercept almost all Cocoa classes instantiation (the exception must be some of the fabric methods, where class-specific optimisation takes place, e.g. +[NSNumber numberWith..] family of methods), but there are other problems to be aware of. The allocated instances returned from alloc method are not always that straightforward. E.g. for NSMutableString example above NSLog will print NSPlaceholderMutableString:

TDWObject *customObj = [TDWObject new]; // NSLog prints "Allocating TDWObject"
TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Allocating TDWObject"
NSObject *obj = [NSObject new]; // NSLog prints "Allocating NSObject"
NSString *hiddenStr = [[NSMutableString alloc] initWithString:@"Test"]; // NSLog prints "Allocating NSPlaceholderMutableString"
NSURL *url = [[NSURL alloc] initWithString:@"http://www.google.com"]; // NSLog prints "Allocating NSURL"

That's is because Foundation framework uses Class Cluster design pattern heavily and instances returned by alloc are often some kind of abstract factories, which are later leveraged by Cocoa classes to make a concrete instance of the class requested.


Both approaches have their own downsides, but I struggle to come up with anything more concise and reliable.

NSArray of weak references (__unsafe_unretained) to objects under ARC

As Jason said, you can't make NSArray store weak references. The easiest way to implement Emile's suggestion of wrapping an object inside another object that stores a weak reference to it is the following:

NSValue *value = [NSValue valueWithNonretainedObject:myObj];
[array addObject:value];

Another option: a category that makes NSMutableArray optionally store weak references.

Note that these are "unsafe unretained" references, not self-zeroing weak references. If the array is still around after the objects are deallocated, you'll have a bunch of junk pointers.



Related Topics



Leave a reply



Submit