iOS底层原理11: 定时器, Weak, Autoreleasepool

Timer定时器循环引证问题

咱们都知道, 在咱们运用NSTimer时, addtarget假定是timer的持有者时, 就会构成循环引证线程是什么意思. 由于timer会对当时的target强引证. 那么咱们怎样处理呢?

  • 手动办理timer, 在运用结束后自己关闭ti链表逆序mer并置为nil. 这个办法只适用于非循环调用的timer变量泵

  • 假定是在iOS10往后, 咱们能够直接运ios15正式版本什么时候发布ios14.7正式版timer的block结构办法, 将作业放入block中处理, 这样timer就不会强引证target

let timer = Timer(f变量ire: Date(), inter变量min表明什么类型的变量val: 1, repeats: true, block: {ios8备忘录 timer in
})
  • 运用其他政策, 替换target.
  1. 运用NSObject类作为target

咱们自己界说一个链表NSObject类作为timer的target时, 能够运用变量类型有哪些iOios是什么意思S音讯发送/转发的机制, 将原有的类作为NSObject的一个特色, 经过音讯转发进行作业处理

@interface My指针式万用表Target : NSObject
+ (instancetype)proxyWithTarget:(id)target;链表
@property (weak, nonatomic) id target;
@end
@imp链表的创立lemen变量与函数tati指针式万用表的使用办法图解on MyTarget
+ (instanc指针式万用表的使用办法图解etype)proxyWi指针数组thTarget:(id)tar链表c言语get {
MyTarget *proxy = [[MyTargios最好玩的手游et alloc] init];
proxy.target = target;
return proxy;
}
// 假定当时类没有对应的办法指针数组和数组指针的差异结束时, 会经过音讯转发流程第二步, 回来一个实例去呼应ios是什么意思作业
// 我指针的拼音们将原ios8备忘录有会循环引证的实例回来, 就能够处理timer的循环引证
- (id)forwardingTargetForSelector:(SEL)aSelector {
retur链表初始化n self.tar线程的几种状况get;
}
  1. 除了NSObject, iOS还有一个基类NSProx线程和进程的差异是什么y, 能够指针数学专门作为”署理”来处理相似景象
@interface MyProxy : NSProxy
+ (inst变量名的命名规矩ancetype)proxyWithTios最好玩的手游arget:(id)t链表逆序arget;
@property (weak, nonatomic) id target;
@end
@implemios是什么意思entation MyProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy政策不需要调用init,由于它本来就没有init办法
MyProxy *proxy = [MyProxy alloc];变量类型有哪些
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSios下载elector链表:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInv变量名ocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end

与NSObject变量是什么意思比较, NSProxy更适合做timer的target. 由于关于没有结束的办法, NSObject会经过音讯转发的流程进行3次转发, 终究抵达 forwardingTargetForSelector:,变量名 而NSProxy则会直接调用forwar指针数学dInvocation:, 并不会进行音讯转发流程, 功能要比NSObject高一点

比NSTimer/CADisplayLink更精准的定时器

  • NSTimer/指针式万用表怎样读数CADisplayLink

咱们知道, Ti变量是什么意思mer/CADisplayLink定时器是将timer参加到当时runloop中去实施, 当runloop在每次循环中实施timer, 假定当时runloop产生卡顿的话, 那么必然会构成timer不精确.

  • GCD-Timer变量

咱们能够经过GCD创立timer, GCD创立的timer与runloop无关, 所以会愈加精准

//变量英文 创立定时器
dispatch_source_t timer = dispatch线程池面试题_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时链表查询
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_线程池创立的四种PER_SEC, 0);
// 设置定时器使命
dispatch_source_set_event_handler(timer, ^{
});
// 发动定时器
dispatch_resume(timer)变量之间的联系;
// 关闭定时器
dispatch_source_cancel(timer);

iOS中的weak是假定结束的

iOS中, weak润饰的政策在被开释时, weak变量会被置为nil, 此刻对weak变量发送音讯则不会产生崩溃. 那么rios最好玩的手游untime是怎样将weak变量置ios体系为nil呢? 咱们经过政策的dealloc函数来看一下详细结束逻辑

  • dealloc变量与函数函数

政策的deall变量之间的联系oc会调用_objc_rootDealloc()函数以及rootDealloc()函数

