2013-05-11 2 views
2

Я думал, что я понял довольно слабые ссылки и блоки, однако при попытке найти ниже фрагменты кода есть несколько вещей, которые я не понимаю.Блоки ARC, слабый и удерживаемый счет

Метод test1: все в порядке, если объект не сохраняется

Метод test2: Я не понимаю, почему объект, кажется, не получить сохраняется до конца метода test3! Даже явно установка object = nil в конце метода test2 ничего не меняет.

Способ test3: объект не сохраняется. Почему метод test2 не ведет себя так?

Как побочный вопрос, я действительно задавался вопросом, являются ли слабые переменные потокобезопасными? т.е. если я никогда не получу исключение BAD_ACCESS при попытке доступа к слабой переменной из разных потоков.

@interface Object : NSObject 
@property (nonatomic) NSInteger index; 
@end 

@implementation Object 

- (id)initWithIndex:(NSInteger) index { 
    if (self = [super init]) { 
     _index = index; 
    } 
    return self; 
} 

- (void)dealloc { 
    NSLog(@"Deallocating object %d", _index); 
} 

@end 

Методы испытаний

- (void) test1 { 
    NSLog(@"test1"); 
    Object* object = [[Object alloc] initWithIndex:1]; 
    NSLog(@"Object: %@", object); 
    __weak Object* weakObject = object; 
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{ 
     //NSLog(@"Weak object: %@", weakObject); 
     [NSThread sleepForTimeInterval:2]; 
     NSLog(@"Exiting dispatch"); 
    }); 
    [NSThread sleepForTimeInterval:1]; 
    NSLog(@"Exiting method"); 
} 

- (void) test2 { 
    NSLog(@"test2"); 
    Object* object = [[Object alloc] initWithIndex:2]; 
    NSLog(@"Object: %@", object); 
    __weak Object* weakObject = object; 
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{ 
     NSLog(@"Weak object: %@", weakObject); 
     [NSThread sleepForTimeInterval:2]; 
     NSLog(@"Exiting dispatch"); 
    }); 
    [NSThread sleepForTimeInterval:1]; 
    NSLog(@"Exiting method"); 
} 

- (void) test3 { 
    NSLog(@"test3"); 
    Object* object = [[Object alloc] initWithIndex:3]; 
    NSLog(@"Object: %@", object); 
    NSValue *weakObject = [NSValue valueWithNonretainedObject:object]; 
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{ 
     NSLog(@"Weak object: %@", [weakObject nonretainedObjectValue]); 
     [NSThread sleepForTimeInterval:2]; 
     NSLog(@"Exiting dispatch"); 
    }); 
    [NSThread sleepForTimeInterval:1]; 
    NSLog(@"Exiting method"); 
} 

- (void) test { 
    [self test1]; 
    [NSThread sleepForTimeInterval:3]; 
    [self test2]; 
    [NSThread sleepForTimeInterval:3]; 
    [self test3]; 
} 

Выход вышеперечисленное:

2013-05-11 19:09:56.753 test[1628:c07] test1 
2013-05-11 19:09:56.754 test[1628:c07] Object: <Object: 0x7565940> 
2013-05-11 19:09:57.755 test[1628:c07] Exiting method 
2013-05-11 19:09:57.756 test[1628:c07] Deallocating object 1 
2013-05-11 19:09:58.759 test[1628:1503] Exiting dispatch 
2013-05-11 19:10:00.758 test[1628:c07] test2 
2013-05-11 19:10:00.758 test[1628:c07] Object: <Object: 0x71c8260> 
2013-05-11 19:10:00.759 test[1628:1503] Weak object: <Object: 0x71c8260> 
2013-05-11 19:10:01.760 test[1628:c07] Exiting method 
2013-05-11 19:10:02.760 test[1628:1503] Exiting dispatch 
2013-05-11 19:10:04.761 test[1628:c07] test3 
2013-05-11 19:10:04.762 test[1628:c07] Object: <Object: 0x71825f0> 
2013-05-11 19:10:04.763 test[1628:1503] Weak object: <Object: 0x71825f0> 
2013-05-11 19:10:05.764 test[1628:c07] Exiting method 
2013-05-11 19:10:05.764 test[1628:c07] Deallocating object 3 
2013-05-11 19:10:05.767 test[1628:c07] Deallocating object 2 
2013-05-11 19:10:06.764 test[1628:1503] Exiting dispatch 
+0

Хорошо, что заявление NSLog делает разницу между тестом 1 и тестом 2! (Потому что он упоминает «слабый объект», внося его в блок.) – matt

+0

Получаю, однако, слабый объект является локальной переменной __weak, поэтому не следует ли увеличивать счетчик удержания? Или он может быть скопирован как сильная переменная, когда он доступен внутри блока? – quentinadam

+0

См. Http://stackoverflow.com/a/11603363/341994 для одной части вашего q. – matt

ответ

1

У меня есть два замечания на ваших трех тестов, прежде чем я затрону некоторые из ваших вопросов:

  1. Тестирование что вы выполняете все три теста подряд, не возвращаясь к циклу запуска, и, таким образом, ваш пул автозаполнения не очищается (поэтому он делает вещи похожими на то, что они сохраняются дольше, чем обычно) , Вы должны провести тестирование, по одному тесту, чтобы понять, что происходит. Нехорошо, если вы делаете выводы о продолжительности жизни какого-либо объекта, тогда как вы действительно можете просто испытывать некоторый артефакт о том, что вы не позволяете очистить пул авторекламы.

  2. Вы делаете все эти тесты, как dispatch_async, который запускает переданную блок очень быстро, иногда быстрее, чем требуется основной объект выпадать из сферы, и вы часто доступ к weakObject как один из первых шаги в отправленном блоке. Я бы предложил использовать dispatch_after (так что вы действительно даете вызывающему методу возможность вывести переменные из области), поэтому вам лучше посмотреть, что происходит.

