前一段在一個公眾號里看到一個面試題,也是WWDC中的一段代碼,如下:
- (BOOL)validateDictionary:(NSDictionary *)dict usingChecker:(Checker *)checker error:(NSError **)error {
__block BOOL isValid = YES;
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if([checker checkObject:obj forKey:key]) return;
*stop = YES;isValid = NO;
if(error) *error = [NSError errorWithDomain:...];
}];
return isValid;
}
對內(nèi)存管理熟悉的同學(xué)應(yīng)該很快能發(fā)現(xiàn)這段代碼的問題。這段代碼主要涉及兩個點。第一是error參數(shù)的類型,error參數(shù)的類型應(yīng)該為(NSError *__autoreleasing *))。第二點是某些類的方法會隱式地使用自己的autorelease pool,NSDictionary的[enumerateKeysAndObjectsUsingBlock]方法就是這樣,
所以這段代碼可以翻譯如下:
- (BOOL)validateDictionary:(NSDictionary *)dict usingChecker:(Checker *)checker error:(NSError **)error {
__block BOOL isValid = YES;
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
@autoreleasepool{
if([checker checkObject:obj forKey:key]) return;
*stop = YES;isValid = NO;
if(error) *error = [NSError errorWithDomain:...];
}];
return isValid;
}
}
下面我們來說一下為什么這樣寫會有問題。
首先,__autoreleasing這個關(guān)鍵字在ARC中主要用在參數(shù)傳遞返回值(out-parameters)和引用傳遞參數(shù)(pass-by-reference)的情況下。比如常用的NSError的使用:
NSError *__autoreleasing error;
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error])
{
NSLog(, error);
}
(在上面的writeToFile方法中error參數(shù)的類型為(NSError *__autoreleasing *))
注意,如果你的error定義為了strong型,那么,編譯器會幫你隱式地做如下事情,保證最終傳入函數(shù)的參數(shù)依然是個__autoreleasing類型的引用。
NSError *error;
NSError *__autoreleasing tempError = error;// 編譯器添加
if(![data writeToFile:filename options:NSDataWritingAtomic error:&tempError])
{
error = tempError;// 編譯器添加
NSLog(@"Error: %@", error);
}
所以為了提高效率,避免這種情況,我們一般在定義error的時候?qū)⑵渎暶鳛?code>__autoreleasing類型的:
NSError *__autoreleasing error;
在這里,加上__autoreleasing之后,相當(dāng)于在MRC中對返回值error做了如下事情:
*error = [[[NSError alloc] init] autorelease];
\**error指向的對象在創(chuàng)建出來后,被放入到了autoreleasing pool中,等待使用結(jié)束后的自動釋放,函數(shù)外error的使用者并不需要關(guān)心\**error指向?qū)ο蟮尼尫拧?/p>
但是在上面的代碼中被__autoreleasing修飾的error被放到autoreleasepool里,等autoreleasepool釋放的時候error會被釋放掉,這樣在外面再訪問error的話就會報錯了。
如果要解決這個問題可以在便利前新建一個NSError對象,遍歷完再對傳入的error賦值。