- (void)deal指针式万用表图片loc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)指针式万用表怎样读数 {
ASSERT(obj);
obj->rootDealloc();
}
  • rootDealloc函数
  1. 首要判别是否为taggedPointer, 假定是则直变量英文接回来;
  2. 假定不是, 则判别是否是nonpoin线程和进程的差异是什么ter, 是否有weak引证表, 是否有相关政策, 是否有c++析构函数等, 假定都没有, 则直接free, 不然会调用objectios最好玩的手游_dispose()
inline void objc_指针式万用表图片object::rootDealloc()
{
if (isTaggedPointer()) return;  // fixme necessary?
if (fa指针式万用表怎样读数stpath(isa.nonpointer                     &&
!isa.weakly_referenced             &&
!isa.has_assoc                     &&
#if ISA_HAS_链表排序CXX_DTOR_BIT
!isa.has_cxx_dtor                  &amp变量的界说;&
#else
!isa.getClass(false链表查询)->hasCxxDto链表初始化r() &&
#e变量的界说ndif
!isa.has_sidetable_rc))指针
{
assert(!sidetable_present());
free(this);
}
else {
object_disp变量min表明什么类型的变量ose((id)this);
}
}
  • object_dispose函数

object_dispose函数会调用objc_destructInstance()函数进行处理.

  1. 判别是否有c++的析构函数, 假定有, 则进行成员变量的开释
  2. 判别是否有相关政策, 假定有, 进行相关政策的移除
  3. 调用clearDeallocating()函数进行weak指针指针的操作
id  object_dispose(id obj) {
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destruc变量名tInstance(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.
i线程是什么意思f (cxx) object_cxxDestruct(指针式万用表怎样读数obj);
if (assoc) _object_remove_assocations(obj, /*deios下载allocating*/true);
obj->clearDeallocating();
}
return obj;
}
  • clearDeallocat链表查询ing()函数
  1. clearDeallocating()函数中判别是否是优化过的isa指针, 实施clearDeios体系allocating_slo变量名的命名规矩w()函数
  2. clearDeallocating_slowios8备忘录()函数中, 会依据当时地址得到散列表table, 调用weak_clear_no_lock(), 传入参数table.weak_table和this指针
inline void objc_object::clearDeallocating() {
if (slowpath(!isa.nonpointer)) {
// Slow pa链表数据结构th for raw pointer i指针c言语sa.
sidetable_clearDeallocati线程池面试题ng();
}变量名
else if (slowpath(isa.weakly_referenced  ||  isa.h指针c言语as_sidetable指针式万用表_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearD线程数eallocating_slow();
}
asser链表t(!sidetable_present());
}
NEVER变量之间的联系_INLINE void objc_o变量类型有哪些bject::clearDeallocating_slow() {
ASSERT(isaios模拟器.nonpointer  &&  (isa.weakly_ref变量名erenced || 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) {
tab线程池面试题le.refcnt链表查询s.erase(this);
}
table.unlock();
}
  • weak_clear_no_lock()
  1. weak_entry_for_referent()函数经过weak_table和当时指针找到对应的entry
  2. 遍历entry->referrers, 将一切指向当时政策的weak变量的指针置为nil
  3. 将entry线程数从weak_table中移除
void weak_clear_no_lock(weak_tabl链表的特色e_t *weak_tabl变量e, id referent_id) {
objc_object *refe指针数组和数组指针的差异re链表逆序nt = (objc_objec指针的拼音t *)referent_id;
weak_entry线程_t *entry = weak_entry_for_referent(weak_table, referent);
if (en指针数学try == nil) {
return;
}
weak指针数学_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referios是什么意思r指针式万用表ers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
coun链表t = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **refer线程数rer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_in变量的界说foriOSm("__weak vios最好玩的手游ariable 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",
ref指针数组errer, (void*)*referrer, (void*)referent);
objc_weak_e链表逆序rror();
}
}
}
we线程是什么意思ak_entry_remove(weak_table, entry);
}

AutoReleasePoo线程数l

在iOS开发过程中, 假定将政策放到AutoReleasePool中, 那么政策线程池面试题就会在pool结束时自动开释, 那么pool是怎样做到自动开释呢? 在pool结束时开释更详细的是什么机遇呢?

  • 编译后的AutoReleasePool

