所用版本:
- 处理器: Intel Core i9
- MacOS 12.3.1
- Xcode 13.3.1
- objc4-838
建议先看下 OC底层探索(十三): 类的加载(一)
还是先看下类的初始化方法_objc_init
代码以及示意图
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
// 环境变量初始化
environ_init();
// 线程处理
tls_init();
// 运行C++静态构造函数。
static_init();
// runtime运行时初始化
runtime_init();
// objc异常处理系统初始化
exception_init();
#if __OBJC2__
// 缓存初始化
cache_t::init();
#endif
// 启动回调机制
_imp_implementationWithBlock_init();
// dyld通知注册
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
以及_read_images
的示意图. _dyld_objc_notify_register(&map_images, load_images, unmap_image);
第一个参数: &map_images
→ map_images_nolock
→ _read_images
read_image
read_image
做了:
- 条件控制进行一次加载
- 修复预编译阶段的
@selector
混乱问题 - 错误混乱的类处理
- 修复重新映射的类
- 修复消息
- 协议读取
- 分类处理
- 类的加载处理
- 优化类
[修复重新映射的类]
其中
// 类
GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs");
// 父类
GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs");
循环判断, 如果找到未映射当前类 &父类, 进行修复。
[修复消息]
一些特殊的消息处理, 正常消息已经被llvm
给处理了, 这里主要修复一些特殊消息。例如: alloc的消息改为直接调用objc_alloc (而不是走alloc的实现) 这种。
[协议读取]
GETSECT(_getObjc2ProtocolList, protocol_t * const, "__objc_protolist");
- 创建协议表
NXMapTable *protocol_map = protocols()
- Mach-O中获取
__objc_protolist
中协议 - 遍历读取插入表, 方法:
readProtocol
稍微打印一下, 看一下:
printf("%s - 协议- %s\n",__func__,newproto->mangledName);
可发现系统的代理还是蛮多的 -_- !!!, 接下来我们自定义一个代理走下流程
// 自定义代理
@protocol SRTestDelegate <NSObject>
- (void)SRTestAction;
@end
很遗憾发现并没有出现
为什么呢? 因为我并未对代理进行调用, 这里也体现了苹果严谨性。每调用还想让我给你个空床位, 做你大头梦 :)
加个代理调用就行, 实现方法可以不写。, 接下来我们跟一下源码看下流程。
可看到, 直接走到这里
- 先通过
initIsa
创建isa指针,与mach-o里面的协议相关结构体进行绑定 - 把绑定协议名, 协议结构体, 插入新建的协议表里面
当然如果协议出现没加载情况, 也会有一步修复
[分类处理]
- 只有在初始类完成后才能执行此操作。
- 对于启动时存在的分类,需要推迟到+ load之后调用。
[类的加载处理]
[非懒加载类]
只有非懒加载类才走, 正常添加的类不会走这里, 比如我加一个打印
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "SRTest") == 0) {
printf("%s 测试 %s\n",__func__,mangledName);
}
可发现并没走。其中classref_t const *classlist = hi->nlclslist(&count);
是从非懒加载类找数据。
其中_getObjc2NonlazyClassList
是获取Mach-O
的静态段__objc_nlclslist
非懒加载类表
GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
这个里稍微提一下懒加载类与非懒加载类
懒加载类与非懒加载类
-
懒加载
:- 并未实现
+load方法
- 数据加载推迟到第一次消息发送
objc_msgSend
,lookUpImpOrForward
消息慢速查找的时候进行初始化的。
- 并未实现
-
非懒加载
:自己实现
+load
方法 (会出现在__objc_nlclslist中)子类实现了
+ load
方法。(父类不会出现在__objc_nlclslist中, 但是子类在)分类实现了
+load
方法。(自己以及子类的分类,不是在map_images
而是在prepare_load_methods
中实例化类的,自己类/子类/分类 都不会出现在__objc_nlclslist
中)map_images
的时候加载所有类数据(提前加载),为+ load
的调用做准备。
两者调用流程也有区别
懒加载类:
执行顺序:
- dyld start
- main
- objc_alloc
- objc_alloc
- lookUpImpOrForward → realizeAndInitializeIfNeeded_locked → realizeClassMaybeSwiftAndLeaveLocked
- realizeClassWithoutSwift
- methodizeClass
......
非懒加载类:
执行顺序:
- dyld`start
- dyld4::prepare
- dyld4::Loader::findAndRunAllInitializers
- _objc_init
- dyld4::APIs::_dyld_objc_notify_register
- dyld4::RuntimeState::setObjCNotifiers
- map_images
- _read_images
- realizeClassWithoutSwift
- methodizeClass
...
[子类 +load]
子类的+load
方法稍微看下, SRTestSub
是SRTest
子类, 子类有load
方法, 父类没有, 运行看一下
可验证: 父类不会出现在
__objc_nlclslist
中, 但是子类在__objc_nlclslist
中
[分类 +load]
看下分类的+load
, 分类如果忘记创建参考: OC分类创建
分类添加+load方法
运行, 可发现
- 非懒加载并没有断住, 所以不在
__objc_nlclslist
, - 在main中
可看出分类的
+load` 在加载当前类之前就运行了
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
再回头看下官方注释, 可发现分类 +load也是在prepare_load_methods()
执行的
回到之前看一下, 其实我们是想看下非懒加载类的执行顺序, 那么我们在普通类写一个+load
方法, 把它变成非懒加载类看源码流程.
-
addClassTableEntry
: 将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加.
接着往下走了 realizeClassWithoutSwift
类的初始化核心方法: realizeClassWithoutSwift
realizeClassWithoutSwift
实现类方法realizeClassWithoutSwift
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
继续探索前, 先了解下2个概念
clean memory 和 dirty memory
-
clean memory
: 加载后不会发生更改的内存, 例如:class_ro_t
, 因为它是只读的 -
dirty memory
: 进程运行时会发生更改的内存,
类结构
一经使用就会变成dirty memory
, 因为runtime
会向它写入新的数据。例如: 创建一个新的方法缓存并从类中指向它。dirty memory
比 clean memory
要"昂贵"的多, 只要进程运行, 它就必须在。比如手机64G
, 升级到128G
是不是要添"马内", 侧面也表示了有多昂贵 (^ - ^)。同时clean memory
可以进行移除从而节省更多的内存空间。因为如果你需要clean memory
, 系统可以从磁盘从新加载。同时因为IOS不使用swap
, 所以dirty memory
在ios中代价是很大的。苹果更建议保持"清洁"的数据越多越好, 通过分类出那些永远不会更改的数据, 可以把大部分数据储存为clean memory
.
realizeClassWithoutSwift
会操作ro
, rw
信息。其中
-
ro
:Read Only
只读 --clean memory
--结构体
, 包含类的名称
,方法
,协议
,实例变量
等编译期的信息。 -
rw
:Read Write
读写 --dirty memory
--结构体
, 复制ro
, 目的是对于方法, 属性等进行操作。 -
rwe
:Read Write Extra
, 额外读写 --dirty memory
--结构体
,ro
部分复制, 只操作需要改变的信息。
class_ro_t
, class_rw_t
, class_rw_ext_t
结构体详细内容看末尾附录 (class_rw_ext_t 真是真滴少 :) )
因为ro
是沙盒路径加载, 只读不能操作, 那么当需要操作的时候会将ro
复制一份存放到rw
上。
这也体现了 Objective-C 是一门动态语言, 可以在运行时更改方法, 属性等。分类可以在不改变类设计的前提下, 将新方法添加到类中。
但是对于一个类, 我们需要大部分改变的就是methods, properties, protocols等 , 其他的例如父类关系, Flags等不会进行更改, 所以这部分"dirty memory" 很浪费的。由此苹果对class_rw_t
进行进一步拆分出class_rw_ext_t
用来存储这部分可能被修改的methods, properties, protocols等,class_rw_ext_t
只有在需要的时候,才被创建, 相对于旧版runtime可以节省下的内存空间。
熟悉完下一章继续看 realizeClassWithoutSwift
附: class_ro_t, class_rw_t , class_rw_ext_t 详细
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
const char *getName() const {
return name.load(std::memory_order_acquire);
}
class_ro_t *duplicate() const {
bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER;
size_t size = sizeof(*this);
if (hasSwiftInitializer)
size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
if (hasSwiftInitializer)
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
#if __has_feature(ptrauth_calls)
// Re-sign the method list pointer.
ro->baseMethods = baseMethods;
#endif
return ro;
}
Class getNonMetaclass() const {
ASSERT(flags & RO_META);
return nonMetaclass;
}
const uint8_t *getIvarLayout() const {
if (flags & RO_META)
return nullptr;
return ivarLayout;
}
};
[class_rw_ext_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
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, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("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};
}
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
void setFlags(uint32_t set)
{
__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
}
void clearFlags(uint32_t clear)
{
__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
ASSERT((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
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);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
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 *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
[class_rw_ext_t]
struct class_rw_ext_t {
DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
class_ro_t_authed_ptr<const class_ro_t> ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};