0

Say I have two classes, BulbDevice and FanDevice, both are subclasses of Device and has a method signature like this:

+ (BOOL)isMyId:(NSInteger)someId;

If I wanted to create a class I could test it out:

if ([BulbDevice isMyId:someId]) {
    Device *dev = [BulbDevice alloc] initWithId:someId];
}

But what I really want is to create a factory method inside a factory class, with minimum fuss when new device are added:

+ (Device)createDevice:(NSInteger)someId {
    // say I have an array registered
    NSArray *arr = @[[BulbDevice class], [FanDevice class]];

    // Loop through it.
    Device *device;
    for (Class *c in arr) {

        // The idea is kind of like this but I'm not sure how to make it work
        if ([c isMyId]) {
            device = [[c alloc] init];
        }
    }
}

The idea is that I only need to update arr in the factory method. So I think it is good to have something like this. But I am not sure how to make it work.

EDIT:

I took out the asterisk, but it won't work:

for (Class c in arr) {
    // Now I want to access the isMyId which is a static method, 
    // but I how do I cast to that class? I mean not an object of the class, but to that class itself.
    if ([(Device)c isMyId:]) {
    }
}

But I still need a way to access that class method. Error says Used type 'Device' where arithmetic or pointer type is required, and even if it works, I want to access class method, not sending message to an object.

Or shall I store NSString in the array instead? But it is hard to find way to access the class method as well.

huggie
  • 17,587
  • 27
  • 82
  • 139
  • Just adopt `NSCopying` protocol – Cy-4AH Apr 15 '19 at 14:36
  • @Cy-4AH I do not understand how it is applied here? – huggie Apr 15 '19 at 23:18
  • You can try another approach - _Factory Design Pattern_ (https://www.oodesign.com/factory-pattern.html). This way you won't need to deal with `Classes`. I believe that will solve your problem. – Max Pevsner Apr 16 '19 at 06:28
  • @MaxPevsner Thank for the suggestion. I got this to work, but better design ideas are always welcomed. – huggie Apr 16 '19 at 06:31
  • If you really need to use `Classes`, then **don't** cast the `c` object. It works perfectly without casting. `c` is of correct type, so the correct class method will be called. Otherwise, if you cast it to be its superclass, the supeclass' method will be called, and you won't have a correct object. – Max Pevsner Apr 16 '19 at 06:32
  • @MaxPevsner 1. It looks like the big addition to the factory pattern is the registration process? If I register concret classes to the factory, I still need to register as [BulbDevice class], don't I? I don't see how I can avoid `Class`. 2. The registration process itself needs to be done somewhere else. It's up to discussion, but I think although my implementation violates open-closed principle, adding an extra item to the array is trivial. It is easier for other programmer to find out what I did instead of searching for where the registration code is. What do you think? – huggie Apr 17 '19 at 03:31
  • 1
    @huggie as with every design pattern factory has its own pros and cons. It's definitely up to you to decide what you need for the problem you are trying to solve. – Max Pevsner Apr 17 '19 at 07:28

2 Answers2

2

If I understand correctly what you are trying to achieve, then your approach seems to be correct.

There is only one thing that needs to be fixed:

for (Class c in arr)

c variable is not a pointer - the asterisk should be removed. Your code works.

Max Pevsner
  • 4,098
  • 2
  • 18
  • 32
1

The Class type is not an NSObject type, and although it is a bit special it is object-like or object-equivalent, so you are able to send it messages and store it in collections like you're doing.

You don't use the asterisk as @MaxPevsner says, because Class isn't used as a normal pointer-to-object. Think of Class as a special type like id which also doesn't get the * when you use it to reference an object.

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
  • Hi I updated the question. I need to access the class method and I can't cast to it. – huggie Apr 15 '19 at 23:13
  • 1
    @huggie: You shouldn't have to cast to it. You should be able to call your class method directly on `Class c`-- just like the compiler will let you send any message to an object of type `id` as long as *some* possible object might respond to it, it should let you send any message to a class of type `Class` as long as *some* class it knowns about would respond to it. It should fail (crash) at runtime if that class does not respond to the class method you're trying to call. – Ben Zotto Apr 16 '19 at 04:58
  • 1
    Thanks! I got this to work. I didn't know I was so close to it. I didn't know `Class` could be used this way even if I could cast to it. I only meant it to demonstrate what I wanted. And yeah I forgot I can sent arbitrary messages to classes. How lack of type-safety it is. – huggie Apr 16 '19 at 06:33
  • @huggie: Yeah, modern ObjC has many syntactic guardrails that let you write code that is compiler-checked for type safety, but the underlying system is at heart duck-typed. Another way to think of this case is, you can use `Class` to point to any kind of class, but if you actually *know* what kind of class it is, you don't have cast it, you can just call it `Device`. If you aren't sure at runtime if the `Class` you have responds to `+isMyId:`, you can ask it: `if ([c respondsToSelector:@selector(isMyId:)]) { ... }` – Ben Zotto Apr 16 '19 at 15:13