0、主要解决以下几个问题
- 1、
atomic
究竟是对copy
、weak
、retain
、assign
、strong
中哪个修饰字进行了"安全"
赋值 - 2、为什么
copy
修饰了可变的对象,如NSMutableArray
、NSMutableDictionary
、NSMutableString
,最终都会变成不可变对象? - 3、
atomic
究竟使用了哪个线程锁来保证线程安全? - 4、为什么网上有不少文章说的是,使用了
atomic
其实是编译器是在setter
和getter
使用了@ synchronized
?希望有人可以评论指点一下
1、新建WTPerson的类
WTPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WTPerson : NSObject
@property (nonatomic, strong) NSMutableArray *strongArray;
@property (nonatomic, copy) NSMutableArray *cpyArray;
@property (nonatomic, assign) NSUInteger assignObj;
@property (nonatomic, weak) id idObj;
@property (nonatomic, retain) NSObject *retainObj;
- (void)initVar;
@end
NS_ASSUME_NONNULL_END
WTPerson.m
#import "WTPerson.h"
@implementation WTPerson
- (void)initVar
{
self.strongArray = [NSMutableArray array];
self.cpyArray = [NSMutableArray array];
self.assignObj = 10;
self.idObj = (id)[[NSObject alloc] init];
self.retainObj = [[NSObject alloc] init];
}
@end
2、在相对应项目路径中,以下命令编译成对应的WTPerson.cpp
文件
shell命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 WTPerson.m
3、WTPerson.cpp文件的源代码,找出成员变量的Setter函数的实现
1、strong
、assign
、weak
- 1、其中被
strong
、assign
、weak
修饰的属性的setter方法是一样的
通过(char *)self + OBJC_IVAR_$WTPERSON$_XXX = xxx
; 来赋值的。以strong
为例,设置值是通过(char *)self + OBJC_IVAR_$_WTPerson$_strongArray
;来赋值的- a、
OBJC_IVAR_$_WTPerson$_strongArray
在cpp在这样定义的,返回值是strongArray
这个成员变量在结构体WTPerson
在内存中地址的偏移位置
- b、
__OFFSETOFIVAR__
的实现
- c、结论就是,
strong
、assign
、weak
修饰的属性,都是通过self
,也就是结构体WTPerson
在内存中的地址加上属性的偏移量,得到属性的内存地址,然后直接把地址对应的值修改为新值。
- a、
2、copy
、retain
1、其中被
copy
、retain
修饰的属性的setter方法是一样的, 内部其实调用了objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WTPerson, _xxx), (id)xxx, 0, 1)
函数,以案例中copy
修饰的cpyArray
为例,调用了objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WTPerson, _cpyArray), (id)cpyArray, 0, 1)
-
2、https://opensource.apple.com/release/macos-10141.html在苹果官方中下载objc4的源码
-
3、搜索找到
objc_setProperty
函数的实现, 最终调用了reallySetProperty
函数
-
4、
realyySetProperty
函数,并且参数copy
为true
, 所以新的数组调用了copyWithZone:nil
函数
-
5、参考
GNU
的实现,参考NSArray
的copyWithZone
的实现,发现调用NSArrayClass allocWithZone:
方法,而NSArrayClass
其实就是NSArray
,并且返回一个不可变的数组,并且地址值是全新的。结论就是使用copy
修饰之后,可变数组最终变成不可变数组的原因。
-
6、再回到
realyySetProperty
函数的实现,发现这里判断是否使用atomic
修饰字,如果使用aotmic
对赋值进行加锁
-
7、点击了
spinlock_t
的实现之后,发现内部其实是使用os_unfair_lock
线程锁
-
8、下载了https://opensource.apple.com/release/os-x-1010.html的objc4的源代码,发现使用的是
OSSpinLock
的锁
3、所有修饰字的getter
方法的实现都是通过(char *)self + OBJC_IVAR_$_WTPerson$_xxxx)
, 如案例中strongArray
,就是通过(char *)self + OBJC_IVAR_$_WTPerson$_strongArray)
,就是通过结构体WTPerson
内存地址加上strongArray
的偏移量,来获取对应内存地址的值。
4、测试atomic
、nonatomic
开启6条线程,for循环10000次,各自时间是多少
测试代码
- (void)test
{
TICK;
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.name = @"张三1";
self.name;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.name = @"张三1";
self.name;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.name = @"张三1";
self.name;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.name = @"张三1";
self.name;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.name = @"张三1";
self.name;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.name = @"张三1";
self.name;
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
TOCK;
});
}
结论
:通过以下结果,使用atomic
时间确实相对长一点,使用nonatomic
时间相对短一点。按照以上文章所写,理论上atomic、strong
是不加锁的,为什么使用时间长呢?希望有热心网友评论一下
12.481426 @property (atomic, copy) NSString *name;
11.998869 @property (atomic, copy) NSString *name;
12.355589 @property (atomic, copy) NSString *name;
1.999961 @property (nonatomic, copy) NSString *name;
2.019006 @property (nonatomic, copy) NSString *name;
2.015141 @property (nonatomic, copy) NSString *name;
7.278346 @property (atomic, strong) NSString *name;
6.806363 @property (atomic, strong) NSString *name;
6.928921 @property (atomic, strong) NSString *name;
0.425550 @property (nonatomic, strong) NSString *name;
0.442165 @property (nonatomic, strong) NSString *name;
0.547891 @property (nonatomic, strong) NSString *name;
20190120 更新
1、运行时分析
1、对name进行赋值
测试代码
@property (nonatomic, strong) NSString *name;
- (void)test
{
self.name = @"张三1";
}
2、开启汇编调试模式
3、单步进入
4、发现setName
内部其实是调用了objc_setProperty_nonatomic
函数
5、objc_setProperty_nonatomic
内部还是调用reallySetProperty
函数,第四个参数是false,表明是被nonatomic
修饰的,所以最终不会加锁。当然这个是objc4
源码中所得,根据之前的经验,说明objc4
源码只有参考意义,具体测试还是得运行时查看
6、打上断点,进入objc_setProperty_nonatomic
函数内部
7、在控制台输入si
,表明进入这个函数内部
8、继续回车,表示继承执行si
命令
9、一直回车到dyld_stub_binder
10、在dyld_stub_binder
,最后一行打上断点,并跳过
11、再一次回车进入objc_setProperty_atomic
12、可以发现确实是利用os_unfair_lock
进行加锁的
13、分别测试了
- 1、
atomic, strong
objc_setProperty_atomic
- 2、
atomic, copy
objc_setProperty_atomic_copy
- 3、
nonatomic, copy
objc_setProperty_nonatomic_copy
- 4、
nonatomic, retain
objc_setProperty_nonatomic
- 5、
atomic, retain
objc_setProperty_atomic
- 6、
nonatomic, weak
objc_storeWeak
- 7、
atomic, weak
objc_storeWeak
- 8、
nonatomic, assign
没有其他方法
- 9、
atomic, assign
没有其他方法
2、weak
1、 、使用了weak
修饰词,发现最终调用了objc_storeWeak
,内部其实是调用storeWeak
函数,没有发现类似atomic
的参数,下面进入storeWeak
函数看实现
2、发现函数实现,无条件地加了锁。所以猜测使用weak
修饰字的属性,无论是使用nonatomic
、atomic
,都是加了锁的
3、测试了一下代码结果
通过以下结果, 得出的结论就是使用
weak
修饰字的属性,无论是使用nonatomic
、atomic
,都是加了锁的
// 代码执行时间
#define TICK NSDate *startTime = [NSDate date];
#define TOCK NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])
- (void)test
{
TICK;
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.idObj = nil;
self.idObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.idObj = nil;
self.idObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.idObj = nil;
self.idObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.idObj = nil;
self.idObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.idObj = nil;
self.idObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.idObj = nil;
self.idObj;
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
TOCK;
});
}
结果
7.224174 @property (atomic, weak) id idObj;
7.078933 @property (atomic, weak) id idObj;
6.948409 @property (atomic, weak) id idObj;
7.192473 @property (nonatomic, weak) id idObj;
7.071997 @property (nonatomic, weak) id idObj;
7.238427 @property (nonatomic, weak) id idObj;
4、发现SideTable
的锁,是spinlock_t
, 也就是os_unfair_lock
5、跟踪汇编汇编代码查看, 不停地si
,进入setObj
函数内部
6、再次不停地si
、进入objc_storeWeak
函数内部
7、翻阅整个汇编流程,发现内部还是调用了os_unfair_lock
进行加锁
3、assign
1、测试结果表明,得出的结论就是使用assign
修饰字的属性,无论是使用nonatomic
、atomic
,都不会加锁的
测试代码
// 代码执行时间
#define TICK NSDate *startTime = [NSDate date];
#define TOCK NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])
- (void)test
{
TICK;
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.assignObj = 10;
self.assignObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.assignObj = 10;
self.assignObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.assignObj = 10;
self.assignObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.assignObj = 10;
self.assignObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.assignObj = 10;
self.assignObj;
}
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(group);
for (NSUInteger i = 0; i < 10000000; i++)
{
self.assignObj = 10;
self.assignObj;
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
TOCK;
});
}
结果
1.434344 @property (atomic, assign) NSUInteger assignObj;
1.561307 @property (atomic, assign) NSUInteger assignObj;
1.420304 @property (atomic, assign) NSUInteger assignObj;
1.112815 @property (nonatomic, weak) id idObj;
1.116036 @property (nonatomic, weak) id idObj;
1.458600 @property (nonatomic, weak) id idObj;
2、从运行时分析,汇编看不太懂,但是基本可以看出直接赋值的,具体等我学习汇编,再来更新
结论
- 1、
copy
、retain
、strong
,使用atomic
会进行加锁处理,并且使用的os_unfair_lock
, 使用nonatomic
不会进行加锁处理 - 2、
weak
无论使用的是nonatomic
、atomic
都会利用os_unfair_lock
进行加锁处理 - 3、
assign
无论使用的是nonatomic
、atomic
,都不会进行加锁处理 - 4、使用
copy
关键字,会对对象进行一次copy
操作,是深拷贝。如果可变对象,会被转换成不可变对象。