总资料
全是随笔 笔记 与 学习资料。没有规律。
一、 CADisplayLink、NSTimer
- CADisplayLink、NSTimer 都会对target产生强引用、如果target又对他们强引用,则会循坏引用。
以下是循环引用的解决方法。
1.1 循环引用
1.1.1 解决方法1 - block
//没有循环引用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
}];
//有循环引用, 因为timer 内部包含有target属性,造成循环引用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(xxx) userInfo:nil repeats:YES];
1.1.2 解决方法- proxy
- 创建 中间代理对象
- 代理对象弱引用 target
- 代理对于将自身的所有实例方法,全部转发到target对象本身
1.1.2.1 继承与NSProxy 的实现(推荐)
- 直接继承与NSproxy的实现
- 直接进入消息转发,无中间查找过程,
效率高
- NSProxy 本身就是用于消息转发的类
头文件
//MYProxy1 头文件
@interface MYProxy1 : NSProxy
@property(nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
实现文件
//MYProxy.m 文件
#import "MYProxy1.h"
@implementation MYProxy1
+ (instancetype)proxyWithTarget:(id)target {
//NSProxy 没有init方法
MYProxy1 *proxy = [MYProxy1 alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
1.1.2.2 继承与NSObject 的实现 (效率低)
- 直接继承与NSObject的实现
- 效率低,里面有消息的查找过程,查找转发的步骤比较多
- 需要在父类里面查找方法,当所有的搜索完毕都找不到的时候,才会进入消息转发阶段
头文件
//MYProxy 头文件
#import <Foundation/Foundation.h>
@interface MYProxy : NSObject
@property(nonatomic, weak) id target; //弱引用
+ (instancetype)proxyWithTarget:(id)target;
@end
实现文件
//MYProxy.m 文件
#import "MYProxy.h"
@implementation MYProxy
+ (instancetype)proxyWithTarget:(id)target {
MYProxy *proxy = [[MYProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
return self.target; //将原本消息全部转发
}
@end
1.1.2.3 通用代码调用的实现
代码使用位置
//使用位置,target里面使用了代理对象,然后弱引用target
self.timer = [NSTimer scheduledTimerWithTimeInterval:1
target:[MYProxy proxyWithTarget:self]
selector:@selector(test)
userInfo:nil
repeats:YES];
- (void)test {
NSLog(@"%s", __FUNCTION__);
}
-(void)dealloc {
NSLog(@"%s", __FUNCTION__);
if (self.timer) {
[self.timer invalidate];
}
}
1.2 准确度
NSTimer
依赖于RunLoop,而当RunLoop任务繁重的时候,可能导致RunLoop比较耗时,导致timer触发不准确
GCD
的计时器更加准时,不依赖与RunLoop
2. GCD 计时器 dispatch_source_t
GCD
的计时器更加准时,不依赖与RunLoop
//必须强引用,不然GCD不会帮自己管理内存,所以添加为属性
@property(nonatomic, strong) dispatch_source_t timer_source;
---
//队列 可以是主队列或者异步队列
dispatch_queue_t queue = dispatch_get_main_queue();
self.timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//时间
int64_t start = 2.0; //2秒后开始
int64_t interval = 1.0; //间隔
dispatch_source_set_timer(self.timer_source, dispatch_time(DISPATCH_TIME_NOW, start*NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
//回调
dispatch_source_set_event_handler(self.timer_source, ^{
//这里注意循环引用
NSLog(@"ssss");
});
//默认是暂停的,所以需要开始
dispatch_resume(self.timer_source);
-(void)dealloc {
if(self.timer_source){
//需要停止操作, 这个方法调用后,timer是不能被恢复的。
dispatch_source_cancel(self.timer_source);
self.timer_source = nil;
}
}
3 内存布局
4. Tagged Pointer
Tagged Pointer 是64位引进的技术,iPhone5s是首个使用的手机,主要是为了节省内存和提高使用效率。
主要优化:NSNumber、NSDate、NSString 等小对象的存储
内存布局
以NSNumber *a = @(1);
为例,
- 如果不适用
Tagged Pointer
,则需要栈上8字节指针,加上堆栈16字节的对象。还需要进行对象的创建销毁与引用计数的维护等 - 使用了
Tagged Pointer
后,实质是一个伪指针,对象的指针中存储的数据变成了Tag
+Data
形式,并不是真实的堆地址(因为并没有创建),只需要一个指针8字节大小。 objc_msgSend()
在方法内部会先识别是否是Tagged Pointer
, 如果是的话,就不进行方法调用,直接取值,节省了开销。- 当存储的值大的导致不能存下的时候,才会进行对象开辟,使用以前的方法。
代码混淆
设置环境变量 OBJC_DISABLE_TAG_OBFUSCATION
为 YES, 为关闭 Tagged Pointer
的数据混淆;
不然地址混淆后,不能看见真实的值。
- 在 NSNumber 验证中,代码混淆变量有效。
- 在 NSString 验证中,代码混淆变量有效。
具体见6. NSString 和 NSNumber 的存储位置及方式
章节查看对比。
标志位
//objc_internal.h
static inline bool _objc_isTaggedPointerOrNil(const void * _Nullable ptr)
{
// this function is here so that clang can turn this into
// a comparison with NULL when this is appropriate
// it turns out it's not able to in many cases without this
return !ptr || ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
在上面方法 可以看见是 可以通过某一二进制位
来判断是会否是Tagged Pointer 变量的。
MacOS
下采用LSB
(Least Significant Bit,即最低有效位)为Tagged Pointer标志位。iOS
下则采用MSB
(Most Significant Bit,即最高有效位)为Tagged Pointer标志位。
crash 例子
@property(nonatomic, copy) NSString *name;
dispatch_queue_t q = dispatch_queue_create("0000", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i <1000; i++) {
dispatch_async(q, ^{
self.name = [NSString stringWithFormat:@"abcdefghijklm"];
});
}
crash
报错*** -[CFString release]: message sent to deallocated instance 0x6000020ce340
- 因为
self.name
的字符串内容 超过tagged pointer
长度,所以使用的是原始存储方法,用的是堆内存。 - 在多线程过程中,
setter
方法的_name
释放了在其他线程已经释放的内存导致crash。(可参考下节的strong的的setter代码)
解决方法:选其一
- 使用
atomic
修饰,保证set方法线程安全 - 不适用长度超长的字符串,如:
[NSString stringWithFormat:@"abcdefghij"];
- 直接使用
self.name = @“abcdefghijklm”
赋值,因为代码放在的是在常量区,直接取就行了,不用拷贝 dispatch_async
里面加锁
关于 2 和 3 不会cash的结论可以 去 6. NSString 和 NSNumber 的存储位置及方式
章节查看内存位置
5. Strong 和 Copy 修饰NSString
@property(nonatomic, copy) NSString *name;
@property(nonatomic, strong) NSString *name;
两者有什么不一样呢。
从 setter 方法的角度来看, set 本质来说都是转换成MRC的。
//strong
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name retain];
}
}
//copy
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name copy];
}
}
- 可以看出,其实setter方法里面,最主要的是对 传入的形参的 处理方式不一样,一个调用的是copy。一个是retain 来添加引用计数。
6. NSString 和 NSNumber 的存储位置及方式
NSSTring
可通过地址 和 类对象 进行区别判断
NSString *s = [NSString stringWithFormat:@"abcdefghijklm"]; //0x600002066740
NSString *s1 = [NSString stringWithFormat:@"abc"]; //0xa000000006362613
NSString *s2 = @"abc"; //0x10422f0f0
NSLog(@"\n%p\n%p\n%p", s, s1, s2);
NSLog(@"%@ %@ %@", [s class], [s1 class], [s2 class]);
-
s 堆区
__NSCFString
-
s1 tagged pointer
NSTaggedPointerString
-
s2 字符常量区
__NSCFConstantString
-
其他:
s1 的值 代码 正常打印,即混淆 为:0xc95dbca6c28acc21
禁用混淆后打印为0xa000000006362613
,61
为a
,62
为b
,63
为`c‘、;;;;、
NSNumber
NSNumber *n1 = [NSNumber numberWithInt:4];
NSNumber *n2 = @4;
NSLog(@"%p %p", n1, n2);
//混淆中 [48479:2297830] 0xada560a21cde7f6c 0x1063593b0
//取消混淆 [48499:2298606] 0xb000000000000042 0x1074b43b0
可见同 NSSTring 一样,使用 @4 创建的对象,是直接存在常量区的,
7. 深拷贝、浅拷贝
-
深拷贝:拷贝了内容,新分配内存
-
浅拷贝:只拷贝存储的指针,和以前的公用内存
-
NSString
: copy-浅拷贝、mutableCopy-深拷贝 -
NSMutableCopy
: copy-深拷贝、mutableCopy-深拷贝
原因:浅拷贝只在针对拷贝前后的两个指针都是const类型,不可修改的情况下才共用内存,如果拷贝后的内容修改可能会影响 以前的内容,则一定会使用深拷贝。
8. retain count 引用计数存储
在isa
中,extra_rc
存储的就是引用计数的数量,如果数量超标后,才会存储在has_sidetable_rc
的散列表中。
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
这个是散列表的struct
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; //散列表 引用计数的数量量
weak_table_t weak_table;
}
以下是具体的逻辑代码
//获取 引用计数数量
- (NSUInteger)retainCount {
return _objc_rootRetainCount(self);
}
inline uintptr_t objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this; //tagged pointer 忽略
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
// 判断如果是优化过后的isa 对象
uintptr_t rc = bits.extra_rc; //这里可以看出是直接存的值,而不是别人说的 值-1
if (bits.has_sidetable_rc) {
//has_sidetable_rc 标志位为1 代表散列表有值
rc += sidetable_getExtraRC_nolock(); //rc 加上散列表存的值
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
size_t objc_object::sidetable_getExtraRC_nolock()
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this]; //从全局的散列表中取出 对应的table
RefcountMap::iterator it = table.refcnts.find(this); //在从对应的table取出 遍历器
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT; //取出具体值
}
9. weak
-
weak 被存在全局的 散列表中。
-
weak 被自动销毁,是runtime的功劳
-
weak 指向的指针被释放后会自动 置为 nil的原理, 关键代码为
*referrer = nil;
-
unsafe_unretain也能实现弱引用,但是指向的对象被释放后不能被置为 nil,只会变成野指针
以下是weak 释放的实现
调用流程
- (void)dealloc
- void _objc_rootDealloc(id obj)
- inline void objc_object::rootDealloc()
- id object_dispose(id obj)
- void *objc_destructInstance(id obj)
- inline void objc_object::clearDeallocating()
- NEVER_INLINE void objc_object::clearDeallocating_slow()
- void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
- *referrer = nil;
objc 源码分析
/***********************************************************************
* 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); //据说会释放ivar ,待验证
if (assoc) _object_remove_assocations(obj, /*deallocating*/true); //释放关联属性
obj->clearDeallocating(); //在这里会寻找weak 指针进行nil操作
}
return obj;
}
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(); // 进行往下释放weak,或者大引用计数
}
assert(!sidetable_present());
}
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指针
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
// 含有较大引用计数
table.refcnts.erase(this);
}
table.unlock();
}
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
//在weak 散列表里面查找自己的所有weak 引用
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; //在这里真正将weak 置为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);
}
备注:部分笔记包含有MJ老师的学习资料。