[Cocoa + iPhone] Unraveling Apple’s Pagecurl

First off, I encourage anyone that’s unfamiliar of this topic to read through this short but very sweet blog post on the subject (and to take a quick look at his sample code). We’ll be picking up where Steven left off.

In any case, to summarize the current situation; there exists a private and undocumented API in the iPhone SDK which Apple uses to great effect in their iBook application. The way to interface with this private API has been discovered and even fairly well documented. Using the private API is pretty straightforward but for one small problem: if you use the private API in your application then Apple will reject your app. For whatever non-specified reason (probably to keep potential iBook competitors in check), Apple does not want to open up their private API to developers or to play nice with developers who bend the rules and use the private API.

So our goal is clear. If Apple isn’t going to play nice and open the API up to developers, then perhaps we can do some digging to figure out how Apple’s implementation actually works and create our own implementation that does the same thing. It’s a pretty standard exercise in reverse-engineering, really. The core of the private page-curl API is used like so:

		filter = [[CAFilter filterWithType:kCAFilterPageCurl] retain];
		[filter setDefaults];
		[filter setValue:[NSNumber numberWithFloat:((NSUInteger)fingerDelta)/100.0] forKey:@"inputTime"];
		
		CGFloat _angleRad = angleBetweenCGPoints(currentPos, lastPos);
		CGFloat _angle = _angleRad*180/M_PI ; // I'm far more comfortable with using degrees ;-)
					
		if (_angle < 180 && _angle > 120) {// here I've limited the results to the right-hand side of the paper. I'm sure there's a better way to do this
			if (fingerVector.y > 0)
				[filter setValue:[NSNumber numberWithFloat:_angleRad] forKey:@"inputAngle"];
			else
				[filter setValue:[NSNumber numberWithFloat:-_angleRad] forKey:@"inputAngle"];

			_internalView.layer.filters = [NSArray arrayWithObject:filter];
		}

This is an excerpt straight out of Steven Troughton-Smith’s example. The example includes additional code related to tracking touch positions and interpolating the angle and distance between them, but this is really the core of the private API right here. All of the heavy-lifting is handled by the CAFilter class (private), which has a type of ‘kCAFilterPageCurl‘ (private constant, just the string @”pageCurl”, other filter types also exist), and which takes just a small number of input parameters (‘inputTime‘ and ‘inputAngle‘) and then works its magic behind the scenes.

So given that CAFilter seems to be doing pretty much all the work, it would follow that by constructing our own class that exposes the same interface as CAFilter we can supplant the private-API class with one of our own making (ah, the joys of reflection and weak-typing), thus interfacing with the underlying platform without breaking any of the rules. But what exactly is a CAFilter? Is it as onerous as a UIView with its hundreds of methods and properties? Does it extend another obscure private-API class that will also need to be reverse-engineered? Well thanks to the ‘printObject:toDepth:‘ routine discussed in a previous post we can see that a CAFilter is exactly:

@interface CAFilter : NSObject {
	unsigned int _type;
	NSString* _name;
	unsigned int _flags;
	void* _attr;
	void* _cache;
}

//Constructors
- (id) initWithType:  (NSString*) arg0;
- (id) initWithName:  (NSString*) arg0;

//NSCoding
- (NSObject*) initWithCoder:  (NSCoder*) arg0;
- (void) encodeWithCoder:  (NSCoder*) arg0;

//NSKeyValueCoding
- (void) setValue: (id) arg0 forKey: (NSString*) arg1;
- (id) valueForKey:  (NSString*) arg0;

//NSCopying and NSMutableCopying
- (NSObject*) mutableCopyWithZone:  (NSZone*) arg0;
- (NSObject*) copyWithZone:  (NSZone*) arg0;

//interface methods
- (void) setDefaults;
- (bool) isEnabled;
- (struct UnknownAtomic*) CA_copyRenderValue;

//garbage collection (doesn't need to be declared here)
- (void) dealloc;

