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; DR

あなたのクラッシュはぶら下がりから来てい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. その画像にそのプリミティブなバッキングストア( )を要求すると、そのストアのメモリへの非所有CGImage参照を返すことで応答します。これはObjective-Cオブジェクトではないことに注意してください。
  3. スプライトレイヤーを作成するための引数として、そのバッキングストアへの非所有参照を使用します。

手動参照カウントでは、ステップ1のオブジェクトは、最も近い囲んでいる自動解放プールに存在します。コードの近くには1つも表示されないため、そのオブジェクトは、の実装の範囲よりも長持ちすることが暗黙的viewDidAppear:に保証されています。

ARCでは、そうではありません。
ステップ1で返されたオブジェクトは、ステップ2の後で参照されることはないため、そのオブジェクト、そのオブジェクトへの最後の参照であるステップ2の後で任意の行から消えることがあります。また、バッキングストアはObjective-Cオブジェクトではないため、後で明示的に関心を主張することなく(CGImageRetainたとえば、を通じて)参照するため、が無効になるとすぐに無効にUIImageなります

ARCでは、コンパイラはメソッド、、、および定数と機能的に同等であるが一定特殊関数への呼び出しを挿入するため、冗長な保持/(自動)解放ペアの一部を最適化して、メッセージングのオーバーヘッドと特定のライフタイムを最小限に抑えることができますオブジェクト。retainreleaseautorelease

ただし、これらをどれだけ積極的に最適化するかは、実装の詳細であり、多くのパラメーターに依存します。したがってlayerWithImage:sampleSize:、(でCGImageRefを要求する)を呼び出すと、コンパイラがコードをどれだけ積極的に最適化したかに基づいてself.contents = (__bridge id)img;、最初に渡すパラメータが無効なメモリへのポインタである場合とそうでない場合があります。

于 2012-10-13T13:40:03.357 に答える