The relationship between instances, class objects, and metaclass objects can be illustrated in this classic diagram:

Conclusion:

  • The ISA of the instance points to the class object
  • The ISA of a class object points to a metaclass object
  • Isa pointer to a metaclass object points to the root metaclass object
  • The parent of the root metaclass is the root object

What does it mean that the parent of the root metaclass is a root object?

We know that if we call a class method, we look up the implementation of the method up the inheritance chain of the metaclass object.

Because the parent of the trailing metaclass is the root object, if the implementation of this method cannot be found in the trailing metaclass, it will be found in the root object.

The methods in the root object are instance methods.

So this means that if a class’s class method is not implemented, the instance method of the same name in its root class (NSObject) will be called.

Let’s verify that with code.

Create a new Category for NSObject and add an instance method named foo. The implementation of the foo method simply prints the string foo

//NSObject+Foo.h

#import <Foundation/Foundation.h>
@interface NSObject (Foo)
- (void)foo;
@end


//NSObject+Foo.m

#import "NSObject+Foo.h"
@implementation NSObject (Foo)
- (void)foo {
    NSLog(@"foo foo");
}
@end
Copy the code

Create a new UIView subclass and declare a class method called foo in the.h file of that subclass, but don’t implement it in the.m file.

// CView.h

#import <UIKit/UIKit.h>
@interface CView : UIView
+ (void)foo;
@end

// CView.m
#import "CView.h"
@implementation CView

@end

Copy the code

Then call the class method [CView foo];

Logically we didn’t implement + (void)foo; The program should crash. But, it actually calls the instance method – (void)foo in the Category of NSObject, printing out “foo foo”, which is pretty amazing

What happens when you call a method using the super keyword

If the CView mentioned above has an initialization method like this:

#import "CView.h"

@implementation CView
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        NSLog(@"% @",NSStringFromClass([self class]));
        NSLog(@"% @",NSStringFromClass([super class]));
    }
    return self;
}
@end
Copy the code

Q: What will be printed if this initialization method is called?

To answer this question, we need to understand the process of messaging.

Objc_msgSend (id _Nullable self, SEL _Nonnull op,…)

The first two arguments are fixed, the receiver of the message and the method selector.

Struct objc_super * _Nonnull super, SEL _Nonnull op,… The first argument is the objc_super pointer

Objc_super is the following data structure:

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if! defined(__cplusplus) && ! __OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
}

Copy the code

The receiver is an instance of the class, self.

That is, the final recipient of the message is the instance self. Instead, the objc_msgSendSuper lookup method implementation starts with self’s superclass object.

But the class method is defined in the root class NSObject, so either [self Class] or [super Class] will call the implementation in NSObject, and the receiver of the message will be the same, self instance. So the print structure is the same, it prints the class name of self.