因为有朋友认为代码瑕疵会导致内存爆增而导致崩溃,实际上开辟子线程并不是无限制的,亲测,ios平台最大并发线程为22,测试过程:条件断点 i == 1000触发,保证充分利用了系统的线程。
所以并不是内存爆增导致的崩溃。为了更严谨的证明实际是过度释放。笔者特地打开了僵尸对象模式[zombie Object],得到错误打印:向一个已销毁的对象发送release消息!
*** -[CFString release]: message sent to deallocated instance 0x60800064bd00
系统的东西我只是通过结果猜测。下面我们通过dispatch_semaphore_t
控制线程的并发。 设置线程的并发数为5,运行得到的结果也是一样的,所以,毕竟还是不同线程之间争夺资源而导致的崩溃啊,少年。
dispatch_semaphore_t semQueue = dispatch_semaphore_create(5);
dispatch_queue_t taskQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10000 ; i++) {
dispatch_semaphore_wait(semQueue, DISPATCH_TIME_FOREVER);
dispatch_async(taskQueue, ^{
self.str = [NSString stringWithFormat:@"Monkey-Sun %d",i];
NSLog(@"%@ %lu", self.str, (unsigned long)self.str.retainCount);
dispatch_semaphore_signal(semQueue);
});
}
关于GCD,传送门
================我是华丽的分割线================
没事逛了逛Apple的官网,其中关于weak 和 Strong属性在ARC的运行时代码。
strong
void objc_storeStrong(id *object, id value) {
id oldValue = *object;
value = [value retain];
*object = value;
[oldValue release];
}
retain新值,release旧值
weak:如果值为空指针或其指向的对象已开始销毁,则将对象分配为空和未注册为weak对象。否则,将对象注册为weak对象或将其注册更新为指向值。
也就是说,weak指针没法保持对象的生命。
id objc_storeWeak(id *object, id value);
If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and unregistered as a __weak object. Otherwise, object is registered as a __weak object or has its registration updated to point to value.
Returns the value of object after the call.
retain:如果值是空,则retain没有任何效果,如果不为空,则执行保持对象的操作。
id objc_retain(id value);
Precondition: value is null or a pointer to a valid object.
If value is null, this call has no effect. Otherwise, it performs a retain operation exactly as if the object had been sent the retain message.
Always returns value.
所以strong 和 weak的指针,根本区别在于,strong执行了retain操作,而weak没有。
下面思考一个问题,多线程的情况下,使用strong会导致什么问题。
Objective-C
code
@interface ViewController ()
@property (nonatomic, strong) NSString *str;
@end
-----.m
for (int i = 0; i < 10000 ; i++) {
dispatch_async(dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
self.str = [NSString stringWithFormat:@"Monkey-Sun %d",i];
NSLog(@"%@ %lu", self.str, (unsigned long)self.str.retainCount);
});
}
这里会崩溃,因为,使用strong修饰的属性,实际上它的setter是这样的.
-(void)setStr:(NSString *)str{
[str retain];
[_str release];
_str = str;
}
在多线程下,线程1,如果执行到了release,同时线程2也执行到了release,那么_str所指向的对象就会过度释放。
可以理解会,多线程中,并发的访问一个数据,就会导致数据出错。不过这里可以看到出问题的原因之一。
处理这个问题,有几个解决方案
OC: 使用互斥锁,保证数据访问的唯一性
@property (nonatomic, strong) NSString *str;
----.m
for (int i = 0; i < 10000 ; i++) {
dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
@synchronized (self) {
self.str = [NSString stringWithFormat:@"Monkey-Sun %d",i];
NSLog(@"%@ %lu", self.str, (unsigned long)self.str.retainCount);
}
});
}
由于移动设备内存较小,性能也比mac来的差,所以,一般我们描述属性都是用:nonatomic
,也就是非原子操作。所以OC中还可以使用atomic
来修饰属性来保证数据访问的唯一性。
@property (atomic) NSString *str;
---.m
for (int i = 0; i < 10000 ; i++) {
dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
self.str = [NSString stringWithFormat:@"Monkey-Sun %d",i];
NSLog(@"%@ %lu", self.str, (unsigned long)self.str.retainCount);
});
}
Swift
swift的原理跟OC一致,就不重复解释原理了,但是swift没有这个内存语言的修饰 比如以下代码是等价的。
---this code from swift-3
fileprivate var str : String!
---this code from OC
@property (nonatomic, copy) NSString *str;
因此swift 版本的解决方案是:
fileprivate var str : String!
override init() {
super.init()
let queue = DispatchQueue.global()
for i in 0...10000{
queue.async {
objc_sync_enter(self)
self.str = String("\(i)")
print(self.str)
objc_sync_exit(self)
}
}
}
swift中的互斥锁:
objc_sync_enter(self)
//code
objc_sync_exit(self)