Converting HTML to NSAttributedString - ios

I am working on an ePub reader and it is going pretty well so far. But I need some performance running. Currently the used API to convert HTML strings to NSAttributedString objects is Apple's initialization method
- (instancetype)initWithData:(NSData *)data options:(NSDictionary *)options documentAttributes:(NSDictionary **)dict error:(NSError **)error NS_AVAILABLE_IOS(7_0);
The only issue with this method is that, it can't be done in the background. Only on the UI/Main Thread and it takes so much running memory.
Is there some other solution that can tweak my app up a little to enhance the performance and memory utilization?

What you current code does, if by far the most efficient way.
Load html ONCE and parse it to NSAttributedString's once, save them and render inside a custom view.

Related

How can we decide whether we should use autoreleasepool?

Since Apple's API is not opened source nor it is mentioned in the documentation, when writing in Swift, we have no way, to know whether the returned object is an autorelease objective-c object.
Hence, it becomes unclear when we should use autoreleasepool
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-1041876
If you write a loop that creates many temporary objects.
You may use an autorelease pool block inside the loop to dispose of
those objects before the next iteration. Using an autorelease pool
block in the loop helps to reduce the maximum memory footprint of the
application.
Without autoreleasepool
for ... {
FileManager.default.copyItem
CGImageSourceCreateWithURL
CGImageSourceCopyPropertiesAtIndex
CGImageSourceCreateThumbnailAtIndex
CGImageDestinationCreateWithURL
CGImageDestinationFinalize
}
With autoreleasepool
for ... {
autoreleasepool {
FileManager.default.copyItem
CGImageSourceCreateWithURL
CGImageSourceCopyPropertiesAtIndex
CGImageSourceCreateThumbnailAtIndex
CGImageDestinationCreateWithURL
CGImageDestinationFinalize
}
}
I try to run an intensive loop over the above 2 code for comparison purpose.
I found no significant difference in their memory usage pattern, based on XCode memory report.
I was wondering, what are some good guideline/ thought process, to decide whether we should apply autoreleasepool throughout our code?
I have such concern, as recently I saw autoreleasepool is required in code which involves FileHandle.read - https://stackoverflow.com/a/42935601/72437
Use FileManager to copy item doesn't have a huge payload. And Image I/O you're using will save a lot of memory during the I/O process. In addition, apple's image api will have caches for the same file.
That's why your code won't have a significant difference. Because you didn't make any memory payload.
You could try another way to validate the usage of autoreleasepool. And I can assure that it will have a tremendous difference.
Use for-loop(10000 times) to generate random strings (longer is better), and use each string to transform an UTF-8 data in each loop. Then see different memory growth from the with or without autoreleasepool case.
Try it out.

Getting CDATA from XML using SMXMLDocument in iOS

