How do you make "Class" type safe in Objective-C? - objective-c

I need to write a method that returns a class.
#implementation MyFactory
- (Class)defaultViewClass {
return [MyView class];
}
#end
The returned class is not type safe. You can cast it to any random class. You can also call any random initializer method without a compiler error.
MyFactory *factory = [MyFactory new];
Class viewClass = [factory defaultViewClass];
RandomView *view = [[viewClass alloc] initWithRandomStuff];
How do I redefine my method to return a Class of a particular type? I want to do something like this, but the compiler complains that it can't return specific class types.
- (AbstractViewClass)defaultViewClass {
return [ConcreteViewClass class];
}
The reason why I need to deal with classes rather than object pointers is because I need to call a static function polymorphically.

You won't get the benefit of compile-time type checking when working with Class objects in Objective-C due to the dynamic nature of the language. However, you can enforce such constraints in a strictly typed language like Swift.
protocol AbstractView {
init(parameter: Int)
}
class ConcreteView: AbstractView {
required init(parameter: Int) { }
}
class ViewFactory {
func defaultViewClass() -> AbstractView.Type {
return ConcreteView.self
}
}
let factory = ViewFactory()
let viewClass = factory.defaultViewClass()
let view = viewClass.init(parameter: 0)
print("\(view.self)", terminator: "") // Prints "ConcreteView"
AbstractView is a protocol as there is no concept of an abstract class in Swift, although it could just as easily be a class.

Related

Swift bridging returns parent class object instead of derived class