Ваши тесты хорошая точка данных, но я думаю, что это полезно также протестировать один и тот же материал, используя dispatch_after и сделать один тест, в то время, с меньше тех sleepForTimeInterval. Похоже, некоторые из особенностей ваших тестов подделывают какое-то ключевое поведение.

В любом случае вы спросите:

Метод test2: Я не понимаю, почему объект, кажется, не получить сохраняется до конца метода test3! Даже явно установка объекта = nil в конце метода test2 ничего не меняет.

Это, несомненно, попало в бассейн автореферата, который не будет слит до тех пор, пока не будет выполнен метод test.

В моих предыдущих пунктов, попробуйте еще раз делать test2, но на операцию подождите две секунды перед получением доступа к weakObject (или избавиться от всех этих sleepForTimeInterval заявлений и использовать dispatch_after вместо dispatch_sync):

- (void) test2 { 
    NSLog(@"test2"); 
    Object* object = [[Object alloc] initWithIndex:2]; 
    NSLog(@"Object: %@", object); 
    __weak Object* weakObject = object; 
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{ 
     [NSThread sleepForTimeInterval:2];  // new sleep 
     NSLog(@"Weak object: %@", weakObject); 
     [NSThread sleepForTimeInterval:2]; 
     NSLog(@"Exiting dispatch"); 
    }); 
    // [NSThread sleepForTimeInterval:1];  // not really necessary 
    NSLog(@"Exiting method"); 
} 

Вы увидите, что это ведет себя так, как вы ожидали.

Метод test3: объект не сохраняется. Почему метод test2 не ведет себя так?

Излишне говорить, что ваш test3 - это очень плохая новость, легко разрушая себя. Например, попробуйте закомментировать строку сна:

- (void) test3 { 
    NSLog(@"test3"); 
    Object* object = [[Object alloc] initWithIndex:3]; 
    NSLog(@"Object: %@", object); 
    NSValue *weakObject = [NSValue valueWithNonretainedObject:object]; 
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{ 
     NSLog(@"Weak object: %@", [weakObject nonretainedObjectValue]); 
     [NSThread sleepForTimeInterval:2]; 
     NSLog(@"Exiting dispatch"); 
    }); 
// [NSThread sleepForTimeInterval:1]; 
    NSLog(@"Exiting method"); 
} 

Это поражает меня, что он ведет себя менее, как weak и больше как unsafe_unretained.

В качестве побочного вопроса я действительно задавался вопросом, являются ли слабые переменные потокобезопасными? т.е. если я никогда не получу исключение BAD_ACCESS при попытке доступа к слабой переменной из разных потоков.

Вы можете получить исключения во многих отношениях. Если вы пройдете weakObject до некоторого метода, который требует, чтобы он не был nil (например, NSMutableArray метод addObject), вы получите исключение. Вы также можете получить исключения, если вы разыскиваете ivars для указателя объекта nil, например. obj->objectIvar. Например, представьте себе метод Object экземпляра, doSomethingLater, который использует слабую ссылку, чтобы убедиться, что он не сохраняет Object, но имеет локальную сильную ссылку, чтобы он мог разыменовать Ивар:

- (void)doSomethingLater 
{ 
    __weak Object *weakSelf = self; 

    double delayInSeconds = 10.0; 
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
     Object *strongSelf = weakSelf; 
     NSLog(@"%d", strongSelf->_index); // **BAD** - this can crash of `self` has been released by this point 
    }); 
} 

Таким образом, вы обычно заменяют выше:

- (void)doSomethingLater 
{ 
    __weak Object *weakSelf = self; 

    double delayInSeconds = 10.0; 
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
     Object *strongSelf = weakSelf; 
     if (strongSelf) { 
      NSLog(@"%d", strongSelf->_index); 
     } 
    }); 
} 

Чтобы быть абсолютно честным, хотя, детали, почему это первый пример кода может привести к сбою и второй не может менее важен, чем тот очевидный факт, что разумное использование вашего объекта ссылки в асинхронном программировании важны, и неспособность обрабатывать ситуации ca refully может привести к исключениям. Часто проверка того, что weakObject не является nil, может предотвратить многие из этих проблем (с некоторыми оговорками, которые я не собираюсь входить). Это менее важно при вызове методов объекта (поскольку отправка любого сообщения в nil приводит к nil), но важно, когда ваш weakObject является параметром или разыменован для ivar.

Но, чтобы быть ясным, ничто из этого не имеет никакого отношения к безопасности нитей. Вы обеспечиваете безопасность потоков благодаря правильной обработке синхронизации, например locking mechanisms, или через разумный use of queues (либо последовательную очередь, либо образец считывателя/записи параллельной очереди с dispatch_barrier_async для записей и dispatch_sync для чтения).

Просто потому, что у вас есть код, в котором вы тщательно обрабатываете ссылки на объекты, поэтому вы не получаете исключений, не означает, что вы достигли безопасности потоков. Существует целый ряд проблем, связанных с безопасностью потока.

+0

Спасибо за подробное объяснение. Немного информации, которую я пропустил, является autoreleasepool и для чего он используется. Между тем, я сделал некоторые чтения по этому вопросу и понял, что объект в test2 выпущен пулом автозаполнения (фактически помещая блок autoreleasepool в корень test2, а в корневой части отправки дает тот же результат, что и test1). Тем не менее, все еще немного тенистая область - это понимание того, почему clang ARC иногда преобразует код для назначения/сохранения, а иногда и для авторекламы. – quentinadam

Смежные вопросы