1

我正在尝试使用 CAlayers 制作逐帧动画。我正在使用本教程http://mysterycoconut.com/blog/2011/01/cag1/执行此操作,但一切都适用于禁用 ARC,当我尝试使用 ARC 重写代码时,它可以在模拟器上完美运行,但在设备上我有一个坏的存取存储器。

层类接口

#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>

@interface MCSpriteLayer : CALayer {
    unsigned int sampleIndex;
}

// SampleIndex needs to be > 0
@property (readwrite, nonatomic) unsigned int sampleIndex; 

// For use with sample rects set by the delegate
+ (id)layerWithImage:(CGImageRef)img;
- (id)initWithImage:(CGImageRef)img;

// If all samples are the same size 
+ (id)layerWithImage:(CGImageRef)img sampleSize:(CGSize)size;
- (id)initWithImage:(CGImageRef)img sampleSize:(CGSize)size;

// Use this method instead of sprite.sampleIndex to obtain the index currently displayed on screen
- (unsigned int)currentSampleIndex; 


@end

层类实现

@implementation MCSpriteLayer

@synthesize sampleIndex;

- (id)initWithImage:(CGImageRef)img;
{
    self = [super init];
    if (self != nil)
    {
        self.contents = (__bridge id)img;
        sampleIndex = 1;
    }

    return self;
}

+ (id)layerWithImage:(CGImageRef)img;
{
    MCSpriteLayer *layer = [(MCSpriteLayer*)[self alloc] initWithImage:img];
    return layer ;
}


- (id)initWithImage:(CGImageRef)img sampleSize:(CGSize)size;
{
    self = [self initWithImage:img];  // IN THIS LINE IS BAD ACCESS
    if (self != nil)
    {
        CGSize sampleSizeNormalized = CGSizeMake(size.width/CGImageGetWidth(img), size.height/CGImageGetHeight(img));
        self.bounds = CGRectMake( 0, 0, size.width, size.height );
        self.contentsRect = CGRectMake( 0, 0, sampleSizeNormalized.width, sampleSizeNormalized.height );
    }

    return self;
}


+ (id)layerWithImage:(CGImageRef)img sampleSize:(CGSize)size;
{
    MCSpriteLayer *layer = [[self alloc] initWithImage:img sampleSize:size];
    return layer;
}


+ (BOOL)needsDisplayForKey:(NSString *)key;
{
    return [key isEqualToString:@"sampleIndex"];
}

// contentsRect or bounds changes are not animated
+ (id < CAAction >)defaultActionForKey:(NSString *)aKey;
{
    if ([aKey isEqualToString:@"contentsRect"] || [aKey isEqualToString:@"bounds"])
        return (id < CAAction >)[NSNull null];

    return [super defaultActionForKey:aKey];
}


- (unsigned int)currentSampleIndex;
{
    return ((MCSpriteLayer*)[self presentationLayer]).sampleIndex;
}


// Implement displayLayer: on the delegate to override how sample rectangles are calculated; remember to use currentSampleIndex, ignore sampleIndex == 0, and set the layer's bounds
- (void)display;
{
    if ([self.delegate respondsToSelector:@selector(displayLayer:)])
    {
        [self.delegate displayLayer:self];
        return;
    }

    unsigned int currentSampleIndex = [self currentSampleIndex];
    if (!currentSampleIndex)
        return;

    CGSize sampleSize = self.contentsRect.size;
    self.contentsRect = CGRectMake(
        ((currentSampleIndex - 1) % (int)(1/sampleSize.width)) * sampleSize.width, 
        ((currentSampleIndex - 1) / (int)(1/sampleSize.width)) * sampleSize.height, 
        sampleSize.width, sampleSize.height
    );
}


@end

