How to initialize a subclass when the init method of the superclass is NS_UNAVAILABLE - objective-c

I'm attempting to write a test for an objective-c class. The class I'm trying to test is MyClass and it looks like this:
#interface MyClass : NSObject
- (void)dispatchEvent:(IMAAdEvent *)event;
#end
In order to test this dispatchEvent method, I need to pass in an instance of IMAAdEvent. The IMAAdEvent class comes from Google's library GoogleAds-IMA-iOS-SDK.
Unfortunately, I can't call init on this class because the init method is marked as NS_UNAVAILABLE. In XCode I get an error that reflects this:
'init' in unavailable
Ideally, I would like to make my own mock subclass of IMAAdEvent like this. Is there some way I can initialize my subclass without calling the unavailable init method on the superclass?:
#interface MockImaAdEvent : IMAAdEvent
#end
#implementation MockImaAdEvent
- (id)init {
// is there something I can do here so that I return an instances
// of the subclass without calling [super init]?
}
#end

As of Xcode 12.5, Swift is no longer able to use the previous solution. The compiler has started returning errors for init() is unavailable on lines of code where new init functions have been added. The Xcode 12.5 Release Notes indicate the following:
Clang now infers the availability of +new from availability annotations on -init methods. Since +new calls [[Foo alloc] init], +new isn’t available unless +init is available.
Despite this release note, there is still a valid workaround. By writing the mock class in Objective-C and using a bridging-header to bring it into Swift, the mock class can still call super.new to get a new instance of the parent class (and then customize the subclass from there).
Here is an example:
#interface MockInAppMessagingCampaignInfo ()
#property (strong, nonatomic) NSString *campaignNameValue;
#end
#implementation MockInAppMessagingCampaignInfo
+ (id)newWithCampaignName:(NSString *)campaignName {
MockInAppMessagingCampaignInfo *newObject = super.new;
newObject.campaignNameValue = campaignName;
return newObject;
}
- (NSString *)campaignName {
self.campaignNameWasCalled = YES;
return self.campaignNameValue ?: #"";
}
#end

If I use a method that's not called init then this seems to work. It still seems really weird to not call [super init] in this function, but it's working and returning a new instance of the MockImaAdEvent class
#interface MockImaAdEvent : IMAAdEvent {
enum IMAAdEventType type;
}
#property (nonatomic) enum IMAAdEventType type;
#end
#implementation MockImaAdEvent
#synthesize type;
- (id)initWithType:(NSInteger)_type {
type = _type;
return self;
}
#end
// in my test I can initialize like this:
MockImaAdEvent *adEvent = [[MockImaAdEvent alloc] initWithType:kIMAAdEvent_LOADED];

An alternative solution for Xcode 12.5 that doesn't require Objective-C bridging-header is to create a custom static initialiser that uses the objc runtime to invoke new.
I have used that for fakes that subclass objects with unavailable initialisers and works great.
Example:
static func customInit() -> SomeObjectFake {
let instance = SomeObjectFake.perform(NSSelectorFromString("new")).takeRetainedValue() as! SomeObjectFake
...
return instance
}

Related

Prevent class from being subclassed in Objective-c

How do I prevent a particular class from being subclassed?
I am not aware of such functionality (say final keyword for example) in the language. However Apple says it has done so for all classes in AddressBookUI.framework (in iOS)
For educational purposes, how can I achieve the same functionality, or how would they have done such thing?
From iOS7 Release Notes(Requires login) :
Here's one way: override allocWithZone: from within your "final" class (substituting MyFinalClassName for your actual class name) like this:
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (self != [MyFinalClassName class]) {
NSAssert(nil, #"Subclassing MyFinalClassName not allowed.");
return nil;
}
return [super allocWithZone:zone];
}
This will prevent a subclass that is not a member of MyFinalClassName from being alloc'ed (and therefore init'ed as well), since NSObject's allocWithZone: must be called eventually, and by refusing to call super from your "final" class, you will prevent this.
There's a simpler way to prevent subclassing in Xcode 6 as a result of Swift interop. To prevent Swift classes from being subclassed in Objective-C the objc_subclassing_restricted is added to all class definitions in the {ProjectName}-Swift.h file.
You can use this in your projects:
#if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
# define FOO_FINAL __attribute__((objc_subclassing_restricted))
#else
# define FOO_FINAL
#endif
FOO_FINAL
#interface Foo : NSObject
#end
#interface Bar : Foo
#end
The compiler will halt on the definition of Bar with Cannot subclass a class with objc_subclassing_restricted attribute
Here is possible solution:
#interface FinalClass : NSObject
#end
#implementation FinalClass
- (id)init
{
if (self.class != [FinalClass class]) {
return nil;
}
self = [super init];
if (self) {
// instance initialization
}
return self;
}
#end
#interface InvalidSubclass : FinalClass
#end
#implementation InvalidSubclass
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
#end
I'm not sure this is 100% guaranteed because it's runtime-checking anyway, but it should be enough to block and warn people that they should not subclass this. Subclass might skip superclass's init, but then the instance will not be usable because it's not fully initialised by superclass.
Something like the following will ensure that every time an "impossible subclass" calls +alloc, an object will be allocated that is an instance of FinalClass, and not the subclass. This is essentially what NSObject's +alloc method does, but here we specify an explicit class to create. This is how NSObject allocates instances (in Obj-C 2), but there is no guarantee this will always be the case, so you may want to add an appropriate -dealloc which calls object_dispose. This method also means you don't get a nil object back if you try to instantiate a subclass - you do get an instance of FinalClass.
#interface FinalClass: NSObject
//...
+ (id)alloc; // Optional
#end
// ...
#import <objc/runtime.h>
#implementation FinalClass
+ (id)alloc {
if (![self isMemberOfClass:[FinalClass class]]) {
// Emit warning about invalid subclass being ignored.
}
self = class_createInstance([FinalClass class], 0);
if (self == nil) {
// Error handling
}
return self;
}
#end
#interface InvalidSubclass : FinalClass
// Anything not in FinalClass will not work as +alloc will
// create a FinalClass instance.
#end
Note: I'm not sure I'd use this myself - specifying that a class shouldn't be subclassed is more in the nature of a design-contract with the programmer rather than an enforced rule at compile- or runtime.