//property accessors (don't need to be declared here)
- (bool) enabled;
- (void) setEnabled:  (bool) arg0;
- (bool) cachesInputImage;
- (void) setCachesInputImage:  (bool) arg0;
- (NSString*) name;
- (void) setName:  (NSString*) arg0;
- (NSObject*) type;

//properties
@property(nonatomic, readonly) NSString* type;
@property(nonatomic, retain) NSString* name;
@property(nonatomic) bool enabled;
@property(nonatomic) bool cachesInputImage;

@end

Nineteen methods and a handful of fields. Not bad, not bad at all, particularly when many of the methods are simply implementing various publicly-documented protocols such as NSCoding, NSCopying, and NSKeyValueCoding. As an added bonus, the superclass of CAFilter is NSObject, so the problem has now been reduced to the implementation of a single unknown class (which may still be a Herculean task, but at least now there are clearly-defined boundaries).

But the above code includes some methods that do not need to be part of the publicly declared interface. Let’s clean it up, rename it so that it doesn’t conflict with the existing private-API class, and add the proper definition of the ‘…Atomic‘ struct:

#import <Foundation/Foundation.h>

struct RenderValueResult { 
	int (**x1)(); 
	struct MyAtomic { 
		struct { 
			NSInteger x; 
		} _v; 
	} x2; 
} *_filterResult;

@interface MyCAFilter : NSObject<NSCoding, NSCopying, NSMutableCopying> {
	unsigned int _type;
	NSString* _name;
	unsigned int _flags;
	void* _attr;
	void* _cache;
}

//Constructors
- (id) initWithType:  (NSString*) arg0;
- (id) initWithName:  (NSString*) arg0;

//NSKeyValueCoding
- (void) setValue: (id) arg0 forKey: (NSString*) arg1;
- (id) valueForKey:  (NSString*) arg0;

//interface methods
- (void) setDefaults;
- (bool) isEnabled;
- (struct RenderValueResult*) CA_copyRenderValue;

//properties
@property(nonatomic, readonly) NSString* type;
@property(nonatomic, retain) NSString* name;
@property(nonatomic) bool enabled;
@property(nonatomic) bool cachesInputImage;

@end

Looking better already. That ‘RenderValueResult‘ struct will prove to be a nasty one, but more on that later.

Now that we know the interface, and before we go flying off randomly trying to replicate functionality that we still don’t fully understand, let’s take a simpler step. Let’s create a simple class that exposes the CAFilter interface, wraps an actual CAFilter instance, and logs each method call, parameters, and result, like so:

//MyCAFilter.h (modified to include 'delegate' field)
#import <Foundation/Foundation.h>

struct RenderValueResult {
    int (**x1)();
    struct MyAtomic {
        struct {
            NSInteger x;
        } _v;
    } x2;
} *_renderValueResult;

@class CAFilter;  //private-API

@interface MyCAFilter : NSObject<NSCoding, NSCopying, NSMutableCopying> {
    unsigned int _type;
    NSString* _name;
    unsigned int _flags;
    void* _attr;
    void* _cache;
    
    CAFilter* delegate;  //private-API
}

//Constructors
- (id) initWithType:  (NSString*) arg0;
- (id) initWithName:  (NSString*) arg0;

//NSKeyValueCoding
- (void) setValue: (id) arg0 forKey: (NSString*) arg1;
- (id) valueForKey:  (NSString*) arg0;

//interface methods
- (void) setDefaults;
- (bool) isEnabled;
- (struct RenderValueResult*) CA_copyRenderValue;

//properties
@property(nonatomic, readonly) NSString* type;
@property(nonatomic, retain) NSString* name;
@property(nonatomic) bool enabled;
@property(nonatomic) bool cachesInputImage;

@end

//MyCAFilter.m
#import "MyCAFilter.h"

@implementation MyCAFilter

@dynamic name, cachesInputImage, type, enabled;

- (id) initWithType: (NSString*) theType {
	NSLog(@"initWithType: type='%@'", theType);
    if ((self = [super init])) {
        delegate = [[CAFilter alloc] initWithType: theType];    //TODO:  remove delegate
    }
	return self;
}

