群组
Flutter,iOS,Android,Python等,闲聊工作&技术&问题;
个人主页:https://waitwalker.cn
telegram群链接:https://t.me/joinchat/Ej0o0A1ntlq5ZFIMzzO5Pw
在常规开发中我们经常使用__weak修饰符来修饰一个对象,来解决比如循环引用问题.今天具体来看看weak底层实现原理.
首先创建一个weak修饰的对象,并查看其调用栈:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class newClass = objc_allocateClassPair(objc_getClass("NSObject"), "newClass", 0);
objc_registerClassPair(newClass);
id newObject = [[newClass alloc]init];
NSLog(@"%s",class_getName([newObject class]));
NSLog(@"Hello, World!");
Person *person = [[Person alloc]init];
Person *p2 = person;
[person name];
[person name];
Class cls = [Person class];
// weak
__weak id weakP = person;
NSLog(@"weakP point address: %p",&weakP);
NSLog(@"person point address: %p",&person);
NSLog(@"weakP address: %p",weakP);
NSLog(@"person address: %p",person);
}
return 0;
}
init_weak()
其走到了id objc_initWeak(id *location, id newObj){}函数里面:
其内部调用的是:
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj){}
storeWeak有三个模板参数HaveOld,HaveNew,CrashIfDeallocating默认的是
HaveOld:为true,weak对象已存在,需要清理;
HaveNew:为true需要给weak对象分配一个新值;
CrashIfDeallocating:为true,storeWeak过程要终止,因为对象正在释放或者newObj的类不支持弱引用.
storeWeak()函数的内部实现:
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
SideTable
首先声明了两个SideTable实例,SideTable我们之前文章已经介绍过了,其内部主要有三个成员变量(spinlock_t,RefcountMap,weak_table_t)和一些锁相关的成员方法:
// MARK: - sideTable类
struct SideTable {
spinlock_t slock;//保证原子操作的自旋锁
RefcountMap refcnts;//保存引用计数的散列表
weak_table_t weak_table;//保存weak引用的全局散列表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
weak_table_t
weak_table_t就是一个全局的存取weak引用.其中将对象地址作为key,weak_entry_t结构作为value,存储在weak_table_t中.weak_entry_t中就保存了所有指向该对象的weak指针.
// MARK: - 全局弱引用表, 对象地址作为key,weak_entry_t结构作为value
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
*weak_entries:用于存储引用对象的结合
num_entries:entry数目
mask:当前分配容量
max_hash_displacement:已使用容量
weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
weak_entry_t是存放weak对象和weak指针的地方,其成员变量referent是引用对象.union结构:
weak_referrer_t *referrers;//存放weak指针
uintptr_t out_of_line_ness : 2;//标识当前存储是否在默认WEAK_INLINE_COUNT个数之内
uintptr_t num_refs : PTR_MINUS_2;//引用的个数
uintptr_t mask;//实际分配容量
uintptr_t max_hash_displacement;//实际使用容量
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];//当引用个数小于WEAK_INLINE_COUNT时,用这个存储weak指针地址.
oldTable&newTable逻辑
haveOld
oldObj = *location;
oldTable = &SideTables()[oldObj];
当存在旧的weak对象的时候,要把*location赋值给旧的对象oldObj,然后通过oldObj的hash值,在SideTableBuf中找到对应的SideTable.其中SideTableBuf的实例化过程:
// SideTableBuf静态全局变量
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
// MARK: - runtime初始化时调用(objc_init)
static void SideTableInit() {
new (SideTableBuf) StripedMap<SideTable>();
}
// reinterpret_cast C++里的强制类型转换符
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
StripedMap
我们可以看到StripedMap这个类,StripedMap其实是一个C++模板类,初始化时需要指定一个类型.
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
// MARK: - 重写[]操作符,计算hash
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
}
StripedMap是一个泛型类,重写[]运算符,通过传入的对象地址,计算出hash值,然后通过hash值在SideTableBuf中找出对应的SideTable.
weak_unregister_no_lock
现在我们根据对象的地址找到了对应的SideTable了,然后就可以操作其成员变量weak_table了.
如果haveOld=ture,需要先清理一下旧的weak对象.
// 清理旧的obj
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
具体实现:
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
这步主要用于在指针即将消失时,而对象还没有置为nil.主要防止空指针指向对象造成坏内存访问错误.
首先通过weak_entry_for_referent()函数查找到weak_entry_t:
// MARK: - 通过对象地址,在weak_entries中找到对应的weak_entry_t
/**
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
// 获取hash值 按位与操作
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 遍历weak_table中的weak_entries,比对weak_entries[index].referent对象和referent对象是否相等
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
// 返回weak指针
return &weak_table->weak_entries[index];
}
remove_referrer
// MARK: - 移除weak指针
/**
* Remove old_referrer from set of referrers, if it's present.
* Does not remove duplicates, because duplicates should not exist.
*
* @todo this is slow if old_referrer is not present. Is this ever the case?
*
* @param entry The entry holding the referrers.
* @param old_referrer The referrer to remove.
*/
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
如果weak指针不存放在out_of_line中,从WEAK_INLINE_COUNT移除;如果存在在out_of_line中,将referrers中weak指针置为nil,并将weak指针数目num_refs减1;
主要逻辑思路:
1)根据对象的地址的hash值获取到全局的SideTable
2)从SideTable的成员变量entries中获取到对象地址对应的entry
3)将entry的weak置为nil,更新weak指针数量
haveNew
newTable = &SideTables()[newObj];
也是通过hash值在SideTableBuf中找出对应的SideTable.
weak_register_no_lock
// MARK: - 注册一个新的(对象,weak指针)键值对,创建一个新的entry
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;
// 如果entry已经存在,直接将weak指针存储到entry中
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 如果entry不存在,先创建一个entry,然后把weak指针存到entry中
weak_entry_t new_entry(referent, referrer);
// weak_table size扩容
weak_grow_maybe(weak_table);
// 将entry插入到weak_table的entries中
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
append_referrer()
如果entry已经存在,直接将weak指针存储到entry中:
// MARK: - 将weak指针添加到entry的weak指针集合中
/**
* Add the given referrer to set of weak pointers in this entry.
* Does not perform duplicate checking (b/c weak pointers are never
* added to a set twice).
*
* @param entry The entry holding the set of weak pointers.
* @param new_referrer The new weak pointer to be added.
*/
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
assert(entry->out_of_line());
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
new_entry()
如果entry不存在,先创建一个entry,然后把weak指针存到entry中:
// 如果entry不存在,先创建一个entry,然后把weak指针存到entry中
weak_entry_t new_entry(referent, referrer);
// weak_table size扩容
weak_grow_maybe(weak_table);
// 将entry插入到weak_table的entries中
weak_entry_insert(weak_table, &new_entry);
// MARK: - 将entry插入到weak_table_t的entries中
/**
* Add new_entry to the object's table of weak references.
* Does not check whether the referent is already in the table.
*/
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
主要逻辑思路:
1)根据对象的地址的hash值获取到全局的SideTable
2)从SideTable的成员变量entries中获取到对象地址对应的entry
3)将weak指针存储到entry中,更新weak指针数量
dealloc weak
我们知道对象被销毁了,会走-dealloc()方法,下面我们来看看一个对象的weak指针效果过程:
- (void)dealloc;
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
可以看到dealloc调用的是NSObject.mm文件中的_objc_rootDealloc()方法,这个方法内部调用的是obj的成员方法rootDealloc():
// MARK: - 销毁对象
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
// 如果是nonpointer优化的对象并且没有weakly_referenced,没有has_assoc,没有has_cxx_dtor,没有has_sidetable_rc,快速释放对象
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
// 否则调用此方法
object_dispose((id)this);
}
}
如果对象是nonpointer优化的对象并且没有weak指针指向它,没有SideTable存储引用计数等,将快速释放,否则调用object_dispose()销毁对象:
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
// MARK: - 销毁实例对象
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
调用栈中有objc_destructInstance()方法内有处理关联对象的方法,其接下来调用的方法是clearDeallocating()方法:
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
如果对象本身又weak指针指向它或者在SideTable中存储了引用计数,则通过clearDeallocating_slow()方法处理:
// MARK: - 清除weak指针和SideTable中的引用计数
// Slow path of clearDeallocating()
// for objects with nonpointer isa
// that were ever weakly referenced
// or whose retain count ever overflowed to the side table.
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
weak_clear_no_lock
清除weak指针,首先在SideTable的成员变量entries中找到 dealloced obj对应的entry,遍历entry中的weak指针数组,将所有weak指针置为nil,然后将这个entry从entries中移除:
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
erase()
擦除SideTable中的存储的对象引用计数
table.refcnts.erase(this);
bool erase(const KeyT &Val) {
BucketT *TheBucket;
if (!LookupBucketFor(Val, TheBucket))
return false; // not in map.
TheBucket->second.~ValueT();
TheBucket->first = getTombstoneKey();
decrementNumEntries();
incrementNumTombstones();
compact();
return true;
}
主要逻辑思路:
1)根据对象的地址的hash值获取到全局的SideTable
2)从SideTable的成员变量entries中获取到对象地址对应的entry
3)遍历entry中weak数组将weak指针置为nil
总结
weak引用的实现是多个hash表的嵌套.首先根据对象地址hash value找到全局SideTable;然后在SideTable中找到对应的entry对其进行操作.通过查看源码实现过程,思路还是比较清晰.
编译后源码库
编译后的源码放在Github, 如果对你有帮助,请给一个star吧!
博客地址&相关文章
博客地址: https://waitwalker.cn/
系列文章: