在写Objective-C代码的时候,你平时打log是否也一直是简单地调用NSLog来输出信息呢?有时候甚至只是为了验证一下程序到底有没有执行某一块代码,心烦的时候,我甚至会这么打log:

NSLog(@"Fuck the code")

然后如果不小心调完代码后忘了去掉了,以后翻出来的话就冏了。

好吧,进入正题,首先来聊一聊这个东西:_cmd

你肯定知道self吧,_cmd和他很相似。只不过self表示的是正在调用类方法的实例,作用范围是整个类。而_cmd则表示正在被调用的方法,作用范围是该方法内部。_cmd有个显而易见的应用就在于打log上,比如我有如下的宏:

#define METHOD_LOG (NSLog(@"%@\n%s\n%@", \
                NSStringFromSelector(_cmd), \
                __FILE__, self))

然后我在下面的viewDidLoad方法内调用这个宏:

- (void)viewDidLoad
{
    METHOD_LOG;
}

可以得到如下的log信息:

viewDidLoad
/Users/keywind/Downloads/ndfred-coredata-bit-me-3ec4df7/Source/UI/BenchmarkViewController.m
<BenchmarkViewController: 0x6c5ca40>

能够很清晰地显示出当前正在调用的方法。

关于_cmd我还看到有这么用的:

@property (readwrite) id color;

- (id)color;
{
    //SomeDictionary defined elsewhere in class
    return [SomeDictionary objectForKey:
                [NSStringFromSelector(_cmd) capitalize]];
}
- (void)setColor:(id)someObject;
{
    //3 means we start from the C in Color
    NSString *partial_string;
    partial_string = [NSStringFromSelector(_cmd) 
                        substringFromIndex:3];

    //SomeDictionary defined elsewhere in class
    [SomeDictionary setObject:someObject 
                       forKey:partial_string]; 
}

这么用的好处就是我根本没有创建color实例变量,我不用去管内存,都由NSDictionary代替管理。对于Objective-C这种动态语言来说,用好运行时也是相当有意义的。

回到上面那个log宏来,这个宏里有个预定义宏__FILE__用来显示当前的文件,可以借助其他一些宏来帮助打log,这里我查到stackoverflow上一则Post,给出了一些比较满意的log宏。这里我就直接偷来用上:

#ifdef DEBUG
#   define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#   define DLog(...)
#endif

// ALog always displays output regardless of the DEBUG setting
#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

#ifdef DEBUG
#   define ULog(fmt, ...)  { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:@"%s\n [Line %d] ", __PRETTY_FUNCTION__, __LINE__] message:[NSString stringWithFormat:fmt, ##__VA_ARGS__]  delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil]; [alert show]; }
#else
#   define ULog(...)
#endif

DLog宏用来在debug时调用,一到release版本时就不打出log。这也有个好处,就是一半我们在debug时的那些log有时候我们在release时总是忘记删掉,而且在暗地里打大量log是影响程序的性能的。DLog宏避免了这个问题。ALog则不管,始终打log。且来看看log的样式,比如调用

ALog(@"Hello world")

则相应的log信息则是:

-[LibraryController awakeFromNib] [Line 364] Hello world

所调方法,行号,以及自定义消息都有了,这样看起来一目了然。

Core Data 这货一直饱受争议的活着,之前也早就跟他打过交道,不过最后还是选择轻量许多的SQLite。具体原因不外乎两点:

  • 频繁地操作数据库,这货确实比较慢
  • 这货本身是很大一个模块,学好它要花不少功夫,不然必然是滥用

之前认定这货比较慢,我也是道听途说,其中这篇文章做了一些总结。说它比较大是因为已经出了不少书专门介绍这块,其中Core.Data.Apple’s.API.for.Persisting.Data.on.Mac.OS.X这本口碑不错。

以上的经历都是大概大半年前的事了,现在拿出来说也只是做个铺垫。原本确实已经打算把Core Data搁一边不理他了,但之前跟一个面试官闲聊中聊到Core Data时,他说Core Data这个框架现在已经很快了,应该给予足够重视。我脑子里转了半天,也没翻出来有关最近Core Data性能大幅提升的消息。所以回来后赶紧查了查相关资料。

性能提升的消息倒是没有查到,倒是又查到有人喷Core Data了。今年的NSConference 2012上此人还专门做了一个叫《Core Data Bit Me》的讲解,有关NSConference 2012具体可参考这个链接,没有具体内容,只能看看大致的总结。不过好在Core Data的那个报告作者已经把相关代码放到Github上了,所以能看个究竟。

Clone下来代码后设断点跟了跟,这是个基准测试的代码。主要测试的是三个不同实现的性能:

  • 经典Core Data模式
  • 不带Inverse Relations的Core Data模式
  • 纯SQLite模式

测试的Entity是三个,关系如下图:

大致意思就是比如一台电视有100个channel,每天有1000个program要放松,broadcast就是制定哪个program在哪个channel放松的中枢纽带,并要将这些Entity信息全部存储到数据库。详细的Log包含许多有用信息,建议对比看看,这里就不贴了。

值得说明的是这个benchmark请尽量在真机上跑,真机跟模拟器的I/O速度肯定不好比,不同机器都有差距,对数据比较头疼的,好在已有简洁的图表信息。

背景

之前在写一个新闻类App的时候,由于要支持Retina的高分辨率特性,又没有考虑到图片的优化,因此新闻图片加载都相当耗时,而且时不时就给你来个二级的Memory Warning。最近看到一些优化图片,提高App性能的文章,对图片进行相应的处理后,在基本不影响图片阅读质量的基础上大大减小了图片尺寸,使程序更加稳健流畅。故分享之。

文章一

首先看到的是这一篇文章,作者对PNG,优化后的PNG,以及不同压缩比例的JPEG进行了详尽的比较。如果看英文吃力,这里有我同学的翻译,翻译质量挺好,和原文意思表达一致。当然你可以不在乎其中细节,总的来说,这篇文章要告诉你的主要两点:

  • 不需要Alpha通道的(不需要透明)的PNG图片尽量采用60%-80%压缩比例的JPEG来代替,这对于图片大小和程序对图片的解压速度都有质的飞跃,对于显示来自服务器的图片有大幅度性能提高
  • 如果你需要alpha通道或者必须使用PNG格式,那么我推荐你在你的web服务器上安装pngcrush并处理好所有的png图片

文章二

当然对于一款要做到精致的新闻类应用,图片的Alpha通道是不能少的,像圆角啊,阴影啊这些效果在JPEG上就表现不出来,所以这一篇文章就专门分析了一下PNG图片。原文不长,也没有什么难理解的概念,故不做翻译,只是抽取其中一些比较有用的东西。这篇文章主要是针对Web开发者的,因此侧重于各大浏览器对PNG的表现形式,尤其是IE。这个我倒不怎么关心。我关注的主要在于:

  • PNG图片支持各种各样的颜色深度,透明度和色彩修正的组合,这其中怎么组合就会对具体的表现场景有惊人的差异,对于特定场景,一些组合的特性和附带的臃肿的元数据根本就是不必要的。

工具

对于如何优化PNG图片,作者介绍了一堆工具,先是两个命令行工具:

  • PNGOUT用于删除一堆不必要的元数据使得到最好的压缩效果
  • 使用OptiPNG+Pngcrush的组合也很不错
  • 想要得到小尺寸图片又想要较好的透明效果,可以考虑使用pngnqpngquant

对于Linux服务器,可以这么写:

find . -name '*.png' -print0 | xargs -P4 -0 -n1 pngout
                                       <small>↑ number of CPUs</small>                                                                                   

图形界面工具可以考虑:

推荐

最后说说ImageOptim这个PNG优化工具。 这个小东西把各种优化工具集于一身,具体信息可去软件主页看看。它处理JPEG,PNG,GIF格式。我用它压缩GIF动画,每次都能压缩90%多,而且基本看不出质量差异。而且也有Automator脚本,安装后直接右键->服务就可以对图片处理。使用这个软件处理Xcode里的图片需要注意: Xcode本身对PNG图片有优化处理,用的是pngcrush这个工具,你必须禁掉Xcode的自动处理,否则用ImageOptim处理的效果会被覆盖掉,处理方法为:

CreativeMenu是最近写的一个iOS上的菜单小Demo。设计的灵感是来自于Dribble上的一幅图片,我只是把原作者的设计思想根据我的理解实现了下,参考了Path菜单动画的代码,并且将源码提交到了Github上供有兴趣的同学参考。

效果

实现

主要的侧重点在于CAKeyframeAnimation的使用,中间碰到的一点麻烦是CAKeyframeAnimation的values这个属性。这个array如果里面只有一个值的话,动画并不是我想象的那样。我原来以为是从current的状态动画到这个array里的这个唯一值。但是测试了几次结果是如果只有一个值,则是马上瞬移到这个值的状态,其间没有动画过程(也有可能是我测试不当,感觉这么不太符合常识)。

扩展

菜单的个数理论上不限定,你可以根据自己的需求设定个数。但是一般是4,5个比较常见。

反馈

对代码有任何意见和建议都可以回复或直接Email。

这玩意是别人整的,原文链接在这,你要是懒得看英文,就直接按下面一步到位。

代码是这个:

KEYWORDS="TODO:|FIXME:|\?\?\?:|\!\!\!:"
find ${SRCROOT} \( -name "*.h" -or -name "*.m" \) -print0 | \
xargs -0 egrep --with-filename --line-number --only-matching "($KEYWORDS).*\$" | \
perl -p -e "s/($KEYWORDS)/ warning: \$1/"