- (id) initWithName: (NSString*) theName {
	NSLog(@"initWithName: name='%@'", theName);
    if ((self = [super init])) {
        delegate = [[CAFilter alloc] initWithName: theName];    //TODO:  remove delegate
    }
	return self;
}

- (id) initWithCoder: (NSCoder*) coder {
	NSLog(@"initWithCoder: coder=%@", coder);
	if ((self = [super init])) {
        delegate = [[CAFilter alloc] initWithCoder: coder];     //TODO:  remove delegate
    }
    return self;
}

- (void) setDefaults {
	NSLog(@"setDefaults");
	[delegate setDefaults];  //TODO:  remove delegate
}

- (void) encodeWithCoder: (NSCoder*) encoder {
	NSLog(@"encodeWithCoder:  coder=%@", encoder);
	[delegate encodeWithCoder:encoder];  //TODO:  remove delegate
}

- (id) mutableCopyWithZone: (NSZone*) zone {
    id result = [delegate mutableCopyWithZone:zone];   //TODO:  remove delegate
	NSLog(@"mutableCopyWithZone: zone=%@; result=%@", zone);
	return result;
}

- (id) copyWithZone: (NSZone*) zone {
    id result = [delegate copyWithZone:zone];  //TODO:  remove delegate
	NSLog(@"copyWithZone:  zone=%@; result=%@", zone, result);
	return result;
}

- (void) setValue: (id) value forKey: (NSString*) key {
	NSLog(@"setValue:  key=%@, value=%@", key, value);
	[delegate setValue:value forKey:key];	//TODO:  remove delegate
}

- (id) valueForKey:(id) key {
    id result = [delegate valueForKey:key];  //TODO:  remove delegate
	NSLog(@"valueForKey:  key=%@; result=%@", key, result);
	return result;
}

- (bool) isEnabled {
    bool result = [delegate isEnabled]; //TODO:  remove delegate
	NSLog(@"isEnabled; result=%d", result);
	return result; 
}

- (void) dealloc {
	NSLog(@"dealloc");
	[delegate release];		//TODO:  remove delegate
	[super dealloc];
}

- (bool) enabled {
    bool result = [delegate enabled];		//TODO:  remove delegate
	NSLog(@"enabled; result=%d", result);
	return result;
}
- (void) setEnabled: (bool) val {
	NSLog(@"setEnabled: value=%d", val);
	[delegate setEnabled:val];		//TODO:  remove delegate
}

- (void) setCachesInputImage: (bool) val {
	NSLog(@"setCachesInputImage: val=%d", val);
	[delegate setCachesInputImage:val];		//TODO:  remove delegate
}
- (bool) cachesInputImage {
    bool result = [delegate cachesInputImage];		//TODO:  remove delegate
	NSLog(@"cachesInputImage; result=%d", result);
	return result;
}

- (id) name {
    id result = [delegate name];		//TODO:  remove delegate
	NSLog(@"name; result=%@", result);
	return result;
}

- (void) setName: (NSString*) name {
	NSLog(@"setName: name='%@'", name);
	[delegate setName: name];		//TODO:  remove delegate
}

- (NSString*) type {
    NSString* result = [delegate type];		//TODO:  remove delegate
	NSLog(@"type; result=%@", result);
	return result;
}

- (struct RenderValueResult*) CA_copyRenderValue {
	struct RenderValueResult* result = [delegate CA_copyRenderValue];	//TODO:  remove delegate
    NSLog(@"CA_copyRenderValue; result=0x%08X, result.x1=0x%08X, result.x2=%d", result, result->x1, result->x2);
	return result;
}

@end

Using this class is a simple matter of editing ‘ReadPdfView.m‘ (working with Steven’s example project) to replace both instances of ‘[[CAFilter filterWithType:kCAFilterPageCurl] retain];‘ with ‘[[MyCAFilter alloc] initWithType: @”pageCurl”];‘. Note that it is also now safe to remove the ‘@class CAFilter;‘ and ‘extern NSString *kCAFilterPageCurl;‘ lines from this class.