I'm coding an iOS app and I need to parse some values from an XML file.
I'm parsing the XML using SMXMLDocument and everything works smooth but some tags are returning null value. I noted that those tags are CDATA and after some research I found some methods to workaround this thing. The problem is all those methods refer to NSXMLParser and I can't understand how to do this using SMXMLDocument.
The code in my app is very similar (you could say identical) to the code at this page.
Can you please help me?
Thank you
Apparently SMXMLDocument does not conform to all of the NSXMLParser delegate functions when it parses your document. You are going to have to manually modify SMXMLDocument.m and add the function:
- (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock
{
}
Inside that function should look very similar to the foundcharacters function, you just need to take the NSData that you get and convert it into a string. Beware of encoding though, CData data is not always UTF8.

NSMutableDictionary - EXC BAD ACCESS - simultaneous read/write

I was hoping for some help with my app.
I have a set up where multiple threads access a shared NSMutableDictionary owned by a singleton class. The threads access the dictionary in response to downloading JSON and processing it. The singleton class is basically preventing duplication of some downloaded objects which have an unique id number.
ie.
//NSURLConnection calls:
[[Singleton sharedInstance] processJSON:data];
#interface Singleton
+ (Singleton) sharedInstance;
#property (nonatomic, strong) NSMutableDictionary *store;
#end
#implementation
-(void) processJSON:(NSData*)data {
...
someCustomClass *potentialEntry = [someCustomClass parse:data];
...
if(![self entryExists:potentialEntry.stringId])
[self addNewEntry:potentialEntry];
...
}
-(void) entryExists:(NSString*)objectId {
if(self.store[objectId])
return true;
else return false;
}
-(void) addEntry:(someCustomClass *object) {
self.store[object.stringId] = object;
}
There can be as many as 5-10 threads at a time calling processJSON at once.
Not immediately but after a few minutes of running (quicker on the iPhone than on the simulator) I get the dreaded EXC BAD ACCESS.
I don't confess to know how NSMutableDictionary works but I would guess that there's some kind of hash table in the background which needs to be updated when assigning objects and read when accessing objects. Therefore, if threads were to instantaneously read/write to a dictionary, this error could occur - may be because an object has moved in memory?
Im hoping that someone with more knowledge on the subject could enlighten me!
As for solutions I was thinking of the singleton class having an NSOperationQueue with a maximum concurrent operation number of 1 and then using operationWithBlock: whenever I want to access the NSDictionary. The only problem is that it makes calling processJSON an asynchronous function and I can't return the created object straight away; I'd have to use a block and that would be a bit messier. Is there any way of using #synchronize? Would that work well?
I'd draw your attention to the Synchronization section of the iOS rendition of the Threading Programming Guide that Hot Licks pointed you to. One of those locking mechanisms, or the use of a dedicated serial queue, can help you achieve thread safety.
Your intuition regarding the serial operation queue is promising, though frequently people will use a serial dispatch queue for this (e.g., so you can call dispatch_sync from any queue to your dictionary's serial queue), achieving both a controlled mechanism for interacting with it as well as synchronous operations. Or, even better, you can use a custom concurrent queue (not a global queue), and perform reads via dispatch_sync and perform writes via dispatch_barrier_async, achieving an efficient reader/writer scheme (as discussed in WWDC 2011 - Mastering GCD or WWDC 2012 - Asynchronous Design Patterns).
The Eliminating Lock-Based Code section of the Concurrency Programming Guide outlines some of the rationale for using a serial queue for synchronization versus the traditional locking techniques.
The Grand Central Dispatch (GCD) Reference and the dispatch queue discussion in the Concurrency Programming Guide should provide quite a bit of information.
the simplest solution is to just put all off the code that accesses the dict in an #synchronized block.
serial operation queues are great, but sounds like overkill to me for this, as you aren't guarding a whole ecosystem of data, just one structure..

UIDocument & NSFileWrapper - NSFastEnumerationMutationHandler while changing file wrapper during a save

I have a UIDocument based app that uses NSFileWrappers to store data. The 'master' file wrapper contains many additional directory file wrappers, each of which represents a different page of the document.
Whenever I make a change to the document while the UIDocument is saving (in writeContents:andAttributes:safelyToURL:forSaveOperation:error:), the app crashes. Here is the stack trace:
It seems clear that I am modifying the same instance of file wrapper that the UIDocument is enumerating over in the background. Indeed, I checked that when returning a snapshot of the data model in contentsForType:error:, the returned sub file wrappers point to the same objects as the ones currently residing (and being edited) in the data model, and not copies.
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
if (!_fileWrapper) {
[self setupEmptyDocument];
}
return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]];
}
This is the sanctioned approach to implementing this method (according to WWDC 2012 Session 218 - Using iCloud with UIDocument).
So I suppose the question is: How can this approach be thread safe?
Is the situation somehow different when the master file wrapper's fileWrappers are themselves directory file wrappers? If the sanctioned approach is wrong, how should it be done?
If you are calling any of the writeContents:... methods, you shouldn't be. You should be calling saveToURL:forSaveOperation:completionHandler: instead. The writeContents:... methods are meant for advanced subclassing.
UIDocument uses two threads - the main thread and the "UIDocument File Access" thread (which , if you subclass more of UIDocument, you can do things in via performAsynchronousFileAccessUsingBlock:).
Thread safety with UIDocument is like anything in Objective C - only let the thread owning an object modify it. If the object you want to change is being read, queue it to be changed after the write is complete. Perhaps change a different object owned by your UIDocument subclass and pull them into a new NSFileWrapper in contentsForType:error:. Pass a copy of the fileWrappers NSDictionary.
NSFileWrapper actually loads the entire document into memory. The NSFileWrapper is actually created in the "UIDocument File Access" thread in the readFromURL:error: method, which is then passed to the loadFromContents:ofType:error: method. If you have a large document this can take a while.
When saving you typically want to let UIDocument decide when to do this, and let it know something has changed via the updateChangeCount: method (param is UIDocumentChangeDone). When you want to save something right now you want to use the saveToURL:forSaveOperation:completionHandler: method.
One other thing to note is UIDocument implements the NSFilePresenter protocol, which defines methods for NSFileCoordinator to use. The UIDocument only coordinates writing on the root document, not the subfiles. You might think that coordinating subfiles inside the document might help, but the crash you're getting is related to mutating a dictionary while it's being iterated, so that wont help. You only need to worry about writing your own NSFilePresenter if you (1) wanted to get notifications of file changes, or (2) another object or app was reading/writing to the same file. What UIDocument already does will work fine. You do want to, however, use NSFileCoordinator when moving/deleting whole documents.
I know this is an ancient thread, but I ran into this problem recently, and to help future travelers: If you have subdirectories in your main file wrapper, you need to copy those NSFileWrappers as well (in addition to copying the root NSFileWrapper as above).
Otherwise, a crash can occur when the UIDocument background thread enumerates over them while saving and while simultaneous modifications occur on the main thread. It's not clear, but this might be the problem the OP ran into.
Another tip is you need to also copy over the subdirectory's NSFileWrapper's filename, fileAttributes (and possibly preferredFilename) so that incremental saving works.
HTH.

