前一章提到,由於ARC是透過靜態分析,在Compile TIme決定應該要在程式碼
的那些地方加入retain,release所以,要用ARC基本上非常簡單,就是先把
原本要手動管理記憶體的地方,把retain、release都拿掉,在dealloc的地方
也把[super dealloc]拿掉。
要了解那些地方是weak reference
ARC有時候會在一些地方沒做retain,結果卻又自動多做了一次release最後
導致Bad Access的錯諤。我們在講Selector的時候提到,我們可以將target/action
與必要的參數合起來變成另外一種物件,叫做NSInvocation,在ARC環境下
從NSInvocation拿出參數時,就必須要額外注意記憶體管理問題。
比方說,我們現在要把對UIApplication要求開啟指定URL這件事情,
變成一個Invocation。
NSURL *URL = [NSURL URLWithString:@"http://kkbox.com"]; NSMethodSignature *sig = [UIApplication instanceMethodSignatureForSelector:@selector(openURL:)]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; [invocation setTarget:[UIApplication sharedApplication]]; [invocation setSelector:@selector(openURL:)]; [invocation setArgument:&URL atIndex:2];
但假如我們用以下這段code的方式,把invocation拿出參數的物件的時候, 就會遇到Bad Access錯誤
NSURL *arg = nil;
[invocation getArgument:&arg atIndex:2];
NSLog(@"arg:%@", arg);
之所以會crash的原因是,我們在透過getArgument:atIndex:拿出參數的時候, getArgument:atInde:並不會幫我們把arg多retain一次,而到了用NSLog印出tag 之後,ARC認為我們已經不會用而arg了,所以就對arg多做了一次release,
於是retain 與release就變得不成對。
我們要解決這個問題的方法是要把arg設為Weak Reference或Unsafe Unretained
讓arg這個Objective-C物件的指標不被ARC管理,要求ARC不要幫這個物件做任何自動的retain 與release,在這邊要使用__weak或__unsafe_unretained關鍵字。程式會像這樣:
__weak NSURL *arg = nil;
[invocation getArgument:&arg atIndex:2];
NSLog(@"arg:%@", arg);
ARC也不會排除循環Retain(Retain Cycle)的狀況,遇到了循環Retain,
還是會造成 Memory Leak,循環Retain就是,A物件本身retain了B物件,
但是B物件又retain了 A物件,結果我們要在釋放A的時候才有辦法釋放B,
但是B又得要在B被釋放的時候,才會釋放A, 最後導致A與B都沒有辦法被釋放。
這種狀況最可能出現在:
1.把delegate設為strong reference。
2.某個物件的某個property是一個block,但是在這個block裡頭把物件自已給 retain了一份。
3.使用timer的時候,到了dealloc的時候才停止timer。
假如我們現在有一個view controller我們希望這個view controller可以定時更新, 那麼我們可能會用 +scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 這個method建立物件,指定定時執行某個selector。
我們要特別注意, 在建立這個timer的時候,我們指定給timer的target,也會被timer retain一份, 因此,我們想要在view controller在dealloc的時候,才停止timer就會有問題: 因為view controller已經被timer retain起來了,所以只要timer還在執行, view controller就不可能走到dealloc的地方。
所以應該在viewDidDisappear的時候就要停止timer。
Foundation Framework裡頭的每個物件,都有對應的C實作,
這一層C的實做叫作Core Foundation,當我們在使用Core Foundation
裡頭的C型態時,像是CFString、CFArray等,我們可以讓這些型態
變成可以接受ARC的管理。這種讓C型態也可以被當作Objective-C物件,
接受ARC的記憶體管理的方式,叫做Toll-Free Bridged
Toll-Free Bridged有三個語言關鍵字: __bridge、__bridge_retained、以及
__bridge_transfer。
* __bridge會把Core Foundation的C資料型態轉換成Objective-C物件,
但是不會多做retain與release。
* __bridge_retained會把CoreFoundation的C資料型態轉換成Objective-C
物件,並且會做一次retain,但是之後必須由我們自已手動物叫CFRelease,
釋放記憶體。
* __bridge_transfer會把Core Foundation物件轉換成Objective-C物件,
並且會讓ARC主動添加retain與release。不見得每個Core Foundation型態
都有辦法轉換成Objective-C物件。
其它
Objective-C語言有了ARC之後,除了禁止使用retain、release這些關鍵字之外
也禁止我們手動建立NSAutoreleasePool,同時禁止了一些我們在ARC之前的程式寫作方式。包括我們不可以把Objective-C物件放進C structure裡頭,Compiler會告訴我們
語法錯誤。
在有ARC之前,我們之所以會把Objective-C物件放進C Structure裡,大概會有幾個目的,
其一是,假如我們有某個Class有很多成員變數,那我們可能會想以下這種寫法將成員變數分成群組:
@interface MyClass : NSObject {
struct {
NSString *memberA;
NSString *memberB;
} groupA;
struct {
NSString *memberA;
NSString *memberB;
} groupB;
}
@end
這樣如果我們想要使用groupA裡頭的memberA,可以用self.groupA.memberA呼叫。另外一種目的,則是有時候,我們可能會想要刻意隱藏某個Objective-C Class裡頭
有那些成員變數。像下面這段code裡頭,我們原本有一個Class叫做MyClass,裡頭
有privateMemberA與privateMemberB兩個成員變數,原本應該直接寫在MyClass的宣告裡頭,但是我們卻刻意把這兩個成員變數包進_Privates這個C Structure裡頭,而原本放在MyClass成員變數宣告的地方,只剩下了一個叫做privates的指標,光看到這個指標,讓人難以理解這個Class裡頭到底有什麼東西。
@interface MyClass : NSObject {
void *privates;
}
@end
typedef struct {
NSString *privateMemberA;
NSString *privateMemberB;
} _Privates;
@implementation MyClass
- (void)dealloc {
_Privates *privateMembers = (_Privates *)privates;
[privateMembers->privateMemberA release];
[privateMembers->privateMemberB release];
free(privates);
privates = NULL;
[super dealloc];
}
- (instancetype)init {
self = [super init];
if (self) {
privates = calloc(1, sizeof(_Privates));
_Privates *privateMembers = (_Privates *)privates;
privateMembers->privateMemberA = @"A";
privateMembers->privateMemberB = @"B";
}
return self;
}
@end
這種寫法其實是種程式碼保護的技巧,主要在防範class-dump,或是從class-dump衍生出的class-dump-z這些工具。class-dump可以從編譯好的Binary中
還原出每個class的header,當我們從class-dump抽出別人的App的header,
看出有那些Class,每個Class有那些成員變數,有那些method,也就可以看出整個
App的架構大致如何。這種寫法就是讓別人用class-dump倒出我們App的headers時,
不會太容易就可以了解我們一些重要的Class是如何運作,不過對於做軟體破解的人來說,
其實只要花上時間,所有軟體都有辦法破解就對了。
總之有了ARC之後,我們都無法繼續使用以上兩種的程式寫作方式。
沒有留言:
張貼留言