值得一提的是,使用这个方法貌似需要你的工程的绝对路径不带空格,就是不能有“XXX XXX”这样的目录,不然就报错。至于你信不信,反正我遇到过这种情况。

七:实现Object的Copy

这一节介绍两种方法使用NSCopying的协议copyWithZone方法去实现objects的copy。 你有两种方法通过实现NSCopying的协议copyWithZone创建copies:

(1).单纯使用alloc和init
(2).使用 NSCopyObject

对于如何选择,你需要考虑:

(1).我需要深拷贝还是浅拷贝
(2).我需要继承NSCopying的一些方法吗

1.深拷贝还是浅拷贝

拷贝一个object也就是创建一个新的实例,并且初始化为拷贝源的值。对于像boolean,integer这类值,拷贝就是直接赋值。对于指针形的object就分为浅拷贝和深拷贝。浅拷贝是只创建一个新的指针,并指向同一块数据。深拷贝就是数据和指针都创建。我们来看: 深拷贝:

- (void)setMyVariable:(id)newValue
{
    [myVariable autorelease];
    myVariable = [newValue copy];
}

浅拷贝:

- (void)setMyVariable:(id)newValue
{
    [myVariable autorelease];
    myVariable = [newValue retain];
}

浅拷贝2:

- (void)setMyVariable:(id)newValue
{
    myVariable = newValue;
}
独立的拷贝

顾名思义,独立的拷贝只能是深拷贝,但通常我们需要把深拷贝和浅拷贝结合获得最佳效果。比如指针这种可以看成数据容器的object,我们需要深拷贝,而像委托这种复杂的object,浅拷贝会比较好,比如:

@interface Product : NSObject <NSCopying>
{
    NSString *productName;
    float price;
    id delegate;
}

@end

通过继承NSCopying,我们现在深拷贝一个Product,这时对应于这个深拷贝的Product的数据区的productName; float price; delegate; 三个object,我们看图:

productName的值不同,是深拷贝,而delegate的值相同,是浅拷贝,price无所谓深浅。

2.使用“alloc, init…”的方法

如果你的类么有继承NSCopying,那么你需要用alloc, init…和setter方法自己去实现copyWithZone,比如上面那个Product的例子,我们可以这样实现:

- (id)copyWithZone:(NSZone *)zone
{
    Product *copy = [[[self class] allocWithZone: zone]
            initWithProductName:[self productName]
            price:[self price]];
    [copy setDelegate:[self delegate]];

    return copy;
}

六:Accessor方法

这一节主要讲述为什么推荐使用Accessor方法以及如何声明和实现他们,主要也是前面讲过的内容的细化。 使用Accessor方法的好处在于大大提高了类的封装性,使你可以简化一些基本的内存管理的内容

1.声明Accessor方法

声明相当简单,如果你稍微有点基础,就应该很熟悉下面举例的代码:

@property (copy) NSString *firstName;
@property (readonly) NSString *fullName;
@property (retain) NSDate *birthday;
@property NSInteger luckyNumber;

2.实现Accessor方法

首先你需要在实现文件的开头添加类似这样的语句:

@synthesize firstName;
@synthesize fullName;
@synthesize birthday;
@synthesize luckyNumber;

之后如果你什么也不添加了,那么系统用默认的实现方法实现他们。但是如果你想自己写实现方法,有三种方法(此处为了不失原文档的准确性,故直接粘贴如下):

1.Getter retains and autoreleases the value before returning it; setter releases the old value and retains (or copies) the new value.
2.Getter returns the value; setter autoreleases the old value and retains (or copies) the new value.
3.Getter returns the value; setter releases the old value and retains (or copies) the new value

当然有几个例子会加深理解,基于以上三种方式我们各给出一个例子

方式一:

- (NSString*) title {
    return [[title retain] autorelease];
}

- (void) setTitle: (NSString*) newTitle {
    if (title != newTitle) {
        [title release];
        title = [newTitle retain]; // Or copy, depending on your needs.
    }
}

方式二:

- (NSString*) title {
    return title;
}

- (void) setTitle: (NSString*) newTitle {
    [title autorelease];
    title = [newTitle retain]; // Or copy, depending on your needs.
}

当getter方法比setter更频繁的使用时,方式二比较合适。

方式三:

- (NSString*) title {
    return title;
}

- (void) setTitle: (NSString*) newTitle {
    if (newTitle != title) {
        [title release];
        title = [newTitle retain]; // Or copy, depending on your needs.
    }
}

当频繁使用getter方法和setter方法时,方式三是很合适的。它也适用于不想延长值的生命周期的那些objects,比如集合类。 方式三缺点在于:更新值时是很快把旧的值给释放掉的,而如果另有一个object有这个旧的值的引用,但没有所有权,此时去引用这个值就要出错了,比如:

