"

Archive for the ‘Dev Technique’ Category

Undo and Redo

Wednesday, June 17, 2009 @ 09:06 AM
posted by Geppy

This topic is quite difficult to digest, but after few exercises you should be able to manage it.
When you work with an application, it usually happens that, after applying some edits to your data, you realize that the final result is not what you were expecting. So, you wish to go back and remove what you have done. I believe you are familiar with the Undo and Redo items in the Edit menu of your Mac. But, how can you implement these functionalities in your application?
Cocoa and Cocoa Touch use an intelligent approach to manage this. To understand it, I have to introduce the concept of NSInvocation. An NSInvocation is an object containing all the elements of an Objective-C message: a target, a selector, some arguments, and the return value. Each of these elements can be set directly and the return value is set automatically when the NSInvocation object is dispatched. NSInvocation works together with the NSUndoManager to make the Undo/Redo mechanism possible.
When you apply an edit onto your data and you want to be able to undo that edit, you need to prepare (coding) the inverse operation corresponding to that particular edit. Then, you pack this inverse message (including selector, arguments and return value) in an NSInvocation object and forward it to the NSUndoManager. This will store this object in a stack and wait for the user action.
Indeed, there are two stacks: the Undo Stack and the Redo Stack and their scope is very similar. The management of the two stacks is assigned to NSUndoManager and you don’t need to take care of it. When you undo some operation, NSUndoManager removes from the Undo Stack that operation and adds it to the Redo Stack. If you redo that operation, this time the NSUndoManager removes it from the Redo Stack and adds it to the Undo Stack.
Let’s look at the details and how to code it. Suppose you are writing a simple calculator with a plus and minus operations:

1
2
- (NSNumber *)add:(NSNumber *)aNumber to:(NSNumber *)anotherNumber;
- (NSNumber *)subtract:(NSNumber *)aNumber from:(NSNumber *)anotherNumber;

You want to be able to undo and redo each of these operations after you apply them to your data. When you start your application, an NSUndoManager object is created for you with no costs. In reality, if you are writing a document-based application in Cocoa, an NSUndoManager is created automatically. In the other cases (iPhone included), you have to create it explicitly.
You usually need only an NSUndoManager for each application. To register the above two methods to the NSUndoManager mechanism, you do the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (NSNumber *)add:(NSNumber *)aNumber to:(NSNumber *)anotherNumber
{
    NSUndoManager *undo = [self undoManager];
    [[undo prepareWithInvocation] subtract:aNumber from:anothernumber];

    float F1 = [aNumber floatValue];
    float F2 = [anotherNumber floatValue];
    F1 += F2;
    return [NSNumber numberWithFloat];
}

- (NSNumber *)subtract:(NSNumber *)aNumber from:(NSNumber *)anotherNumber
{
    NSUndoManager *undo = [self undoManager];
    [[undo prepareWithInvocation] add:aNumber to:anotherNumber];

    float F1 = [aNumber floatValue];
    float F2 = [anotherNumber floatValue];
    F1 -= F2;
    return [NSNumber numberWithFloat];
}

The undoManger method (lines 3 and 4) is defined in the NSDocument, NSResponder, NSManagedObjectContext and WebView classes. Please, refer to the documentation of these classes for details.
The trick is at the lines 4 and 15. There, you register to the Undo/Redo mechanism the opposite operation to your current edit.
You can also change the label of the Undo and Redo operation using the

1
- (void)setActionName:(NSString *)actionName;

So, you can add this line after line 4:

1
[undo setActionName:@"Add"];

Once implemented, you will have in the edit menu “Undo Add”.

Post to Twitter Post to Facebook

Solving Leaks With Instruments

Thursday, May 14, 2009 @ 04:05 PM
posted by Geppy

Memory management is something really critical, because it can reduce the application stability. A very important topic in software development is the quality control (QA). It is the moment in which the developer should check how the application behaves in term of resource and processor usage.

You already know that when you allocate an object with the sequence alloc-init, you need to release the object, otherwise the object keeps part of the memory for ever. If this happens very often in your code, the result can be a disaster: the application crashes or starts to become very slow and then, crashes. The Garbage Collector is a possible solution to this problem, but you cannot always use it (Cocoa Touch does not have a Garbage Collector).