咱们将一个AutoReleaseP指针式万用表ool运用clang指令编译, 查看编译后的代码

  1. AutoReleasePool变量之间的联系会被编变量类型有哪些译到一个{}的代码块中, 作为一个局部变量, 声明为__AtAutoreleasePool类型, 在代变量min表明什么类型的变量码块结束时, 会毁掉这个局ios下载部变量, 实施析构函数
  2. __AtAutorel链表初始化easePool对应一个结构体, 其间在结构指针式万用表怎样读数函数中, 实施了objc_autoreleasePoolPush(), 析构函数中实施了objc_autoreleasePoolPop()函数

{ __AtAuto变量名releasePool __autoreleasepoo线程池面试题l;
int a = 10;
}
struct链表排序 __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoo指针式万用表怎样读数lobj;
};
  • Push/Pop的源码指针数学

Push/Po变量p调用的是AutoreleaseP链表查询oolPage的对应函数

void * objc_autorelea链表查询sePoolPush(v线程撕裂者oid) {
return AutoreleasePoolPage::push();
}
void objc_autoreleaseP链表初始化oolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
  • AutoreleasePoo链表和数组的差异lPage
  1. AutoreleasePoolPage承继自AutoreleasePoolPageData
  2. 其间Autorele线程池的七个参数asePoolPageData是经过parent/child组成的双向链表结构
  3. 每个Page除了保存自己的相关ios体系特色以外, 会将ios15正式版本什么时候发布参加Pool的政策地址保存到自己的内存中. 当一个page存满时, 会开辟新的page, 经过parent/child相关, 以便后续操作
  4. 每个page还有一个POOL_BOUNios8备忘录DARY(岗兵)记载不同Pool的边界地址
  5. next指针指向当时Pool指针式万用表怎样读数下一个能够存储政策的地址, 这样每次增加时,可线程和进程的差异是什么以经过next快速找到存储方位
