Setting a large NSDictionary property on a non-main thread - ios

I have a class called Dictionary, where the init method looks like this:
- (id) init{
self = [super init];
if (self){
[self makeEmojiDictionaries];
}
return self;
}
- (void)makeEmojiDictionaries{
//next line triggers bad_exc_access error
self.englishEmojiAllDictionary = #{#"hi" : #"👋"}; //this is a strong, atomic property of NSDictionary
};
My issue is that the actual emoji dictionary is quite large, and I want to do all the heavy lifting in a non-main thread using GCD. However, whenever I get to the line where I set self.englishEmojiAllDictionary, I always get a bad_access error.
I am using GCD in the most normal way possible:
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
//Do long process activity
Dictionary *dictionary = [[Dictionary alloc] init];
});
Are there particular nuances to GCD or non-main thread work that I am missing? Any help is much appreciated - thank you!
Edit 1:
In case you'd like to try it yourself. I have uploaded a sample project that replicates this exception. My theory is that the NSDictionary I am initialization is simply too large.

I have moved your data from code to a plist file in the form:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>arancia meccanica</key><string>⏰🍊</string>
<key>uno freddo</key><string>🍺</string>
<key>un drink</key><string>🍸</string>
...
<key>bacio</key><string>💋</string>
<key>baci</key><string>💋👐</string>
</dict>
</plist>
(I took your data and used find-replace three times: ", => </string>, then ":#" => </key><string> and #" => <key>).
Then I have loaded the data using:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"dictionary"
ofType:#"plist"]
dictionary = [NSDictionary dictionaryWithContentsOfFile:filePath];
That has fixed the problem. Note that you should never hardcode your data into source code.
The exact reason for the bug was pretty hard to pinpoint. The NSDictionary literal uses method +[NSDictionary dictionaryWithObjects:forKeys:count:].
My assembler knowledge is very poor but I think that before calling this initializer, all the keys & values are put on the stack.
However, there is a difference between the stack size of the main thread and the stack size of the background thread (see Creating Threads in Thread Programming Guide).
That's why the issue can be seen when executing the code on the background thread. If you had more data, the issue would probably appear on the main thread too.
The difference between stack size on main thread and background thread can be also demonstrated by the following simple code:
- (void)makeEmojiDictionaries {
// allocate a lot of data on the stack
// (approximately the number of pointers we need for our dictionary keys & values)
id pointersOnStack[32500 * 2];
NSLog(#"%i", sizeof(pointersOnStack));
}

First of all, I suggest you use a file (plist, txt, xml, ...) to store large data, then read it at runtime, or download it from a remote server.
For your issue, it is because of the limitation of stack size. On iOS, the default stack size for the main thread is 1 MB, and 512 KB for the secondary threads. You can check it out via [NSThread currentThread].stackSize.
Your hardcoded dictionary costs almost 1 MB of stack, that is why your app will be crash on a secondary thread, but be OK on the main thread.
If you want to do this work on a background thread, you must increase the stack size for that thread.
For example:
// NSThread way:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(populateDictionaries) object:nil];
thread.stackSize = 1024*1024;
[thread start];
Or
// POSIX way:
#include <pthread.h>
static void *posixThreadFunc(void *arg) {
Dictionary *emojiDictionary = [[Dictionary alloc] init];
return NULL;
}
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
pthread_t posixThread;
pthread_attr_t stackSizeAttribute;
size_t stackSize = 0;
pthread_attr_init (&stackSizeAttribute);
pthread_attr_getstacksize(&stackSizeAttribute, &stackSize);
if (stackSize < 1024*1024) {
pthread_attr_setstacksize (&stackSizeAttribute, REQUIRED_STACK_SIZE);
}
pthread_create(&posixThread, &stackSizeAttribute, &posixThreadFunc, NULL);
}
#end
Or
// Create mutable dictionary to prevent stack from overflowing
- (void)makeEmojiDictionaries {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[#"arancia meccanica"] = #"⏰🍊";
dict[#"uno freddo"] = #"🍺";
dict[#"un drink"] = #"🍸";
.....
self.englishEmojiAllDictionary = [dict copy];
}
FYI:
Thread Costs
Customizing Process Stack Size

The correct pattern when you need to do something slow is to do the work privately on a background queue, and then dispatch back to the main queue to make the completed work available to the rest of the app. In this case, you don't need to create your own queue. You can use one of the global background queues.
#import "ViewController.h"
#import "Dictionary.h"
#interface ViewController ()
#property (nonatomic, strong, readonly) Dictionary *dictionary;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self updateViews];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
Dictionary *dictionary = [[Dictionary alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
_dictionary = dictionary;
[self updateViews];
});
});
}
- (void)updateViews {
if (self.dictionary == nil) {
// show an activity indicator or something
} else {
// show UI using self.dictionary
}
}
#end
Loading the dictionary from a file is a good idea, and you can do that in the background queue and then dispatch back to the main thread with the loaded dictionary.

Related

How to wait till a function is completely executed when multiple objects call a function in iOS?

I have an architecture where I have to call a local function to display an Image and then in the background need to upload the image to server so that once the Uploading is finished I can remove the local path used for displaying the image.
Functions
DidCmpletePickingImage() , DisplayImageUsingLocalPath() , UploadImageToServer() and RemoveImageFromLocal().
These are the activities. Now i have option to upload multiple images.
This is my current approach. I pick an array of images from and call The function to show them using local path.
for (NSInteger i = 0;i < photos.count ; i ++){
UIImage *img = photos[i];
img = [self imageWithImage:img scaledToWidth:400];
NSData *imageData = UIImageJPEGRepresentation(img, 0.40);
[self showLocally:imageData img:img];
}
After they are showed I start uploading them to server on background thread
-(void) showLocally:(NSData *)imageData img:(UIImage *)img{
// Code for showing it using temp path.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
[self fileUpload:img];
});
}
Then using the background thread File is uploaded to the server using AFNetwork and then after I get a response I call remove local file path.
But when I do this calling on Background all the images are being simultaneously calling fileUpload method and doing it concurrently which is increasing load on server. How can I block a call of the function till a the previous object which called the function is completely executed ?
You may want to have a look at using NSOperationQueue in conjunction with maxConcurrentOperationCount -- limiting the number of operations that can occur all at once.
Here's a small example of what I mean:
#import "ViewController.h"
#interface ViewController ()
#property (strong, nullable) NSOperationQueue *imageUploaderQ;
- (void)_randomOperationWithDelayOutputtingInteger:(NSInteger)intToOutput;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.imageUploaderQ = [[NSOperationQueue alloc] init];
// you can experiment with how many concurrent operations you want here
self.imageUploaderQ.maxConcurrentOperationCount = 3;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
for (NSInteger index = 0; index < 100; index++) {
[self.imageUploaderQ addOperationWithBlock:^{
[self _randomOperationWithDelayOutputtingInteger:index];
}];
}
}
- (void)_randomOperationWithDelayOutputtingInteger:(NSInteger)intToOutput {
// simulating taking some time to upload
// don't ever explicitly call sleep in your actual code
sleep(2);
NSLog(#"Integer output = %li", intToOutput);
}
#end
Here are links to Apple's documentation on
NSOperationQueue
maxConcurrentOperationCount

Objects released too soon while in a background thread

I'm trying to build an array of dictionaries in a background thread while keeping access to the current array until the background operation is done. Here's a simplified version of my code:
#property (nonatomic, strong) NSMutableArray *data;
#property (nonatomic, strong) NSMutableArray *dataInProgress;
- (void)loadData {
self.dataInProgress = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
[self loadDataWorker];
});
}
- (void)loadDataWorker {
for (int i=0; i<10000; i++) {
[self addDataItem];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self loadDataFinish]; // the crash occurs before we get to this point
});
}
- (void)addDataItem {
// first check some previously added data
int currentCount = (int)[self.dataInProgress count];
if (currentCount > 0) {
NSDictionary *lastItem = [self.dataInProgress objectAtIndex:(currentCount - 1)];
NSDictionary *checkValue = [lastItem objectForKey:#"key3"]; // this line crashes with EXC_BAD_ACCESS
}
// then add another item
NSDictionary *dictionaryValue = [NSDictionary dictionaryWithObjectsAndKeys:#"bar", #"foo", nil];
NSDictionary *item = [NSDictionary dictionaryWithObjectsAndKeys:#"value1", #"key1", #"value2", #"key2", dictionaryValue, #"key3", nil];
// as described in UPDATE, I think this is the problem
dispatch_async(dispatch_get_main_queue(), ^{
[dictionaryValue setObject:[self makeCustomView] forKey:#"customView"];
});
[self.dataInProgress addObject:item];
}
- (UIView *)makeCustomView {
return [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
}
- (void)loadDataFinish {
self.data = [NSMutableArray arrayWithArray:self.dataInProgress];
}
This works fine in most cases, but when the dataset is large, I start to get crashes on the line indicated above. The likelihood of a crash is greater with more data or a device with less memory. On an iPhone 6 with 10,000 items, it happens about one in five times. So it looks like when memory gets tight, the dictionaries inside the data array are destroyed before I access them.
If I do everything in the main thread there are no crashes. I originally had this problem with non-ARC code, then I converted my project to ARC and the same problem remains.
Is there a way to ensure the objects added earlier in the build process are retained until I'm done? Or is there a better way to do what I'm doing?
Here's a stack trace:
thread #17: tid = 0x9c586, 0x00000001802d1b90 libobjc.A.dylib`objc_msgSend + 16, queue = 'com.apple.root.background-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0x10)
frame #0: 0x00000001802d1b90 libobjc.A.dylib`objc_msgSend + 16
frame #1: 0x0000000180b42384 CoreFoundation`-[__NSDictionaryM objectForKey:] + 148
frame #2: 0x00000001002edd58 MyApp`-[Table addDataItem](self=0x000000014fd44600, _cmd="addDataItem", id=0x00000001527650d0, section=3, cellData=0x0000000152765050) + 1232 at Table.m:392
frame #4: 0x00000001002eca28 MyApp`__25-[Table loadData]_block_invoke(.block_descriptor=0x000000015229efd0) + 52 at Table.m:265
frame #5: 0x0000000100705a7c libdispatch.dylib`_dispatch_call_block_and_release + 24
frame #6: 0x0000000100705a3c libdispatch.dylib`_dispatch_client_callout + 16
frame #7: 0x0000000100714c9c libdispatch.dylib`_dispatch_root_queue_drain + 2344
frame #8: 0x0000000100714364 libdispatch.dylib`_dispatch_worker_thread3 + 132
frame #9: 0x00000001808bd470 libsystem_pthread.dylib`_pthread_wqthread + 1092
frame #10: 0x00000001808bd020 libsystem_pthread.dylib`start_wqthread + 4
UPDATE
I traced through my full code with the answers below in mind, particularly those about locking while multithreading, and realized that part of the data I'm adding to my data array is a UIView that I'm creating during the build process. Since it's bad to build views in a background thread, and I did see problems when doing that, I'm jumping back to the main thread for makeCustomView. See the lines of code I added above with "UPDATE" in the comment. This must be the problem now; when I skip adding the custom view, I have no more crashes.
I could rework the build workflow so that all the data except the custom views are added on the background thread, then I could make a second pass and add the custom views on the main thread. But is there a way to manage the threads in this workflow? I tried locking with NSLock before and after calling makeCustomView, but that made no difference. I also found an SO answer saying NSLock is basically outdated, so I didn't go further with that.
If I understood you correctly, concurrent accesses to the dataInProgress array causes the problem, because the array is filled in a background thread and used in the main thread. But NSMutableArray is not thread safe. This fits to my intention that the array itself is corrupted.
You could solve that with NSLock to serialize the accesses to the array, but this is akin of outdated and does not fit to the rest of your code, which uses the more modern (and better) GCD.
A. Situation
What you have:
a builder control flow, which has to run in background
a creation of views control flow, which has to run in the main queue (thread). (I'm not completely sure, whether the pure creation of a view has to be done in main thread, but i would do.)
both control flows accesses the same resource (dataInProgress)
B. GCD
With classical thread/lock approach you start async control flows and serialize them with locks, when they access concurrently a shared resource.
With GCD you start control flows concurrently to each other, but serialized for a given shared resource. (Basically, there are more features, more complexity, but this is, what we need here.)
C. Serializing
It is correct to start the builder in a background queue ("thread") to run it without blocking the main thread. Done.
It is correct to switch back to main thread, if you want to do something with UI elements, esp. creating a view.
Since both control flows accesses the same resource, you have to serialize the accesses. You do this by creating a (serial) queue for that resource:
…
#property dispatch_queue_t dataInProgressAccessQ;
…
// In init or whatever
self. dataInProgressAccessQ = dispatch_queue_create("com.yourcompany.dataInProgressQ", NULL);
After doing that, you put every access to the dataInProgress array in that queue. There is a simple example for that:
// [self.dataInProgress addObject:item];
dispatch_async( self.dataInProgressAccessQ,
^{
[self.dataInProgress addObject:item];
});
In this case it is very easy, because you have to switch the queue at the and of the code. If it is in the middle, you have two options:
a) Use the queue similar to a lock. Let's have an example:
// NSInteger currentCount = [self.dataInProgress count]; // Why int?
NSInteger currentCount;
dispatch_sync( self.dataInProgressAccessQ,
^{
currentCount = [self.dataInProgress count];
});
// More code using currentCount
Using dispatch_sync() will let the code execution wait, until accesses from other control flows are finished. (It is like a lock.)
Edit: As with locks, the access is guaranteed to be serialized. But there might be the problem, that another thread removes objects from the array. Let's have a look to such a situation:
// NSInteger currentCount = [self.dataInProgress count]; // Why int?
NSInteger currentCount;
dispatch_sync( self.dataInProgressAccessQ,
^{
currentCount = [self.dataInProgress count];
});
// More code using currentCount
// Imagine that the execution is stopped here
// Imagine that -makeCustomView removes the last item in meanwhile
// Imagine that the execution continues here
// -> currentCount is not valid anymore.
id lastItem = [self.dataInProgress objectAtIndex:currentCount]; // crash: index out of bounds
To prevent this, you really have to isolate your concurrent code. This highly depends on your code. However, in my example:
id lastItem;
dispatch_sync( self.dataInProgressAccessQ,
^{
NSInteger currentCount;
currentCount = [self.dataInProgress count];
lastItem = [self.dataInProgress objectAtIndex:currentCount]; // don't crash: bounds are not changed
});
// Continue with lastItem
As you can imagine, when getting the last item, if can be removed from the array in the very next moment after you read it. Maybe this causes problems of inconsistency in your code. It really depends on your code.
End of edit
b) Maybe you get performance problems, because is works like a lock (synch). If so, you have to analyze your code and extract parts, that can run concurrently again. The pattern looks like this:
// NSInteger currentCount = [self.dataInProgress count]; // Why int?
dispatch_async( self.dataInProgressAccessQ, // <-- becomes asynch
^{
NSInteger currentCount = [self.dataInProgress count];
// go back to the background queue to leave the access queue fastly
dispatch_async( dispatch_get_global_queue(),
^{
// use current count here.
});
});
dispatch_async( self.dataInProgressAccessQ,
^{
// Another task, that can run concurrently to the above
});
What you can do there, is a matter of your concrete code. Maybe it is a help for you, to have your own private builder queue instead of using the global queue.
But this is the basic approach: Move a task into a queue and do not wait, until it is finished, but add code at the end, that completes the task in another control flow.
Instead of
Code
--lock--
var = Access code
--unlock--
More Code using var
it is
Code
asynch {
var Access Code
asynch {
More code using var
}
}
Of course, you have to do the same inside -makeCustomView.
I agree with Phillip Mills. This looks like a thread safety issue around your self.dataInProgress object.
From Apple docs https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html :
Mutable objects are generally not thread-safe. To use mutable objects in a threaded application, the application must synchronize access to them using locks. (For more information, see Atomic Operations). In general, the collection classes (for example, NSMutableArray, NSMutableDictionary) are not thread-safe when mutations are concerned. That is, if one or more threads are changing the same array, problems can occur. You must lock around spots where reads and writes occur to assure thread safety.
If addDataItem is being called from various background threads, you need to lock around reading and writing to self.dataInProgress.
I don't think you need deep copies. If the dictionaries aren't mutable, all you need is for them to not be released...and a copy of the array they're in will do that for you.
What I believe you need is synchronization around any access to self.data. I suggest creating a NSLock object for your class and wrapping each of the following two lines with lock/unlock method calls:
self.data = [NSMutableArray arrayWithArray:self.dataInProgress];
//...
NSDictionary *item = [self.data objectAtIndex:index];
Also, why does self.data need to be mutable? If it doesn't, self.data = [self.dataInProgress copy]; is simpler...and quite possibly more efficient for memory and performance.
The one thing that's worrying me is, what about the caller of getData. It may not know that the self.data array has changed. If the array becomes shorter, you're headed for an "index out of bounds" crash.
It would be good to only call getData when you know the array is going to be stable. (In other words, synchronize the data gets at a higher level.)
I would attempt at passing in a weak reference to self. I bet if you might have a strong retain cycle happening there somewhere. If I remember correctly, __weak doesn't up the retain count, and __block allows you to change the variable
- (void)loadData {
self.dataInProgress = [NSMutableArray array];
__weak __block SelfClassName *weakSelf = self;
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
[weakSelf loadDataWorker];
});
}
- (void)loadDataWorker {
for (int i=0; i<10000; i++) {
[self addDataItem];
}
__weak __block SelfClassName *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf loadDataFinish];
});
}

ios creating a write lock based on a key

I know about using dispatch_barrier_async to lock a given resource, but in my case it isn't a good candidate because I am not modifying a shared data structure, rather a resource on disk and don't want to block the whole queue, rather just a given key as the action could take a long time. I'm not certain how the file system works pertaining to accessing the same file (by name) from several threads simultaneously and couldn't find a clear answer in the documentation, just best practices. I think I would like to lock by "file name" - and am missing a method "tryLock(key)"
Something like:
-(void)readFileAtPath:(NSString *)path completion:(void(^)(NSData *fileData))completion
{
dispatch_async(self.concurrentQueue,^{
// acquire the lock for a given key and block until can acquire
trylock(path);
NSData *fileData = [self dataAtPath:path];
unlock(path);
completion(fileData);
});
}
-(void)writeData:(NSData *)data toPath:(NSString *)path completion:(void(^)())completion
{
dispatch_async(self.concurrentQueue,^{
// if someone is reading the data at 'path' then this should wait - otherwise should write
trylock(path);
[data writeToFile:path atomically:YES];
unlock(path);
completion();
});
}
EDIT:
Does #synchronized do this? Is this a proper use case?
If you want to create "scoped queues", just do it. Create a serial queue for each file, and have them target your concurrent queue. It might look like this:
#interface Foo : NSObject
#property (readonly) dispatch_queue_t concurrentQueue;
#end
#implementation Foo
{
NSMutableDictionary* _fileQueues;
dispatch_queue_t _dictGuard;
}
#synthesize concurrentQueue = _concurrentQueue;
- (instancetype)init
{
if (self = [super init])
{
_concurrentQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
_dictGuard = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
_fileQueues = [[NSMutableDictionary alloc] init];
}
return self;
}
- (dispatch_queue_t)queueForFile: (NSString*)path
{
__block dispatch_queue_t retVal = NULL;
dispatch_sync(_dictGuard, ^{
retVal = _fileQueues[path];
if (!retVal)
{
retVal = dispatch_queue_create(path.UTF8String, DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(retVal, self.concurrentQueue);
_fileQueues[path] = retVal;
}
});
return retVal;
}
- (void)doStuff: (id)stuff withFile: (NSString*)path
{
dispatch_queue_t fileQueue = [self queueForFile: path];
dispatch_async(fileQueue, ^{
DoStuff(stuff, path);
});
}
#end
That said, this queue-per-file thing has a little bit of a "code smell" to it, especially if it's intended to improve I/O performance. Just off the top of my head, for max performance, it feels like it would be better to have a queue per physical device than a queue per file. It's not generally the case that you as the developer know better than the OS/system frameworks how to coordinate file system access, so you will definitely want to measure before vs. after to make sure that this approach is actually improving your performance. Sure, there will be times when you know something that the OS doesn't know, but you might want to look for a way to give the OS that information rather than re-invent the wheel. In terms of performance of reads and writes, if you were to use dispatch_io channels to read and write the files, you would be giving GCD the information it needed to best coordinate your file access.
It also occurs to me that you also might be trying to 'protect the application from itself.' Like, if you were using the disk as a cache, where multiple tasks could be accessing the file at the same time, you might need to protect a reader from another writer. If this is the case, you might want to look for some existing framework that might address the need better than rolling your own. Also, in this use case, you might want to consider managing your scope in-application, and just mmaping one large file, but the cost/benefit of this approach would depend on the granule size of your files.
It would be hard to say more without more context about the application.
To your follow-on question: #synchronized could be used to achieve this, but not without much the same mechanics required as posted above for the GCD way. The reason for this is that #synchronized(foo) synchronizes on foo by identity (pointer equality) and not value equality (i.e. -isEqual:), so NSString and NSURL (the two most obvious objects used to refer to files) having value semantics, makes them poor candidates. An implementation using #synchronized might look like this:
#interface Bar : NSObject
#property (readonly) dispatch_queue_t concurrentQueue;
#end
#implementation Bar
{
NSMutableDictionary* _lockObjects;
dispatch_queue_t _dictGuard;
}
- (instancetype)init
{
if (self = [super init])
{
_concurrentQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
_dictGuard = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
_lockObjects = [[NSMutableDictionary alloc] init];
}
return self;
}
#synthesize concurrentQueue = _concurrentQueue;
- (id)lockForFile: (NSString*)path
{
__block id retVal = NULL;
dispatch_sync(_dictGuard, ^{
retVal = _lockObjects[path];
if (!retVal)
{
retVal = [[NSObject alloc] init];
_lockObjects[path] = retVal;
}
});
return retVal;
}
- (void)syncDoStuff: (id)stuff withFile: (NSString*)path
{
id fileLock = [self lockForFile: path];
#synchronized(fileLock)
{
DoStuff(stuff, path);
}
}
- (void)asyncDoStuff: (id)stuff withFile: (NSString*)path
{
id fileLock = [self lockForFile: path];
dispatch_async(self.concurrentQueue, ^{
#synchronized(fileLock)
{
DoStuff(stuff, path);
}
});
}
#end
You'll see that I made two methods to do stuff, one synchronous and the other asynchronous. #synchronized provides a mutual exclusion mechanism, but is not an asynchronous dispatch mechanism, so if you want parallelism, you still have to get that from GCD (or something else.) The long and short of it is that while you can use #synchronized to do this, it's not a good option these days. It's measurably slower than equivalent GCD mechanisms. About the only time #synchronized is useful these days is as a syntactic shortcut to achieve recursive locking. That said, many smart folks believe that recursive locking is an anti-pattern. (For more details on why, check out this link.) The long and short of it is that #synchronized is not the best way to solve this problem.

Issue updating label in iOS when iterating over array

I am new to iOS programming, and I could not find an answer out there already.
In Xcode 5, I am iterating over an array, and attempting to update a label with the values as they change.
here is the .h file...
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic) NSArray *currentNumber;
#property (strong, nonatomic) IBOutlet UILabel *showLabel;
- (IBAction)start;
#end
here is the main part of the .m file...
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.currentNumber = [NSArray arrayWithObjects:#"1", #"2", #"3", #"4", nil];
}
This is where it gets tricky...
The following works perfectly...
- (IBAction)start {
self.showLabel.text = [NSString stringWithFormat:#"new text"];
}
#end
As does this...
- (IBAction)start {
for (NSString *p in self.currentNumber) {
NSLog(#"%#", p);
sleep(3);
}
}
#end
But when I replace the NSLog with setting the .text attribute, it "fails". The timing still happens, and the label updates with the last item in the array after...
- (IBAction)start {
for (NSString *p in self.currentNumber) {
self.showLabel.text = [NSString stringWithFormat:#"%#", p];
sleep(3);
}
}
#end
And the last bit of weirdness, if I use the NSLog, and try to change the .text attribute before the "for" loop is called, the text change is ignored until AFTER the loop completes...
- (IBAction)start {
self.showLabel.text = [NSString stringWithFormat:#"5"];
for (NSString *p in self.currentNumber) {
NSLog(#"%#", p);
sleep(3);
}
}
#end
What am I missing?
(If you want to see the source files, you can get them at https://github.com/lamarrg/iterate
As you've realized, the UI will only update when the main thread is processing events. In a loop, it won't be.
There's a couple ways around this.
The simplest is to perform your loop in a background thread. There's a wrinkle, though: This will allow the user to continue to interact with your UI. And also, the UI can only be updated from the main thread.
You'll want to dispatch your work to the background, then have the background dispatch your work back to the main thread.
This sounds complicated, and it is. Thankfully, Apple added blocks and Grand Central Dispatch to Objective-C. You can use those to break down the chunks of code and make sure they're executed on the correct thread.
- (IBAction)start {
[self disableMyUI];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_NORMAL, 0), ^{
// this code will be executed "later", probably after start has returned.
// (in all cases, later should be considered "soon but not immediately.")
for (NSString *p in self.currentNumber) {
dispatch_async(dispatch_get_main_queue(),^{
// this code will be executed "later" by the main loop.
// You may have already moved on to the next thing, and even
// dispatched the next UI update.
// Don't worry; the main queue does things in order.
self.showLabel.text = [NSString stringWithFormat:#"%#", p];
});
sleep(3); // do your heavy lifting here, but keep in mind:
// you're on a background thread.
}
dispatch_async(dispatch_get_main_queue,^{
// this occurs "later," but after other all other UI events queued
// to the main queue.
[self enableMyUI];
});
}
// this line of code will run before work is complete
}
You'll have to write disableMyUI and enableMyUI; make sure they disable everything (including the back button if you're using navigation, the tab bar if you're using a tab bar controller, etc).
Another way around this is to use a NSTimer. However, if you do this you're still doing your work on the main thread. It'll work if you can split your work into predictable, small pieces, but you're better off doing it on a background thread.
One thing to keep in mind: Although you're not likely to run into problems while developing, doing heavy work on the main thread will lead to user crashes. On iOS there is a process that watches if applications are responding to events, such as drawing updates. If an application isn't responding to events in a timely fashion, it will be terminated. So living with the lack of UI updates isn't an option for you; you need to only do time consuming operations from background thread.
See also:
Programming with Objective-C: Working with Blocks
If you want to update the label periodically, don't use sleep. If you call it on the main thread you'll be blocking the UI, which is not very desirable.
Use a NSTimer instead, making it fire every N seconds.
Something like this will do:
- (void)startUpdatingLabel {
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(updateLabelWithIndex:) userInfo:#0 repeats:NO];
}
- (void)updateLabel:(NSTimer *)timer {
NSInteger index = [timer.userInfo integerValue];
if (index >= self.currentNumber.count) {
return;
}
self.showLabel.text = [NSString stringWithFormat:#"%#", self.currentNumber[index]];
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(updateLabelWithIndex:) userInfo:#(index+1) repeats:NO];
}
Every time updateLabel: is invoked it schedules a new timer which will call it again in 3 seconds. Each time the index value is increased and passed along.

is it necessary to use dispatch_queue at here

Hopefully it is an appropriate question to ask. My goal is
1.add a controller into an array name 'arrControllers'
2.access and get current controller from 'arrControllers' to do sth with it
and I just wanna make sure the arrControllers should be ready before I access and use it. That is why I am using serial queue and dispatch_sycn like like following.
-(void)viewDidLoad
{
[super viewDidLoad];
firstViewController = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
[self setUpWithView:firstViewController];
}
and in setUpWithView: is
-(void)setUpWithView:(UIViewController*)viewController {
dispatch_queue_t queue;
queue = dispatch_queue_create("my queue", NULL);
containerController = [ContainerViewController sharedContainerController];
// What I am taking about is from now on
dispatch_sync(queue, ^{
[containerController initWithRootViewController:viewController];
});
dispatch_sync(queue, ^{
[containerController setUpView];
});
}
and initWithRootViewController: is
- (void)initWithRootViewController:(UIViewController*)rootViewController {
arrControllers = [[NSMutableArray alloc] init];
arrControllers = [NSMutableArray arrayWithObject:rootViewController];
}
and setUpView is
-(void)setUpView {
/* Process to ADD currentController TO hierarchy */
[self addChildViewController:[arrControllers lastObject]];
............................................................
}
As far as I know the compiler will execute codes line by line, it means by do following
-(void)setUpWithView:(UIViewController*)viewController {
containerController = [ContainerViewController sharedContainerController];
// Not using serial_queue and dispatch_sync
[containerController initWithRootViewController:viewController];// line1
[containerController setUpView]; //line2
}
compiler will execute line2 until it finished the task at line1.
My question is
1. is it necessary to using `serial_queue` and `dispatch_sycn`for this situation.
PS: if you think it is a bad question to ask. please comment what you think about it.
First of all, you should have a look at UIView and UIViewController life cycles, this will tell you when a UIVie or a UIViewController is "ready to be used". Second, you should check when the serial ques and GCD is used.
For the second point I can give you a short summary because it's kind of faster that the first point.
Use CGD and queues (other then main queue) if you want to do computing or other stuff, that don't involve UI changes, on a background thread without freezing the UI.
Use GCD with main queue to switch between a background queue and the main (UI) queue.
All UI operations must be performed on the main thread.
So in your case, you want to create a view controller and store it into an array, so as a suggestion, all the UI related calls of your view controllers should be performed on the UI thread, so no need for GCD or background threads.
In case you are doing some complicated stuffs on your view controller's init methods, just put those complicated stuffs on a GCD block on a separated thread.
In this situation if you do not use dispatch_sync, you will get exactly what you need:
-(void)setUpWithView:(UIViewController*)viewController {
containerController = [ContainerViewController sharedContainerController];
// Not using serial_queue and dispatch_sync
[containerController initWithRootViewController:viewController];// line1
[containerController setUpView]; //line2
}
Line 1 will execute initWithRootViewController:, going deeper and executing lines
arrControllers = [[NSMutableArray alloc] init];
arrControllers = [NSMutableArray arrayWithObject:rootViewController];
then it will return from this function one level above and proceed, moving to line 2 and going down to line
[self addChildViewController:[arrControllers lastObject]];
ALSO. Never write code like this
arrControllers = [[NSMutableArray alloc] init];
arrControllers = [NSMutableArray arrayWithObject:rootViewController];
First line creates an empty NSMutableArray and assigns a pointer to it into arrControllers.
Second line creates another array (autoreleased) and assigns a pointer to it in arrControllers again. So a link to array created in the first line is lost and it is leaked under manual memory management.
If you use manual memory management, do the following:
arrControllers = [[NSMutableArray alloc] init];
[arrControllers addObject:rootViewController];
OR
arrControllers = [[NSMutableArray alloc] initWithObjects:rootViewController,nil];
Under ARC leave only second line.
Hope I understood you correctly. Feel free to ask questions.

Resources