Now obviously this still won’t fly with Apple, as it continues to use the private-API CAFilter class. But consider what we’ve accomplished; we’ve now inserted our own custom object into the rendering pipeline, and the core-animation framework is none-the-wiser. If we can now figure out how to get the same results without internally using the CAFilter instance, we will have cracked the page-curl animation.

Moving along, if we run this code through a complete page-curl animation, we see a very simple pattern emerge:

2011-02-09 00:59:11.694 PageCurlDemo[5501:207] initWithType: type='pageCurl'
2011-02-09 00:59:11.707 PageCurlDemo[5501:207] setDefaults
2011-02-09 00:59:11.711 PageCurlDemo[5501:207] setValue:  key=inputTime, value=0
2011-02-09 00:59:11.716 PageCurlDemo[5501:207] setValue:  key=inputAngle, value=-3.141593
2011-02-09 00:59:11.734 PageCurlDemo[5501:207] CA_copyRenderValue; result=0x04AF09E0, result.x1=0x00D96448, result.x2=65538
2011-02-09 00:59:11.767 PageCurlDemo[5501:207] valueForKey:  key=inputTime; result=0
2011-02-09 00:59:11.808 PageCurlDemo[5501:207] dealloc

This sequence of calls is repeated a number of times as the animation runs. None of the other methods that exist on the object are called. Every single one of these calls with the exception of ‘CA_copyRenderValue‘ originates in the example code; so now our task is constrained to the implementation of a single unknown method. But what a method it is. ‘CA_copyRenderValue‘ returns an instance of a fairly obtuse structure that has the following definition:

struct RenderValueResult { 
	int (**x1)(); 
	struct MyAtomic { 
		struct { 
			NSInteger x; 
		} _v; 
	} x2; 
} *_renderValueResult;

I’ve changed the name of the structure and its nested structure to avoid any issues with name collisions, but since the order and type of fields matches the private-API version there should be no issues in terms of compatibility between the different declared versions. At runtime this structure should be indistinguishable from the private-API version for all practical purposes (barring reflection, which could detect the difference in the naming).

Anyways, this structure contains two fields; ‘x1‘, which is a pointer to an array of functions that return integers, and ‘x2‘, which is simply an integer. Interestingly enough, the memory address of the returned data structure never differs by more than 256 bytes between calls, nor do the absolute values of ‘x1‘ or ‘x2‘ change. And here is where things start to get a bit murky. I’m going to forget about ‘x2‘ for a moment, as it is a simple type and its value never seems to vary. ‘x1‘ is not so easy.

By inspecting the value of ‘x1‘, I’ve determined that it references no more than 11 distinct functions (the 12th element in the result returned by CAFilter is NULL, and I assume that the NULL indicates the probable end of the meaningful data in the array). Moreover, the addresses of the functions returned do not appear to vary, even between independent runs of the application. Which implies to me that perhaps the result being returned is simply referencing some pre-existing object in memory.

But this is all speculation on my part. What’s needed here is more digging, so let’s create our own callback functions and see what we can discover about the way this data structure is being used by the core-animation framework. We can do that by adding the following to the MyCAFilter implementation:

int (**originalFuncs)();  //cache for the actual function pointers

//copy/paste this 11 times, incrementing both '0's each time...it's inelegant but it works
int callback0( id firstParam, ... ) {
	int myIndex = 0;
	NSLog(@"callback%d invoked, stack=%@", myIndex, [NSThread callStackSymbols]);
	
	va_list args;
	va_start(args, firstParam);
	int originalResult = originalFuncs[myIndex](firstParam, args);  //pass any params we recieved on to the original function; not sure if this is the correct way to do this
	
	NSLog(@"callback%d will return result:  %d", myIndex, originalResult);
	
	return originalResult;
}

And then by revising ‘CA_copyRenderValue‘ like so:

void* myCallbacks[11] = {&callback0, &callback1, &callback2, &callback3, &callback4, &callback5, &callback6, &callback7, &callback8, &callback9, &callback10};

