<?xml version="1.0" encoding="UTF-8"?>
<!-- generator="wordpress/2.3.3" -->
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	>

<channel>
	<title>Vacuous Virtuoso &#187; Apple</title>
	<link>http://lipidity.com</link>
	<description>Despotic Development</description>
	<pubDate>Wed, 02 Apr 2008 14:18:15 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.3.3</generator>
	<language>en</language>
			<item>
		<title>More CoreGraphics Hackery</title>
		<link>http://lipidity.com/apple/cocoa/cgsinternal/</link>
		<comments>http://lipidity.com/apple/cocoa/cgsinternal/#comments</comments>
		<pubDate>Sat, 12 Jan 2008 04:21:03 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Cocoa]]></category>

		<category><![CDATA[Apple]]></category>

		<category><![CDATA[CoreGraphics]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Review]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/cocoa/cgsinternal/</guid>
		<description><![CDATA[Alacatia Labs, Inc. bring you the most thorough investigation of undocumented CoreGraphics functions to date, with CGSInternal.

There&#8217;s a subversion repository as well if you&#8217;re keen to stay up to date.
]]></description>
			<content:encoded><![CDATA[<p><a href="http://alacatialabs.com/">Alacatia Labs, Inc.</a> bring you the most thorough investigation of undocumented <a href="http://lipidity.com/tag/core-graphics+animation">CoreGraphics</a> functions to date, with <a href="http://alacatialabs.com/toys/cgsinternal/">CGSInternal</a>.</p>

<p>There&#8217;s a <a href="http://svn.alacatialabs.com/CGSInternal/trunk/">subversion repository</a> as well if you&#8217;re keen to stay up to date.</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/cocoa/cgsinternal/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Cleaning up Quicksilver</title>
		<link>http://lipidity.com/apple/cleaning-up-quicksilver/</link>
		<comments>http://lipidity.com/apple/cleaning-up-quicksilver/#comments</comments>
		<pubDate>Fri, 07 Dec 2007 08:38:31 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Quicksilver]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Source Code]]></category>

		<category><![CDATA[Xcode]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/cleaning-up-quicksilver/</guid>
		<description><![CDATA[Given the Quicksilver source code is now out in the open, I&#8217;m trying to learn from it while at the same time cleaning it up. Here&#8217;s what I&#8217;ve done so far.



Bug fixes

A lot of features were buried in the source, available only by setting some preferences to allow them. Mostly it was due to buggy [...]]]></description>
			<content:encoded><![CDATA[<p>Given the <a href="http://code.google.com/p/blacktree-alchemy/source">Quicksilver source code</a> is now out in the open, I&#8217;m trying to learn from it while at the same time cleaning it up. Here&#8217;s what I&#8217;ve done so far.</p>

<!--more-->

<h3>Bug fixes</h3>

<p>A lot of features were buried in the source, available only by setting some preferences to allow them. Mostly it was due to buggy implementation or pending improvements. I managed to resurrect a few of these nifty ones and fix a few old bugs that have annoyed me for ages.</p>

<h4>Color handling</h4>

<p>Color handling was commented out in the Quicksilver source; probably because it resulted in errors. A simple change in the source allowed correct handling of color values, and the code for drawing the icon was already there. I added functionality to allow dropping colors from the Quicksilver object pane into color wells and the like - which is what makes this useful.</p>

<p class="centre"><img src="http://images.lipidity.com/QS-Color-handling-20071207-185619.jpg" alt="Handling color in the object pane is now possible" title="A color stored in the object pane" /></p>

<h4>Uninstalled plugins</h4>

<p>Added a plugin set to the plugins preference pane to list plugins that you haven&#8217;t yet installed or downloaded. Not sure why this was commented out; I didn&#8217;t have to add any new code here.</p>

<p class="centre"><img src="http://images.lipidity.com/QS-Plug-ins-20071207-185845.jpg" alt="Uninstalled Plugins can now be listed" title="Listing uninstalled plugins in the preference pane" /></p>

<h4>Triggers</h4>

<p>There was a very annoying bug in the triggers preference pane that haunted me from the day I started using Quicksilver. Switching to the iTunes trigger set resulted in errors, and often froze or messed up Quicksilver. Finally, it&#8217;s fixed, and we can see and use these triggers.</p>

<p class="centre"><img src="http://images.lipidity.com/QS-Triggers-20071207-190435.jpg" alt="Quicksilver triggers preference pane has been fixed" /></p>

<p>Speaking of plugins; I think I heard mention of a &#8220;plugin check bug&#8221; fixed somewhere, but it wasn&#8217;t working for me so I&#8217;ve also fixed the issue where the plugin list would be blank until you hit refresh.</p>

<h4>Miscellaneous bug fixes</h4>

<p>Other things like crashing when you try to relaunch with the &#8220;Customize&#8221; preference pane open, and with the source, hundreds of warnings when trying to compile, errors in the shell scripts when working in a path with non-alphanumeric characters, etc. have also been fixed.</p>

<p class='centre'><img src="http://images.lipidity.com/QS-Customize-20071207-190804.jpg" alt="Fixed a crash with the Quicksilver customize pane" /></p>

<h3>Tidy up</h3>

<blockquote>
  <p>[I wonder] how long it&#8217;ll take people to notice the Frankensteinian nature of the source and respond  - appropriately - with pitchforks. <cite><a href="http://twitter.com/alcor/statuses/388774142" rel="nofollow">Alcor</a></cite></p>
</blockquote>

<p>I&#8217;ll have to agree with Alcor - to say the source is a mess would be an understatement. There are hundreds of compiler warnings, unused or unimplemented features, classes, objects, code, structures, nibs, etc. Hundreds. I&#8217;ve tried to remove as much of the redundant or test code as possible and I&#8217;ve yet to reach the end. Mind you, I&#8217;ve been doing this for hours a day for the past week. The formatting and indentation is almost as bad. I&#8217;m cleaning that up too, with some good old regular expressions and elbow grease. As I go, I&#8217;m also trying to improve the logic where I see potential for improvement. Already the application is feeling more responsive but I want to also reduce the memory footprint.</p>

<p>Why go to all this trouble? Mainly, to</p>

<ul>
<li>Make the source more readable; make it easier for people to understand what&#8217;s going on.</li>
<li>Improve the performance of the application in both speed and memory usage.</li>
<li>Fix existing bugs and add new features.</li>
</ul>

<hr />

<p>In the meantime, I&#8217;ll continue my clean up of the Quicksilver source and when it&#8217;s lookin&#8217; fine perhaps post it up (the QS license doesn&#8217;t really say much about the source, so I might have to ask Alcor first. Perhaps he&#8217;d like to add it to the subversion repository after reviewing?).</p>

<p>Lastly, let me just say, I&#8217;ve learnt a lot from looking at the source code to Quicksilver. There are some very neat tricks in there, and great little snippets that could be very useful elsewhere. So thank you Mr. Jitkoff; thank you for writing the application, and thank you for the releasing the source code. It is highly appreciated.</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/cleaning-up-quicksilver/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Another guide to Quicksilver</title>
		<link>http://lipidity.com/apple/another-guide-to-quicksilver/</link>
		<comments>http://lipidity.com/apple/another-guide-to-quicksilver/#comments</comments>
		<pubDate>Tue, 04 Dec 2007 10:49:13 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Quicksilver]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Review]]></category>

		<category><![CDATA[Source Code]]></category>

		<guid isPermaLink="false">http://lipidity.com/general/another-guide-to-quicksilver/</guid>
		<description><![CDATA[Joe Dunn is doing a nice series of articles on Quicksilver. He&#8217;s covered triggers and scripts so far with more on the way.

One thing I disagree with, though, is the excessive use of the &#8220;do shell script&#8221; AppleScript. I like the &#8220;Run Command in Shell&#8221; action just fine. But if you want an icon, writing [...]]]></description>
			<content:encoded><![CDATA[<p>Joe Dunn is doing a nice series of articles on Quicksilver. He&#8217;s covered <a href="http://jwdunn.com/2007/11/24/how-i-use-quicksilver-i-triggers/">triggers</a> and <a href="http://jwdunn.com/2007/11/29/how-i-use-quicksilver-ii-scripts/">scripts</a> so far with more on the way.</p>

<p>One thing I disagree with, though, is the excessive use of the &#8220;do shell script&#8221; AppleScript. I like the &#8220;Run Command in Shell&#8221; action just fine. But if you want an icon, writing an Obj-C wrapper isn&#8217;t too much work. Especially since you have <a href="http://pub.lipidity.com/CocoaShell.tar.bz2">this source code</a>.</p>

<p>I&#8217;ve also got a bit more on Quicksilver coming up shortly.</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/another-guide-to-quicksilver/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Modifier flags during app launch</title>
		<link>http://lipidity.com/apple/modifier-flags-launch/</link>
		<comments>http://lipidity.com/apple/modifier-flags-launch/#comments</comments>
		<pubDate>Tue, 27 Nov 2007 10:45:57 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Cocoa]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Source Code]]></category>

		<category><![CDATA[Tutorial]]></category>

		<category><![CDATA[Xcode]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/modifier-flags-launch/</guid>
		<description><![CDATA[Unfortunately, [[NSApp currentEvent] modiferFlags] can&#8217;t be used to determine if any modifier keys were held down during launch. To accomplish this, it seems one must delve into the world of Carbon.



The GetCurrentEventKeyModifiers() call works on Mac OS X v10.2 and later through which one can check for shiftKey, cmdKey, optionKey and controlKey.

I use the something [...]]]></description>
			<content:encoded><![CDATA[<p>Unfortunately, <code>[[NSApp currentEvent] modiferFlags]</code> can&#8217;t be used to determine if any modifier keys were held down during launch. To accomplish this, it seems one must delve into the world of Carbon.</p>

<!--more-->

<p>The <code>GetCurrentEventKeyModifiers()</code> call works on Mac OS X v10.2 and later through which one can check for <code>shiftKey</code>, <code>cmdKey</code>, <code>optionKey</code> and <code>controlKey</code>.</p>

<p>I use the something like the following to check if the shift key is pressed during launch for <a href="http://lipidity.com/software/zippit/">my compression app</a>.</p>

<pre><code>#import &lt;Cocoa/Cocoa.h&gt;
#import &lt;Carbon/Carbon.h&gt;

int main(int argc, char *argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    if((GetCurrentEventKeyModifiers() &amp; shiftKey)!=0) {
        // Change something useful
        NSLog(@"Shift key pressed during launch");
    }
    [[NSApplication sharedApplication] run];
    [pool release];
    return 0;
}
</code></pre>

<p>In the highly unlikely event that you&#8217;ve just got a call to <code>NSApplicationMain</code> in your <code>main</code> function (joking), you can just test <code>(GetCurrentEventKeyModifiers() &amp; shiftKey)!=0</code> in the <code>-applicationWillFinishLaunching:</code> method in your NSApplication delegate.</p>

<p>Any new APIs in Leopard to achieve this or any easier way?</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/modifier-flags-launch/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Quicksilver Smoke</title>
		<link>http://lipidity.com/apple/quicksilver-smoke/</link>
		<comments>http://lipidity.com/apple/quicksilver-smoke/#comments</comments>
		<pubDate>Fri, 23 Nov 2007 03:53:18 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Quicksilver]]></category>

		<category><![CDATA[Animation]]></category>

		<category><![CDATA[Download]]></category>

		<category><![CDATA[GUI]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[plugin]]></category>

		<category><![CDATA[smoke]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/quicksilver-smoke/</guid>
		<description><![CDATA[I wrote this a while back as part of the Quicksilver Internal Commands tutorial. The plugin hooks onto the current Quicksilver interface so you can watch it go up in flames.





If you&#8217;re interested in this sort of thing, you can download the Quicksilver Smoke Actions plugin here.
]]></description>
			<content:encoded><![CDATA[<p>I wrote this a while back as part of the <a href="http://lipidity.com/apple/quicksilver-internal-commands/">Quicksilver Internal Commands</a> tutorial. The plugin hooks onto the current Quicksilver interface so you can watch it go up in flames.</p>

<!--more-->

<p class='centre'><a rel="lightbox" href="http://dev.lipidity.com/wp-content/uploads/2007/06/primer-smoking.jpg" title="Quicksilver Primer interface smoking"><img src="http://dev.lipidity.com/wp-content/uploads/2007/06/primer-smoking-thumb.jpg" alt="[Primer interface smoking]" title="Quicksilver Primer interface smoking" width="426" height="343" class='feature' /></a></p>

<p>If you&#8217;re interested in this sort of thing, you can download the Quicksilver Smoke Actions plugin <a href="http://lipidity.com/plugins/quicksilver/smoke">here</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/quicksilver-smoke/feed/</wfw:commentRss>
		</item>
		<item>
		<title>CTGradient code bloat</title>
		<link>http://lipidity.com/apple/ctgradient-code-bloat/</link>
		<comments>http://lipidity.com/apple/ctgradient-code-bloat/#comments</comments>
		<pubDate>Tue, 20 Nov 2007 21:05:12 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Cocoa]]></category>

		<category><![CDATA[CoreGraphics]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Review]]></category>

		<category><![CDATA[Source Code]]></category>

		<category><![CDATA[Tutorial]]></category>

		<category><![CDATA[Xcode]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/cgradient-code-bloat/</guid>
		<description><![CDATA[While getting rid of extraneous junk in an application package is easy using Trimmit, the only way to prevent &#34;code bloat&#34; (and accompanying excess RAM and CPU usage) is through good programming practices. Where most developers fall short is in poor optimization of borrowed code. Let&#8217;s take CTGradient as an example as it&#8217;s well known [...]]]></description>
			<content:encoded><![CDATA[<p>While getting rid of extraneous junk in an application package is easy using <a href="http://lipidity.com/software/trimmit/">Trimmit</a>, the only way to prevent &quot;code bloat&quot; (and accompanying excess RAM and CPU usage) is through good programming practices. Where most developers fall short is in poor optimization of borrowed code. Let&#8217;s take CTGradient as an example as it&#8217;s well known and used (or more accurately, abused) in dozens of applications.</p>

<!--more-->

<p>CTGradient contains an incredible diversity of built-in gradients, gradient styles, and methods for creating fancy rainbows, radial gradients, linear gradients, aqua gradients, and a number of other interesting class and instance methods. It allows you to dynamically alter gradients by adding or removing colors, changing the level of transparency, filling NSRects or NSBezierPaths, rotating the gradient, etc. For demonstration purposes, all these features are excellent. For production, this is a nightmare.</p>

<p><strong><a href="http://pub.lipidity.com/CTGradient/CTGradient.m">CTGradient.m</a> weighs in at over 1300 lines of code.</strong></p>

<p>Ignoring Mr. Weider&#8217;s unique style of formatting, take a look through the code. If you&#8217;re putting this class in an application, you can immediately remove over a thousand lines. Just like that. But if you take some time to understand what&#8217;s going on, you can optimize this thing till it runs like a Ferrari.</p>

<p>You&#8217;ll notice <a href="http://lipidity.com/software/trimmit/">Trimmit</a> uses a gradient background for it&#8217;s window. Let&#8217;s cut down CTGradient until it matches the level of optimization of Trimmit&#8217;s gradient code.</p>

<h2>Clear the junk</h2>

<p>Firstly, let&#8217;s remove the methods we know for sure we won&#8217;t need. Remove the following methods completely from both the interface and the implementation (scroll down for more):</p>

<pre style='height:340px;overflow:scroll'><code>// We don't need the preset gradients

+ (id)dividerGradient;
+ (id)statusBarGradient;
+ (id)aquaSelectedGradient;
+ (id)aquaNormalGradient;
+ (id)aquaPressedGradient;
+ (id)unifiedSelectedGradient;
+ (id)unifiedNormalGradient;
+ (id)unifiedPressedGradient;
+ (id)unifiedDarkGradient;
+ (id)sourceListSelectedGradient;
+ (id)sourceListUnselectedGradient;
+ (id)rainbowGradient;
+ (id)hydrogenSpectrumGradient;

// We won't make any drastic modifications to the gradient once created

- (CTGradient *)gradientWithAlphaComponent:(float)alpha;
- (CTGradient *)addColorStop:(NSColor *)color atPosition:(float)position;
- (CTGradient *)removeColorStopAtIndex:(unsigned)index;
- (CTGradient *)removeColorStopAtPosition:(float)position;
- (CTGradientBlendingMode)blendingMode;
- (NSColor *)colorStopAtIndex:(unsigned)index;
- (NSColor *)colorAtPosition:(float)position;

// Now we don't need to conform to the NSCopying and NSCoding protocols

- (id)copyWithZone:(NSZone *)zone;
- (void)encodeWithCoder:(NSCoder *)coder;
- (id)initWithCoder:(NSCoder *)coder;

// We only need to fill a simple NSRect

- (void)drawSwatchInRect:(NSRect)rect;
- (void)radialFillRect:(NSRect)rect;
- (void)fillBezierPath:(NSBezierPath *)path angle:(float)angle;
- (void)radialFillBezierPath:(NSBezierPath *)path;

// Remove the entire (Private) category;
// but leave -addElement:

- (void)_commonInit;
// move setBlendingMode code into init
- (void)setBlendingMode:(CTGradientBlendingMode)mode;
- (CTGradientElement *)elementAtIndex:(unsigned)index;
- (CTGradientElement)removeElementAtIndex:(unsigned)index;
- (CTGradientElement)removeElementAtPosition:(float)position;
</code></pre>

<p>The following C functions are now unused:</p>

<pre><code>static void chromaticEvaluation(void *info, const float *in, float *out);
static void inverseChromaticEvaluation(void *info, const float *in, float *out);
static void transformRGB_HSV(float *components);
static void transformHSV_RGB(float *components);
static void resolveHSV(float *color1, float *color2);
</code></pre>

<p>And also remove the following from the header:</p>

<pre><code>typedef enum  _CTBlendingMode
    {
    CTLinearBlendingMode,
    CTChromaticBlendingMode,
    CTInverseChromaticBlendingMode
    } CTGradientBlendingMode;
</code></pre>

<p>Remove the <code>&lt;protocols&gt;</code> and unnecessary instance variables so the interface looks like this:</p>

<pre><code>@interface CTGradient : NSObject {
    CTGradientElement* elementList;
    CGFunctionRef gradientFunction;
}
+ (id)gradientWithBeginningColor:(NSColor *)begin endingColor:(NSColor *)end;
- (void)fillRect:(NSRect)rect angle:(float)angle;
- (void)addElement:(CTGradientElement *)newElement;
@end
</code></pre>

<p>Before we go further, we&#8217;ll need to take a detour and fix the remaining code so that it compiles. That&#8217;s easy enough - just remove any references to code we&#8217;ve removed.</p>

<p>If you&#8217;ve been following along, your CTGradient should now look like <a href="http://pub.lipidity.com/CTGradient/CTGradient2.m">this</a> (also made it readable!).</p>

<p>In a few short minutes, we&#8217;re down from more than <strong>1300</strong> to a little over <strong>200 lines</strong>.</p>

<p><big>It gets better.</big></p>

<h2>Optimize it</h2>

<p>First stop, <code>fillRect:angle:</code>. Since the gradient for Trimmit&#8217;s window only runs vertically (angle 90), we can straight away take out the <var>angle</var> argument, and also cut the entire if / else structure with the angle down to the two lines that are under the <code>if(angle == 90)</code>. Ah, much better.</p>

<p>Now things start getting a teeny bit more complex - and fun.</p>

<hr />

<p>The CTGradient code is written so that one can have many different color stops. The CTGradientElement struct has a nextElement which points to the next CTGradientElement and the position of the current element is stored under the float <var>position</var>. However, we need just two - the starting and ending shades.</p>

<p>Let&#8217;s take a look at cutting the multiple elements down to just two.</p>

<p>The <code>addElement:</code> method has a lot of looping to insert the element at the correct place. However, since we know that we only ever need two, we can cut it down to this:</p>

<pre><code>- (void)addElement:(CTGradientElement *)newElement {
    if(elementList) {
        // elementList exists, add second element
        elementList-&gt;nextElement = malloc(sizeof(CTGradientElement));
        *(elementList-&gt;nextElement) = *newElement;
        elementList-&gt;nextElement-&gt;nextElement = 0;
    } else {
        // no elements - add first element
        elementList = malloc(sizeof(CTGradientElement));
        *elementList = *newElement;
        elementList-&gt;nextElement = 0;
    }
}
</code></pre>

<p>Similarly with <code>dealloc</code>,</p>

<pre><code>- (void)dealloc {
    CGFunctionRelease(gradientFunction);
    free(elementList-&gt;nextElement);
    free(elementList);
    [super dealloc];
}
</code></pre>

<p>While we&#8217;re at it, let&#8217;s clean up <code>init</code> as well:</p>

<pre><code>- (id)init {
    if(self = [super init]) {
        CGFunctionCallbacks evaluationCallbackInfo = {0 , &amp;linearEvaluation, 0};
        static const float input_value_range[2] = { 0, 1 };
        static const float output_value_ranges[8] = { 0, 1, 0, 1, 0, 1, 0, 1 };
        gradientFunction = CGFunctionCreate(&amp;elementList, 1, input_value_range, 4, output_value_ranges, &amp;evaluationCallbackInfo);
    }
    return self;
}
</code></pre>

<p>Now we head to the <code>linearEvaluation</code> function. <var>info</var> passed in refers to <var>elementList</var> (see the CGFunctionCreate call). Again, since we know that we&#8217;ll only ever have two elements we can reduce it to:</p>

<pre><code>void linearEvaluation (void *info, const float *in, float *out) {
    float position = *in;

    CTGradientElement *color1 = *(CTGradientElement **)info;
    CTGradientElement *color2 = color1-&gt;nextElement;

    out[0] = (color2-&gt;red - color1-&gt;red)*position + color1-&gt;red; 
    out[1] = (color2-&gt;green - color1-&gt;green)*position + color1-&gt;green;
    out[2] = (color2-&gt;blue - color1-&gt;blue)*position + color1-&gt;blue;
    out[3] = (color2-&gt;alpha - color1-&gt;alpha)*position + color1-&gt;alpha;
}
</code></pre>

<p>And now we can finally remove the lines from <code>+gradientWithBeginningColor:endingColor:</code> where the positions are set, and also remove the <var>position</var> float from the CTGradientElement struct.</p>

<hr />

<p>We&#8217;re now down to a little over <strong>100 lines</strong>. Your CTGradientElement.m should now be looking something like <a href="http://pub.lipidity.com/CTGradient/CTGradient3.m">this</a>. Going well, but let&#8217;s take things up a notch.</p>

<hr />

<p>For Trimmit&#8217;s background, we&#8217;re not interested in the red, green, blue and alpha components - we just need a grayscale shading. This is the most fun part.</p>

<p>Instead of <code>float red, green, blue, alpha;</code> in the CTGradientElement struct, we can have just <code>float shade;</code>.</p>

<p>Now, during <code>init</code> we have:</p>

<pre><code>CGFunctionCallbacks evaluationCallbackInfo = {0 , &amp;linearEvaluation, 0};
static const float input_value_range[2] = { 0, 1 };
static const float output_value_ranges[8] = { 0, 1, 0, 1, 0, 1, 0, 1 };
gradientFunction = CGFunctionCreate(&amp;elementList, 1, input_value_range, 4, output_value_ranges, &amp;evaluationCallbackInfo);
</code></pre>

<p>The third argument, <var>input_value_range</var> is the domain. This gets passed into linearEvalution through <var>*in</var>.This is the independent variable. When we&#8217;re drawing the gradient, linearEvaluation is called with <var>*in</var> (the domain) starting at the first value of <var>input_value_range</var> and ending up at the second. So, something like 0.000, 0.001, 0.002 &#8230; 0.998, 0.999, 1.000. The function&#8217;s job is to set the the color components for each given value of the domain.</p>

<p>The first optimization we can make here is to return only one channel instead of four. This changes it to:</p>

<pre><code>- (id)init {
    if (self = [super init]) {
        CGFunctionCallbacks evaluationCallbackInfo = {0, &amp;linearEvaluation, 0};
        static const float range[2] = {0, 1};
        static const float domain[2] = {0, 1};
        gradientFunction = CGFunctionCreate(&amp;elementList, 1, domain, 1, range, &amp;evaluationCallbackInfo);
    }
    return self;
}
</code></pre>

<p>This change impacts back on the linearEvaluation function - now it only needs to return one channel:</p>

<pre><code>void linearEvaluation (void *info, const float *in, float *out) {
    CTGradientElement *color1 = *(CTGradientElement **)info;
    out[0] = (color1-&gt;nextElement-&gt;shade - color1-&gt;shade)*(*in) + color1-&gt;shade;
}
</code></pre>

<p>And don&#8217;t forget <code>+gradientWithBeginningColor:endingColor:</code>. We improve performance here, as we only need the shade - not a color. We can rename it to something appropriate.</p>

<pre><code>+ (id)gradientWithBeginningShade:(float)begin endingShade:(float)end {
    id newInstance = [[[self class] alloc] init];
    CTGradientElement color1, color2;

    color1.shade = begin; color2.shade = end;

    [newInstance addElement:&amp;color1];
    [newInstance addElement:&amp;color2];
    return [newInstance autorelease];
}
</code></pre>

<p>Now we&#8217;re only returning one grayscale channel. But hold on, we&#8217;re still in the RGB colorspace! That&#8217;s easily fixed. In <code>fillRect:</code>, replace:</p>

<pre><code>#if MAC_OS_X_VERSION_MAX_ALLOWED &gt;= MAC_OS_X_VERSION_10_4
    CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
#else
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
#endif
</code></pre>

<p>with:</p>

<pre><code>CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
</code></pre>

<p class="note">CGColorSpaceCreateDeviceGray is device-dependent on Mac OS X 10.3 and below, but starting from Tiger, it&#8217;s now device-independent which is good in terms of appearance.</p>

<p>While we&#8217;re in <code>fillRect:</code>, we can also make a few more improvements:</p>

<pre><code>- (void)fillRect:(NSRect)rect {
    CGContextRef currentContext = [[NSGraphicsContext currentContext] graphicsPort];
    CGContextSaveGState(currentContext);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
    CGContextClipToRect(currentContext, *(CGRect *)&amp;rect);
    CGShadingRef myCGShading = CGShadingCreateAxial(colorspace, CGPointMake(0, 0), CGPointMake(0, NSMaxY(rect)), gradientFunction, 0, 0);
    CGContextDrawShading(currentContext, myCGShading);

    CGShadingRelease(myCGShading);
    CGColorSpaceRelease(colorspace);
    CGContextRestoreGState(currentContext);
}
</code></pre>

<p>I moved the startPoint and endPoint CGPoints straight into the CGShadingCreateAxial call. Also, we only ever fill the whole view with the gradient, so just put in <code>0</code> instead of using NSMinX and NSMinY. We could replace the <code>CGPointMake(0, 0)</code> with <code>(CGPoint){0,0}</code>, but I&#8217;m not sure if that&#8217;d be noticeably faster.</p>

<hr />

<p>Our code is at a lean <strong>80 lines</strong>. Now for the biggest change yet.</p>

<hr />

<p>Heading back to dear old linearEvaluation. Look at this line:</p>

<pre><code>out[0] = (color1-&gt;nextElement-&gt;shade - color1-&gt;shade)*(*in) + color1-&gt;shade;
</code></pre>

<p>What&#8217;s happening here? Think about it like this:</p>

<p>Essentially, we&#8217;re just &quot;graphing&quot; a straight line:</p>

<pre><code>y = mx + c
</code></pre>

<p>where <var>x</var> is the position, <var>*in</var>, and <var>y</var> is the shade we return (<var>out[0]</var>), and <var>c</var> is the initial shade.</p>

<p>Since our <var>x</var> value only goes from 0 to 1 (see domain in CGFunctionCreate), at <code>x = 0</code>, we will have <code>y = c</code>. At <code>x = 1</code>, we&#8217;ll have <code>y = m + c</code>.</p>

<p><img src='http://lipidity.com/wordpress/wp-content/uploads/2007/11/gradient-graph.png' alt='Graph of Gradient Function' class='feature' /></p>

<p><strong>So</strong> we only need the start shade as our <var>*info</var> value, since we can have <var>m</var> as the difference between the final and initial shades. Let&#8217;s effect this in our code.</p>

<p>We can get rid of the CTGradientElement struct. Remove from the header:</p>

<pre><code>typedef struct _CTGradientElement {
    float shade;
    struct _CTGradientElement *nextElement;
} CTGradientElement;
</code></pre>

<p>Instead of the struct, we&#8217;ll use a single <var>shade</var> instance variable. We don&#8217;t need the <code>+gradientWithBeginningShade:endingShade:</code> anymore, nor do we need <code>-addElement:</code>. So our interface now looks like this:</p>

<pre><code>@interface CTGradient : NSObject {
    float shade;
    CGFunctionRef gradientFunction;
}
- (void)fillRect:(NSRect)rect;
@end
</code></pre>

<p>From the implementation, we can remove the <code>free</code> calls in <code>dealloc</code> as well as the whole of <code>+gradientWithBeginningShade:endingShade:</code> and <code>addElement:</code>. We can set the <code>shade</code> in the init method (and replace <code>&amp;elementList</code> with <code>&amp;shade</code>), and change the linearEvaluation function to:</p>

<pre><code>void linearEvaluation (void *info, const float *in, float *out) {
    out[0] = *(float*)info + (*in)*0.1;
}
</code></pre>

<p>The <code>0.1</code> is what determines how much brighter the top of the gradient is (it&#8217;s the <var>m</var> value of our straight line) compared to the bottom.</p>

<p><strong>But</strong> since the <code>(*in)*0.1</code> will go from <code>0</code> to <code>0.1</code> (as <var>*in</var> goes from 0 to 1), we can actually change the <code>domain</code> we declare for CGFunctionCreate to {0, 0.1} and have our function as:</p>

<pre><code>void linearEvaluation (void *info, const float *in, float *out) {
    out[0] = *(float*)info + *in;
}
</code></pre>

<p>You can now add accessors for <code>shade</code> if you need to change the shade of the gradient or you could even have an <code>initWithShade:</code> method. In fact, here&#8217;s an Xcode project with the gradient.</p>

<p class="download"><a href="http://pub.lipidity.com/CTGradient/GradientSample.tar.bz2">Lean Gradient Xcode Project</a></p>

<p>We now have our gradient code down <big>from <strong>1300 lines</strong> to just over <strong>30 lines</strong></big>.</p>

<hr />

<p>We&#8217;ve almost reached a similar level of optimization to Trimmit&#8217;s gradient. There will be further optimizations you can make, but it depends on what you&#8217;re using the gradient for. For example, if the gradient you&#8217;re drawing is always the same, you can optimize further by removing the <code>shade</code> instance variable and all references to it, and hardcode the value into the linearEvaluation function.</p>

<p>Getting to the end of this article, you&#8217;re probably thinking that it would be a bit of a waste to use CTGradient if by the time you optimize it for your application, it doesn&#8217;t resemble the original at all. And you&#8217;re quite right. The documentation already shows you <a href="http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_shadings/chapter_9_section_6.html">how to draw gradients</a>, yet the number of applications using CTGradient - the whole 1300 lines of it - is astonishing.</p>

<p>Please: When you use other people&#8217;s code, don&#8217;t put it in without a thought. Go through it, understand it, and optimize it for your specific need. For the better performance and reduced RAM usage, computers will thank you.</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/ctgradient-code-bloat/feed/</wfw:commentRss>
		</item>
		<item>
		<title>IconGrabber</title>
		<link>http://lipidity.com/apple/icongrabber/</link>
		<comments>http://lipidity.com/apple/icongrabber/#comments</comments>
		<pubDate>Tue, 20 Nov 2007 07:51:15 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Application]]></category>

		<category><![CDATA[Download]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[plugin]]></category>

		<category><![CDATA[Quicksilver]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/icongrabber/</guid>
		<description><![CDATA[
I often need to use OS X icons on the web. Unfortunately, it&#8217;s not an easy matter to extract icons from Mac files or folders. The solution? IconGrabber.



It couldn&#8217;t be easier. Just drop the file or folder that you want the icon from onto IconGrabber, and choose a place to save it. The extension you [...]]]></description>
			<content:encoded><![CDATA[<p><a href='http://lipidity.com/software/icongrabber/' title='IconGrabber'><img class='fright' src='http://lipidity.com/wordpress/wp-content/uploads/2007/11/demo.png' alt='IconGrabber' /></a>
I often need to use OS X icons on the web. Unfortunately, it&#8217;s not an easy matter to extract icons from Mac files or folders. The solution? <a href="http://lipidity.com/software/icongrabber/">IconGrabber</a>.</p>

<!--more-->

<p>It couldn&#8217;t be easier. Just drop the file or folder that you want the icon from onto <a href="http://lipidity.com/software/icongrabber/">IconGrabber</a>, and choose a place to save it. The extension you choose determines the format, otherwise it defaults to tiff. That&#8217;s it!</p>

<hr />

<p>If you&#8217;re a Quicksilver user, you can use the <a href="http://lipidity.com/plugins/quicksilver/icongrabber/">IconGrabber Quicksilver plugin</a>. (It doesn&#8217;t require the IconGrabber application.) Once installed, you&#8217;ll get a &#8220;Save Icon To&#8230;&#8221; action. Not only can you save the icons of files and directories, you can save the icon of <em>whatever</em> appears in the first &#8220;object&#8221; pane. This is <em>extremely handy</em>.</p>

<p>Enjoy!</p>

<hr />

<p><strong>Update:</strong></p>

<p>The Quicksilver plugin is now at version 0.95. Notable changes include:</p>

<ul>
<li>Forcing direct object to load icon.</li>
<li>Indirect object for &#8220;Save Icon To&#8230;&#8221; starts at <code>IconGrabberSaveTo</code> defaults key. Format used is set by the <code>IconGrabberFormat</code> defaults key. Uses png by default.</li>
<li>&#8220;Save Icon As&#8230;&#8221; action added. Indirect objects have different image formats. Images are saved to the <code>IconGrabberSaveTo</code> defaults key.</li>
<li>Image files have the <code>IconGrabberAppend</code> key appended to the filename (&#8221;-icon&#8221; by default).</li>
</ul>

<p>For example, if you want to use the tiff format for the &#8220;Save Icon To&#8230;&#8221; action, run <code>defaults write com.blacktree.Quicksilver IconGrabberFormat tiff</code>.</p>

<p>If you want to change the directory the images are generated in by &#8220;Save Icon As&#8230;&#8221;, run <code>defaults write com.blacktree.Quicksilver IconGrabberSaveTo "~/NewDirectory"</code>.</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/icongrabber/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Beyond Repair</title>
		<link>http://lipidity.com/apple/beyond-repair/</link>
		<comments>http://lipidity.com/apple/beyond-repair/#comments</comments>
		<pubDate>Thu, 15 Nov 2007 09:16:33 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Review]]></category>

		<category><![CDATA[Application]]></category>

		<category><![CDATA[Download]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Xcode]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/beyond-repair/</guid>
		<description><![CDATA[Where can you find an application that comprises a single shell command wrapped in so many layers that it ends up 10,000 times bigger, slower, more obtrusive, less intuitive and full of junk, but still performs the same function as the original command?

Here&#8217;s your answer.

AppleScript can be handy sometimes, but when people release applications like [...]]]></description>
			<content:encoded><![CDATA[<p>Where can you find an application that comprises a single shell command wrapped in so many layers that it ends up 10,000 times bigger, slower, more obtrusive, less intuitive and full of junk, but still performs the same function as the original command?</p>

<p><a href="http://www.nwwnetwork.net/software.php?app=wallsaver">Here&#8217;s your answer</a>.</p>

<p>AppleScript can be handy sometimes, but when people release applications like this, and get awards for it, alarm bells should be going off. WallSaver is almost two megabytes in size. For a single command-line.</p>

<p>Just reinforce the point, I wrote another wrapper for the same command in Objective-C. It&#8217;s 100 kb, or 60 kb without the icon. It can pause the screensaver (which brings it down to 0 CPU usage), resume it or restart it. And not once will it throw an &#8220;AppleScript error&#8221; <img src='http://lipidity.com/wordpress/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> . <a href="http://lipidity.com/software/desksaver/">Download it</a> and see for yourself.</p>

<p>As for the source code, all you need is:</p>

<pre><code>/System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine -background
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/beyond-repair/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Eliminate Bloatware</title>
		<link>http://lipidity.com/apple/eliminate-bloatware/</link>
		<comments>http://lipidity.com/apple/eliminate-bloatware/#comments</comments>
		<pubDate>Fri, 02 Nov 2007 02:00:45 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Application]]></category>

		<category><![CDATA[Cocoa]]></category>

		<category><![CDATA[Download]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Review]]></category>

		<category><![CDATA[Xcode]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/eliminate-bloatware/</guid>
		<description><![CDATA[The Vikings left their trash on the ground - Mac developers are stuffing theirs into their apps.]]></description>
			<content:encoded><![CDATA[<p>We&#8217;ve heard the excuses and listened to the complaints; the amount of junk shipped in third-party applications just doesn&#8217;t seem to change. If anything, it&#8217;s getting worse. Just because users may not realise how much bloat they&#8217;re getting along with the app, doesn&#8217;t change the fact that it&#8217;s bloat. Unnecessary junk.</p>

<p>Finder info, .DS_Store files, resource forks, debug symbols&#8230; They do nothing for the user short of wasting disk space. For the developer, extra bandwidth costs. Do people not realise this is a problem, or are they just too lazy to act? Either way, the solution is simple.</p>

<p>You can clean apps (sometimes to less than half their original size) with <a href="http://lipidity.com/apple/cleaning-apps-mac-os-x/">some basic commands</a> in the Terminal. But this takes time, and what&#8217;s more, Mac users in general have an aversion to the command line. Enter <a href="http://lipidity.com/trimmit/">Trimmit</a>.</p>

<p class='centre'><a href='http://lipidity.com/trimmit/' title='Trimmit'><img src='http://lipidity.com/wordpress/wp-content/uploads/2007/11/flow2.jpg' alt='[Trimmit]' title="Trimming Flow.app" /></a></p>

<blockquote><p>Trimmit is a svelte utility which takes the pain out of keeping your applications as tight as possible. I use it on Flow each time I seed a build, and I seriously doubt the process could any easier.</p> <cite><a href="http://extendmac.com/" title="ExtendMac Flow">Brian Amerige</a></cite></blockquote>

<p>Trimmit uses superfast UNIX APIs, Apple&#8217;s own developer tools, and the rock-solid Cocoa framework to automate your cleaning process. Just drop your built app onto it, maybe configure a few settings, and you&#8217;re away. <big>It doesn&#8217;t get any easier - or faster.</big></p>

<p>Other applications claim to save disk space by stripping universal binaries, or removing languages. <big>Trimmit does all of that - and more.</big> But it works. Look at this screenshot from Xslimmer regarding Xcode:</p>

<p class='centre'><img src='http://lipidity.com/wordpress/wp-content/uploads/2007/11/xcode-xslim.jpg' alt='Xslimmer on Xcode' title='Shows only 2 architectures' /></p>

<p>Xslimmer naively believes that only two architectures can exist in any application, but the truth is revealed by Trimmit:</p>

<p class='centre'><img src='http://lipidity.com/wordpress/wp-content/uploads/2007/11/xcodetrim-2.png' alt='i386, ppc, ppc64, x86_64' title='Architectures in Xcode' /></p>

<p><br />Because Trimmit interacts with the shell (<a href="http://zsh.sourceforge.net/FAQ/zshfaq01.html#l3">zsh</a>, in fact), no similar application can even come close to it&#8217;s level of power, accuracy and performance.</p>

<p><big><a href="http://rixstep.com/1/20071101,01.shtml">The results</a> speak for themselves.</big></p>

<p>Use Trimmit to recover disk space that third-party applications so rudely waste.</p>

<p class='centre' style='margin:2em 0'><big><a href="http://lipidity.com/trimmit/">Go get Trimmit.</a></big></p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/eliminate-bloatware/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Hide in your shell</title>
		<link>http://lipidity.com/apple/hide-in-your-shell/</link>
		<comments>http://lipidity.com/apple/hide-in-your-shell/#comments</comments>
		<pubDate>Wed, 03 Oct 2007 14:00:15 +0000</pubDate>
		<dc:creator>Ankur</dc:creator>
		
		<category><![CDATA[Apple]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[shell]]></category>

		<category><![CDATA[terminal]]></category>

		<category><![CDATA[Tutorial]]></category>

		<guid isPermaLink="false">http://lipidity.com/apple/hide-in-your-shell/</guid>
		<description><![CDATA[Your Mac has a UNIX core - learn to use it.]]></description>
			<content:encoded><![CDATA[<p>The &#8220;command line&#8221; on your Mac can be accessed using the Terminal in /Applications/Utilities.</p>

<p>If you&#8217;re not familiar with shell scripting, there&#8217;s a good tutorial <a href="http://www.linuxcommand.org/learning_the_shell.php">here</a>. You can also find, run or manage shell commands using the free <a href="http://rixstep.com/4/0/clix/">CLIX</a> utility from Rixstep.</p>

<hr />

<p>These commands are ones I use frequently - in many instances they are given a pretty Cocoa front-end and a hefty price-tag when you already have all this functionality inside your computer.</p>

<p>For more information about any of these commands, type in <code>man command-name</code>, where <code>command-name</code> is the name of the command you want information about (type <kbd>q</kbd> to quit the man page once you&#8217;re done).</p>

<h3>Downloading</h3>

<p>The <code>curl</code> command is invaluable when it comes to downloading. To download a file from a specific URL (be it ftp or http), use the command:</p>

<pre><code>curl -o <kbd>local-file.ext</kbd> <kbd>http://www.url.com/file.ext</kbd></code></pre>

<p>If you were downloading something, but your internet connection died, or you had to stop it for some other reason (even if you were downloading with some other program such as your browser), you can use curl to resume the download:</p>

<pre><code>curl -C - -o <kbd>local-file.ext</kbd> <kbd>http://remote.tld/file.ext</kbd></code></pre>

<p>Very nifty!</p>

<h3>Compression</h3>

<p>A few different compression formats. Bzip2 generally provides the most compressions, but for text, gzip is often better. To compress multiple files (ie. folders), you&#8217;ll need to use either the <code>tar</code> or <code>zip</code> command.</p>

<pre><code>bzip2 <kbd>file</kbd></code></pre>

<pre><code>gzip <kbd>file</kbd></code></pre>

<pre><code>zip <kbd>output</kbd> <kbd>files</kbd></code></pre>

<pre><code>tar -cjf <kbd>output.tbz</kbd> <kbd>files</kbd></code></pre>

<pre><code>tar -czf <kbd>file.tgz</kbd> <kbd>file</kbd></code></pre>

<h3>Cleaning</h3>

<p>If you <a href="http://lipidity.com/downloads/trim-app/">download</a> the trim-app shell script, you can do a lot of cleaning up of your system very easily. Move it to /usr/bin/, then you can do the following:</p>

<p>Remove .DS_Store files from the current directory:</p>

<pre><code>trim-app -d</code></pre>

<p>Trim universal binaries to a single architecture (use i386 instead of ppc for Intel):</p>

<pre><code>trim-app -a ppc</code></pre>

<p>Compress tiff images:</p>

<pre><code>trim-app -t</code></pre>

<p>Clean resource forks:</p>

<pre><code>trim-app -r</code></pre>

<p>Or all of the above:</p>

<pre><code>trim-app --all ppc</code></pre>

<p>Since trim-app is a shell script, you can open it in a text editor to see how it works. You can download an uncompressed version of trim-app <a href="http://pub.lipidity.com/trim-app-unc.gz">here</a> to dissect it.</p>

<h3>Misc</h3>

<p>If you need to search the <em>contents</em> of files for a particular string, use the following:</p>

<pre><code>grep -r &#34;<kbd>search-query</kbd>&#34; *</code></pre>

<p>Find out the size of the current directory in kilobytes:</p>

<pre><code>du -csk</code></pre>

<p>Delete all languages other than English in applications in the current folder.</p>

<pre><code>find . -name \*.lproj -and \! \( -name En\* -or -name en\* \) -exec rm -rf {} \;</code></pre>

<p>Create a symbolic link:</p>

<pre><code>ln -s <kbd>source</kbd> <kbd>dest</kbd></code></pre>

<p>Create an .icns file from a tiff image:</p>

<pre><code>tiff2icns <kbd>image.tiff</kbd></code></pre>

<hr />

<p>These are just a few of the useful shell commands available - explore a bit and you&#8217;ll find plenty of fantastic tools that will make your life much, much easier.</p>

<p>Now it&#8217;s your turn - know any super time-saving commands?</p>
]]></content:encoded>
			<wfw:commentRss>http://lipidity.com/apple/hide-in-your-shell/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