NSString *oldTitle = [anObject title];
[anObject setTitle:@"New Title"];
NSLog(@"Old title was: %@", oldTitle);

anObject是oldTitle唯一的拥有者,当anObject更新Title值时,就把oldTitle释放掉了,所以那个Log就要出错了。

3.Value Objects使用Copy

这里首先明确一个概念Value Object,文档中说:A value object is in essence an object-oriented wrapper for a simple data element such as a string, number, or date. The common value classes in Cocoa are NSString, NSDate, and NSNumber.意思就是一些简单数据的封装,以NSString, NSDate, and NSNumber为代表,实际开发也就只需要记住这三个。 对于这类object,推荐使用copy而不是retain,如下:

- (NSString *)name {
    return [[name copy] autorelease];
}

- (void)setName:(NSString *)aName {
    [name autorelease];
    name = [aName copy];
}

五:Autorelease Pools

这一节相对于上几节,详细讲解了Autorelease Pools

1.Autorelease Pools综述

Autorelease Pools是NSAutorelease的实例,它是一个容器,容纳各种接到autorelease消息的objects。一个object可以被多次放入autorelease pools中,放进去几次就会release几次,不会造成内存泄露。而且,相比较release而言,autorelease延长了object的生命周期。

Cocoa总是相信你有可用的autorelease pool,这也是我们在iOS开发中的main文件中看到autorelease pool的缘由。当然你可以自己alloc一个autorelease pool,并且用drain释放。值得注意的是,不要对autorelease pool发送autorelease或retain或release,而且你drain掉autorelease pool时应该是在同一context(这个词解释不好,还是直接英文,大致就是比如说在同一个函数,同一个循环等)。

Autorelease pools是以栈的形式整理的,而而不是一般认为的内嵌方式。也就是说你新创建的autorelease pool放在栈顶,并且这事你对一个object实行autorelease是把它放在栈顶那个autorelease pool。

为什么autorelease pool被误认为内嵌方式也是情有可原,因为它在外表现形式确实是内嵌,比如你在main文件看到最外层的autorelease pool,当你进入appdelegate时,就可以创建算是内嵌的auotrelease pool。

有三种情况你值得创建自己的autorelease pool:

(1)基于Application Kit的程序是自动创建一个autorelease pool的,但如果你写的是像命令行程序那样非基于Application Kit的程序,就要自己创建了。
(2)如果你写多线程程序,那么在新开的线程里需要自己写autorelease pool,而且是一开线程就创建,外面那个它不能使用。
(3)如果你写了一个循环,这个循环包含大量暂时的object,那么为了内存不告急,你应该在这个循环内创建一个autorelease pool,并把那些暂时的object都扔进去。

2.在不基于AppKit的程序里创建Autorelease Pools

这个还是代码更加直观:

void main()
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSArray *args = [[NSProcessInfo processInfo] arguments];

    for (NSString *fileName in args) {

        NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];

        NSError *error = nil;
        NSString *fileContents = [[[NSString alloc] initWithContentsOfFile:fileName
                                           encoding:NSUTF8StringEncoding error:&error] autorelease];

        /* Process the string, creating and autoreleasing more objects. */

        [loopPool drain];
    }

    /* Do whatever cleanup is needed. */
    [pool drain];

    exit (EXIT_SUCCESS);
}

在for循环也创建了一个,减少内存消耗,其他应该没啥可说。

3.Autorelease Pools和多线程

为什么多开的线程不能使用原来就创建的autorelease pools呢?主要是因为,每一个线程都独立得维护着自己特有的autorelease pool堆栈,不同线程间的autorelease pools不能交错使用。如果你新开的线程涉及到大量objects的创建,你值得开一个autorelease pool,而且可以完全仿照AppKit自带的,也不麻烦。

4.始终遵循所有权基本规则

通过autorelease而不是release,使object延长了生命周期。要注意的是,如果一个object已经autorelease了,所有权已经放弃了,就没必要再release了。而且你也不能再去访问它。但有时我们确实需要把autorelease pool范围内的object拉出来在这个autorelease pool范围外使用,你可以retain一下这个object,然后在autorelease pool被drain掉以后,并且使用完这个object后把它autorelease掉,代码如下:

– (id)findMatchingObject:(id)anObject
{
    id match = nil;

    while (match == nil) {
        NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];

        /* Do a search that creates a lot of temporary objects. */
        match = [self expensiveSearchForObject:anObject];

        if (match != nil) {
            [match retain]; /* Keep match around. */
        }
        [subPool drain];
    }

    return [match autorelease];   /* Let match go and return it. */
}

仔细观察可发现这个match不是在subPool这个autorelease pool,而是在上一级的autorelease pool里了