NSObject call to self gives XCode error

I have a class WebServices that inherits from NSObject. I am using xcode4.2 and ARC turned on.
When I created the class, there was no other method in the NSObject lie viewDidLoad or init.
The issues is that when I try to call self.something or [self someMethod] Xcode flags my code red and complains with:
implicit conversion of Objective-C pointer type 'Class' to C pointer type 'struct obj_class*' requires a bridge cast
Please help. Why isn't cocoa like java where you call "this" and get the object you are in?
// WebService.h file
#interface WebService : NSObject
#property (weak, nonatomic) NSString * myString;
+(void) setAndPrintMyString:(NSString*) someString;
#end
//WebService.m file
#import "WebService.h"
#implementation WebService
#synthesize myString=_myString;
+(void) printMyString:(NSString*) someString{
[self setMyString:someString]; //XCode does not allow
NSLog(#"myString is set to %#",self.myString); //XCode dose not allow
}
#end
Declaring a method with + means that it is a class method. Within a class method self refers to the class itself, which in your case would be [WebService class]. If you declared and instance method (using -) then inside the method self would refer to the instance, which is what you want.
To set an instance variable - you need an instance
WebService *webService = [[WebService alloc] init];
webService.myString = #"some string";
Now to make your method work you need to declare it with a - instead of + which makes it an instance method
- (void)printMyString:(NSString *)someString
{
[self setMyString:someString];
NSLog(#"myString is set to %#",self.myString);
}
Now
[webService printMyString:#"boom"];
results in the instance variable myString being set to boom and the console logging out `myString is set to boom".
viewDidLoad method doesn't fit with NSObject subclass. It's a method for UI which will be there in UIViewController subclasses.
Now, Coming to point about self.something OR [self someMethod], That works perfectly well with NSObject subclasses. You need to show us the code, in which you are facing problem.
Just for your reference (I think you should start developing for iOS after going through this):
NSObject Class Reference
UIViewController Class Reference

Objective-C: why a custom object will be a zombie

I'm developing an app in Objective-C using ARC.
My simplified code looks like this:
ClassA (.m)
MyCustomClass *obj = [[MyCustomClass alloc] initWithValue1:#"abc" value2:1000];
MyViewController *vc = [[MyViewController alloc] initWithObject:obj];
// "vc" will become the first item of a UITabBarController
MyViewController (.h)
- (id)initWithObject:(MyCustomClass *)obj {
...
localReferenceToOjbect = obj;
...
}
- (void)viewWillAppear:(BOOL)animated {
// do something with "localRefernceToObject" <---
}
launching the app will result in a call to a zombie: when the ViewController is shown, the "obj" will be already deallocated and so i can't use it anymore.
my workaround is:
ClassA (.h)
#interface ClassA : UIViewController {
MyCustomClass *obj;
}
ClassA (.m)
obj = [[MyCustomClass alloc] initWithValue1:#"abc" value2:1000];
MyViewController *vc = [[MyViewController alloc] initWithObject:obj];
// "vc" will become the first item of a UITabBarController
is this the right way?! i don't think so: why i've to store an istance of an object that is useless for ClassA?
i can't get an explanation on what's actually happening. could you help me?
You're right in the fact that it is not logical to keep around a reference to obj in ClassA.
But if you need to keep around the reference to obj for MyViewController to use it, retain it in MyViewController, not in ClassA, because that's MyViewController that will use it.
The easiest way to do this is to transform your localReferenceToObject you use in MyViewController into a #property(retain) propertyToObject; (or #property(strong) propertyToObject if you use ARC) and access it in your MyViewController.m with self.propertyToObject (instead of localReferenceToObject, to be sure to call the property's setter and thus really retain the object).
This way, the object will be retained and kept around while your MyViewController instance is still alive.
[EDIT] If you want this property to be private, you can declare it in the class extension so that it is not accessible from other classes, as in the below example. See here in Apple's documentation for more details.
In your MyViewController.h header file
#interface MyViewController : UIViewController
// Here you write the public API in the .h / public header
// If you don't want your property to be visible, don't declare it there
#end
In your MyViewController.m file
#interface MyViewController ()
// This is the private API, only visible inside the MyViewController.m file and not from other classes
// Note the "()" to declare the class extension, as explained in Apple doc
#property(nonatomic, retain) MyCustomClass* referenceToObject; // Note: use strong (which is a synonym of retain) if you use ARC
#end
#implementation MyViewController
#synthesize referenceToObject = _referenceToObject; // not even needed with modern ObjC and latest LLVM compiler
- (id)initWithObject:(MyCustomClass *)obj
{
self = [super init];
if (self) {
...
self.referenceToOjbect = obj;
...
}
return self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// do something with "self.refernceToObject"
}
// This memory management code is only needed if you don't use ARC
-(void)dealloc
{
self.referenceToObject = nil; // release memory
[super dealloc];
}
Personally, as suggested by Apple in some WWDC sessions, I now really rarely use instance variables and prefer the use of properties instead, either public in the .h or private in the .m.
If you use ARC, you can still use an instance variable instead of a property as ARC will retain it for you, but as long as you make sure your instance variable is declared as strong and not weak.

About moving few methods to the superclass

I need to move the same method from 4 different classes to the superclass.
Such methods are exactly the same except for the type of a variable declared in them:
For example, in the method in the first class I have
FirstClass var = [[FirstClass alloc] init]
in the second class
SecondClass var = [[SecondClass alloc] init]
and so on.
What's the best way to implement this variation in the superclass ?
Should I use NSClassFromString in the superclass and get each string from each method in the subclasses?
thanks
I'm not 100% sure I get what you mean. So I could be answering the wrong question
If inside your class you need to use an object (I've called it worker below) to do your work, but the class of this object is not known til later, you can use dependency injection (DI).
MyClass.h
#interface MyClass : NSObject
#property (nonatomic, retain) id<WorkerInterface> worker;
#end
MyClass.m
#implementation MyClass
#synthesize worker = _worker;
- (void)myMethod;
{
[self.worker doSomething];
}
// You could also provide a default class to use if one is not passed in
//
// - (id<WorkerInterface)worker;
// {
// if (!_worker) {
// _worker = [[DefaultWorker alloc] init];
// }
// return _worker;
// }
#end
Now whenever I instantiate this class I can simply pass in the appropriate object to be used e.g:
MyWorkerClass *worker = [[MyWorkerClass alloc] init]; // <- Conforms to #protocol(WorkerInterface)
MyClass *instance = [[MyClass alloc] init];
instance.worker = worker;
[instance doSomething];
If all the different types of iVar's you intend on initializing in the subclasses are descended from a common class, then I'd store that class in the super, or else just store it as an id. Then, setup a property accessor in each of your subclasses the casts the iVar as you need it.
#interface superClass : NSObject{
id _superIvar;
}
#end
#implementation superClass : NSObject
....super's code....
#end
Now in the implementation of the subclass declare a property in a category, shown below (or in the interface, if you want it public)
#interface subClass (private)
#property (strong) ClassType *superIvar;
#end;
#implementation
- (void) setSuperIvar:(ClassType *)superIvar{
_superIvar = superIvar;
}
- (ClassType *) superIvar{
return (ClassType *) _superIvar;
}
- (void) someMethodThatUsesSuperIvar{
[self.superIvar doSomething];
}
#end
Alternatively, if you don't want to open your _superIvar to direct access, you can set a property on the superclass and access through the property on the subclass. But in this way you can easily access super's ivars cast to the appropriate type.

Objective-C inheritance; calling overridden method from superclass?

I have an Objective-C class that has a method that is meant to be overridden, which is uses in a different method. Something like this:
#interface BaseClass
- (id)overrideMe;
- (void)doAwesomeThings;
#end
#implementation BaseClass
- (id)overrideMe {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void)doAwesomeThings {
id stuff = [self overrideMe];
/* do stuff */
}
#end
#interface SubClass : BaseClass
#end
#implementation SubClass
- (id)overrideMe {
/* Actually do things */
return <something>;
}
#end
However, when I create a SubClass and try to use it, it still calls overrideMe on the BaseClass and crashes due to doesNotRecognizeSelector:. (I'm not doing a [super overrideMe] or anything stupid like that).
Is there a way to get BaseClass to call the overridden overrideMe?
What you are describing here should work so your problem is likely elsewhere but we don't have enough information to help diagnose it.
From your description, I'd say either the instance you're messaging is not the class you think it is or you made some typo in your code when declaring the method names.
Run your application under gdb, add a symbolic breakpoint on objc_exception_throw, reproduce your problem. Once your process has stopped on the "doesNotRecognizeSelector" exception, print object description and it's class.
Or log it before calling -overrideMe:
NSLog(#"object: %# class: %#", obj, [obj class])
Write a category for BaseClass to override the method.
#interface BaseClass (MyCategory)
- (id) overrideMe;
#end
#implementation BaseClass (MyCategory)
- (id) overrideMe
{
/* Actually do things */
return <something>;
}
#end
Now all instances of BaseClass will respond to selector overrideMe with the new implementation.