In exploring OC Class, we know that object methods exist in the Class class_rw_T, and the Class methods are stored in the corresponding Class metaclass class_rw_t. And the member variables are stored in class_ro_T. As can be seen from the structure name, one is readable and writable, and the other is read-only. Why do you do that? With Advancements in the Objective-C Runtime, clean memory and dirty memory can be understood and optimized. Hence the design of class_RO_T and class_rw_T data structures. Because when a class is loaded from disk, the information about the class does not change, it can only change at run time (such as loading classes). So in this case, Apple designed class_ro_t. You can remove the memory space corresponding to class_ro_t and retrieve it from disk again when needed for memory optimization purposes. Class_rw_t is generated while the class is running and gets clean memory class_ro_t.

In [explore OC Class], LLDB prints the attributes and methods from the Class class_data_bits_t* bits, and then continues to explore member variables and attributes and encodings.

Class member variables and attributes and encodings

@interface LGPerson : NSObject
{
    NSString * hobby;
}
//isa 8 0-7
@property(atomic)NSString * name; //8   8 9 10 11 12 13 14 15
@property(nonatomic, assign)int age; //4   16-19
    
-(void)say;
+(void)play;
Copy the code
(lldb) x/5gx LGPerson.class 0x100008248: 0x0000000100008220 0x000000010036a140 0x100008258: 0x0000000100362380 0x0000802c00000000 0x100008268: 0x00000001013833b4 (lldb) p (class_data_bits_t *)0x100008268 (class_data_bits_t *) $2 = 0x0000000100008268 (lldb) p $2->data() (class_rw_t *) $3 = 0x00000001013833b0 (lldb) p $3->methods() (const method_array_t) $4 = { list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = { = { list = { ptr = 0x00000001000080d8 } arrayAndFlag = 4295000280 } } } (lldb) p $4.list (const method_list_t_authed_ptr<method_list_t>) $5 = { ptr = 0x00000001000080d8 } (lldb) p $5.ptr (method_list_t *const)  $6 = 0x00000001000080d8 (lldb) p *$6 (method_list_t) $7 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6) } (lldb) p $7.get(0).big() (method_t::big) $8 = { name = "say" types = 0x0000000100003f62 "v16@0:8" imp = 0x0000000100003d90 (KCObjcBuild`-[LGPerson say]) } (lldb) p $7.get(1).big() (method_t::big) $9 = { name = "name" types =  0x0000000100003f78 "@16@0:8" imp = 0x0000000100003da0 (KCObjcBuild`-[LGPerson name]) } (lldb) p $7.get(2).big() (method_t::big) $10 = { name = ".cxx_destruct" types = 0x0000000100003f62 "v16@0:8" imp = 0x0000000100003e30 (KCObjcBuild`-[LGPerson .cxx_destruct]) } (lldb) p $7.get(3).big() (method_t::big) $11 = { name = "setName:" types = 0x0000000100003f80 "v24@0:8@16" imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson setName:]) } (lldb) p $7.get(4).big() (method_t::big) $12 = { name = "age" types = 0x0000000100003f8b "i16@0:8" imp = 0x0000000100003df0 (KCObjcBuild`-[LGPerson age]) } (lldb) p $7.get(5).big() (method_t::big) $13 = { name = "setAge:" types = 0x0000000100003f93 "v20@0:8i16" imp = 0x0000000100003e10 (KCObjcBuild`-[LGPerson setAge:]) }Copy the code

Method Encode String

The LLDB print gets all the methods is a big structure containing name, types, imp where types = 0x0000000100003f93 “v20@0:8i16” has some special symbols for types, what do they stand for?

The meaning of the corresponding symbol can be found in Type Encodings.

Such as v20 @ 0:8 i16:

  1. V -> void, return void
  2. 20 -> The length of all parameters is 20 bytes
  3. @ -> Object type (here represents self of type ID)
  4. 0 -> starts at byte 0
  5. : -> SEL
  6. 8 -> Start at position 8
  7. Type I -> Int
  8. 16 -> Start at position 16

The general meaning is: The method is of type, a setAge method that returns a void, has three arguments, 20 bytes in total, and the first argument is the object type, self, starting at zero bytes (id typedef struct objc_object * ID, pointer 8 bytes), The second argument is SEL (SEL typedef struct objc_selector *SEL, 8 bytes from SEL), and the third argument is int, 16 bytes from SEL, 20 bytes in total, so it matches perfectly.

Property Encode String

Struct property_t {const char *name; const char *attributes; };

(lldb) p/x LGPerson.class (Class) $1 = 0x0000000100008258 LGPerson (lldb) p (class_data_bits_t *)0x0000000100008278 (class_data_bits_t *) $2 = 0x0000000100008278 (lldb) p $2->data() (class_rw_t *) $3 = 0x0000000100723020 (lldb) p $3->ro() (const class_ro_t *) $4 = 0x00000001000080a0 (lldb) p *$4 (const class_ro_t) $5 = { flags = 388 instanceStart =  8 instanceSize = 32 reserved = 0 = { ivarLayout = 0x0000000100003f2b "\x01\x11" nonMetaclass = 0x0000000100003f2b } name = { std::__1::atomic<const char *> = "LGPerson" { Value = 0x0000000100003f22 "LGPerson" } } baseMethodList = 0x00000001000080e8 baseProtocols = 0x0000000000000000 ivars = 0x0000000100008180 weakIvarLayout = 0x0000000000000000 baseProperties = 0x00000001000081e8 _swiftMetadataInitializer_NEVER_USE = {} } (lldb) p $5.baseProperties (property_list_t *const) $6 = 0x00000001000081e8 (lldb) p *$6 (property_list_t) $7 = { entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2) } (lldb) p $7.get(0) (property_t) $8 = (name = "name", attributes = "T@\"NSString\",&,V_name") (lldb) p $7.get(1) (property_t) $9 = (name = "age", attributes = "Ti,N,V_age")Copy the code

Contains attributes for name, where (property_t) $8 = (name = “name”, attributes = “T@\”NSString\,&,V_name”) What do they all mean?

Declared Properties where T and V represent the start and end of the code @ -> are also object types, “NSString” -> NSString, & -> stand for retain strong, _NAME is the name of the underlying member variable generated by the name attribute. The N in the string encoded by the age attribute stands for nonatomic. This means that the attribute list contains two attributes named name, which have the following properties: atomic, type NSString, name of the member corresponding to the attribute _name, and strong reference. The property is nonatomic, the type is int, and the member corresponding to the property is _age, which is not strongly referenced.

Member variables/instance variables

@interface LGPerson : NSObject
{
    NSString * hobby;
    int age;
}
Copy the code

Where Hobby and age are member variables, and exactly Hobby is instance variables, because it is an object type, not a primitive type. In fact, the encoded string of member/instance variables is similar to that of methods.

(ivar_t) $12 = { offset = 0x0000000100008218 name = 0x0000000100003f33 "hobby" type = 0x0000000100003f78 "@\"NSString\""  alignment_raw = 3 size = 8 } (lldb) p $11.get(1) (ivar_t) $13 = { offset = 0x0000000100008220 name = 0x0000000100003f39  "_age" type = 0x0000000100003f84 "i" alignment_raw = 2 size = 4 } (lldb) p $11.get(2) (ivar_t) $14 = { offset = 0x0000000100008228 name = 0x0000000100003f3e "_name" type = 0x0000000100003f78 "@\"NSString\"" alignment_raw = 3 size = 8}Copy the code
struct ivar_t { #if __x86_64__ // *offset was originally 64-bit on some x86_64 platforms. // We read and write only 32 bits of it. // Some metadata provides all 64 bits. This is harmless for unsigned // little-endian values. // Some code uses all 64 bits. class_addIvar() over-allocates the // offset for their benefit. #endif int32_t *offset; const char *name; const char *type; // alignment is sometimes -1; use alignment() instead uint32_t alignment_raw; uint32_t size; uint32_t alignment() const { if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT; return 1 << alignment_raw; }};Copy the code

Ivar_t contains offset, name, type, alignment_RAW, and size. You can see what they do by naming them. Other type-encoded strings have the same meaning as those for attributes and methods, so we won’t go into detail.

The effect of attribute modifiers on class

In the isa nature of the object, through Clang, we find that the object is an objC_Object structure at the bottom, and find that the property generation getSet method is to get the pointer to the object property by memory translation of the self pointer, and then obtain and assign the value. We continue our analysis.

@interface LGPerson : NSObject { // STRING int double float char bool NSString *hobby; // string int a; NSObject *objc; } @property (nonatomic, copy) NSString *nickName; @property (atomic, copy) NSString *acnickName; @property (nonatomic) NSString *nnickName; @property (atomic) NSString *anickName; @property (nonatomic, strong) NSString *name; @property (atomic, strong) NSString *aname; @endCopy the code
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_nickName;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_nnickName;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_anickName;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_aname;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *hobby;
	int a;
	NSObject *objc;
	NSString *_nickName;
	NSString *_acnickName;
	NSString *_nnickName;
	NSString *_anickName;
	NSString *_name;
	NSString *_aname;
};

Copy the code
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }

static NSString * _I_LGPerson_nnickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)); }
static void _I_LGPerson_setNnickName_(LGPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)) = nnickName; }

static NSString * _I_LGPerson_anickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)); }
static void _I_LGPerson_setAnickName_(LGPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)) = anickName; }

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }

static NSString * _I_LGPerson_aname(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)); }
static void _I_LGPerson_setAname_(LGPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)) = aname; }
Copy the code

Using Clang to rewrite the OC file into a C++ file (clang-rewrite-objc xx.c -o xx.cpp), we can clearly see that LGPerson is an objC_object, generating the corresponding underlined members and generating the corresponding getSet methods.

One difference, however, is that some getSet methods assign values by memory translation of the self pointer. Some of them are assigned by objc_setProperty, objc_getProperty.

Why do you do that?

Objc_setProperty, objc_getProperty, objc_setProperty, Objc_getProperty internally binds SEL to the real IMP.

extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

As we can probably guess from the function definition, the argument contains self and SEL. Then SEL remaps to IMP we don’t see run-time based code, so it must have been mapped at compile time. So look at the LVVM source and look at objc_setProperty

getSetPropertyFnMethod is now createdobjc_setPropertyMethod, continue to lookgetSetPropertyFnWhere is the method called

PropertyImplStrategy strategy(CGM, propImpl); switch (strategy.getKind()) { case PropertyImplStrategy::Native: { // We don't need to do anything for a zero-size struct. if (strategy.getIvarSize().isZero()) return; Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin()); LValue ivarLValue = EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0); Address ivarAddr = ivarLValue.getAddress(*this); // Currently, all atomic accesses have to be through integer // types, so there's no point in trying to pick a prettier type. llvm::Type *bitcastType = llvm::Type::getIntNTy(getLLVMContext(),  getContext().toBits(strategy.getIvarSize())); // Cast both arguments to the chosen operation type. argAddr = Builder.CreateElementBitCast(argAddr, bitcastType); ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType); // This bitcast load is likely to cause some nasty IR. llvm::Value *load = Builder.CreateLoad(argAddr); // Perform an atomic store. There are no memory ordering requirements. llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr); store->setAtomic(llvm::AtomicOrdering::Unordered); return; }Copy the code

In case PropertyImplStrategy: : Native in this branch, it gained the classImpl propImpl, through

ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
ObjCMethodDecl *setterMethod = propImpl->getSetterMethodDecl();
Copy the code

Get ivar, setterMethod, and then translate it to get the address and the value of the corresponding variable. Then the case for case PropertyImplStrategy: : GetSetProperty: Case PropertyImplStrategy: : SetPropertyAndExpressionGet: it needs to be mapped to objc_setProperty, when modifying PropertyImplStrategy kind? Hence the constructor of PropertyImplStrategy.

As you can see, imp is remapped when the modifier that was originally a property is copy. Then do the sameobjc_getPropertyIf the property is not Atomic, the get method does not need to be remapped.

Class method storage

Object methods are stored in the Class, and Class methods are stored in the corresponding metaclass.

@interface LGPerson : NSObject
{
    NSObject *objc; 
    NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSObject *obj;


- (void)sayHello;
+ (void)sayHappy;

@end
Copy the code
void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
Copy the code

From the print, the sayHello method from metaClass is empty, and the sayHappy method from pClass is empty. Everything is OK.

void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

//    - (void)sayHello;
//    + (void)sayHappy;
    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
Copy the code

Then use the getClass method to check:

Unexpectedly, the metaclass fetch class method also returns. View the source code

Method class_getClassMethod(Class cls, SEL sel) { if (! cls || ! sel) return nil; return class_getInstanceMethod(cls->getMeta(), sel); } Class getMeta() { if (isMetaClassMaybeUnrealized()) return (Class)this; else return this->ISA(); }Copy the code

As you can see, there is no such thing as a class method at the bottom. All methods are object methods, rather, member functions of a structure. When a metaclass retrieves the class mode, it determines whether the current metaclass is a metaclass, and if so, it returns the object method directly.

We then test the IMP of the print methodIt gets even weirder, there’s a pointer back to IMP, butThe metaclassprintsayHelloIMP andclassprintsayHappyThe IMP of is the same. Conventionally these two IMPs should be nil, but they both return and the IMP pointer is the same.

IMP class_getMethodImplementation(Class cls, SEL sel) { IMP imp; if (! cls || ! sel) return nil; lockdebug_assert_no_locks_locked_except({ &loadMethodLock }); imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER); // Translate forwarding function to C-callable external version if (! imp) { return _objc_msgForward; } return imp; }Copy the code

View the source code, the original when not found IMP, unified return _objc_msgForward, so resulting in the return of the IMP consistent.

Conclusion:

  1. Apple implemented memory optimization at the bottom of OC Runtime, and proposed clean and dirty memory designs. Hence the two data types class_ro_t and class_rw_t. When a class is loaded from disk, a clean copy of memory is stored in class_ro_t. At runtime (classes may be added, classes may be modified), a dirty copy of memory class_rw_t is generated, which can be cleaned and loaded from disk when needed for memory optimization purposes.

  2. By printing methods, attributes, and member variables in class memory, you can understand the encoded types of methods and attributes.

  3. To further share the OC object bottom layer, we learned the effect of attribute modifiers on getSet methods by rewriting OC files with CLang and combining LVVM.

The set method is mapped through objc_setProperty, which is affected by the property modifier: Copy Affects the set method is mapped through objc_setProperty, which is otherwise assigned by memory translation.

If the property is modified by both atomic and copy, get goes to the objc_getProperty map, and vice versa. Retain and atomic also affect whether objc_getProperty is mapped or memory shifted.

  1. By printing the method store of class in the form of API, it is found that all methods are functions at the bottom, there is no such thing as object method class method. When the metaclass gets the class method, the object will be returned directly, and when the IMP is not found, the unified return _objc_msgForward, as the message forward identifier.