首页 > 代码库 > 【iOS开发-45】Tom猫案例:动画、imageNamed与imageWithContentOfFile对内存影响、图片文件夹放哪儿以及文档注释

【iOS开发-45】Tom猫案例:动画、imageNamed与imageWithContentOfFile对内存影响、图片文件夹放哪儿以及文档注释

今天tom猫案例效果:



(1)最傻最笨的办法:

——所有的点击都是按钮,只不过有6个有图标的按钮,有些头部、左右脚、肚子、尾巴那块也是一个按钮,只不过没背景没文字没边框的按钮用户按不到而已。

——这里的帧动画核心是UIImageView对象的一个属性animationImages,这个属性里面是以数组形式存放的图片。当然还有个重要的方法startAnimating用来播放前面那个属性里面的图片,就形成动画。再当然一下,还有设置时间和播放次数的属性。


注意点:

——我们一般把图片放在Images.xcassets里面,而且无论是png格式还是jpg格式貌似都可以省略后缀。

——目前为止,几乎只使用了一个代码简化方法,就是格式化输出,用%02d表示00、01......18、19......这些序列,从而导入众多的图片。

//使用3.5英寸屏幕模拟效果更好
//无论是jpg还是png,在引用文件时都可以省略图片后缀
#import "ViewController.h"

@interface ViewController ()

@property(nonatomic,retain) UIImageView *imgView1;

@end

@implementation ViewController

