Опитвам се да науча повече за ръчното преброяване на препратки. Създадох клас с променлива на екземпляр и неговия метод за получаване/настройка, който използва ръчно освобождаване:
@interface Foo: NSObject {
id bar;
}
- (id)bar;
- (void)setBar:(id)value;
@end
@implementation Foo
- (id)bar {
return bar;
}
- (void)setBar:(NSNumber*)value {
[bar release];
bar = value;
}
@end
След това го използвам така:
int main() {
Foo *f = [[Foo alloc] init];
NSNumber *n = [[NSNumber alloc] initWithInteger: 3];
[f setBar: n];
[n release];
NSLog(@"%@\n", [[f bar] description]);
return 0;
}
Компилирах го с Address Sanitizer и очаквам да прихване, тъй като [[f bar] description]
трябва да извиква метод на освободен обект:
clang -fno-objc-arc -fsanitize=address -g -framework Foundation main.m
ASAN_OPTIONS=detect_leaks=1
Но за моя изненада работи добре!
2018-04-08 18:18:38.470300-0400 a.out[3457:291626] 3
След това го опитах с NSDate
вместо това, за да видя дали има разлика:
NSDate *n = [[NSDate alloc] init];
[c setBar: n];
[n release];
И прави:
ASAN:DEADLYSIGNAL
=================================================================
==3379==ERROR: AddressSanitizer: SEGV on unknown address 0x7fff0b800018 (pc 0x7fff577f3e9d bp 0x7ffee4491670 sp 0x7ffee44915f8 T0)
==3379==The signal is caused by a READ memory access.
#0 0x7fff577f3e9c in objc_msgSend (libobjc.A.dylib:x86_64h+0x6e9c)
#1 0x7fff5841b014 in start (libdyld.dylib:x86_64+0x1014)
...
Прегледах метода dealloc
на NSNumber
и открих, че не се извиква:
@interface Dealloc: NSObject
@end
@implementation Dealloc
- (void)dealloc {
NSLog(@"dealloc");
}
@end
...
method_exchangeImplementations(class_getInstanceMethod([Dealloc class], @selector(dealloc)),
class_getInstanceMethod([NSNumber class], @selector(dealloc)));
2018-04-08 18:18:38.470300-0400 a.out[3457:291626] 3
В сравнение с NSDate
:
2018-04-08 18:21:03.899490-0400 a.out[3483:293512] dealloc
2018-04-08 18:21:03.900538-0400 a.out[3483:293512] dealloc
2018-04-08 18:21:03.900598-0400 a.out[3483:293512] dealloc
Така че след това го опитах с куп други класове:
NSString
не се освобождаваNSMeasurement
се разпределяNSArray
не се освобождаваNSDictionary
не се освобождаваNSData
не се освобождаваNSURL
не се освобождава
Какво става тук? Защо само някои от тях се улавят от Address Sanitizer
Актуализация:
Както посочиха Rob и rmaddy, с малки цели числа за NSNumber
се съхранява като маркиран указател, който всъщност не заделя допълнителна памет:
(lldb) p/t n (__NSCFNumber *) $1 = 0b0000000000000000000000000000000000000000000000000010101000100111 (int)42
Последният бит е зададен на 1, което показва, че е маркиран, и всичко освен първите 4 бита е цяло число 42.
За постоянен низ (__NSCFConstantString
) с [[NSString alloc] initWithString:@"blah"]
, изглежда, че не е маркиран указател, тъй като последният бит не е зададен, но все още е странен адрес:
(lldb) p n
(__NSCFConstantString *) $1 = 0x0000000100002068 @"blah"
(lldb) p/t n
(__NSCFConstantString *) $2 = 0b0000000000000000000000000000000100000000000000000010000001101000 @"blah"
NSDate
и NSMeasurement
всички се показват в "нормалния" адресен диапазон > 0x600000000000.
Празен NSArray
има адрес в този диапазон, но не освобождава, но запълването му с обекти прави:
[[NSArray alloc] initWithObjects:@2, @3, @5, nil]
NSNumber
с голяма произволна стойност като65465.23424
. Малките цели числа са специално оптимизирани. Същото със статичнитеNSString
стойности. - person rmaddy   schedule 09.04.2018[[NSNumber alloc] initWithFloat:65465.23424]
се освобождава. Има ли други специални оптимизации като някакъв вид пул заNSString
и другите типове колекция? - person Luke   schedule 09.04.2018@"..."
е статичен низ, който няма да бъде освободен. Не съм сигурен за типовете колекции. - person rmaddy   schedule 09.04.2018release
. Можете само да предположите, че те ще бъдат управлявани от паметта така, сякаш са (т.е. няма да изтекат; но никога няма обещание, че ще се сринете). Малките цели числа, празните колекции и статичните низове са със специален регистър. На 64-битови платформи мисля, че почти всички цели числа са маркирани указатели. Вярвам, че късите низове също могат да станат маркирани указатели. Просто няма обещание, чеrelease
илиdealloc
наистина ще бъдат извикани, само че ще работи. - person Rob Napier   schedule 09.04.2018[[NSNumber alloc] initWithInt: 42]
получавам(lldb) p/t n (__NSCFNumber *) $1 = 0b0000000000000000000000000000000000000000000000000010101000100111 (int)42
Сега ще разгледам по-подробно другите типове. - person Luke   schedule 09.04.2018