我在 viewDidAppear 上创建图层并通过单击按钮开始动画,但是在初始化之后我得到了一个错误的访问错误

    -(void)viewDidAppear:(BOOL)animated
{

    [super viewDidAppear:animated];
    NSString *path = [[NSBundle mainBundle] pathForResource:@"mama_default.png" ofType:nil];
    CGImageRef richterImg = [UIImage imageWithContentsOfFile:path].CGImage;
    CGSize fixedSize = animacja.frame.size;
    NSLog(@"wid: %f, heigh: %f", animacja.frame.size.width, animacja.frame.size.height);
    NSLog(@"%f", animacja.frame.size.width);

    richter = [MCSpriteLayer layerWithImage:richterImg sampleSize:fixedSize];
    animacja.hidden = 1;
    richter.position = animacja.center;

    [self.view.layer addSublayer:richter];
}

-(IBAction)animacja:(id)sender
{
    if ([richter animationForKey:@"sampleIndex"])
    {NSLog(@"jest");
    }
    if (! [richter animationForKey:@"sampleIndex"])
    {
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"sampleIndex"];
    anim.fromValue = [NSNumber numberWithInt:0];
    anim.toValue = [NSNumber numberWithInt:22];
    anim.duration = 4;
    anim.repeatCount = 1;

    [richter addAnimation:anim forKey:@"sampleIndex"];
    }

}

你知道如何解决它吗?非常感谢。

4

1 回答 1

5

TL;博士

你的崩溃来自一个悬空CGImageRef

您可以通过例如更改线路来修复它

richter = [MCSpriteLayer layerWithImage:richterImg sampleSize:fixedSize];

richter = [MCSpriteLayer layerWithImage:[[UIImage imageNamed:@"mama_default"] CGImage] sampleSize:fixedSize];

或许多等效选项。

解释

为了解释发生了什么,我将稍微修改并注释您的实现viewWillAppear:

- (void)viewDidAppear:(BOOL)animated
{

    [super viewDidAppear:animated];

    // 1
    UIImage *richterImage = [UIImage imageNamed:@"mama_default"];
    // 2
    CGImageRef backingImage = richterImage.CGImage;
    CGSize fixedSize = animacja.frame.size;

    // 3
    richter = [MCSpriteLayer layerWithImage:backingImage sampleSize:fixedSize];
    animacja.hidden = 1;
    richter.position = animacja.center;

    [self.view.layer addSublayer:richter];
}

让我们来看看评论:

  1. 你向UIImage班级索要一张图片,它通过返回一个非拥有的引用来回答。
  2. 您向该图像询问其原始后备存储 (the ),它通过返回对该存储的内存的非拥有CGImage引用来回答。请注意,这不是一个 Objective-C 对象!
  3. 您使用对该后备存储的非拥有引用作为创建精灵图层的参数。

在手动引用计数下,步骤 1 中的对象位于最近的封闭自动释放池中。因为您在代码附近的任何地方都看不到任何对象,所以隐含地保证该对象比您的viewDidAppear:.

在 ARC 下,情况并非如此:
因为在步骤 1 中返回的对象在步骤 2 之后从未被引用,所以该对象可能会在步骤 2 之后离开任何行,这是对它的最后引用。并且因为后备存储不是一个 Objective-C 对象,所以在没有明确声明对它感兴趣的情况下(通过CGImageRetain例如)在任何时候引用它,一旦它变得无效,它就会UIImage变得无效

因为在 ARC 下,编译器会插入对功能等效的特殊函数的调用retainrelease并且是常量autorelease,它可以优化一些冗余的保留/(自动)释放对,从而最大限度地减少消息传递开销,以及某些特定的生命周期。对象。

然而,它对这些优化的积极程度是一个实现细节,并且取决于许多参数。因此,当您调用layerWithImage:sampleSize:, (它声称在 CGImageRef 中self.contents = (__bridge id)img;)时,您传入的第一个参数可能是也可能不是指向无效内存的指针,具体取决于编译器优化代码的积极程度。

于 2012-10-13T13:40:03.357 回答