iOS的内存管理和runloop
全栈老韩
全栈工程师,擅长iOS App开发、前端(vue、react、nuxt、小程序&Taro)开发、Flutter、React Native、后端(midwayjs、golang、express、koa)开发、docker容器、seo优化等。
内存管理 & Runloop
一、内存管理
- 自动引用计数Related Count会自动给对象加retain,release.
- 函数是
objc_retain
objc_retain(id _Nullable objc)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
- 传进需要引用计数的对象
- isa指针是不是 TaggedPointer
- TaggedPointer针对小类型,当我们存储,比如NSNumber、NSString,字符串的长度小于固定长度时,我们使用TaggedPointer来优化它们的isa指针.目的是,存储的更高效.
- 并不是所有对象的isa指针类型都是一样的
- 所以对于NSNumber、NSString不用进行内存引用计数加1,内存管理语义是不一样的
- isa指针,64位,如果都用来存储地址,是不明智的。还存储了别的信息.
- retain函数
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
- rootRetain()
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
....
}
-
操作了sideTable:
是HashTable,存储的是我们当前对象的引用计数,弱引用表,锁 -
执行了函数
id objc_object::sidetable_retain()
{
...
}
SideTable的定义
* // Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
struct SideTable {
spinlock\_t slock;
// 引用计数,也是一个hash表
RefcontMap refcnts;
// 弱引用表,hash表
weak\_table\_t weak\_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTabel.");
}
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(SideTabel *lock1, SideTabel *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTabel *lock1, SideTabel *lock2);
}
-
引用计数加1
// 位移运算
refcntStorage += SIDE_TABLE_RC_ONE;
这里的1,是经过位移后的1.
weak:
- Runtime维护了一张全局的弱引用表
- 调用了
id objc_initWeak(id *location, id newObj)函数,location是地址,然后其中调用了
storeWeak方法来存储 - 根据传进来的newobj来操作SideTabel来操作.
- 在弱引用表中插入的是
weak_entry_t,找到表起始地址,while循环找到我们存储的对象. - 有性能情况,不断的通过hash运算来查询、插入、删除等.
- 总之,weak就是通过hash运算找到弱引用表,插入或者删除
- dealloc: 自动清理弱引用表.
Runloop
- 使用NSRunLoop currentLoop,是对CFRunLoop的封装.
struct __CFRunLoop {
...
// 唤醒
// mach_port内核通讯
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
...
pthread_t _pthread;
...
CFMutableSetRef _commonModes;
// timer,source, observer
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
...
}
- 线程和RunLoop一一对应.
- CommonMode并不是mode类型,而是一个特殊标识
struct __CFRunLoopMode {
...
CFMutableSetRef _source0; // 不具备主动唤醒线程能力,手动唤醒线程。可创建并添加到当前的runloop中的
CFMutableSetRef _source1; // 基于port端口事件,可主动唤醒线程
...
}
示例解说
Dispatch\_async(dispatch\_get\_main\_queue(), ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject\:nil afterDelay:0];
NSLog(@"3");
})
(void)test {
NSLog("current Thread -- %@", \[NSThread currentThread]);
NSLog(@"2");
}
结果输出:
1
3
current thread -- <NSThread: ..>{number = 1, name = main}
2
- 查看performSelector的注释,就是往当前线程中的RunLoop中注册了一个NSTimer.
- performSelector要求, 运行在default mode中,并且当前的runloop是执行的
- 主线程默认runloop开启
- 当runloop回来的时候,timer时间到了,再去执行selector,也就是performselector就是注册了一个timer,然后等待timer时间到了,回到default mode中时,再执行selector.
注:如果是在dispatch_get_global_queue中时,输出:
1
3
原因:
global队列的runloop默认不开启.
发布于2024-02-01 05:52:07
浏览量66·
暂无评论,快来发表第一条评论吧