总资料 全是随笔 笔记 与 学习资料。没有规律。
一、 CADisplayLink、NSTimer
- CADisplayLink、NSTimer 都会对target产生强引用、如果target又对他们强引用,则会循坏引用。
以下是循环引用的解决方法。
1.1 循环引用
1.1.1 解决方法1 - block
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
}];
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 本身就是用于消息转发的类
头文件
@interface MYProxy1 : NSProxy
@property(nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
实现文件
#import "MYProxy1.h"
@implementation MYProxy1
+ (instancetype)proxyWithTarget:(id)target {
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的实现
- 效率低,里面有消息的查找过程,查找转发的步骤比较多
- 需要在父类里面查找方法,当所有的搜索完毕都找不到的时候,才会进入消息转发阶段
头文件
#import <Foundation/Foundation.h>
@interface MYProxy : NSObject
@property(nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
实现文件
#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 通用代码调用的实现
代码使用位置
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
@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;
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){
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 的存储位置及方式 章节查看对比。
标志位
static inline bool _objc_isTaggedPointerOrNil(const void * _Nullable ptr)
{
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:@"abcdefghij"];
});
}
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的。
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name retain];
}
}
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name copy];
}
}
- 可以看出,其实setter方法里面,最主要的是对 传入的形参的 处理方式不一样,一个调用的是copy。一个是retain 来添加引用计数。
6. NSString 和 NSNumber 的存储位置及方式
NSSTring
可通过地址 和 类对象 进行区别判断
NSString *s = [NSString stringWithFormat:@"abcdefghijklm"];
NSString *s1 = [NSString stringWithFormat:@"abc"];
NSString *s2 = @"abc";
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);
可见同 NSSTring 一样,使用 @4 创建的对象,是直接存在常量区的,
备注:部分笔记包含有MJ老师的学习资料。
|