Apple provides us with a large set of tools, which can help us to dynamically analyze our application. Instruments offers you this tools. Even if Apple suggests to use always Instruments, I have to say that the available documentation is quite poor in term of operations. This quite forces many developers to skip this very important phase of the development. You can see this in the poor quality of many applications sold in the AppStore. If one of your iPhone app crashes, it is quite probable that the developer did not use Instruments.

In this post, I will show you how to use Instruments to solve memory leaks. In future post, we will see how to use other analysis tools.

You can launch Instruments in different ways. For sure, the most convenient way is to use the Xcode menu while you are working on your project. After building the project, choose Start with Performance Tool -> Leaks from the menu Run.

Instruments launches. Immediately after, your application is executed and Instruments starts to record data.

If you launch Instruments double-clicking its icon, then you need to add the app you want to analyze (Default Target) and press the Start button.

While Instruments is recording the data, use your application. When you feel you have enough information, stop the recording. If you notice well, Instruments loaded two tools: Leaks and ObjectAlloc. ObjectAlloc is useful to track each object during its life from its creation until its destruction. On the other hand side, Leaks is useful to find leaking objects.

In the upper part of the window (see next picture), there is a small triangular cursor that moves during the recording session. Using the Inspector Range, you can filter our the results of your analysis in a defined time interval. This can help you to localize easily what happens in a specific period of time in your application.

Now, select View->Detail. You get a new view with the information about each object category created during the recording session. The table view shows the object type (Category), the number of objects assigned to the memory and still alive (# Net), the overall number of objects (#Overall), the quantity of used memory in both cases (Net Bytes and Overall Bytes) and finally a proportion between the number of object still in memory and the total number of assigned object (#Allocations (Net/Overall)).

If you select any of this objects, you see a small arrow on the right side of the object category.

Click on this arrow. Instruments show you every object of this category created during the recording session.

If you click on any of them, you get the details related to that particular object.

The Event Type columns shows a list of Malloc, Free, CFRelease, CFRetain, etc. and the method that generated that event (Responsible Caller). If you select one of the events and open the Extended Detail from the View menu, Instruments provides you with more information about the selected object. The Extended Detail view is very important, because it contains direct links to the chuck of code generating the memory leak. To make it more useful, click and hold on the small gear icon and choose all the detail as shown here.



The ObjectAlloc tool is very useful to improve how your application responds to your touches. Indeed, it is very common to create objects o load data into the memory when we do not need them. If this happens with a very large number of objects or a considerably large data set at the same time, your app starts to slow down and does not respond quickly to the user interactions as it should do. Using ObjectAlloc you can find where this problem occurs and improve your code.

Let’s look at the Memory Leak detail. The Leaks tool shows the leaking objects. If you get a pulse similar to what is shown in Figure 2, then, you have a memory leak and you need to solve it.

Click on the Leaks tool. In this way, in the lower view you get the list of the leaking objects (Leaked Blocks). Now, your analysis begins.

Carefully, you need to analyze each object of the list, one by one. Don’t be scared. Even if your list is very large, most of the time it is sufficient to solve only few of the leaks in the list, due to the object dependency. The column Self% can be useful. Start always your analysis with the object showing a big Self%.

Once you select an object, Instruments shows you the life of that object (a sequence of events showing the object creation, the retain and release sequence, and so on). If your object is leaking, the reason is that a retain or a release is missed in the chain. Look at the column and find where this happens. Then, open the Extended Detail view (View -> Extended Detail). Its General section shows the Retain Count of the analyzed object.

The Stack Trace of the Detailed View is similar to what is shown here.



In this example, you can see that a vector (CFArray) has been created (Malloc) and its Retain Count is 1. This events has been generated by the viewDidLoad method. If you double click on this (the line #2 of the Stack Trace), Instruments calls Xcode at the code line generating this problem. Next picture shows this code. As you can see, the array anArray has been created with alloc-init, but never released. This generates the Leak. To fix it, it is enough to release this object before the end of the external for-loop.


Keep coding.

++Geppy

Post to Twitter Post to Facebook