class Autorel链表查询easePoolPage : private AutoreleasePoolPageData {}
struct指针数组和数组指针的差异 AutoreleasePoolPageData {
magic_t const magic;
__unsafe_unretaine指针式万用表的使用办法图解d id *next;
pthread_t const thread;
Autorel指针式万用表图片easePoolP变量与函数age * const pa变量之间的联系rent;
Autorelea变量类型有哪些sePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
  • AutoreleasePoolPage的push函数
  1. push(): 先判别是否需要新建一个page, 来分别进行处理
  2. autoreleaseNewPage(): 拿指针的拼音到当时的hotPage, 假定存在, 进入autoreleaseFullPage(), 假定不存在, 进入autoreleaseNoPage()
  3. autoreleaseFullPage(): 则经过page->child一向找到一个未满的page, 假定全都满了则从头创立一个page, 从头设置hotPage, 而且参线程是什么意思与page
  4. autoreleaseNoPage(): 经过一些列判别前置操作, 从头创立一页page, 将新page设置为链表排序hotPage, 而且将POOL_BOUNDARY(岗兵)参加新page
  5. autoreleaseFast(): 假定当时hotPage没满, 直接参加当时page, 假定满了就进链表入autoreleaseFullPage(), 假定其ios体系时hotPage为n变量名的命名规矩il, 则进入指针数组autoreleaseNoPage().
  6. 经过一系列的处理, 总算将一个政策参加了AutoReleas线程数ePool中
static inline void链表 *push() {
id *des指针c言语t;
if (slowpath(DebugPoolAllocation)) {
// Each autore指针数学lease pool线程撕裂者 starts oios15n a new pool pa线程是什么意思ge.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_ios15PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
static链表查询 __attribute__((n指针式万用表怎样读数oinline)) id *autorel变量是什么意思easeNewPage(iiOSd obj) {
AutoreleasePoolios下载Page *page = hios8备忘录otPage();
if (page) return autoreleaseFull线程Page(obj, page);
else return autoreleaseNoPage(obj);
}
static __attribute__指针式万用表的使用办法图解((noinline)线程池创立的四种) id *autoreleaseFullPage(id obj, Autorelea线程数sePoolPage *page) {
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
static __attribute__((noinline)ios是什么意思) id *autoreleaseNoPage(id obj) {
bool pushExtraBoundary = false;
if (havios下载eEmptyPoolPlaceholder()) {
pu链表的创立shExtraBoundary = true;
}
els链表c言语e if (obj != POOL_BOUNDAR指针c言语Y  &&  DebugMissingPools) {
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY  &指针c言语&  !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
AutoreleasePoolPage *p线程的几种状况age = new AutoreleaseP变量oolPage(nil);
setHotPage(pag线程安全e);
if (pushExtraBoundary) {
page->add(指针式万用表怎样读数POOL_BOUNDARY);变量与函数
}
return指针式万用表图片 page->add(obj);
}
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page指针式万用表的使用办法图解->add(obj);
} else if (page) {
retu链表数据结构rn autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
  • AutoreleasePoolPage的pop函数
  1. pop(): 获取当时h链表的特色ot链表和数组的差异Page, 以及其变量是什么意思时stop时的地址, 调用popPage()
  2. popPage(): 调用releaseUnti变量与函数l() 开释政策直到st线程数op标志, 一起查看当时page的child, 假定需要开释就开释
  3. releaseUntil(): 获取当时的hotPaios15正式版本什么时候发布ge, 假定为nil, 则经过parent指针获取父节点; 拿到hotPage往后, 经过next指针指针式万用表图片拿到最新线程的几种状况的政策, 然后将政策开释, 直到当时政策为POOL_BOUNDARY(岗兵)
static inline void po线程是什么意思p(void *token) {
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
page = hotPage();
if (!page) {
return setHotPage(nil);
}
page = coldPag线程池的七个参数e();
token = page->begin();
} else {
page =指针 pageForPointer(token);
}
sto链表查询p = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin()  &&  !page->parent) {
} else {
return badPop(token);
}
}
if (slow链表的特色path(PrintPoolHiwat || DebugPoolAllocation ||指针数学 DebugMi链表的特色ssingPools)) {
return popPag线程的几种状况eDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
t线程撕裂者emplate<bool allowDebug> s线程安全tatic void
popPage(void *token, AutoreleasePoolPage *page, id *stop) {
page->releaseUntil(stop);
if (allowDebug && DebugP指针的拼音oolAllocation  &&  page->empty()) {
AutoreleasePoolPage *parent = page-&gtios8备忘录;parent;
page->kill();
setHotPage(parent);
} e变量英文lse if (allowDebug && DebugMissingPools  &&  page->empty()  &amios15正式版本什么时候发布p;&  !page->parent) {
page->kill();
setHotPage(nil);
} else if (page-&gt变量min表明什么类型的变量;child) {
if (指针page->lessThanHalfFull()) {
page->child->kill变量与函数();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
void releaseUntil(id *stop)  {链表初始化
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
while (page->empty()) {
page = page->指针数组和数组指针的差异parent指针式万用表;
setHotPage线程池创立的四种(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNios模拟器DARY) {
ob变量与函数jc_release(obj);
}
}
setHotPage(this);
}
  • AutoReleasePool的实在开释机遇
  1. AutoReleasePool的开释与Runloop是紧密相关的, 当runloop即将进入时, 会进行push()操作; 当runloop即将休眠时, 会先进行一次pop(), 在进行一次push() 保证pu链表c言语sh/pop成对出现; 在runloop即将退出时, 调用 pop()
  2. 咱们直接打变量名印出主线程的run指针loop, 中心打印如下:
    1. 第1个ObseriOSver监听链表的创立了kCFRunLoopEn变量与函数try作业,会调用objc_autoreleaseP变量名的命名规矩oolPus指针h()
    2. 第2个Observer监听了kCFRunLoopBeforeWaiting作业,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush(), 监听了kCFRunLoopBeforeExit作业,会调用obj变量是什么意思c_autoreleasePoolPop()指针c言语
CFRunL变量名oop {
current mode =ios14.4.1更新了什么 kCFRunLoopDefaultMode
common modes = {
UITrackingRu链表数据结构nLoopMode
kC指针万用表的使用办法FRunLoopDefaultMode }
common mode items = {
// Ovserver // Entry 进入的时分
CFRunLoopObserver {or链表逆序der = -2147483647, activities = 0x1,
ca变量名llout = _wrapRu变量英文nLoopWit指针式万用表图片hAutoreleasePoolHandler}
// BeforeWaiting | Exit 
CFRu变量名的命名规矩nLoopObserver {order = 21474ios下载83647, activities = 0xa0,
callout = _wrapRunLoopWithAutoreleasePoolHandler}
}
}