I'm developing an iOS application with latest SDK.
My app is a port from an Android application and I have these two methods:
- (MyObject*)getMyObject:(MyObjectType)myObjectType
{
#synchronized(self)
{
for (int index = 0; index < [myObjects count]; index++)
{
MyObject* myObject = (MyObject*)[myObjects objectAtIndex:index];
if (myObject.Type == myObjectType)
return myObject;
}
return nil;
}
}
- (BOOL)isMyObjectVisible:(MyObjectType)myObjectType
{
#synchronized(self)
{
return ([self getMyObject:myObjectType] != nil);
}
}
I have isMyObjectVisible:, that is #synchronized, calling another #synchronized method.
Is it necessary that isMyObjectVisible: has to be #synchronized?
To answer your first question, no, the double locking is not needed.
You can keep the lock in getMyObject. That protects it. However, there is nothing in isMyObjectVisible other than a call to getMyObject, so there's nothing else to protect in that method.
However, borrrden's comment is not an issue here. You get a recursive lock when using #synchronized, so you can nest synchronized calls like you're doing without a deadlock. There's just no need to, in your case.
Here is an Example of that you need to use double #synchronized :
NSString * str;
str = [[NSString alloc] initWithFormat:#"str"];
-(void)viewDidLoad{
NSString *foo = #"foo";
NSString *bar = #"bar";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self firstAction:foo];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self secondAction:bar];
});
}
- (void)firstAction:(NSString *)sender {
NSLog(#"firstAction");
#synchronized (self) {
str = sender;
for (int i=0; i<5; i++) {
NSLog(#"first: %#",str);
}
}
}
- (void)secondAction:(NSString *)sender {
NSLog(#"secondAction");
#synchronized (self) {
str = sender;
for (int i=0; i<5; i++) {
NSLog(#"second: %#",str);
}
}
}
(str is static variable )
-Try to run it without the #synchronized (self) and see what will happen.
Related
I made a bug when I implement the search function. I opened an asynchronous thread. But when deleting a character (a digit of a phone number), the app would crash.
Error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x117d7320> was mutated while being enumerated.'
Code:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//
self.isSearch = YES;
//remove the last search all the contacts
[self.resultArr removeAllObjects];
//
[self.rcs_SearchTableView reloadData];
//
dispatch_queue_t uploadQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t getMainQueue = dispatch_get_main_queue();
dispatch_async(uploadQueue, ^{
NSMutableArray *phoneArr = (NSMutableArray *)[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText];
//
//NSLog(#"清空上次搜索的数据:%#", self.resultArr);
//NSLog(#"输入的关键字是---%#---%lu",searchText,(unsigned long)searchText.length);
if (0 == searchText.length || [searchText isEqualToString:#" "]) {
self.isSearch = NO;
//[self.rcs_SearchTableView reloadData];
[self.resultArr removeAllObjects];
}
//[self.rcs_SearchTableView reloadData];
if (0 != phoneArr.count) {
//
for (NSUInteger i = 0; i < phoneArr.count; i ++) {
RCSPhoneModel *flagPhoneModel = phoneArr[i];
for (NSUInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *flagModel = self.rcsRecentSearchDataSource[i];
if ([flagPhoneModel.serverId isEqualToString:flagModel.serverId] || [flagPhoneModel.phone isEqualToString:flagModel.name]) {
//the same contact has multiple Numbers To prevent repeated add the same contacts
if (![self.resultArr containsObject:flagModel]) {
[self.resultArr addObject:flagModel];
continue;
}
}
}
}
}else{
//search contacts by name
for (NSInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *model = self.rcsRecentSearchDataSource[i];
NSString *nameStr = model.name;
if (nameStr.length >= searchText.length) {
//search all the name
if ([nameStr containsString:searchText]) {
[self.resultArr addObject:model];
}
}
}
}
//
if (self.resultArr.count > 0) {
self.isSearch = YES;
//[self.rcs_SearchTableView reloadData];
}
//The phone contacts or local contact synchronized to the server
dispatch_async(getMainQueue, ^{
[self.rcs_SearchTableView reloadData];
});
});
}
A for loop should not enumerate anything that could change on any other thread or that could change within that loop. You should only enumerate an object that you are certain is not going to change while being enumerated (either in another thread, or within the loop itself). One way to do this is to only use a local copy of the array to enumerate over.
I can't see where anything being enumerated in your for loops is changed within the loop, so I would guess that in some other code in some other thread, you are changing either self.rcsRecentSearchDataSource or phoneArr. This crashes the for loop that enumerates self.rcsRecentSearchDataSource or phoneArr because it is required to not change while being enumerated.
Does this really need to be run on a separate thread?
If so, use a thread-local copy of the array to enumerate over, instead of the original array. That way you can be sure that nothing else can modify it, because it does not exist in any other scope.
Eg, there are two places where you could change your code to:
NSArray *localSearchDataSource = [self.rcsRecentSearchDataSource copy];
for (NSUInteger i = 0; i < localSearchDataSource.count; i ++) {
and one place where you could change to:
NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];
for (NSUInteger i = 0; i < localPhoneArr .count; i ++) {
I got it answer and like unders codes:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//
self.isSearch = YES;
//
if (0 == searchText.length || [searchText isEqualToString:#" "]) {
self.isSearch = NO;
//[self.resultArr removeAllObjects];
}
//Remove the last search all the contacts
[self.resultArr removeAllObjects];
//
[self.rcs_SearchTableView reloadData];
//
NSMutableArray *localSearchDataSource = [self.resultArr mutableCopy];
//Create an array of objects as well as the original array
//According to the input access to the phone number of the data
NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];
//
//[self.rcs_SearchTableView reloadData];
if (0 != localPhoneArr.count) {
//Because when data matching number takes longer, using asynchronous thread
dispatch_queue_t uploadQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t getMainQueue = dispatch_get_main_queue();
dispatch_async(uploadQueue, ^{
//
for (NSUInteger i = 0; i < localPhoneArr.count; i ++) {
RCSPhoneModel *flagPhoneModel = localPhoneArr[i];
for (NSUInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *flagModel = self.rcsRecentSearchDataSource[i];
if ([flagPhoneModel.serverId isEqualToString:flagModel.serverId] || [flagPhoneModel.phone isEqualToString:flagModel.name]) {
//The same contact has multiple Numbers To prevent repeated add the same contacts
if (![localSearchDataSource containsObject:flagModel]) {
[localSearchDataSource addObject:flagModel];
}
}
}
}
//Add the search results to the search data source
dispatch_async(getMainQueue, ^{
[self.resultArr addObjectsFromArray:localSearchDataSource];
[self.rcs_SearchTableView reloadData];
});
});
}else{
//Search contacts by name
for (NSInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *model = self.rcsRecentSearchDataSource[i];
NSString *nameStr = model.name;
if (nameStr.length >= searchText.length) {
//Search all name
if ([nameStr containsString:searchText]) {
[self.resultArr addObject:model];
}
}
}
}
//
if (self.resultArr.count > 0) {
self.isSearch = YES;
[self.rcs_SearchTableView reloadData];
}
}
I made two change that 'NSMutableArray *localSearchDataSource = [self.resultArr mutableCopy];' and 'NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];'. And finish it . Collection <__NSArrayM: 0x117d7320> was mutated while being enumerated.'
We can use instruments for various kinds of analysis. But many programmers find this tool to be too complicated and too heavy to bring real value.
Is there a simple way to track all objects of a specific class, and for each to know who exactly was allocating them and to verify that they are being freed correctly?
The answer is yes! there is a way, and I'll demo it in my answer below
Tracking allocations easily:
How to use: you can put the 150 lines of code below into a file named AllocTracker.m and drag it into your project files.
Use the check box at the right pane of Xcode to enable/disable it in your compilation target.
What you'll get?
when enabled, this module will track all allocations and deallocations of UIImage objects and log them. (It can easily be modified for tracking other classes.)
In addition to logging every allocation and deallocation, it will periodically (currently every 15 seconds) dump all objects which are currently allocated, with some added info and the call stack which allocated them.
What is the added value?
This code was used in big projects to get rid of orphan objects which were left allocated without notice, allowing to significantly reduce the memory footprint of the app and fix memory leaks.
So here is the code for AllocTracker.m:
#define TRACK_ALLOCATIONS
#ifdef TRACK_ALLOCATIONS
#import <UIKit/UIKit.h>
#define TIMER_INTERVAL 15
#implementation UIApplication(utils)
+(NSString *)dateToTimestamp:(NSDate *)date
{
if (date == nil) {
date = [NSDate date];
}
static NSDateFormatter *dateFormatter = nil;
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[dateFormatter setDateFormat:#"HH:mm:ss.S"];
}
NSString *ts = [dateFormatter stringFromDate:date];
return ts;
}
+(NSString*) getCaller:(int)stackDepth
{
#ifndef DEBUG
return #"NON DBG";
#else
NSArray *symbols = [NSThread callStackSymbols];
int lastIndex = (int)(symbols.count - 1);
if (lastIndex < 3) {
return #"NO DATA";
}
NSMutableString *result = [NSMutableString string];
int foundCount = 0;
for (int ix=3; ix <= lastIndex; ix++) {
NSString *line = symbols[ix];
NSRange rng1 = [line rangeOfString:#"["];
if (rng1.location == NSNotFound) {
continue;
}
NSRange rng2 = [line rangeOfString:#"]"];
NSString *caller = [line substringWithRange:NSMakeRange(rng1.location+1, rng2.location-rng1.location-1)];
if (foundCount > 0) { //not first
[result appendString:#"<--"];
}
[result appendString:caller];
if (++foundCount == stackDepth) {
break;
}
}
return (foundCount > 0) ? result : #"NO SYMBOL";
#endif
}
#end
#implementation UIImage(memoryTrack)
static NSMapTable *g_allocsMap;
static NSTimer *g_tmr;
static NSDate *g_lastDump = nil;
+(void)gotTimer:(NSTimer *)timer
{
[self dumpAllocs];
}
+(void)startTimer
{
static int count = 0;
g_tmr = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(gotTimer:) userInfo:#(count++) repeats:YES];
NSLog(#"starting timer %i", count);
}
+(void)cancelTimer
{
[g_tmr invalidate];
g_tmr = nil;
}
+(void)dumpAllocs
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
NSMutableString *str = [NSMutableString string];
[str appendString:#"\n#$# ========== Non-freed UIImages =========\n"];
NSMutableArray *sorted = [NSMutableArray array];
//make sure map is not changed while enumerating
static int s_ts_start = -1;
#synchronized (g_allocsMap) {
NSEnumerator *keysEnum = [g_allocsMap keyEnumerator];
UIImage *img;
while (img = [keysEnum nextObject]) {
NSString *value = [g_allocsMap objectForKey:img];
if (value) { //might be nulled because declared as weak
NSUInteger memUsed = CGImageGetHeight(img.CGImage) * CGImageGetBytesPerRow(img.CGImage);
NSString *objData = [NSString stringWithFormat:#"mem=%5ikb, size=%4ix%-4i", (int)(memUsed/1024), (int)img.size.width, (int)img.size.height];
NSString *line = [NSString stringWithFormat:#"%p - %# [%#]\n", img, objData, value];
if (s_ts_start<0) {
s_ts_start = (int)[line rangeOfString:#"["].location + 1;
}
if (line.length > (s_ts_start+10)) {
[sorted addObject:line];
}
}
}
}
if (sorted.count > 0) {
[sorted sortUsingComparator: ^NSComparisonResult(NSString *s1, NSString *s2)
{
//we expect '0x15a973700 - mem=3600kb, size=640x360 [16:14:27.5: UIIma...'
NSString *ts1 = [s1 substringWithRange:NSMakeRange(s_ts_start, 10)];
NSString *ts2 = [s2 substringWithRange:NSMakeRange(s_ts_start, 10)];
return [ts1 compare:ts2];
}];
int ix = 0;
for (NSString *line in sorted) {
[str appendFormat:#"#$# %3i) %#", ix++, line];
}
}
[str appendString:#"#$# ======================================================\n"];
NSLog(#"%#", str);
});
}
+(instancetype)alloc
{
NSString *caller = [UIApplication getCaller:4];
#synchronized (self) {
id obj = [super alloc];
NSLog(#"#$# UIImage alloc: [%p], caller=[%#]", obj, caller);
NSDate *now = [NSDate date];
NSString *value = [NSString stringWithFormat:#"%#: %#", [UIApplication dateToTimestamp:now], caller];
if (!g_allocsMap) {
g_allocsMap = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory];
}
[g_allocsMap setObject:value forKey:obj];
if (!g_lastDump) {
[self startTimer];
g_lastDump = now;
}
return obj;
}
}
-(void)dealloc
{
NSLog(#"#$# UIImage dealloc: [%#]", self);
}
#end
#endif //TRACK_ALLOCATIONS
How it works?
We create a category of UIImage and set our own version for alloc and dealloc. Every allocated object is saved into an NSMapTable object which works like a dictionary but allow storing object with weak pointers.
For convenience we were adding two methods under UIApplication which can be used by other modules if an appropriate header file is created. One method is for formatting the timestamp, and the other is for reading the call stack (only works in debug builds).
Tip for use:
if you use a real device and install idevicesyslog (brew install libimobiledevice), you can use the terminal to see all allocation debug, like this:
idevicesyslog | grep "#\$#"
This is my code in ViewController.m file,
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"%f",[self usedMemory]);
NSMutableArray *array= [[NSMutableArray alloc]init];
for (int i = 0; i < 100; i++) {
NSMutableData *data = [NSMutableData dataWithLength:10000];
[array addObject:data];
}
NSLog(#"%f",[self usedMemory]);
for (int i = 0; i < 100; i++) {
[array removeObjectAtIndex:0];
}
NSLog(#"%f",[self usedMemory]);
}
Here is the usedMemory method:
- (double)usedMemory
{
task_basic_info_data_t taskInfo;
mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
kern_return_t kernReturn = task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&taskInfo,
&infoCount);
if (kernReturn != KERN_SUCCESS
)
{
return NSNotFound;
}
return taskInfo.resident_size / 1024.0 / 1024.0;
}
Here is the result:
2015-01-26 22:39:00.058 audio_memory_test[9050:874963] 25.011719
2015-01-26 22:39:00.060 audio_memory_test[9050:874963] 26.312500
2015-01-26 22:39:00.060 audio_memory_test[9050:874963] 26.312500
Why has memory not been released when I deleted objects in the array? What has the removeObjectAtIndex method done? How can I release this memory?
When you call [self usedMemory] after the final loop, your objects are still held in memory. The autorelease pool to which they belong hasn't yet been drained; this generally happens when you leave the scope of your source code and the system takes control again.
All because [NSMutableData dataWithLength: ] returns an autoreleased object, so you get exactly the expected behaviour.
To fix this: Either use [[NSMutableData alloc] initWithLength: ] or use an autorelease pool.
As others have said, the problem is that you're creating auto-released objects. Here is a change you could make to your code so your objects would actually be released:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"%f",[self usedMemory]);
//all autoreleased objects created inside the braces
//of the #autorleasepool directive will be released
//when we leave the braces
#autoreleasepool
{
NSMutableArray *array= [[NSMutableArray alloc]init];
for (int i = 0; i < 100; i++) {
NSMutableData *data = [NSMutableData dataWithLength:10000];
[array addObject:data];
}
NSLog(#"%f",[self usedMemory]);
for (int i = 0; i < 100; i++) {
[array removeObjectAtIndex:0];
}
}
NSLog(#"%f",[self usedMemory]);
}
I am playing around with NSOperationQueue in order to run some code in the background and have it update a UILabel. Here's the viewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(counterTask) object:nil];
[queue addOperation:operation];
}
And here's the method called as the invocation operation:
- (void)counterTask {
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:[NSString stringWithFormat:#"%d", i]
waitUntilDone:YES];
}
}
[self.firstLabel performSelectorOnMainThread:#selector(setText:) withObject:#"finished." waitUntilDone:NO];
}
As the loop counts up, and more and more #"%d" NSStrings are created, the memory usage naturally goes up. Once the loop finishes however, the memory doesn't seem to deallocate. I expected the memory to fall as the setText: message uses new instances of NSString and releases the old ones.
If I change the loop condition to i<5000000*2, the memory usage is roughly double by the end – so it's definitely something happening on each iteration causing the leak.
Why is memory leaking here?
EDIT: Forgot to mention that I'm using ARC.
ARC doesn't remove retain / release / autorelease, it just controls the calling of these methods. You can add your own autorelease pool into your loop to force cleanup as it goes:
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
#autoreleasepool {
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:[NSString stringWithFormat:#"%d", i]
waitUntilDone:YES];
}
}
}
Let's try:
- (void)counterTask {
#autoreleasepool {
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:[NSString stringWithFormat:#"%d", i]
waitUntilDone:YES];
}
}
}
[self.firstLabel performSelectorOnMainThread:#selector(setText:) withObject:#"finished." waitUntilDone:NO];
}
What happening in your loop that you are creating NSString and ARC add it to auto release pool.
not releases the memory(NSString*) immediately , will release later.
One more thing is that, actually performSelectorOnMainThread retains both target and objects.
So best way is that you create nsstring instance after sending it to selector set it to nil so ARC will release it.
NSString* strText=[[NSString alloc]initWithFormat:#"%d",i ];
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:strText
waitUntilDone:YES];
strText=nil;
IMO, the suggested approach of #Wain should fix the issue.
But you may also use this:
- (void)counterTask {
assert([NSThread currentThread] != [NSThread mainThread]);
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
dispatch_sync(dispatch_get_main_queue(), ^{
#autoreleasepool {
self.firstLabel.text = [NSString stringWithFormat:#"%d", i];
}
});
}
}
dispatch_async(dispatch_get_main_queue, ^{
self.firstLabel.text = #"finished";
});
}
Try this
(void)counterTask {
__weak NSString *str;
for (int i=0; i<50000; i++) {
if (i % 100 == 0) {
str = [NSString stringWithFormat:#"%d", i];
[self.logInTxtField performSelectorOnMainThread:#selector(setText:)
withObject:str
waitUntilDone:YES];
}
}
[self.logInTxtField performSelectorOnMainThread:#selector(setText:) withObject:#"finished." waitUntilDone:NO];
}
Below is the method with the code, where I am getting a memory leak using manual memory management. The memory leak is detected by using Xcode instruments and specifically points to the line where I am using NSJSONSerialization. I am running the target app (on a device with iOS 6.1).
The first time that i tap on the refreshButton there is no leak. Any subsequent tap generates the leak(and more leaks on top of that if i continue tapping the button). Below is the code - This is basic stuff for consuming JSON web services(the web service link is bogus but the real one that I am using works). You will notice that I am using Grand Central Dispatch so that I can update the UI without waiting for the parsing of the JSON to finish.
The line detected by instruments is surrounded by the asterisks. I would like to get some help to anyone who might have an idea of what is going on here. The full stack trace(as mentioned in the below comments i will put here:)
+(NSJSONSerialization JSONObjectWithData:option:error:] -> -[_NSJSONReader parseData:options:] -> -[_NSJSONReader parseUTF8JSONData:skipBytes:options]->newJSONValue->newJSONString->[NSPlaceholderString
initWithBytes:length:encoding:]
-(void)parseDictionary:(NSDictionary *)dictonary{
self.transactions = [dictonary objectForKey:#"transactions"];
if(!self.transactions){
NSLog(#"Expected 'transactions' array");
return;
}
for (int arrayIndex = 0; arrayIndex < [self.transactions count]; arrayIndex++) {
TransactionResult *result = [[[TransactionResult alloc] init] autorelease];
result.transactionID = [[self.transactions objectAtIndex:arrayIndex] objectForKey:#"ID"];
result.transactionDescription = [[self.transactions objectAtIndex:arrayIndex] objectForKey:#"description"];
result.transactionPrice = [[self.transactions objectAtIndex:arrayIndex] objectForKey:#"price"];
self.totalPrice += [result.transactionPrice doubleValue];
NSLog(#"total price: %f", self.totalPrice);
[self.transactionResults addObject:result];
result = nil;
}
}
- (IBAction)refreshButtonPressed:(UIBarButtonItem *)sender {
__block id resultObject;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSURL *url = [NSURL URLWithString:#"http://mywebservice.php"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error;
***resultObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];***
if(!error){
if([resultObject isKindOfClass:[NSDictionary class]]){
NSDictionary *dictonary = resultObject;
[self parseDictionary:dictonary];
NSLog(#"Done parsing!");
dispatch_async(dispatch_get_main_queue(), ^{
self.isLoading = NO;
[self.transactionsTableView reloadData];
});
}
else{
NSLog(#"JSON Error: Expected Dictionary");
resultObject = nil;
return;
}
}
else{
NSLog(#"JSON Error: %#", [error localizedDescription]);
dispatch_async(dispatch_get_main_queue(), ^{
resultObject = nil;
[self.transactionsTableView reloadData];
[self showError];
});
return;
}
});
}
I used a ARC as soon as it came out with 4.3, and put an app in the store with it - point being you could switch to ARC. That said, I tried to reproduce your problem by creating a class/file that has the no-arc flag applied to it, but cannot reproduce the problem. This makes me believe your problem is elsewhere. In the code below, I create a Test object in another file, retain it, and send it the test message. No matter what I set "i" to, it always deallocs the object:
#import "Tester.h"
#interface Obj : NSObject <NSObject>
#end
#implementation Obj
- (id)retain
{
NSLog(#"retain");
id i = [super retain];
return i;
}
- (oneway void)release
{
NSLog(#"release");
[super release];
}
- (void)foo
{
}
- (void)dealloc
{
NSLog(#"Obj dealloced");
[super dealloc];
}
#end
#implementation Tester
- (void)test
{
int i = 2;
__block Obj *obj;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
obj = [[Obj new] autorelease];
if(i == 0) {
Obj *o = obj;
dispatch_async(dispatch_get_main_queue(), ^
{
[o foo];
} );
} else if(i == 1) {
obj = nil;
} else if(i == 2) {
dispatch_async(dispatch_get_main_queue(), ^
{
obj = nil;
} );
}
} );
}
#end