This method will be called from multiple threads and returns a static dictionary which will only be read and not written to by the calling threads. The initial values are dynamic and thus the internal implementation is an NSMutableDictionary.
From what I understand, since the assignment to dictionary is not a static initialiser and its state is also further mutated, the #synchronized block is required before it's returned to ensure the initialised values are seen by all calling threads. Is this correct or should I remove the #synchronized and set a memory barrier as the last line of the dispatch_once block?
I only need to ensure that the initially assigned values are visible to all caller threads.
+ (NSDictionary *)threadSafeMethod {
static NSMutableDictionary *dictionary;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:dynamicValue1 forKey:#"key1"];
[dictionary setValue:dynamicValue2 forKey:#"key2"];
});
#synchronized(dictionary) {
return dictionary;
}
}
#synchronized is basically a mutex on the lock object.
The way you use it protects only the return of the object and not it's use. Thus, the threads will block each other to obtain the dictionary pointer.
Update: as your object is immutable on the outside, the protection is not needed there.
I doubt that #synchronize is of any use at all in your case, just leave it as is. Initialization will be done once, others are blocked until the first in line finished the block.
Then everybody get's his pointer to the dictionary created, so all threads read the same dictionary and there is no blocking any more.
Related
I am facing a problem. I am passing an object to another class by its reference & setting the value in that object. Now when I access this variable in callback handler then It is nil.
My sample code is:
Class A:
__block NSString *getListJobId = nil;
ClassB *bobject = [[ClassB alloc]init];
[bobject getItemsWithJobId:&getListJobId onSuccess:^(NSArray *response) {
NSLog(#"job id %#",getListJobId); //It is nil, It should be **shiv**
} onFailure:^(NSError *error) {
}];
Class B:
.h
- (void)getItemsWithJobId:(NSString **)jobId onSuccess:(void (^)(NSArray *))completedBlock onFailure:(void (^)(NSError *))failureBlock;
.m
- (void)getItemsWithJobId:(NSString **)jobId onSuccess:(void (^)(NSArray *))completedBlock onFailure:(void (^)(NSError *))failureBlock
{
*jobId = #"shiv";
completedBlock([NSArray new]);
}
I am getting this jobId nil in class A in callback response. How can I get this value from class B to class A.
I will appreciate your help.
You should not pass by reference to get an updated value in the method, because the getListJobId at ClassA and ClassB do not point same address.
An Obj-C block capture the value of variables outside of its enclosing scope.
See "Blocks Can Capture Values from the Enclosing Scope" section.
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
Instead of passing by reference, we can get the updated value from arguments of the block and update getListJobId in the block.
Class A:
__block NSString *getListJobId = nil;
ClassB *bobject = [[ClassB alloc] init];
[bobject getItemsWithJobId:getListJobId onSuccess:^(NSArray *response, NSString *updatedJobId) {
getListJobId = updatedJobId;
NSLog(#"job id %#", getListJobId); // job id **shiv**
} onFailure:^(NSError *error) {
}];
Class B: .h
- (void)getItemsWithJobId:(NSString *)jobId onSuccess:(void (^)(NSArray *, NSString *))completedBlock onFailure:(void (^)(NSError *))failureBlock;
.m
- (void)getItemsWithJobId:(NSString *)jobId onSuccess:(void (^)(NSArray *, NSString *))completedBlock onFailure:(void (^)(NSError *))failureBlock
{
NSString *updatedJobId = #"**shiv**";
completedBlock([NSArray new], updatedJobId);
}
Taking the address of a __block variable does not always do what you expect.
In the current implementation, __block variables are initially allocated on the stack, and then "moved" to the heap upon any of the blocks that use it being moved to the heap (which is caused by the block being copied).
Therefore, the address of a __block variable changes over its lifetime. If you take the address of it, and it moves, then you will no longer be pointing to the version of the variable that everyone else is using.
Here, what is happening is that you take the address of the __block variable getListJobId while it is still on the stack. It is still on the stack at that point because it is caused to be moved to the heap by the copying of any block that uses it, but no block has been created at the point yet.
Then, a block that uses getListJobId gets copied somewhere and getListJobId gets moved to the heap. Exactly where this happens is not very clear, because ARC is allowed to insert copies of blocks in various places. Plus, the code you are showing here does not seem like your real code, because there would be no point to calling a "completion block" at the end of a method synchronously (in that case you would just return and let the caller perform the operations they want when completed). Rather, your real code probably performs an asynchronous operation, at the end of which the completion handler is called. dispatch_async and related asynchronous functions copy the blocks passed to them (which in turn copy any blocks captured, and so on).
I am guessing that in your real code, both the *jobId = #"shiv"; line and the calling of the completion block happen in the asynchronous operation. What is happening is that the creation of the asynchronous operation copies the block and causes getListJobId to be moved to the heap. So inside the asynchronous operation, getListJobId refers to the heap version of the variable. However, the *jobId = #"shiv"; writes to the stack version of the variable, because jobId is a pointer taken from the address of the variable when it was still on the stack. So you are writing to and reading from different variables.
Furthermore, what you are doing in *jobId = #"shiv"; is very dangerous, because by the time of the asynchronous operation, the stack frame of the original function call no longer exists. And writing to a variable on the stack after the stack frame is gone is undefined behavior, and you may be overwriting other unknown variables in memory. You are lucky it didn't crash.
While building my app, Marco Polo (getmarcopolo.com), I found that one of the most challenging parts of the app was pulling data from the server without slowing down the interface and without it crashing. I've got it settled now, and wanted to share my knowledge with any other developers having the same issue.
When pulling data from a server, there are a number of factors that need to be taken into consideration:
Data integrity - No data is ever missed from the server
Data persistence - Data is cached and can be accessed even when offline
Lack of interference with the interface (main thread) - Achieved using multithreading
Speed - Achieved using thread concurrency
Lack of thread collisions - Achieved using serial thread queues
So the question is, how do you achieve all 5?
I've answered this below, but would love to hear feedback on how to improve the process (with this example), as I feel it is not very easy to find in one place right now.
I'll be using the example of refreshing the marco's in the notification feed. I'll also be referring to Apple's GCD library (see https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html). First, we create a singleton (see http://www.galloway.me.uk/tutorials/singleton-classes/):
#implementation MPOMarcoPoloManager
+ (MPOMarcoPoloManager *)instance {
static MPOMarcoPoloManager *_instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
#end
This allows for us to call [MPOMarcoPoloManager instance] at any time, from any file, and access the properties in the the singleton. It also ensures that there is always only one instance of the marco polos. 'static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{' ensures thread stability.
The next step is to add the data structure we will be accessing publicly. In this case, add an NSArray for the marcos to the header file, as well as a public declaration of 'instance':
#interface MPOMarcoPoloManager : NSObject
+ (MPOMarcoPoloManager *)instance;
#property (strong, nonatomic) NSArray *marcoPolos;
#end
Now that the array and the instance are accessible publicly, it's time to ensure data persistence. We will achieve this by adding the ability to cache the data. The following code will
1. Initializes our serverQueue to the global queue, which allows multiple threads to run concurrently
2. Initializes our localQueue to a serial queue, which allows only one thread to be run at a time. All local data manipulation should be done on this thread to ensure no thread collisions
3. Gives us a method to call for caching our NSArray, with objects that conform to NSCoding (see http://nshipster.com/nscoding/)
4. Attempts to pull the data structure from the cache, and initializes a new one if it cannot
#interface MPOMarcoPoloManager()
#property dispatch_queue_t serverQueue;
#property dispatch_queue_t localQueue;
#end
#implementation MPOMarcoPoloManager
+ (MPOMarcoPoloManager *)instance {
static MPOMarcoPoloManager *_instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)init {
self = [super init];
if (self) {
_marcoPolos = [NSKeyedUnarchiver unarchiveObjectWithFile:self.marcoPolosArchivePath];
if(!self.marcoPolos) {
_marcoPolos = [NSArray array];
}
//serial queue
_localQueue = dispatch_queue_create([[NSBundle mainBundle] bundleIdentifier].UTF8String, NULL);
//Parallel queue
_serverQueue = dispatch_queue_create(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NULL);
}
return self;
}
- (NSString *)marcoPolosArchivePath {
NSArray *cacheDirectories = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [cacheDirectories objectAtIndex:0];
return [cacheDirectory stringByAppendingFormat:#"marcoPolos.archive"];
}
- (BOOL)saveChanges {
BOOL success = [NSKeyedArchiver archiveRootObject:self.marcoPolos toFile:[self marcoPolosArchivePath]];
return success;
}
#end
Now that we have the structure of the singleton, It's time to add the ability to refresh our marco's. Add the declarations of refreshMarcoPolosInBackgroundWithCallback:((^)(NSArray *result, NSError *error)) to the header file:
...
- (void)refreshMarcoPolosInBackground:((^)(NSArray *result, NSError *error))callback;
...
Now it's time to implement the refresh. Notice that all server calls are performed on the serverQueue (which is parallel), and any data manipulation is done on the localQueue (which is serial). When the method is completed, we use what is called a C block (see https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html) to callback the result to the main thread. Any task that acts on a background thread should have a callback to the main thread to inform the interface that the refresh has completed (whether it be successful or not).
...
- (void)refreshMarcoPolosInBackground:((^)(NSArray *result, NSError *error))callback {
//error checking ommitted
//Run the server call on the global parallel queue
dispatch_async(_serverQueue, ^{
NSArray *objects = nil;
NSError *error = nil;
//This can be any method with the declaration "- (NSArray *)fetchMarcoPolo:(NSError **)callbackError" that connects to a server and returns objects
objects = [self fetchMarcoPolo:&error];
//If something goes wrong, callback the error on the main thread and stop
if(error) {
dispatch_async(dispatch_get_main_queue(), ^{
callback(nil, error);
});
return;
}
//Since the server call was successful, manipulate the data on the serial queue to ensure no thread collisions
dispatch_async(_localQueue, ^{
//Create a mutable copy of our public array to manipulate
NSMutableArray *mutableMarcoPolos = [NSMutableArray arrayWithArray:_marcoPolos];
//PFObject is a class from Parse.com
for(PFObject *parseMarcoPoloObject in objects) {
BOOL shouldAdd = YES;
MPOMarcoPolo *marcoPolo = [[MPOMarcoPolo alloc] initWithParseMarcoPolo:parseMarcoPoloObject];
for(int i = 0; i < _marcoPolos.count; i++) {
MPOMarcoPolo *localMP = _marcoPolos[i];
if([marcoPolo.objectId isEqualToString:localMP.objectId]) {
//Only update the local model if the object pulled from the server was updated more recently than the local object
if((localMP.updatedAt && [marcoPolo.updatedAt timeIntervalSinceDate:localMP.updatedAt] > 0)||
(!localMP.updatedAt)) {
mutableMarcoPolos[i] = marcoPolo;
} else {
NSLog(#"THERE'S NO NEED TO UPDATE THIS MARCO POLO");
}
shouldAdd = NO;
break;
}
}
if(shouldAdd) {
[mutableMarcoPolos addObject:marcoPolo];
}
}
//Perform any sorting on mutableMarcoPolos if needed
//Assign an immutable copy of mutableMarcoPolos to the public data structure
_marcoPolos = [NSArray arrayWithArray:mutableMarcoPolos];
dispatch_async(dispatch_get_main_queue(), ^{
callback(marcoPolos, nil);
});
});
});
}
...
You may be wondering why we would manipulate the data on a queue for something like this, but lets add a method where we can mark the marco as viewed. We don't want to have to wait for the server to update, but we also don't want to manipulate the local object in a manor that can cause a thread collision. So let's add this declaration to the header file:
...
- (void)setMarcoPoloAsViewed:(MPOMarcoPolo *)marcoPolo inBackgroundWithlocalCallback:((^)())localCallback
serverCallback:((^)(NSError *error))serverCallback;
...
Now it's time to implement the method. Notice that the local manipulation is done on the serial queue, then immediately calls back to the main thread, allowing the interface to update without waiting for a server connection. It then updates the server, and calls back to the main thread on a separate callback to inform the interface that the server save was completed.
- (void)setMarcoPoloAsViewed:(MPOMarcoPolo *)marcoPolo inBackgroundWithlocalCallback:(MPOOrderedSetCallback)localCallback
serverCallback:(MPOErrorCallback)serverCallback {
//error checking ommitted
dispatch_async(_localQueue, ^{
//error checking ommitted
//Update local marcoPolo object
for(MPOMarcoPolo *mp in self.marcoPolos) {
if([mp.objectId isEqualToString:marcoPolo.objectId]) {
mp.updatedAt = [NSDate date];
//MPOMarcoPolo objcts have an array viewedUsers that contains all users that have viewed this marco. I use parse, so I'm going to add a MPOUser object that is created from [PFUser currentUser] but this can be any sort of local model manipulation you need
[mp.viewedUsers addObject:[[MPOUser alloc] initWithParseUser:[PFUser currentUser]]];
//callback on the localCallback, so that the interface can update
dispatch_async(dispatch_get_main_queue(), ^{
//code to be executed on the main thread when background task is finished
localCallback(self.marcoPolos, nil);
});
break;
}
}
});
//Update the server on the global parallel queue
dispatch_async(_serverQueue, ^{
NSError *error = nil;
PFObject *marcoPoloParseObject = [marcoPolo parsePointer];
[marcoPoloParseObject addUniqueObject:[PFUser currentUser] forKey:#"viewedUsers"];
//Update marcoPolo object on server
[marcoPoloParseObject save:&error];
if(!error) {
//Marco Polo has been marked as viewed on server. Inform the interface
dispatch_async(dispatch_get_main_queue(), ^{
serverCallback(nil);
});
} else {
//This is a Parse feature that your server's API may not support. If it does not, just callback the error.
[marcoPoloParseObject saveEventually];
NSLog(#"Error: %#", error);
dispatch_async(dispatch_get_main_queue(), ^{
serverCallback(error);
});
}
});
}
With this setup, a refresh can be occuring the background, while setting a marco as viewed at the same time, while ensuring that the local model is not manipulated at the same time. While the necessity of the localQueue may not be obvious with only two methods, when having many different types of manipulation available, it becomes critical.
I use a dataManager that contains two sub managers, core data fetch manager and restkit manager, which maps to core data.
for example:
anywhereInApp.m
[dataManager getData: someSearchPrecate withCompletionBlock: someBlock];
dataManager.m
- (void) getData: somePredicate withCompletionBlock: someblock{
[self.coreDataManager fetchData: somePredicate withCompletionBlock: some block];
[self.restkitManager fetchData: somePredicate withCompletionBlock: some block];
}
and then core data manger runs on a thread to fetch data and executes completion block.
and reskitmanager runs a thread and executes completion block when http request and object mapping complete.
usually the completion block updates the data shown in a collection view.
only need to worry about old data getting removed from core data, but that's another story and can involve comparing the results from the two different calls and taking appropriate action. I try to picture a venn diagram of result sets and it all makes sense or I am too tired & drinking too good of beer.
I'm trying to add a dispatch_queue_t to a NSMutableArray like so:
NSMutableArray* queuesWaitingForTargetQueue = (__bridge NSMutableArray*)dispatch_queue_get_specific(targetQueue, WAITING_QUEUE_LIST_KEY);
NSLog(#" dispatch_get_current_queue() = %#x", (unsigned int)dispatch_get_current_queue());
NSLog(#" dispatch_get_main_queue() = %#x", (unsigned int)dispatch_get_main_queue());
NSLog(#" currQueueId = %#x", (unsigned int)currQueueId);
NSLog(#" queuesWaitingForTargetQueue = %#x (%#)", (unsigned int)queuesWaitingForTargetQueue, NSStringFromClass([queuesWaitingForTargetQueue class]));
[queuesWaitingForTargetQueue addObject:(__bridge id)currQueueId];
The last line sometimes fails with EXC_BAD_ACCESS:
Thread 1: EXC_BAD_ACCESS (code=1, address=0xc08314eb)
While the output was:
dispatch_get_current_queue() = 0x2879640
dispatch_get_main_queue() = 0x2879640
currQueueId = 0x2879640
queuesWaitingForTargetQueue = 0x8672ae0 (__NSArrayM)
What could be wrong here?
Your memory management is going to be a real problem here - your crash is almost for sure a pointer to a previously released object. If you really need to move the array around, then move it back and forth using the high level bridge casts (CFBridging...) from an id to a CFType (like a CFMutableDictionary), then use the CF object as the key. When done, CFRelease the CF object.
Well, no.
If you look up the return type of the various methods you're using, like dispatch_get_current_queue(), they return a value of type dispatch_queue_t. If you look up dispatch_queue_t, the docs say:
dispatch_queue_t A dispatch queue is a lightweight object to which
your application submits blocks for subsequent execution.
typedef struct dispatch_queue_s *dispatch_queue_t;
It's a struct. Structs are not objects. NSArrays can only hold objects.
I'm trying to understand why this code is leaking, using ARC:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:#"Key"];
}
As you can see, I put a block inside a collection (NSMutableDictionary, but it's the same if I use NSDictionary, NSArray ecc...), then the method returns and the dictionary is deallocated. The block should then be released. But, using instruments, I see a leak
"just to be sure" that the block has no other references, I added this line at the end of the method:
[dict setObject:[NSNull null] forKey:#"Key"];
same result.
I've found this post but the answers point to another problem:
Blocks inside NSMutableArray leaking (ARC)
Then, this is the magic:
If I change this line:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:#"Key"];
to:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[aBlock copy] forKey:#"Key"];
the leak disappear.
I know that, under non-ARC, before passing a reference of a block literal, I must copy it (when declared literal, it's on the stack, so I need to copy it to the heap before passing outside the scope of the function where is declared)...but using ARC I shouldn't care about it.
Any indication?
This is happening with all versions from 5.0 to 6.1.
EDIT: I've made some tests, trying to understand if I'm doing something wrong or if there is some bug...
First: Am I reading wrong instruments informations?
I don't think, the leak is real and not my mistake. Look at this image...after executing the method 20 times:
Second: what happens if I try to do the same thing in a non arc environment?
this adds some strange behavior:
same function in NON-ARC environment:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
[aString release];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:#"Key"];
}
With the previous non-arc implementation, I have a leak only for the block (not for the string)
Changing the implementation to use an autorelease on the mutable string declaring solves the leak!!! I can't understand why, and I'm not sure if it could be related to the main post issue
// version without leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[[NSMutableString alloc] init] autorelease];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:#"Key"];
}
CONCLUSIONS:
After various answers and further investigating, I understood some things:
1- Apple docs says that you must use [^{} copy] when you pass a block to a collection. This is because ARC doesn't add the copy itself. If you don't, the collection (array, dictionary..) sends a retain on a STACK ALLOCATED OBJECT - which does nothing. When the method ends, the block goes out of scope and becomes invalid. You will probably receive a bad access when using it. But note: this is not my case, I'm experiencing a different problem
2- the problem I'm experiencing is different: the block is over-retained (the opposite problem --> the block is still alive even when it shoulnd't be). Why?
I've found this: in my example, I'm using this code
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
this code, under NON-ARC, stores a reference (aBlock) to the literal block. The block is allocated on the stack, so if you NSLog(#"%p", aBlock) -> you will see a stack memory address
But, this is the "strange" (I don't find any indication in Apple docs), if you use the same code under ARC and NSLog aBlock address, you will see that now it's on the HEAP!
For this reason the behavior is different (no bad access)
So, both incorrect but different behavior:
// this causes a leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:#"Key"];
}
// this would cause a bad access trying to retrieve the block from the returned dictionary
- (NSMutableDictionary *)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
return [NSMutableDictionary dictionaryWithObject:^{
NSMutableString __unused *anotherString = aString;
} forKey:#"Key"];
}
3 - about my last test under NON-ARC, I think that the release is in the wrong place. I released the string before adding the block to the dictionary with copy-autorelease.
The block automatically retains the variables referenced inside the block, but the retain message is sent at the copy moment, not at the declaration. So, If I release aString before copying the block, it's retain count goes to 0, then the block sends a retain message to the "zombie" object (with unexpected behavior, it can leak the block, crash, ecc ecc)
See this question for reference iOS 5 Blocks ARC bridged cast; it demonstrates the nightmare that are Blocks and ARC.
Typically, if you assign a block to a variable that lives beyond your current scope, the compiler will be automatically able to copy the block to the heap. This means when you fall out of scope, you still have the block hanging around. Similarly, the same goes with block paramaters. The compiler is aware that it'll need to make a copy of those parameters, and hence does so.
The issue with classes such as NSArray is that they don't usually need to copy an object to keep it correctly; typically they only retain the object. Whereas an object going out of scope is part of the language (hence it copies), keeping it within an object like NSArray is an application level operation. As such, the compiler isn't clever enough yet to determine that the block needs copying (Blocks are standard Obj-C objects after all, it thinks all it needs to do is retain it). In a similar vain, thats why any properties that hold blocks need to specify the copy keyword. The automatic synthesis of the property methods aren't aware a block is being stored, and need to be given a nudge to copy them when being set.
This demonstrates why the whole thing works when you use - copy on your block, you're doing what the compiler should be doing, but is not clever enough to do so...Apple even recommends this technique within its Transitioning to ARC documentation, see the FAQs.
Bootnote: In case you're wondering why I'm on about retaining, even when you're using ARC, is that this is what ARC does under the hood. The memory management model is still the same as before, but the onus is now on the system to manage it for us based on naming and conventions, whereas previously the onus was on the developer to manage their memory correctly. It's just that for blocks, the system isn't able to manage it as fully as it should, and hence the developer needs to step in from time to time.
Blocks begin their life on the stack for performance reasons. If they should live longer than the stack is around, they have to be copied to the heap.
In MRR, you had to do that copying yourself. ARC is doing that automatically for you if you pass a block up the stack (i.e. return it from a method). But if pass a block down the stack (for example, store it in an NSMutableDictionary or NSMutableArray), you have to copy it yourself.
This is documented in Apple's Transitioning to ARC documentation, search for "How do blocks work in ARC" inside that document.
For your Non-ARC examples (as you wrote in your conclusion), the copy of the block should happen before releasing aString, as aString is retained when the block is copied. Otherwise your code will show undefined behavior, it may even crash. Here is some code that demonstrates the problem with Non-ARC:
NSObject *object = [[NSObject alloc] init];
void (^aBlock)() = ^{
NSLog(#"%#", object);
};
[object release];
aBlock(); // undefined behavior. Crashes on my iPhone.
I'm basically implementing a fancier NSURLConnection class that downloads data from a server parses it into a dictionary, and returns an NSDictionary of the data. I'm trying add a completion block option (in addition to a delegate option), but it crashes anytime I try to store that data in another class.
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
I can NSLog that data just fine, and basically do whatever I want with it, but as soon as I try to save it into another variable it crashes with a really obscure message.
EDIT: the crash message is EXC_BAD_ACCESS, but the stack trace is 0x00000000 error: address doesn't contain a section that points to a section in a object file.
I'm calling this function in the init method of a singleton. It DOES let me save the data if I set this in the completion block.
[SingletonClass sharedInstance].contentDictionary = data
But then the app gets stuck forever because sharedInstance hasn't returned yet, so the singleton object is still nil, so sharedInstance in the completion block calls init again, over and over.
EDIT 2: The singleton code looks like this:
+ (SingletonClass*)sharedInstance {
static SingletonClass *instance;
if (!instance) {
instance = [[SingletonClass alloc] init];
}
return instance;
}
- (id)init {
self = [super init];
if (self) {
dataFetcher_ = [[DataFetcher alloc] init];
NSString *testURL = #"..."
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
[SingletonClass sharedInstance].contentDictionary = data;
}];
}
return self;
}
Like I said, this works fine but repeats the initialize code over and over until the app crashes. This only happens the first time I run the app on a device, because I cache the data returned and it doesn't crash once I have the data cached. I would like to be able to just say self.contentDictionary = data, but that crashes.
Specify a variable to be used in the block with the __block directive outside of the block:
__block NSDictionary *contentDictionary_;
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
You're invoking recursion before ever setting the "instance". (which I now see you understand from OP).
In your block, you can use the ivar or an accessor instead of
[SingletonClass sharedInstance].contentDictionary
use:
_contentDictionary = [data copy]; or self.contentDictionary=data;
assuming that the ivar backing the contentDictionary property is _contentDictionary.
It sounds like you tried self.contentDictionary and it failed? I got it to work in a test, with ARC turned, so there may be something about your dataFetcher that is affecting this. In my test dataFetcher just returns a dictionary with a single element.
Turns out the issue was with a bunch of different parts. My URL was empty sometimes, and my data fetcher would just fail immediately and call the completion block. In my completion block I hadn't included any error handling, so if the singleton class hadn't initialized, it would repeat forever. With a real URL this doesn't happen.
I still would like to figure out why it crashes when I try to assign the data to an ivar, though.