This article explores some of the lessons learned in a WWDC2020 video on optimization in Objective-C at runtime. Check out this video if you’re interested, or check out my previous post on wwDC2020-Objective-C runtime improvements.
Verify some of the points in the video:
-
Class_ro_t stores Flags, Size, Name, Methods, Protocols, Ivars, Properties.
-
Class_rw_t stores Methods, Protocols, and Properties.
-
Why should class_rw_t store Methods, Protocols, and Properties when class_ro_t already stores them?
WWDC says class_ro_t is read-only, storing information that is determined at compile time, cannot be changed, and can be cleared if necessary, and reloaded from disk when needed. Class_rw_t is readable and writable. The information stored in class_rw_t is written at runtime and is always stored in memory when needed.
There is a list of methods, a list of protocols, and a list of attributes, but the protocol list is not authenticated. The same is true for class_rw_t.
Let’s start by declaring a few classes: SHPerson inherited from NSObject, SHPerson(Home), and SHTeacher inherited from SHPerson.
#pragma mark: - SHPerson
@interface SHPerson : NSObject
{
NSString *_name;
NSString *_age;
NSObject *family;
}
@property (nonatomic, copy) NSString *nickname;
@end
@implementation SHPerson
- (void)play_basketball {}
+ (void)playFootball {}
@end
#pragma mark: - SHPersonThe classification of@interface SHPerson(Home)
- (void)write_homework;
+ (void)eat;
@end
@implementation SHPerson(Home)
- (void)write_homework {}
+ (void)eat {}
@end
#pragma mark: - SHTeacher
@interface SHTeacher : SHPerson
@property (nonatomic, strong) NSString *course;
@end
@implementation SHTeacher
- (void)attend_class {}
+ (void)change_homework {}
@end
Copy the code
Class_ro_t memory structure
In class_rw_t, there is a method like this:
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t * >())) {
return v.get<class_rw_ext_t * >(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t * >(&ro_or_rw_ext);
}
Copy the code
Ro () is the method to get class_ro_t, so let’s see.
Class_ro_t has member variables for Flags, Size, Name, Methods, Protocols, Ivars, Properties, but baseProtocols is nil. Add protocol to SHPreson and run again.
#pragma mark: - Protocol
@protocol SHPersonProtocol<NSObject>
- (void)run;
- (void)sleep;
+ (void)work;
@end
#pragma mark: - SHPerson
@interface SHPerson : NSObject<SHPersonProtocol>
{
NSString *_name;
NSString *_age;
NSObject *family;
}
@property (nonatomic, copy) NSString *nickname;
@end
@implementation SHPerson
- (void)play_basketball {}
+ (void)playFootball {}
/// SHPersonProtocol
- (void)run {}
- (void)sleep {}
+ (void)work {}
@end
Copy the code
Let’s reprint it:
Now that baseProtocols has a value, let’s look at the structure of class_ro_t in the source code.
Ivars of class_ro_t
Let’s take a look at the ivars for class_ro_t:
There are four member variables in SHPerson, but if we add attributes or member variables to the class, will the ivars and baseProperties of class_ro_T also be stored?
You can see that the syntax for adding instance variables to a class doesn’t work, so we’re going to have to add attributes, but you can probably guess that adding attributes doesn’t even store them in class_ro_t. So let’s verify that.
Adding attributes to a category also requires the addition of corresponding getters and setters.
With that in mind, let’s verify:
There is no height property, we guessed correctly! This is why you can’t add attributes to a category. If you want to add attributes and make them work, you need to use the associative object method.
Class_ro_t baseMethodList
Let’s look at the memory structure of baseMethodList:
We find that baseMethodList is a void *const type. Remember what we learned earlier about the types of method lists? So method_list_t, we’re going to convert void *const to method_list_t *. It then prints out the big() method of method_t returned by GET in method_list_t.
Let’s look at the big() implementation:
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
Copy the code
BaseMethodList (baseMethodList)
If it points to a small list, it is signed, but if it points to a large list, it may be unsigned.
Since big() doesn’t work, try printing it by name().
You’ll notice that the method name is printed, and the baseMethodList also prints out all instance methods, including classes and protocols!
BaseProtocols for class_ro_t
Next, let’s see if baseProtocols also has our corresponding protocol methods.
Protocol_list_t ¶
struct protocol_list_t {
// count is pointer-sized by accident.
uintptr_t count;
protocol_ref_t list[0]; // variable-size
size_t byteSize() const {
return sizeof(*this) + count*sizeof(list[0]);
}
protocol_list_t *duplicate() const {
return (protocol_list_t *)memdup(this, this->byteSize());
}
typedef protocol_ref_t* iterator;
typedef const protocol_ref_t* const_iterator;
const_iterator begin() const {
return list;
}
iterator begin() {
return list;
}
const_iterator end() const {
return list + count;
}
iterator end() {
return list +count; }};Copy the code
List [0] = list[0] = list[0]
It is protocol_ref_t. The protocol_ref_t is protocol_ref_t.
Protocol_ref_t is a protocol_t *. The protocol_ref_t is of type PROTOCOL_t.
This is what we want, and we want to turn protocol_ref_t to PROTOCOL_t * via LLDB and print:
It has exactly what we want, and here’s a meal:
It is verified that baseProtocols does hold protocol methods.
Class_ro_t baseProperties
Next, let’s see if our corresponding attribute information is also available in baseProperties.
There is not only the nickname attribute, but also the height attribute of the classification and other attributes of the system.
Six, firstSubclass
To see why firstSubclass is nil, take a look at the LLDB print:
Seven,
Class_rw_t stores information about class instance methods, protocol methods, and attributes
Class_ro_t stores Flags(some other data, such as reference count), class size, class name, class instance method list, protocol method list, member variables, and attribute information.