The introduction
Custom JPerson class inherits from NSObject
@interface JPerson : NSObject
{
NSString *nickName;
}
@end
Copy the code
Let’s write the following code in the main method and look at the underlying implementation
int main(int argc, char * argv[]) {
@autoreleasepool {
JPerson *p = [[JPerson alloc] init];
NSLog(@"%@",p);
}
return 0;
}
Copy the code
The terminal goes to the main.m file directory to view its underlying implementation through Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
Copy the code
Open the generated main. CPP file
. typedef struct objc_object NSObject; struct NSObject_IMPL { Class isa; }; . typedef struct objc_object JPerson; struct JPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *nickName; }; .Copy the code
You can see that underneath NSObject isa struct objc_object structure type that contains isa inside.
The underlying JPerson structure is also a struct objc_object structure containing a struct NSObject_IMPL NSObject_IVARS (ISA) and a custom nickName.
objc_object
struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA Class rawISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); uintptr_t isaBits() const; . };Copy the code
Isa_t As we explained in # OC’s isa exploration, the objc_object structure contains an ISA and some methods.
We know that a pointer to a class object is stored in the ISA of the instance object, and we have verified this above. Let’s look at the implementation details
inline Class objc_object::ISA() { ASSERT(! isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; return classForIndex((unsigned)slot); } return (Class)isa.bits; #else return (Class)(isa.bits & ISA_MASK); #endif # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL }Copy the code
SUPPORT_INDEXED_ISA=1 represents isWatchABI, as described in # iOS Low-level Exploration – Instance object Creation
Isa.bits & ISA_MASK is used to get the pointer to the class object
Class
struct objc_class;
struct objc_object;
typedef struct objc_class *Class;
typedef struct objc_object *id;
Copy the code
The underlying Class is a structure pointer of type struct objC_class
objc_class
struct objc_class : objc_object { ...... // Class ISA; //8 bytes Class superclass; // 16 bytes cache_t cache; // formerly cache pointer and vtable // 8 bytes class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *data() const { return bits.data(); }... };Copy the code
The objC_class structure integrates ISA from ObjC_Object and defines its own superclass, cache, and bits
cache_t
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits\
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8字节
//联合体占用8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4字节
#if __LP64__
uint16_t _flags; //2字节
#endif
uint16_t _occupied; //2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8字节
};
};
Copy the code
The cache can cache methods and improve the efficiency of method calls, which will be discussed in more detail when we examine method caches later
class_data_bits_t
struct class_data_bits_t { friend objc_class; Uintptr_t bits; // Uintptr_t bits; private: bool getBit(uintptr_t bit) const { return bits & bit; }... public: class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); } void setData(class_rw_t *newData) { ASSERT(! data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); // Set during realization or construction only. No locking needed. // Use a store-release fence because there may be concurrent // readers of data and data's contents. uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; atomic_thread_fence(memory_order_release); bits = newBits; } // Get the class's ro data, even in the presence of concurrent realization. // fixme this isn't really safe without a compiler barrier at least // and probably a memory barrier when realizeClass changes the data field const class_ro_t *safe_ro() { class_rw_t *maybe_rw = data(); if (maybe_rw->flags & RW_REALIZED) { // maybe_rw is rw return maybe_rw->ro(); } else { // maybe_rw is actually ro return (class_ro_t *)maybe_rw; }}};Copy the code
Class_data_bits_t stores a bit. Using bits to calculate class_rw_t, class_RO_t can be obtained
class_rw_t
struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint16_t witness; #if SUPPORT_INDEXED_ISA // abi uint16_t index; #endif explicit_atomic<uintptr_t> ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; private: using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>; const ro_or_rw_ext_t get_ro_or_rwe() const { return ro_or_rw_ext_t{ro_or_rw_ext}; }... // access class_rw_ext_t or class_ro_t const method_array_t methods() const {auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { return v.get<class_rw_ext_t *>()->methods; } else { return method_array_t{v.get<const class_ro_t *>()->baseMethods()}; } } const property_array_t properties() const { auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { return v.get<class_rw_ext_t *>()->properties; } else { return property_array_t{v.get<const class_ro_t *>()->baseProperties}; } } const protocol_array_t protocols() const { auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { return v.get<class_rw_ext_t *>()->protocols; } else { return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols}; }}};Copy the code
Ro_or_rw_ext supports class_ro_t and class_rw_ext_t templates, which can be verified by methods(), properties(), and protocols()
class_ro_t
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; . };Copy the code
Class_ro_t contains information about methods, attributes, member variables, and so on that are determined at compile time. InstanceStart and instanceSize are used when dyLD is loaded. See # Non Fragile Ivars for details
class_rw_ext_t
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
Copy the code
Class_rw_ext_t stores Pointers to class_ro_t and dynamically added information.
For details, see # WWDC2020
Simple summary
OC objects are inherited from NSObject. The underlying implementation of NSObject is an objC_Object structure that contains only ISA. Subclasses inherit ISA from NSObject.
Isa in objc2 isa federation that stores information through bits and bitfields.
- Instance variable of
isa
Contains a pointer toClass object
A pointer to the Class object
theisa
Contains a pointer toYuan class object
The pointer.Yuan class object
theisa
Contains a pointer toThe root metaclass object
The pointer.The root metaclass object
theisa
Contains a pointer to itself.
The underlying implementation of the Class object Class is an objc_class structure, which in addition to isa inherited from objC_Object contains:
superclass
Pointer to the parent classcache
Caching information such as methods (used later when exploring method flow)bits
containsclass_rw_t
Information such as
Class_rw_t contains an important variable ro_or_rw_ext. Ro_or_rw_ext supports two templates: class_rw_ext_t and class_ro_t. You can store either class_rw_ext_t or class_ro_t, depending on the case
Class_ro_t stores information determined at compile time, such as method lists, attribute lists, protocol lists, and so on. Because OC is a dynamic language, information can be added dynamically at run time. If the Class object does not have dynamic information, ro_OR_rw_ext stores class_RO_T, and if it does, ro_OR_rw_ext stores class_rw_ext_t
Class_rw_ext_t contains Pointers to class_ro_t and other dynamically added information.
Note that class_rw_ext_t contains information copied from class_ro_t and dynamically added by the user
Code validation
NS_ASSUME_NONNULL_BEGIN
@protocol JProtocol <NSObject>
@property(nonatomic,strong) NSString *protocolProperty;
- (void)method_1; //在JPerson.m中实现了该方法
- (void)method_2; //在JPerson.m中没有实现该方法
@end
@interface JPerson : NSObject<JProtocol>
{
NSString *name;
NSInteger age;
}
@property (nonatomic,strong)NSString *nickName;
- (void)instanceMethod; //.m实现之
+ (void)classMethod; //.m实现之
@end
NS_ASSUME_NONNULL_END
Copy the code
We create a JPerson class that implements the JProtocol, but only some of the methods in the protocol, and create a category for JPerson, JPerson_category, that also implements only some of the methods
#import "JPerson.h" NS_ASSUME_NONNULL_BEGIN @interface JPerson (JPerson_category) @property (nonatomic,strong) NSString *categoryProperty; -(void)method_cat1; // implement this method in.m -(void)method_cat2; @end NS_ASSUME_NONNULL_END @assume_nonNULl_endCopy the code
Write the following code in the main function
JPerson *p1 = [[JPerson alloc] init]; Class pClass = object_getClass(p1); NSLog(@"%@-%@",p1,pClass); / / break pointCopy the code
P1 is the pointer to the instance object, and pClass is the pointer to the class object JPerson, which is the focus of our study in this paper
(lldb) x/5gx pClass
0x100008900: 0x00000001000088d8 0x000000010036a140
0x100008910: 0x0000000101404960 0x0001802800000003
0x100008920: 0x00000001014044b4
Copy the code
We know from the previous analysis
0x00000001000088d8
forisa
0x000000010036a140
Is a pointer to the parent classsuperclass
0x0000000101404960 0x0001802800000003
forcache
0x00000001014044b4
forbits
.0x100008920
forbits
The pointer,bits
The type ofclass_data_bits_t
, which contains methodsdata()
Access toclass_rw_t
(lldb) p (class_data_bits_t *)0x100008920 (class_data_bits_t *) $1 = 0x0000000100008920 (lldb) p *$1.data() (class_rw_t) $2 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std::__1::atomic<unsigned long> = { Value = 4295001368 } } firstSubclass = nil nextSiblingClass = NSUUID } Fix-it applied, fixed expression was: *$1->data() (lldb) p/x 4295001368 (long) $3 = 0x0000000100008518Copy the code
Ro_or_rw_ext contains ro_OR_rw_ext, and ro_OR_rw_ext supports class_RO_T and class_rw_ext_t templates. So $3 is a pointer to class_ro_t
(lldb) p (class_ro_t *)$3
(class_ro_t *) $4 = 0x0000000100008518
(lldb) p *$4
(class_ro_t) $5 = {
flags = 0
instanceStart = 8
instanceSize = 32
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "JPerson" {
Value = 0x0000000100003c29 "JPerson"
}
}
baseMethodList = 0x0000000100008048
baseProtocols = 0x0000000100008500
ivars = 0x0000000100008560
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000080b0
_swiftMetadataInitializer_NEVER_USE = {}
}
Copy the code
Class_rw_t also has methods(), properties(), and protocols() methods
(lldb) p $2.methods()
(const method_array_t) $6 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008048
}
arrayAndFlag = 4295000136
}
}
}
(lldb) p $2.properties()
(const property_array_t) $7 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000080b0
}
arrayAndFlag = 4295000240
}
}
}
(lldb) p $2.protocols()
(const protocol_array_t) $8 = {
list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008500
}
arrayAndFlag = 4295001344
}
}
}
Copy the code
We can see that the pointer to methods() in class_rw_t is the same as the pointer to baseMethodList in class_ro_t, as well as the attribute class list and protocol list. Let’s take the method list as an example
(lldb) p $6.list.ptr (method_list_t *const) $9 = 0x0000000100008048 (lldb) p *$9 (method_list_t) $10 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 4) } (lldb) p $10.get(0).big() (method_t::big) $11 = { name = "method_cat1" types = 0x0000000100003ea8 "v16@0:8" imp = 0x00000001000038f0 (KCObjcBuild`-[JPerson(JPerson_category) method_cat1]) } (lldb) p $10.get(1).big() (method_t::big) $12 = { name = "method_1" types = 0x0000000100003ea8 "v16@0:8" imp = 0x0000000100003900 (KCObjcBuild`-[JPerson method_1]) } (lldb) p $10.get(2).big() (method_t::big) $13 = { name = "nickName" types = 0x0000000100003ec3 "@16@0:8" imp = 0x0000000100003910 (KCObjcBuild`-[JPerson nickName]) } (lldb) p $10.get(3).big() (method_t::big) $14 = { name = "setNickName:" types = 0x0000000100003f72 "v24@0:8@16" imp = 0x0000000100003930 (KCObjcBuild '-[JPerson setNickName:])} (LLDB) p $10.get(4).big(Copy the code
We see method_1 in the protocol JProtocol, but we don’t see method_2. We see method_cat1 in the JPerson_category but not method_cat2. This indicates that unimplemented methods do not exist in the method list.
We see that the instanceMethod instanceMethod does not see the classMethod because the classMethod is stored in the metaclass
Big () is because the information in method_t is wrapped so it can’t be accessed directly
struct method_t { ...... // The representation of a "big" method. This is the traditional // representation of three pointers storing the selector, types // and implementation. struct big { SEL name; const char *types; MethodListIMP imp; }; . public: big &big() const { ASSERT(! isSmall()); return *(struct big *)this; }... };Copy the code
Here we simply verify where the method is stored, leaving a lot of questions to verify:
agreement
How are provided properties stored? Are member variables generated?category
How are provided properties stored? Are member variables generated?
We will verify this further in a later article.
Refer to the article
# Class structure analysis
# iOS Low-level exploration – instance object creation
# WWDC2020
# Non Fragile ivars