- (void)viewDidLoad {
    //中间的UIImageView,可以静态显示图片,下面动态播放帧动画也是用它
    self.imgView1=[[UIImageView alloc]init];
    self.imgView1.frame=CGRectMake(0, 0, 320, 512);
    self.imgView1.image=[UIImage imageNamed:@"angry_00.jpg"];
    self.imgView1.contentMode=UIViewContentModeScaleAspectFit;
    [self.view addSubview:self.imgView1];
    
    //定义6个看得见的按钮分列两边,另外还有头、左右脚、肚子、尾巴5个隐形按钮提供点击
    UIButton *btnCymbal=[[UIButton alloc]init];
    btnCymbal.frame=CGRectMake(5, 260, 60, 60);
    [btnCymbal setImage:[UIImage imageNamed:@"cymbal.png"] forState:UIControlStateNormal];
    [btnCymbal addTarget:self action:@selector(cymbalClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnCymbal];
    
    UIButton *btnDrink=[[UIButton alloc]init];
    btnDrink.frame=CGRectMake(5, 340, 60, 60);
    [btnDrink setImage:[UIImage imageNamed:@"drink.png"] forState:UIControlStateNormal];
    [btnDrink addTarget:self action:@selector(drinkClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnDrink];

    UIButton *btnEat=[[UIButton alloc]init];
    btnEat.frame=CGRectMake(5, 420, 60, 60);
    [btnEat setImage:[UIImage imageNamed:@"eat.png"] forState:UIControlStateNormal];
    [btnEat addTarget:self action:@selector(eatClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnEat];

    UIButton *btnFart=[[UIButton alloc]init];
    btnFart.frame=CGRectMake(255, 260, 60, 60);
    [btnFart setImage:[UIImage imageNamed:@"fart.png"] forState:UIControlStateNormal];
    [btnFart addTarget:self action:@selector(fartClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnFart];

    UIButton *btnPie=[[UIButton alloc]init];
    btnPie.frame=CGRectMake(255, 340, 60, 60);
    [btnPie setImage:[UIImage imageNamed:@"pie.png"] forState:UIControlStateNormal];
    [btnPie addTarget:self action:@selector(pieClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnPie];

    UIButton *btnScratch=[[UIButton alloc]init];
    btnScratch.frame=CGRectMake(255, 420, 60, 60);
    [btnScratch setImage:[UIImage imageNamed:@"scratch.png"] forState:UIControlStateNormal];
    [btnScratch addTarget:self action:@selector(scratchClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnScratch];
    
    UIButton *btnLeftFoot=[[UIButton alloc]init];
    btnLeftFoot.frame=CGRectMake(115, 455, 40, 25);
    [btnLeftFoot addTarget:self action:@selector(leftFootClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnLeftFoot];
    
    UIButton *btnRightFoot=[[UIButton alloc]init];
    btnRightFoot.frame=CGRectMake(160, 455, 40, 25);
    [btnRightFoot addTarget:self action:@selector(rightFootClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnRightFoot];
    
    UIButton *btnHead=[[UIButton alloc]init];
    btnHead.frame=CGRectMake(80, 90, 160, 160);
    [btnHead addTarget:self action:@selector(headClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnHead];
    
    UIButton *btnStomach=[[UIButton alloc]init];
    btnStomach.frame=CGRectMake(100, 330, 120, 120);
    [btnStomach addTarget:self action:@selector(stomachClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnStomach];
    
    UIButton *btnTail=[[UIButton alloc]init];
    btnTail.frame=CGRectMake(216, 380, 30, 80);
    [btnTail addTarget:self action:@selector(tailClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnTail];

    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

-(void)cymbalClick{
    //先创建一个可变数组,并用for循环添加图片,因为图片太多,一个个添加会死人的。
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<13; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"cymbal_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    //把这个图片数组赋值给我们之前定义的UIImageView对象imgView1
    self.imgView1.animationImages=muArr1;
    //以下是设置帧动画的播放时间和播放次数,最后记得开始这个动画,其他类似相同
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=muArr1.count*0.1;
    [self.imgView1 startAnimating];
}
-(void)drinkClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<81; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"drink_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=muArr1.count*0.1;
    [self.imgView1 startAnimating];
}
-(void)eatClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<40; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"eat_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)fartClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<28; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"fart_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)pieClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<24; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"pie_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)scratchClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<56; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"scratch_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)leftFootClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<30; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"footLeft_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)rightFootClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<30; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"footRight_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)headClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<81; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"knockout_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)stomachClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<34; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"stomach_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}
-(void)tailClick{
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<26; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"angry_%02d.jpg",i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}

@end

(2)补全漏洞:动画一个动画的时候点击另一个按钮失效,即保证播放完当前动画才能操作下一个。

即,在每一个点击事件的方法代码中添加如下,判断这UIImageView的对象imaView1如果正在播放,那么直接返回,不做任何操作,否则,执行一次动画操作。

[self playTomWithName:@"stomach" Number:34];

(3)合并代码:相同的直接合并,不同的用参数传递来调整,即把11个点击方法重写成如下:

-(void)playTomWithName:(NSString *)name Number:(int)number{
    if (self.imgView1.isAnimating) return;
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<number; i++) {
        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"%@_%02d.jpg",name,i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}

-(void)cymbalClick{
    [self playTomWithName:@"cymbal" Number:13];
}
-(void)drinkClick{
    [self playTomWithName:@"drink" Number:81];
}
-(void)eatClick{
    [self playTomWithName:@"eat" Number:40];
}
-(void)fartClick{
    [self playTomWithName:@"fart" Number:28];
}
-(void)pieClick{
    [self playTomWithName:@"pie" Number:24];
}
-(void)scratchClick{
    [self playTomWithName:@"scratch" Number:56];
}
-(void)leftFootClick{
    [self playTomWithName:@"footLeft" Number:30];
}
-(void)rightFootClick{
    [self playTomWithName:@"footRight" Number:30];
}
-(void)headClick{
    [self playTomWithName:@"knockout" Number:81];
}
-(void)stomachClick{
    [self playTomWithName:@"stomach" Number:34];
}
-(void)tailClick{
    [self playTomWithName:@"angry" Number:26];
}

(4)内存管理,imageNamed加载图像的弊端。

我们运行上述代码后,发现,多点击几次不同的按钮播放动画后,内存使用急剧上升。主要原因在于imageNamed加载图片的方法:它加载完图片后都存放在内存中,方便下次使用,所以内存中不断新增图片,会很大,在手机上会出现因为内存过载而闪退的现象。

点了几个按钮后,内存情况如下:


解决办法,用其他方法加载图片,用imageWithContentOfFile方法:这种方法不会将图片都加载到内存中,用完就释放了。

-(void)playTomWithName:(NSString *)name Number:(int)number{
    if (self.imgView1.isAnimating) return;
    NSMutableArray *muArr1=[[NSMutableArray alloc]init];
    for (int i=0; i<number; i++) {
        NSString *fileName=[NSString stringWithFormat:@"%@_%02d.jpg",name,i];
        NSBundle *bundle1=[NSBundle mainBundle];
        NSString *path=[bundle1 pathForResource:fileName ofType:nil];
        UIImage *tmpImg=[UIImage imageWithContentsOfFile:path];
        
//        UIImage *tmpImg=[UIImage imageNamed:[NSString stringWithFormat:@"%@_%02d.jpg",name,i]];
        [muArr1 addObject:tmpImg];
    }
    self.imgView1.animationImages=muArr1;
    self.imgView1.animationRepeatCount=1;
    self.imgView1.animationDuration=1;
    [self.imgView1 startAnimating];
}


但是,我们发现,内存使用仍然很高,这是因为我们点击按钮播放一组动画后,有self.imgView1.animationImages=muArr1;,这个muArr1没有被释放仍然存在,所以它animationImages里的所有图片也还在。

只是不会把所有的都加载,只是当前播放的那组图片会加载进来而已。


(5)再一次清除内存——再播放完动画后清除内存(其实也可以叫缓存)。

核心在于:要想清除缓存,只需要把self.imgView1.animationImages=nil;那么就没有强指针指向muArr1,muArr1就会被回收。

但,我们不能直接用上述语句,因为使用上述语句,会发现,动画还没有播放就已经被停止了,因为动画播放有一个时间持续,而执行上述的代码只需要极短时间。

所以?我们需要延迟执行上面的语句。延迟多少时间?比动画播放时间长1秒钟,差不多。


除了self.imgView1.animationImages=nil;之外,还可以利用方法进行nil赋值,语句是[self.imgView1 setAnimationImages:nil];

而延迟执行一个方法,正好有performSelector...afterDelay这个方法,所以:

    double delayNum=(double)self.imgView1.animationDuration+1.0;
    [self.imgView1 performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:delayNum];


(6)关于图片放在文件夹,文件夹直接拖放到Xcode中得问题。

直接的图片建议拖放到Images.xcassets中,但是如果是一个文件夹,不建议直接拖放到Images.xcassets,因为用mainBundle查找文件时查找不到。建议拖放到Supporting Files中。从两边文件夹颜色可分辨。总的来说:

——黄颜色的那个文件夹其实不能称之为文件夹,我们可以叫它组,它只是把图片分组而已,因为图片全部都被统一放在mainBudle中,而不是在这些“文件夹”中。

——蓝颜色的那个文件夹就是真正的文件夹,它是一种路径,如果要访问它里面的图片,那么我们需要写出这个文件夹。所以对我们用mainBundle查找文件十分不方便。



(7)补充:文档注释的写法

文档注释和普通注释不一样,文档注释更强大,不仅方便阅读,而且还提供动态输入时候的提示,即如下效果:


要实现这个效果,注释的格式如下,不管哪种方式,都是前面是/**后面是*/:

//写在一行
/**HELLO,这里是文档注释*/

//写在多行
/**
 HELLO,这里是文档注释
 */

不要和普通注释的/* */混淆。

【iOS开发-45】Tom猫案例:动画、imageNamed与imageWithContentOfFile对内存影响、图片文件夹放哪儿以及文档注释