Secure dealloc of an ObjC object on an iOS device

What happens to an object when it's dealloced? Is all the memory nulled out, or do traces still remain?
If I understand it correctly, an app's memory is saved to flash storage if it resigns active. Assume a resourceful hacker that is able to read out this memory. Will he theoretically sometimes be able to read out the contents of a dealloced NSString if that memory hasn't been overwritten with something?
Don't store secure data in Objective C data types. They are opaque data types, and could be making and/or leaving lots of copies of your data in memory every time you try to even clear some portion.
Added: The same appears to be true about Swift data types, including structs, arrays and strings. They are opaque, thus who knows how many copies of data might be left around in DRAM.
Use non-opaque plain C data types (array of chars, etc.), which you can bzero as soon as you are finished using them, and whenever the app resigns being active. You could also obfuscate the array elements to make string searching through memory dumps a little more difficult.
Even with a jailbroken iDevice, that would be unlikely, as the location for the memory is probably so deep. If you are really concerned about that, here is a solution, if you are not worried about overhead of NSMutableString (dealloc of your class):
-(void) dealloc
{
for (int i = 0; i < [myString length]; i++)
{
[myString replaceCharactersInRange:NSMakeRange(i, 1) withString:#"*"];
}
[myString release]; // or dealloc
// clean up rest
[super dealloc]; // dont forget this :)
}
So you also raised the issue about writing off to Flash. If your app goes into background, it will not necessarily release the objects - and give you the opportunity to erase them (as discussed in the other answer).
IF you're really concerned about this - I'd disable Fast App Switching on your app to make sure this never happens, in addition to implementing some sort of object over-erase code on dealloc, as discussed above.

Resources