[摘要]Effective Objective-C 2.0(四)

本文介绍Objective-C中多种遍历方法及优劣对比,包括使用GCD并发执行的最新枚举方式,并探讨NSCache相较于NSDictionary的优势及应用实例。

多用块枚举 少有for循环

遍历collection有四种方式,如下:

  • 最新、最先进的方式,而且能够通过GCD来并发执行遍历操作
NSArray *anArray = /*...*/;
[anArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop){
    if(shouldStop){
        *stop = YES;//相当于break
    }
}];

NSDictionary *aDictionary = /*...*/
[aDictionary enumerateObjectsUsingBlock:^(id key, id object, BOOL *stop){
    if(shouldStop){
        *stop = YES;//相当于break
    }
}];

NSSet *aSet = /*...*/
[aSet enumerateObjectsUsingBlock:^(id object, BOOL *stop){
    if(shouldStop){
        *stop = YES;//相当于break
    }
}];
  • 快速遍历
NSArray *anArray = /*...*/;
for(id object in anArray)
{
    ...
}

NSDiction *aDictionary = /*...*/;
for(id object in aDictionary)
{
    ...
}

NSSet *aSet = /*...*/;
for(id object in aSet)
{
    ...
}

//反序遍历
NSArray *anArray = /*...*/;
for(id object in [anArray reverseObjectEnumerator])
{
    ...
}
  • NSEnumerator
NSArray *anArray = /*...*/
NSEnumerator *enumerator = [anArray objectEnumerator];
id object;
while([object = [enumerator nextObject]] != nil)
{
    ...
}

NSDictionary *aDictionary = /*...*/
NSEnumerator *enumerator = [aDictionary keyEnumerator];
id key;
while([key = [enumerator nextObject]] != nil)
{
    NSDictionary *dic = aDictionary[key];
    ...
}

NSSet *aSet = /*...*/
NSEnumerator *enumerator = [aSet objectEnumerator];
id object;
while([object = [enumerator nextObject]] != nil)
{
    ...
}
  • for循环
NSArray *anArray = /*...*/;
for(int i = 0; i < anArray.count, ++i)
{
    ...
}

NSDiction *aDictionary = /*...*/;
NSArray *keys = [aDictionary allKeys];
for(int i = 0; i < keys.count, ++i)
{
    id object = aDictionary[keys[i]];
    ...
}

NSSet *aSet = /*...*/;
NSArray *objects = [aSet allObjects];
for(int i = 0; i < objects.count, ++i)
{
    id object = objects[i];
    ...
}

构建缓存时使用NSCache而非NSDictionary

原因
  • 使用NSCache,当系统资源耗尽时,能够自动删减内存,而且会删除最久未使用的对象,而NSDictionary却需要开发者手工删减内存。
  • NSCache是线程安全的,可以多个线程同时访问
  • NSPurgeableData与NSCache结合使用效果更好
示例
//FSJClass.h
#import <Foundation/Foundation.h>
@interface FSJClass : NSObject
@end;

//FSJClass.m
@implementation FSJClass (){
    NSCache *_cache;
}

-(id)init
{
    if(self = [super init])
    {
        _cache = [[NSCache alloc] init]
        _cache.countLimit = 100;
        _cache.totalCostLimit = 5 * 1024 * 1024;
    }
    return self;
}

-(void)DownloadDataForURL:(NSURL*)url
{
    NSPurgeableData *cacheData = [_cache objectForKey:url];
    if(cacheData)
    {
        [cacheData beginContentAccess];
        //use cache data
        [cacheData endContentAccess];
    }else{
        //cache miss
        FSJNetworkFetcher *fetcher =[[FSJNetworkFetcher alloc] initWithURL:url];
        [fetcher startWithCompletionHandler:^(NSData *data){
        //这句相当于[purgeableData beginContentAccess],因此已经mark
        NSPurgeableData *purgeableData = [NSPurgeableData dataWithdata:data];
        [_cache setObject:purgeableData forKey:url cost:purgeableData.length];
        //use cache data
        [purgeableData endContentAccess];
        }];
    }
}
@end

精简initialize和load的实现代码

时下编写代码时不需要用它,仅在调试的时候使用,例如判断该分类是否已经加载到系统中

  • 假如运行期系统中的每个类(class)以及分类(category),必定会调用此方法,而且仅调用一次,调用的时机是加载阶段
  • 先调用类里面的load,在调用分类里的
  • 调用子类的load之前,必定执行所有超类躲得load
  • 代码依赖了其他程序库,那么程序库里的相关类的load方法也必定会执行,但是其调用顺序不能确定
  • 应用程序执行load方法会阻塞
  • load方法不参与覆写机制
+(void)load;

在首次使用某个类之前,系统会调用initialize,且只调用一次

  • 无法在编译期设定的全局常量,可以在initialize方法里初始化
+(void)initialize;
initialize与load的区别
  • 惰性调用。initialize只有程序用到相关类的时候,才会调用。而load在加载阶段就把所有类的load方法执行一遍
  • 线程安全。只有执行initialize的那个线程可以操作类或类实例,其他线程都要先阻塞,等着initialize执行完
  • 如果某个类未实现它,而其超类实现了,那么就会运行超类的实现代码,这就是覆写机制,因此应该判断当前要初始化哪个类

NSTimer会保留目标对象

因为NSTimer会保留目标对象,而目标对象会持有NSTimer对象,因此二者陷入循环引用,需要invalidate使NSTimer失效打破循环。
- 如果开发者设置为非重复执行模式,执行完任务后,计时器会失效;
- 调用invalidate也会使计时器失效,特别是设置为重复执行模式的时候,需要开发者调用invalidate来停止计时器

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

直接调用以上方法,target指向self,self又拥有NSTimer,这样容易产生保留环,只有开发者手动调用invalidate使NSTimer无效来打破保留环,但是代码又不一定保证调用invalidate,因此容易产生内存泄露。对此,以下方法可以打破保留环:

#import <Foundation/Foundation.h>

@interface NSTimer (FSJBlockSupport)
+(NSTimer *)fsj_scheduledTimerWithTimeInterval:(NSTimeInterval)ti block:(void(^)())block brepeats:(BOOL)yesOrNo;
@end

@implementation NSTimer (FSJBlockSupport)
+(NSTimer *)fsj_scheduledTimerWithTimeInterval:(NSTimeInterval)ti block:(void(^)())block brepeats:(BOOL)yesOrNo
{
    //让target指向NSTimer,这样不会产生保留环
    return [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(fsj_blockInvoke:) userInfo:[block copy] repeats:yesOrNo];
}

+(void)fsj_blockInvoke:(NSTimer*)timer
{
    void (^block)() = timer.userInfo;
    if(block){
        block();
    }
}
@end

按照如下方法调用


-(void)dealloc
{
    [_timer invalidate];
}

-(void)startTimer
{
    //生成weakself,NSTimer持有self,但不拥有self,打破循环引用
    __weak FSJClass *weakSelf = self;
    _timer = [NSTimer fsj_scheduledTimerWithTimeInterval:0.5 block:^{
    //strongSelf只在block中有效,超出作用域会被回收
    FSJClass *strongSelf = weakSelf;
    [strongSelf doSomething];
    } brepeats:YES];
}

-(void)doSomething
{
    //do something
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值