- (struct RenderValueResult*) CA_copyRenderValue {
	struct RenderValueResult* result = [delegate CA_copyRenderValue];	//TODO:  remove delegate
	struct RenderValueResult* myResult = malloc(sizeof(struct RenderValueResult));
	myResult->x2 = result->x2;  //just copy the integer component of the result; 65538?
	
	//see how many functions there are before we encounter a NULL
	int funcIndex = 0;
	while (result->x1[funcIndex] != NULL) {
		funcIndex++;
		if (funcIndex >= 11) {
			NSLog(@"CA_copyRenderValue;  NULL sigil not found, assuming max number of functions is 11!");
			break;
		}
	}
	NSLog(@"CA_copyRenderValue;  found %d functions in delegate's result...", funcIndex);
	
	myResult->x1 = malloc(sizeof(int*) * (funcIndex + 1));		//we return this to the CA framework
	originalFuncs = malloc(sizeof(int*) * (funcIndex));			//we keep references to the original functions to use in our callbacks
	for (int index = 0; index < funcIndex ; index++) {
		originalFuncs[index] = result->x1[index];		//cache the original function pointers
		myResult->x1[index] = myCallbacks[index];     //put dummy callbacks into the result
	}
	myResult->x1[funcIndex] = NULL;
	
    NSLog(@"CA_copyRenderValue; result=0x%08X, result.x1=0x%08X, result.x2=%d", result, result->x1, result->x2);
	for (int index = 0; index < funcIndex; index++) {
		NSLog(@"CA_copyRenderValue; result->x1[%d]=0x%08X", index, result->x1[index]);
	}
	return myResult;
}

Now if we run the application, we get the following output:

