Search code examples
iosbackwards-compatibility

iOS backwards compatibility - alloc/init of a non existent class


Background:

Let's say I'm developing an app using the iOS 6 SDK. I have my deployment target set to iOS 5. I can then use features from 6 but in order to keep compatibility with 5, you have to have some checks in your code:

// method only available from 6, class of someObj existed in 5
if (someObj respondToSelector:@selector(aMethod)) {
    [someObj aMethod];
}

Or

// entire class only available from 6
if (NSStringFromClass([SKStoreProductViewController class]) != nil) {
    SKStoreProductViewController *store = [[SKStoreProductViewController alloc] init];
}

so far so good. These are the standard ways of doing things, as far as I know.

But I found out today that for the new class example, if I just try and alloc/init an object from that class without any checks, it doesn't crash on iOS 5 which I would have expected but rather returns null.

// run this on an iOS 5 device
NSLog(@"%@", [UICollectionView alloc] init]);

Why isn't that causing a crash on iOS 5? I guess this is something to do with the way the linker works, but I would expect a crash since that symbol doesn't exist in that version.

The secondary question is this: if the normal test is to use the NSStringFromClass method, this implies you can send the +class method to a non-existent class and it will return null - why/how does that work?

Finally, I noticed I can create a ViewController that adopts a protocol that was only defined in iOS 6 and again it causes no issues on 5. ?


Solution

  • This behaviour is due to the NS_CLASS_AVAILABLE macro. This macro has been implemented on most (all?) UIKit classes, and will return nil for a class that is not available. This allows you to check for the existence of a class on a specific iOS version with the code:

    if ([UICollectionView class]) {
        // class exists, must be iOS6+
    }
    

    Now, your call to [[UICollectionView alloc] init] is a class method call on a nil class, which will always return nil.

    To answer your secondary question, the correct way of checking for class existence is to check if the class is nil or not, as above. NSStringFromClass is not needed any more.

    So, on to question 3. I was also surprised by this one, but it appears that protocol objects are compiled directly into the binary. As you are compiling using the latest SDK, the code compiles fine and the protocol will be available when running on an SDK version that has not introduced the protocol yet, as no linking to the missing class is required. This means the Protocol object will be valid, and the class will correctly respond to conformsToProtocol: with no problems, no matter what iOS version you are running. This can be easily seen using otool -l on a compiled binary, which will show the protocols conformed to by a class and their methods. The protocols themselves seem to live in a section called __objc_protolist. Output for a class conforming to UICollectionViewDelegate and DataSource is shown below:

    000050a4 0x5cf4
               isa 0x5d08
        superclass 0x0
             cache 0x0
            vtable 0x0
              data 0x5b30 (struct class_ro_t *)
                        flags 0x80
                instanceStart 156
                 instanceSize 156
                   ivarLayout 0x0
                         name 0x4a0f TTViewController
                  baseMethods 0x5b10 (struct method_list_t *)
               entsize 12
                 count 2
                  name 0x3eb8 viewDidLoad
                 types 0x4955 v8@0:4
                   imp 0x2620
                  name 0x3ec4 didReceiveMemoryWarning
                 types 0x4955 v8@0:4
                   imp 0x2670
                baseProtocols 0x5ad8
                          count 2
                  list[0] 0x5da0 (struct protocol_t *)
                      isa 0x0
                     name 0x4997 UICollectionViewDelegate
                protocols 0x5304
              instanceMethods 0x0 (struct method_list_t *)
                 classMethods 0x0 (struct method_list_t *)
          optionalInstanceMethods 0x5310
             optionalClassMethods 0x0
               instanceProperties 0x0
                  list[1] 0x5dcc (struct protocol_t *)
                      isa 0x0
                     name 0x49ce UICollectionViewDataSource
                protocols 0x53d8
              instanceMethods 0x53e4 (struct method_list_t *)
                   entsize 12
                     count 2
                      name 0x394e collectionView:numberOfItemsInSection:
                     types 0x455d i16@0:4@8i12
                       imp 0x0
                      name 0x3975 collectionView:cellForItemAtIndexPath:
                     types 0x4589 @16@0:4@8@12
                       imp 0x0
                 classMethods 0x0 (struct method_list_t *)
          optionalInstanceMethods 0x5404
             optionalClassMethods 0x0
               instanceProperties 0x0
                        ivars 0x0
               weakIvarLayout 0x0
               baseProperties 0x0