定时器
1. CADisplayLink、NSTimer使用注意
CADisplayLink
、NSTimer
会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用,从而导致对象无法释放。-
解决方案如下:
//解决方式1:使用闭包(>= ios1 0才能使用) [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { // 所指针引用self }]; //解决方式2:使用NSProxy对象(推荐) /// 定时器-代理对象:专门解决定时器内存问题的 @interface TimerProxy : NSProxy /** NSProxy:特殊的专门用来做代理对象的,与NSObject不同的是,转发时它的效率更高(消息转发时,只会从自身类的类对象寻找方法,不会再到父类去寻找方法,且转发阶段,立马会走慢速转发方法:methodSignatureForSelector,不会经历方法解析和快速转发阶段) */ /// 初始化 /// @param target 代理对象(一般传self) + (instancetype)proxyWithTarget:(id)target; @end @interface TimerProxy() @property (nonatomic, weak) id tatget; //目标对象 @end @implementation TimerProxy //初始化 + (instancetype)proxyWithTarget:(id)target { //NSProxy:专门用于解决代理对象问题,效率比NSObject高,然没有init方法 TimerProxy *proxy = [TimerProxy alloc]; proxy.tatget = target; return proxy; } //消息签名 - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { if (self.tatget) { return [self.tatget methodSignatureForSelector:sel]; } return [super methodSignatureForSelector:sel]; } //消息转发 - (void)forwardInvocation:(NSInvocation *)invocation { if (self.tatget) { [invocation invokeWithTarget:self.tatget]; } } @end
2. GCD自定义定时器
//创建线程
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//初始化定时器
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置时间(参数-1.资源本身,2.开始时间,3.间隔时间,4.偏差,默认为0)
//开始时间:dispatch_time(DISPATCH_TIME_NOW, (2*NSEC_PER_SEC))从现在开始,2秒后执行
dispatch_source_set_timer(source, 0, (ti*NSEC_PER_SEC), 0);
//设置回调
dispatch_source_set_event_handler(source, ^{
//处理逻辑。。。
});
//启动定时器
dispatch_resume(source);
iOS程序的内存布局
Tagged Pointer详解
参考链接
// objc-internal.h
static inline bool
// 判断是否是TaggedPointer的指针
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0 // MacOS
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1 // iOS
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63) // _OBJC_TAG_MASK -- iOS
#else
# define _OBJC_TAG_MASK 1UL // _OBJC_TAG_MASK -- MacOS
#endif
字符串继承链:
__NSCFConstantString
->__NSCFString
->NSMutableString
->NSString
->NSObject
-
特点:在64位机器上
-
Tagged Pointer
专门用来存储小的对象,例如NSNumber
,NSDate
,NSString
。这是一个特别的指针,不指向任何一个地址,当指针不够存储数据时,才会使用动态分配内存的方式来存储数据,这个标志位,也是在最高4位来表示的。 -
Tagged Pointer
指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。 - 在内存读取上有着3倍的效率,创建时比以前快106倍。
- 如何判断一个指针是否为
Tagged Pointer
?- iOS平台,最高有效位是1(第64bit)
- Mac平台,最低有效位是1。
- 在字符串长度在9个以内时,iOS其实使用了
Tagged pointer
做了优化的, 直到字符串长度大于9,字符串才真正成为了__NSCFString
类型(即对象类型) - 位图:
- 1. NSNumber(1标识位 2-4类型识位 后四位数据类型,其他存储数据)
- 2. NSString(1标识位 2-4类型识位 后四位字符串长度,其他存储数据)
-
- 什么时候使用的Tagged Pointer?
-
NSString
:[动态字符串调用 copy]且length<=9,[NSString stringWithFormat]且length<=9 -
NSNumber
:存储较小的值,如果 8 字节承载不了时,则又用以前的方式来生成普通的指针。
-
- 经典题目:
//经典题目:加锁、原子、同步、串行解决
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghij"]; //崩溃,因为同时访问,name的setter方法先release后retain,可能遇到两次都release造成过度释放而访问野指针
//self.name = [NSString stringWithFormat:@"abcdefghi"]; //为Tagged Pointer,相当于直接赋值,不是一个真正的OC对象,不会调用setter方法进行
});
}
- (void)setName:(NSString *)name {
if(_name != name) {
[_name release];
_name = [name retain]; // or [name copy]
}
}
MRC的对象的setter方法
重点:使用MRC,记住原则:“谁创建谁释放”
// getter方法直接返回
- (NSString *)name {
return _name;
}
// setter 方法
- (void)setName:(NSString *)name {
if(_name != name) { //保证同一个对象不用重复操作
[_name release]; //保证替换的上一个对象计数器-1
_name = [name retain]; // or [name copy] //计数器+1,保证此对象被当前对象拥有,即使外面对象计数器减一,此对象也不会被释放
}
}
拷贝Copy和mutableCopy
-
重要规则:
- 目的:类似文件夹,拷贝备份之后,源文件修改不影响copy文件,同时,copy文件修改也不影响源文件。总结:拷贝之后,互不影响
- 不可变对象:copy直接引用+1,互相修改也不会影响;mutableCopy的副本能修改,则需生成新的对象
- 可变对象:copy和mutableCopy的源本能修改,则需生成新的对象
- 拷贝:Copy拷贝后都是不可变对象,mutableCopy拷贝后都是可变对象。(不包括自己实现的)
- 目的:类似文件夹,拷贝备份之后,源文件修改不影响copy文件,同时,copy文件修改也不影响源文件。总结:拷贝之后,互不影响
-
定义解释
- 浅拷贝:指针拷贝,相当于retain,引用计数+1
- 深拷贝:内容拷贝,生成新的相同内容的对象,同时指针指向此对象。
-
注意:
- 申明属性时,如果用关键字
copy
,则最好不要用可变对象,因为经copy之后就变成不可变对象,所以在使用的时候就会出现无法增删改的错误。 - 为什么
NSString
,系统常用copy
?为了避免外部赋值的改变从而影响控件的UI显示的值。
- 申明属性时,如果用关键字
-
总结
对象类型 copy mutableCopy 不可变对象 浅copy,指针复制,返回值不可变 深copy,内容复制,返回值可变 可变对象 深copy,内容复制,返回值不可变 深copy,内容复制,返回值可变
引用计数器
-
在64bit中,引用计数可以直接存储在优化过的isa指针(详情见对象的本质)中,也可能存储在SideTable类中
// SideTable的定义 struct SideTable { spinlock_t slock; RefcountMap refcnts; //存放着对象引用计数的散列表 weak_table_t weak_table; //存放着所有弱引用的对象指针的散列表 }
dealloc
释放过程:dealloc
->_objc_rootDealloc
->rootDealloc
->object_dispose
->objc_destructInstance/free
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor(); //是否有C++的析构函数
bool assoc = obj->hasAssociatedObjects(); //是否有设置关联对象
// This order is important.
if (cxx) object_cxxDestruct(obj); //清楚成员变量
if (assoc) _object_remove_assocations(obj); //移除关联对象
obj->clearDeallocating(); //将指向当前对象的弱指针置为nil
}
return obj;
}
自动释放池
主线程的自动释放池
//重新编译为C/C++ 文件后的源码
struct __AtAutoreleasePool {
__AtAutoreleasePool() { //构造函数
atautoreleasepoolobj = objc_autoreleasePoolPush(); //运行时的放入函数
}
~__AtAutoreleasePool() { //析构函数
objc_autoreleasePoolPop(atautoreleasepoolobj); //运行时的释放函数
}
void * atautoreleasepoolobj;
};
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
// @autoreleasepool
{ __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
- 每个
AutoreleasePoolPage
对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放所有的Autoreleautorelease
对象的地址asePoolPage
对象通过双向链表的形式连接在一起; - 调用push方法会将一个
POOL_BOUNDARY
入栈,并且返回其存放的内存地址; - 调用pop方法时传入一个
POOL_BOUNDARY
的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
; -
id *next
指向了下一个能存放autorelease
对象地址的区域。
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
//底层push函数-向RunloopPage里加入对象
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
//底层pop函数-释放RunloopPage里的对象
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage(token, page, stop);
}
ARC下的自动内存管理机制(结合上面的进行理解)
- LLVM + Runtime
- iOS在主线程的Runloop中注册了2个Observer
- 第1个Observer监听了
kCFRunLoopEntry
事件,会调用objc_autoreleasePoolPush()
- 第2个Observer
- 监听了
kCFRunLoopBeforeWaiting
事件,会调用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
- 监听了
kCFRunLoopBeforeExit
事件,会调用objc_autoreleasePoolPop()
- 监听了
- 第1个Observer监听了