2011-02-09 23:50:25.508 PageCurlDemo[10453:207] callback3 invoked, stack=(
	0   PageCurlDemo                        0x00006c2b callback3 + 50
	1   QuartzCore                          0x00d63347 CACopyRenderArray + 188
	2   QuartzCore                          0x00cc373e -[CALayer(CALayerPrivate) _copyRenderLayer:layerFlags:commitFlags:] + 1667
	3   QuartzCore                          0x00cc30b4 CALayerCopyRenderLayer + 55
	4   QuartzCore                          0x00cc11d2 _ZN2CA7Context12commit_layerEP8_CALayerjjPv + 122
	5   QuartzCore                          0x00cc10e1 CALayerCommitIfNeeded + 323
	6   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	7   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	8   QuartzCore                          0x00caf7b9 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 1395
	9   QuartzCore                          0x00caf0d0 _ZN2CA11Transaction6commitEv + 292
	10  QuartzCore                          0x00cdf7d5 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 99
	11  CoreFoundation                      0x00ef8fbb __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 27
	12  CoreFoundation                      0x00e8e0e7 __CFRunLoopDoObservers + 295
	13  CoreFoundation                      0x00e56bd7 __CFRunLoopRun + 1575
	14  CoreFoundation                      0x00e56240 CFRunLoopRunSpecific + 208
	15  CoreFoundation                      0x00e56161 CFRunLoopRunInMode + 97
	16  GraphicsServices                    0x0184c268 GSEventRunModal + 217
	17  GraphicsServices                    0x0184c32d GSEventRun + 115
	18  UIKit                               0x002d242e UIApplicationMain + 1160
	19  PageCurlDemo                        0x00002904 main + 102
	20  PageCurlDemo                        0x00002895 start + 53
)
2011-02-09 23:50:25.514 PageCurlDemo[10453:207] callback3 will return result:  9
2011-02-09 23:50:25.519 PageCurlDemo[10453:207] callback3 invoked, stack=(
	0   PageCurlDemo                        0x00006c2b callback3 + 50
	1   QuartzCore                          0x00ce5d12 _ZN2CA6Render7Encoder13encode_objectEPKNS0_6ObjectE + 30
	2   QuartzCore                          0x00ce670d _ZNK2CA6Render5Array6encodeEPNS0_7EncoderE + 113
	3   QuartzCore                          0x00ce5f24 _ZNK2CA6Render5Layer6encodeEPNS0_7EncoderE + 458
	4   QuartzCore                          0x00ce5cdb _ZN2CA6Render17encode_set_objectEPNS0_7EncoderEmjPNS0_6ObjectEj + 91
	5   QuartzCore                          0x00cc1215 _ZN2CA7Context12commit_layerEP8_CALayerjjPv + 189
	6   QuartzCore                          0x00cc10e1 CALayerCommitIfNeeded + 323
	7   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	8   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	9   QuartzCore                          0x00caf7b9 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 1395
	10  QuartzCore                          0x00caf0d0 _ZN2CA11Transaction6commitEv + 292
	11  QuartzCore                          0x00cdf7d5 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 99
	12  CoreFoundation                      0x00ef8fbb __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 27
	13  CoreFoundation                      0x00e8e0e7 __CFRunLoopDoObservers + 295
	14  CoreFoundation                      0x00e56bd7 __CFRunLoopRun + 1575
	15  CoreFoundation                      0x00e56240 CFRunLoopRunSpecific + 208
	16  CoreFoundation                      0x00e56161 CFRunLoopRunInMode + 97
	17  GraphicsServices                    0x0184c268 GSEventRunModal + 217
	18  GraphicsServices                    0x0184c32d GSEventRun + 115
	19  UIKit                               0x002d242e UIApplicationMain + 1160
	20  PageCurlDemo                        0x00002904 main + 102
	21  PageCurlDemo                        0x00002895 start + 53
)
2011-02-09 23:50:25.527 PageCurlDemo[10453:207] callback3 will return result:  9
2011-02-09 23:50:25.536 PageCurlDemo[10453:207] callback3 invoked, stack=(
	0   PageCurlDemo                        0x00006c2b callback3 + 50
	1   QuartzCore                          0x00ce5d34 _ZN2CA6Render7Encoder13encode_objectEPKNS0_6ObjectE + 64
	2   QuartzCore                          0x00ce670d _ZNK2CA6Render5Array6encodeEPNS0_7EncoderE + 113
	3   QuartzCore                          0x00ce5f24 _ZNK2CA6Render5Layer6encodeEPNS0_7EncoderE + 458
	4   QuartzCore                          0x00ce5cdb _ZN2CA6Render17encode_set_objectEPNS0_7EncoderEmjPNS0_6ObjectEj + 91
	5   QuartzCore                          0x00cc1215 _ZN2CA7Context12commit_layerEP8_CALayerjjPv + 189
	6   QuartzCore                          0x00cc10e1 CALayerCommitIfNeeded + 323
	7   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	8   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	9   QuartzCore                          0x00caf7b9 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 1395
	10  QuartzCore                          0x00caf0d0 _ZN2CA11Transaction6commitEv + 292
	11  QuartzCore                          0x00cdf7d5 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 99
	12  CoreFoundation                      0x00ef8fbb __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 27
	13  CoreFoundation                      0x00e8e0e7 __CFRunLoopDoObservers + 295
	14  CoreFoundation                      0x00e56bd7 __CFRunLoopRun + 1575
	15  CoreFoundation                      0x00e56240 CFRunLoopRunSpecific + 208
	16  CoreFoundation                      0x00e56161 CFRunLoopRunInMode + 97
	17  GraphicsServices                    0x0184c268 GSEventRunModal + 217
	18  GraphicsServices                    0x0184c32d GSEventRun + 115
	19  UIKit                               0x002d242e UIApplicationMain + 1160
	20  PageCurlDemo                        0x00002904 main + 102
	21  PageCurlDemo                        0x00002895 start + 53
)
2011-02-09 23:50:25.566 PageCurlDemo[10453:207] callback3 will return result:  9
2011-02-09 23:50:25.578 PageCurlDemo[10453:207] callback4 invoked, stack=(
	0   PageCurlDemo                        0x00006cca callback4 + 50
	1   QuartzCore                          0x00ce670d _ZNK2CA6Render5Array6encodeEPNS0_7EncoderE + 113
	2   QuartzCore                          0x00ce5f24 _ZNK2CA6Render5Layer6encodeEPNS0_7EncoderE + 458
	3   QuartzCore                          0x00ce5cdb _ZN2CA6Render17encode_set_objectEPNS0_7EncoderEmjPNS0_6ObjectEj + 91
	4   QuartzCore                          0x00cc1215 _ZN2CA7Context12commit_layerEP8_CALayerjjPv + 189
	5   QuartzCore                          0x00cc10e1 CALayerCommitIfNeeded + 323
	6   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	7   QuartzCore                          0x00cc1069 CALayerCommitIfNeeded + 203
	8   QuartzCore                          0x00caf7b9 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 1395
	9   QuartzCore                          0x00caf0d0 _ZN2CA11Transaction6commitEv + 292
	10  QuartzCore                          0x00cdf7d5 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 99
	11  CoreFoundation                      0x00ef8fbb __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 27
	12  CoreFoundation                      0x00e8e0e7 __CFRunLoopDoObservers + 295
	13  CoreFoundation                      0x00e56bd7 __CFRunLoopRun + 1575
	14  CoreFoundation                      0x00e56240 CFRunLoopRunSpecific + 208
	15  CoreFoundation                      0x00e56161 CFRunLoopRunInMode + 97
	16  GraphicsServices                    0x0184c268 GSEventRunModal + 217
	17  GraphicsServices                    0x0184c32d GSEventRun + 115
	18  UIKit                               0x002d242e UIApplicationMain + 1160
	19  PageCurlDemo                        0x00002904 main + 102
	20  PageCurlDemo                        0x00002895 start + 53
)

