-
Notifications
You must be signed in to change notification settings - Fork 4
Reference Count
iOS 中 Object-C
与 Swift
都是引用计数的方式来管理内存,在实现上存在一些区别
暂时不考虑 weak
引用和 TaggedPointer
小对象这种特殊场景情况
目前网上看到的资料基本出自小码哥李明杰和逻辑教育的 OC 底层课程
,这里我将结合 objc-781
和暂时最新的 objc-818.2
进行对比学习、总结
-
Object-C
经过优化后的isa
,首先会直接存储在isa
的extra_rc
这块19
个二进制位空间中 (存储的值为引用计数-1
) -
由于存储空间有限,就有可能会超出,因此在
isa
中额外有一个标记位has_sidetable_rc
-
当
has_sidetable_rc
为1
,时,引用计数就会被存储在一个叫SideTable
的类的属性中struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; 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); };
这里的
slock
不要看类型就认为是自旋锁,通过818.2
源码可见,是互斥锁的别名typedef mutex_t spinlock_t;
RefcountMap refcnts;
就是用来存储引用计数的,是一个散列表的结构 -
非优化后的
isa
, 直接存储在SideTable
中关于
isa
的结构布局,参考笔记 isa
在调用 retainCount
方法时,内部会调用到 _objc_rootRetainCount
函数
- (NSUInteger)retainCount {
return _objc_rootRetainCount(self);
}
紧接着调用到 rootRetainCount
函数
uintptr_t
_objc_rootRetainCount(id obj)
{
ASSERT(obj);
return obj->rootRetainCount();
}
这里会通过 SUPPORT_NONPOINTER_ISA
标记判断是否是优化后的 isa
执行不同的函数,由于目前是优化后的 isa
, 所以 rootRetainCount
函数的具体实现是
// 781 源码
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
// 818.2 源码
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
// 这行代码与上面的处理没有区别, LoadExclusive(&isa.bits) 函数也是直接调用了 _c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED) 返回
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
// isa 优化前直接从 SideTable 取计数值的方法 (781 和 818.2下无变化)
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
// isa 优化后 从 SideTable 取计数值的方法 (781 和 818.2下无变化)
size_t
objc_object::sidetable_getExtraRC_nolock()
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
获取引用计数函数调用过程解读
- 判断是
TaggedPointer
的直接返回这里也是为什么某些情况下我们打印
NSString
类型的引用计数为-1
的原因,因为TaggedPointer
指针足够存储当时的字符串,并不会进行引用计数管理 - 取出
isa.bits
64位数据,判断是否是优化后指针类型,如果是优化后的指针,取出extra_rc
2.1 如果has_sidetable_rc
标记为为1
,说明有通过SideTable
额外存储,再取一下SideTable
的值,加上extra_rc
中的值,就是最终的引用计数值 2.2 如果has_sidetable_rc
标记为为0
,就直接返回extra_rc
里面的计数值就可以了 - 如果是非优化后的指针(早期版本),是通过
sidetable_retainCount
函数直接返回计数值
变化
- 最新的
818.2
源码与781
源码有所调整,早期文章提到extra_rc
里面存储的是引用计数值 - 1
,因此在获取计数值的时候,会进行+1
(uintptr_t rc = 1 + bits.extra_rc;
);但从818.2
源码可见,已经不再+1
-
函数的调用过程:
-
781
- (void)release
->_objc_rootRelease(self)
->obj->rootRelease()
->rootRelease(false, false)
最终来到
ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
函数-
rootRelease
718 源码rootRelease (718 源码)
ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { if (isTaggedPointer()) return false; bool sideTableLocked = false; isa_t oldisa; isa_t newisa; retry: do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (rawISA()->isMetaClass()) return false; if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); } // don't check newisa.fast_rr; we already called any RR overrides uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- if (slowpath(carry)) { // don't ClearExclusive() goto underflow; } } while (slowpath(!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: // newisa.extra_rc-- underflowed: borrow from side table or deallocate // abandon newisa to undo the decrement newisa = oldisa; if (slowpath(newisa.has_sidetable_rc)) { if (!handleUnderflow) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } // Transfer retain count from side table to inline storage. if (!sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; // Need to start over to avoid a race against // the nonpointer -> raw pointer transition. goto retry; } // Try to remove some retain counts from the side table. size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); // To avoid races, has_sidetable_rc must remain set // even if the side table count is now zero. if (borrowed > 0) { // Side table retain count decreased. // Try to add them to the inline count. newisa.extra_rc = borrowed - 1; // redo the original decrement too bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); if (!stored) { // Inline update failed. // Try it again right now. This prevents livelock on LL/SC // architectures where the side table access itself may have // dropped the reservation. isa_t oldisa2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2; if (newisa2.nonpointer) { uintptr_t overflow; newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); if (!overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); } } } if (!stored) { // Inline update failed. // Put the retains back in the side table. sidetable_addExtraRC_nolock(borrowed); goto retry; } // Decrement successful after borrowing from side table. // This decrement cannot be the deallocating decrement - the side // table lock and has_sidetable_rc bit ensure that if everyone // else tried to -release while we worked, the last one would block. sidetable_unlock(); return false; } else { // Side table is empty after all. Fall-through to the dealloc path. } } // Really deallocate. if (slowpath(newisa.deallocating)) { ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); // does not actually return } newisa.deallocating = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); __c11_atomic_thread_fence(__ATOMIC_ACQUIRE); if (performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); } return true; }
-
-
818.2
- (void)release
->_objc_rootRelease(self)
->obj->rootRelease()
->rootRelease(true, RRVariant::Fast)
最终来到
ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
函数-
rootRelease
818.2 源码, 代码比较长,有多处变化rootRelease (818.2源码)
LWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant) { if (slowpath(isTaggedPointer())) return false; bool sideTableLocked = false; isa_t newisa, oldisa; oldisa = LoadExclusive(&isa.bits); if (variant == RRVariant::FastOrMsgSend) { // These checks are only meaningful for objc_release() // They are here so that we avoid a re-load of the isa. if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) { ClearExclusive(&isa.bits); if (oldisa.getDecodedClass(false)->canCallSwiftRR()) { swiftRelease.load(memory_order_relaxed)((id)this); return true; } ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release)); return true; } } if (slowpath(!oldisa.nonpointer)) { // a Class is a Class forever, so we can perform this check once // outside of the CAS loop if (oldisa.getDecodedClass(false)->isMetaClass()) { ClearExclusive(&isa.bits); return false; } } retry: do { newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); return sidetable_release(sideTableLocked, performDealloc); } if (slowpath(newisa.isDeallocating())) { ClearExclusive(&isa.bits); if (sideTableLocked) { ASSERT(variant == RRVariant::Full); sidetable_unlock(); } return false; } // don't check newisa.fast_rr; we already called any RR overrides uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- if (slowpath(carry)) { // don't ClearExclusive() goto underflow; } } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits))); if (slowpath(newisa.isDeallocating())) goto deallocate; if (variant == RRVariant::Full) { if (slowpath(sideTableLocked)) sidetable_unlock(); } else { ASSERT(!sideTableLocked); } return false; underflow: // newisa.extra_rc-- underflowed: borrow from side table or deallocate // abandon newisa to undo the decrement newisa = oldisa; if (slowpath(newisa.has_sidetable_rc)) { if (variant != RRVariant::Full) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } // Transfer retain count from side table to inline storage. if (!sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; // Need to start over to avoid a race against // the nonpointer -> raw pointer transition. oldisa = LoadExclusive(&isa.bits); goto retry; } // Try to remove some retain counts from the side table. auto borrow = sidetable_subExtraRC_nolock(RC_HALF); bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there if (borrow.borrowed > 0) { // Side table retain count decreased. // Try to add them to the inline count. bool didTransitionToDeallocating = false; newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too newisa.has_sidetable_rc = !emptySideTable; bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits); if (!stored && oldisa.nonpointer) { // Inline update failed. // Try it again right now. This prevents livelock on LL/SC // architectures where the side table access itself may have // dropped the reservation. uintptr_t overflow; newisa.bits = addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow); newisa.has_sidetable_rc = !emptySideTable; if (!overflow) { stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits); if (stored) { didTransitionToDeallocating = newisa.isDeallocating(); } } } if (!stored) { // Inline update failed. // Put the retains back in the side table. ClearExclusive(&isa.bits); sidetable_addExtraRC_nolock(borrow.borrowed); oldisa = LoadExclusive(&isa.bits); goto retry; } // Decrement successful after borrowing from side table. if (emptySideTable) sidetable_clearExtraRC_nolock(); if (!didTransitionToDeallocating) { if (slowpath(sideTableLocked)) sidetable_unlock(); return false; } } else { // Side table is empty after all. Fall-through to the dealloc path. } } deallocate: // Really deallocate. ASSERT(newisa.isDeallocating()); ASSERT(isa.isDeallocating()); if (slowpath(sideTableLocked)) sidetable_unlock(); __c11_atomic_thread_fence(__ATOMIC_ACQUIRE); if (performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); } return true; }
-
-
部分解读 (代码逻辑较多,
818.2
增加了不少代码 )
-
TaggedPointer
直接返回 -
根据缓存的计数值做
-1
操作
2.1 如果不是优化后的isa
直接SideTable
散列表-1
2.2 如果是优化后的isa
,则对extra_rc
中的引用计数值进行-1
- 通过
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
这样代码可以判断出在做-1
操作 - 当
extra_rc
为0
后,并且has_sidetable_rc
标记了有额外的存储计数size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
取出一半-1
后给extra_rc
- 通过
-
如果最终
-1
后为0
,performDealloc
条件成立,通过msg_send
执行dealloc
-
函数的调用过程:
-
781
-(id) retain
->_objc_rootRetain(self)
->obj->rootRetain()
->rootRetain(false, false)
最终来到
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
函数-
rootRetain
718 源码rootRetain (718源码)
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { if (isTaggedPointer()) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (rawISA()->isMetaClass()) return (id)this; if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } // don't check newisa.fast_rr; we already called any RR overrides if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; }
-
-
818.2
-(id) retain
->_objc_rootRetain(self)
->obj->rootRetain()
->rootRetain(false, RRVariant::Fast)
最终来到
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
函数-
rootRetain
818.2 源码rootRetain (818.2源码)
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) { if (slowpath(isTaggedPointer())) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; oldisa = LoadExclusive(&isa.bits); if (variant == RRVariant::FastOrMsgSend) { // These checks are only meaningful for objc_retain() // They are here so that we avoid a re-load of the isa. if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) { ClearExclusive(&isa.bits); if (oldisa.getDecodedClass(false)->canCallSwiftRR()) { return swiftRetain.load(memory_order_relaxed)((id)this); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain)); } } if (slowpath(!oldisa.nonpointer)) { // a Class is a Class forever, so we can perform this check once // outside of the CAS loop if (oldisa.getDecodedClass(false)->isMetaClass()) { ClearExclusive(&isa.bits); return (id)this; } } do { transcribeToSideTable = false; newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(sideTableLocked); } // don't check newisa.fast_rr; we already called any RR overrides if (slowpath(newisa.isDeallocating())) { ClearExclusive(&isa.bits); if (sideTableLocked) { ASSERT(variant == RRVariant::Full); sidetable_unlock(); } if (slowpath(tryRetain)) { return nil; } else { return (id)this; } } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (variant != RRVariant::Full) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits))); if (variant == RRVariant::Full) { if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); } else { ASSERT(!transcribeToSideTable); ASSERT(!sideTableLocked); } return (id)this; }
-
-
代码调用过程与
release
很相似, 之前是减操作,这里是加操作
-
在优化
isa
以前,直接存在SideTable
散列表中 -
在优化
isa
以后,肯定优先存在extra_rc
里 2.1 如果这里存满了,那么会取出一半存放到SideTable
中去,并将has_sidetable_rc
标记为1
(slowpath(carry))
条件成立时,就是extra_rc
满了-
newisa.extra_rc = RC_HALF;
和sidetable_addExtraRC_nolock(RC_HALF);
分别是半劈存储 😁
这么操作的目的在于提高性能,因为如果都存在散列表中,当需要
release-1
时,需要去访问散列表,每次都需要开解锁,比较消耗性能。extra_rc
存储一半的话,可以直接操作extra_rc
即可,不需要操作散列表。性能会提高很多-
isa
的bits
为 8字节 = 64 bit,根据结构体位域内存分布,nonpointer
是最低位,bits
中的1ULL<<45
(arm64)后就是extra_rc
最低位,通过addc
函数执行加法运算 (对比release
通过subc
实现-1
运算),也就是要在extra_rc
的存储空间最低位上去+1
-
__arm64__
# define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18)
-
在了解 Swift
引用计数前,应该先了解下 Swift
类结构,可以先参考笔记 Swift实例对象内存结构 和 HeapObject
在之前简单探索 Swift
源码得知,非继承自 NSObject
的 Swift
类结构中专门有个 SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
8字节来存放引用计数,这可比优化后 isa
指针那 19个二进制位
大得多了
通过 Swift
源码可以大概了解, Swift
有三种引用计数
@_silgen_name("swift_retainCount")
public func _getRetainCount(_ Value: AnyObject) -> UInt
@_silgen_name("swift_unownedRetainCount")
public func _getUnownedRetainCount(_ Value: AnyObject) -> UInt
@_silgen_name("swift_weakRetainCount")
public func _getWeakRetainCount(_ Value: AnyObject) -> UInt
Swift
源码看的还不够深,下次得重新编译一个 Xcode 工程来阅读,目前用 VSCode 看起来很不舒服
TBD.
ShenYj.github.io - 简书地址 - 返回首页
-
Apple
Common
蓝牙
LBS
音视频
- AVAudioSessionchange_route
- 切换线路
- StreamingKit
- Audio Unit 基础
OC 与 Swift 混编
Object-C
- 代码混淆
- autoreleasepool
- 忽略编译器(clang)警告
- 定时器
- 锁
- RunLoop
- block
- NS_REFINED_FOR_SWIFT
- NS_CLOSED_ENUM
- NS_TYPED_ENUM、NS_STRING_ENUM
- NS_TYPED_EXTENSIBLE_ENUM、NS_EXTENSIBLE_STRING_ENUM
- 关键字nonnull和nullable
- class、objc_getClass和object_getclass方法区别
- isKindOfClass和isMemberOfClass
- 应用程序的加载
- non-lazy classes & lazy classes
- load方法
- initialize方法
- 方法的本质
- 类型编码
- self和super
- 类的内存分析
Swift
- precondition
- 权限控制
- Array常用Api
- String初始化、定义
- String常用Api
- String截取演练
- Set定义、创建
- Set访问和修改
- Dictionary操作
- Dictionary和KeyValuePairs
- Dictionary与String转换
- 常用高阶函数
- enum原始值
- enum关联值
- enum遍历
- 递归enum
- enum内存分配
- 指针
- for循环
- break跳出循环
- 变量名与关键字冲突
- 类的定义
- 类的继承和初始化
- 关键字: final
- 关键字: mutating
- 关键字: lazy
- 修饰类方法的关键字: static
- 关键字: final、dynamic、objc和_dynamicReplacement
- 关键字:@dynamicMemberLookup和@dynamicCallable
- 关键字: propertyWrapper
- 自定义运算符
- 下标: subscript
- 扩展: extension
- 协议: protocol
- 协议和扩展
- 为什么需要泛型
- 泛型函数定义
- 泛型类型
- 泛型的类型约束
- 关联类型
- 为泛型定义要求
- 泛型下标
- 多线程
- Attributes
- 错误处理
- Codable
- DispatchSourceTimer
- Swift 5.x 演练: 更多功能编辑页
- Swift 5.x 类库收集
- 单元测试笔记
- 实例对象内存结构
- 元类型、Type、Self
- frozen
- convention
- Swift(5.3.2)源码编译
- SQLite.Swift类库演练
- Swift 5.5 关键字: async/await
- Swift 5.5 新特性: Continuations
- Swift 5.5 新特性: Actor
- Swift 方法调度
- Swift Mirror
- Swift 关键字: @_silgen_name
- Swift 关键字: @_disfavoredOverload
- swiftmodule
- Swift 5.6 新特性: Type placeholders
- Swift 5.6 新特性: #unavailable
- Swift 5.6 新特性: CodingKeyRepresentable
- Swift 5.6 新特性: existential any
- Swift 5.7 新特性: if-let/guard 语法简化
- Swift 5.7 新特性: Multi-statement closure type inference
- Swift 5.8 新特性: @backDeployed
- Swift 5.9 新特性: if switch expressions
- Swift 6.0 新特性:@preconcurrency
RxSwift
macOS - AppKit
-
iOS Assembly(ARM64)
-
C++
C++ 基础
- cout、cin
- 函数重载
- 默认参数
- extern "C"
- pragma once
- inline function
- const
- Reference
- 汇编
- 类和对象
- 堆空间内存管理
- Constructor
- Destructor
- 成员变量初始化
- 声明与实现分离
- namespace
- 继承
- 访问权限
- 初始化列表
- 多态:虚函数
- 多态:虚函数实现原理
- 多态:虚析构函数
- 多态:纯虚函数
- 多态:抽象类
- 多继承
- static
- static: 单例模式
- const 成员
- 引用类型成员
- 拷贝构造函数
- 调用父类的拷贝构造函数
- 浅拷贝、深拷贝
- 对象型参数和返回值
- 匿名对象
- 隐式构造
- 编译器自动生成的构造函数
- 友元
- 内部类
- 局部类
- 运算符重载
- 模板
- 类型转换
- C++标准
- Lambda
- 异常
- 智能指针
-
Flutter
Dart
Flutter
-
Go
Go 基础
-
Ruby
Ruby 基础
-
React-Native
React-Native
-
工具篇
-
Swift Package Manager
-
自动化
-
TroubleShooting
-
扩展