Lets suppose I have a class in Objective C, via bridging I want to use in my swift project.
#implementation MyClass
+ (instancetype)startVideo:(NSInt *)videoName
{
return [[MyClass alloc] initWithName:videoName
offset:#"0"]
}
- (instancetype)initWithName:(NSString *)name
offset:(NSString *)offset
{
self = [super init];
return self;
}
Now there are two constructors will be available in the swift version after bridging
class MyClass{
public convenience init!(videoName: String!)
public init(name: String!, offset: String)
}
I derive class from MyClass in my swift project
class MyClass2 : MyClass{
var purposeOfClass: String = "Child class"
}
Create an object of MyClass2 using convenience method
var obj = MyClass2("Swift.mov")
print(obj.purposeOfClass) // You will get exception at this point, because the constructor returned Parent's class object.
Here my question is why the bridging conversion make the static method initializer convenience initializer if the derived class return object can't be checked.
In a Swift init method the truncated parameter name from Objective-C
- (instancetype)initWithFoo:(NSString *)foo
to Swift
init(foo : String)
is mandatory. You have to write
let obj = MyClass2(videoName:"Swift.mov")
print(obj.purposeOfClass)

How to check if an id is an instance or a class?

Say I have a method:
- (void)method:(id)anObject
{
// do something
}
As we all know Objective-C classes or objects too. So I've been wondering how do I determine if the id passed inside the method an INSTANCE of a class or a CLASS itself? Because it may be used it like:
[object method:[NSObject new]];
or:
[object method:[NSObject class]];
how can I find out that the passed argument is an instance? I can't check it just by sending class message because for a class it will return the same result as for an instance
EDITED
The approach provided by Alexander does not suits me because I don't know what class will be passed so I can't check for membership of a particular class. I just need to know if it is a class (no matter what class) or is an instance (of no matter what class)
Classes return self from the class message, instances return their class. So just test if the result from class is identical to the receiver:
- (void)method:(id)anObject
{
if (anObject == [anObject class]) {
// it's a class
} else {
// it's an instance
}
}
Edit: Though simple, there are two subtle problems with above code:
The receiver has to implement class. All NSObject (and NSProxy) derived classes do so but for custom root classes this might not be the case. Extremely rare.
If a class does not return self the code would break. This might be the case with classes of objects that are being observed (KVO). Not so rare.
Here's code that will always work and is immune to these issues:
#import <objc/runtime.h>
- (void)method:(id)anObject
{
if (class_isMetaClass(object_getClass(obj))) {
// it's a class
} else {
// it's an instance
}
}

How to check if a class level method is in superclass or in subclass in Objective C?

I have class tructure like this
#interface SuperClass: NSObject
+ (void) someName;
#end
#interface MyClass: SuperClass
#end
There is the case that i only want to call the someName if it is a class method of MyClass not MyClass's superclass. Since [[MyClass class] respondsToSelector:#selector(someName)] return YES if a class or its super response to the selector. How to tell that MyClass doesnt contain tosomeName?
In my application i want to print the string that contains chains of string return from a class method.
Take abve class structure as a example, i want to print something like:
somenameFromSuper.somenameFromClass.someNameFromeSubClass.
if a class doesnot implement someName method, i want to replace it by `notavailable, for ex:
somenameFromSuper.notavailable.someNameFromeSubClass.
_Bool class_implementsMethodForSelector( Class cls, SEL selector )
{
unsigned methodsCount;
Method* methods = class_copyMethodList(cls, &methodsCount);
for (unsigned methodIndex=0; methodIndex<methodsCount; methodIndex++)
{
if (method_getName(methods[methodIndex]) == selector)
{
break;
}
}
free(methods);
return methodsIndex<methodsCount;
}
…
Class classToTest = …;
classToTest = object_getClass(classToTest); // For checking class methods
if (class_implementsMethodForSelector(classToTest, #selector(someName))
{
…
}
else
{
…
}
Typed in Safari.
Edit: Made a function of it. Still typed in Safari.

How to call an Objective-C singleton from Swift?

I have an objective-C singleton as follows:
#interface MyModel : NSObject
+ (MyModel*) model;
...
+ (MyModel*) model
{
static MyModel *singlton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^ {
singlton = [[MyModel alloc] initSharedInstance];
});
return singlton;
}
- (MyModel*) initSharedInstance
{
self = [super init];
if (self)
etc.
}
Which gets called in multiple places within the GUI code as:
[[MyModel model] someMethod];
And therefore the model will get created as a consequence of whichever part of the GUI happens to reference it first.
I'm not sure how to implement the equivalent of accessing the class via [[MyModel model] someMethod] in Swift as all examples of using Swift involve creating an object using an initializer and when Objective C class method code is converted to Swift initializer code there is a problem with it not working when the method does not have parameters.
UPDATE
++++++++++
The workaround below is only necessary if you name your singleton method with a name derived from the suffix of the class name i.e. the OPs question the method name is model and the class is called MyModel.
If the method is renamed to something like singleton then it is possible to call it from Swift just like this:
let m = MyModel.singleton()
+++++++++++
I don't know if this is good/bad practice but I was able to get around the problem with initializer conversion not working when there are no parameters by adding a dummy init method. So using the code from the other answer as an example:
#interface XYZThing : NSObject
+ (XYZThing*) thing;
+ (XYZThing*) thingWithFoo:(int)foo bar:(int)bar;
#end
#implementation XYZThing
+ (XYZThing*) thing
{
NSLog(#"This is not executed");
return nil;
}
+ (XYZThing*)thingWithFoo:(int)foo bar:(int)bar
{
NSLog(#"But this is");
return nil;
}
#end
...
let thing = XYZThing()
let otherThing = XYZThing(foo:3, bar:7)
With this code above the thing method is not called, but the thingWithFoo:bar: method is.
But if it is changed to this then now the thing method will get called:
#interface XYZThing : NSObject
+ (XYZThing*) init;
+ (XYZThing*) thing;
+ (XYZThing*) thingWithFoo:(int)foo bar:(int)bar;
#end
#implementation XYZThing
+ (XYZThing*) init
{
return nil;
}
+ (XYZThing*) thing
{
NSLog(#"Now this is executed");
return nil;
}
+ (XYZThing*)thingWithFoo:(int)foo bar:(int)bar
{
NSLog(#"And so is this");
return nil;
}
#end
...
let thing = XYZThing()
let otherThing = XYZThing(foo:3, bar:7)
If the Swift compiler mistakenly identifies a method as a class factory method, you can use the NS_SWIFT_NAME macro, passing the Swift signature of the method to have it imported correctly. For example:
+ (id)recordWithQuality:(double)quality NS_SWIFT_NAME(record(quality:));
so,your method should be this:
+ (MyModel*)model NS_SWIFT_NAME(log());
Do exactly what the compiler warning tells you to:
MyModel().someMethod()
Read on to see why...
Swift automatically recognizes ObjC conventions for initializers and convenience constructors. If you have a class that looks like this:
#interface XYZThing : NSObject
+ (instancetype)thing;
+ (instancetype)thingWithFoo:(int)foo bar:(int)bar;
#end
...then, when Swift turns them into initializers, it elides the part of the method name that's the generic name of the class (Thing/thing), moves the part of the selector that refers to the parameter to be a parameter label, and drops any prepositions connecting those parts. So the initializer declarations look like this in Swift:
class XYZThing: NSObject [
init()
init(foo: Int, bar: Int)
}
and you construct objects like this:
let thing = XYZThing()
let otherThing = XYZThing(foo:3, bar:7)
A followup: because class methods like +[XYZThing thing] are treated like initializers by the ObjC to Swift translator (even if that doesn't seem to fully work right now), that naming pattern is a bad idea for singletons. A singleton retrieval method shouldn't be an initializer, because an initializer always creates a new instance.
A singleton retrieval method should instead have a name that doesn't start with the generic name of the class; e.g. +sharedThing, +defaultThing, +oneThingToRuleThemAll, etc.

Objective-C call class method on Class variable

I need to call a class method on a variable of type Class, which I know holds a subclass of a base class, MyBaseClass...
- (void)foo:(Class)clazz {
// Now i want to call +myClassMethod on clazz
}
I don't know if this is actually possible. If it is, is there a safer way to guarantee that clazz is actually a subclass of MyBaseClass?
Thanks in advance
You can use -respondsToSelector:, just like you would with any Objective-C object. You can also use +isSubclassOfClass: to test whether the class is a subclass of another class.
- (void)foo:(Class)clazz
{
if ([clazz respondsToSelector:#selector(myClassMethod)])
{
[clazz myClassMethod];
}
else
{
// clazz does not implement that class method.
}
if ([clazz isSubclassOfClass:[MyBaseClass class]])
{
[clazz myClassMethod];
}
else
{
// clazz is not a subclass of MyBaseClass.
}
}
Try this:
if ( [clazz isKindOfClass:[MyBaseClass class]] ) {
[clazz myClassMethod];
}