Followed by a crash. Something causes the attempt to invoke the fourth callback function to die; probably related to the questionable way that I’m passing arguments to it. Not knowing what the proper signature for the callback functions is, I’ve made them all accept a variable number of ‘id’ parameters, which should cover most cases. However the best way to pass these arguments on to the original implementation is not clear.

For what it’s worth, I tried a number of alternate ways to invoke this function, all of which resulted in a crash. Skipping the invocation and just returning a hard-coded value from my callback prevented the crash, but didn’t result in any more callbacks being invoked. Presumably core-animation noticed that my hard-coded return value didn’t match what it was expecting, and decided to abort the rest of its rendering transaction.

And unfortunately, here is where I need to leave this interesting little diversion for now, unless/until I can figure out a way to move it forward. If you have any suggestions please don’t hesitate to let me know. I feel like I’m getting close to the answer here, but it’s still quite a ways away.

Update

If anyone is interested, you can download a complete XCode project containing the latest revision of my code. If you decide to take a crack at solving this problem, I wish you luck, and please do consider reporting back with your results.

This entry was posted in coding, objective-c and tagged , , , . Bookmark the permalink.

10 Responses to [Cocoa + iPhone] Unraveling Apple’s Pagecurl

  1. nikhil dhamsaniya says:

    hi is that fine to use this api in my application ?
    does it cause the rejection of the application on the appstore if i used the content of that

    • aroth says:

      Using this API directly in your application risks rejection from Apple. That said, there are some people who do it and are able to get away with it. But right now, the only safe way to get a page-curl effect in your application is to implement your own page-curl algorithm.

  2. smokeless says:

    Thanks for that awesome posting.

  3. bt says:

    I bookmarked this link. Thank you for good job!

  4. bmalbuck says:

    aroth, Thank you for this very informative post. Nice work with introspection on CAFilter. I was hoping to see this public in latest iOS [redacted] but no such luck. If you have made any progress on these 11 functions, please post the results. I will see if I can make progress myself.

    • aroth says:

      Thanks, I haven’t had any time to continue exploring this at the moment. Though if you manage to uncover anything, I’d certainly be interested in hearing about it.

  5. Rithirun says:

    anyone have complete the code sample for this tutorial, Please share to me.

    Please to my email : joomla.khmer@gmail.com
    Thanks

  6. Sumit Sharma says:

    Above Code is still Crashing.Is there any updated code

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>