1

みなさん、こんにちは。

obj-c と cocoa で作成したスクリーンセーバーがあります。以下を除いて、OsX 10.6.2 ではすべて正常に動作します。私のスクリーンセーバー内には、いくつかのアプリケーションが実行されている WebView があります。Objective-C アプリ (スクリーンセーバー) を JavaScript 経由で呼び出そうとすると、エラーが発生し、スクリーンセーバーとシステム設定パネルがクラッシュします。

システム環境設定 [86666] *** キャッチされない例外 'NSInvalidArgumentException' によりアプリを終了します

理由: '-[NSCFArray ドレイン]: 認識されないセレクターがインスタンス 0x20049b1e0 に送信されました'

*** Call stack at first throw:(
0 CoreFoundation 0x00007fff8123a444 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x00007fff81f130f3 objc_exception_throw + 45
2 CoreFoundation 0x00007fff812931c0 +[NSObject(NSObject) doesNotRecognizeSelector:] + 0
3 CoreFoundation 0x00007fff8120d08f forwarding + 751
4 CoreFoundation 0x00007fff812091d8 _CF_forwarding_prep_0 + 232 5 WebCore 0x00007fff847adee0 _ZN3JSC8Bindings12ObjcInstance10virtualEndEv + 48
6 WebCore 0x00007fff8470d71d _ZN3JSC16RuntimeObjectImp18getOwnPropertySlotEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 397
7 JavaScriptCore 0x00007fff80862b66 NK3JSC7JSValue3getEPNS_9ExecStateERKNSS_12Proty10 あたりの識別子 6
)

これはメモリ リークのように見えますが、コードでわかるように、実際にはほとんどオブジェクトが割り当てられていません。

これは、スクリーンセーバーのシステム設定から「テスト」ボタンを使用してスクリーンセーバーを起動した場合にのみ発生します。ターミナル経由でスクリーンセーバーを起動するか、自動的に起動する場合、同じアクション (javascript から obj-c を呼び出す) が正常に機能します。

たぶん、誰かがエラーの原因を知っているかもしれません。実装からのコードを次に示します。

@implementation ScreensaverView

- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {

    self = [super initWithFrame:frame isPreview:isPreview];

    if (self) {

        [self setAnimationTimeInterval:-1];
        [self setAutoresizesSubviews:YES];

        // ::::::::::::::::::::::: Init stuff ::::::::::::::::::    

        // init 
        quitFlag = false;
        previewMode = isPreview;

        // find out the path the screensaver bundle
        pMainBundle = [NSBundle bundleForClass:[self class]];
        pBundlePath = [pMainBundle bundlePath];

        // read Info.plist
        infoDict = [pMainBundle infoDictionary];
    }

    return self;
}

- (void)startAnimation
{   
    [super startAnimation];

    // combine: bundle path + filename for screensaver file 
    NSString *pathToScreensaver = [NSString stringWithString:pBundlePath];
    NSString *valueScreensaverFile;

    if(!previewMode)
    {
        valueScreensaverFile = [infoDict objectForKey:@"ScreensaverFile"];
    }
    else 
    {
        valueScreensaverFile = [infoDict objectForKey:@"PreviewFile"];
    }

    // add filename to bundle path
    pathToScreensaver = [pathToScreensaver stringByAppendingString:valueScreensaverFile];

    // complete NSURL to the screensaver file
    NSURL *screensaverUrl = [NSURL fileURLWithPath: pathToScreensaver];

    webView = [WebView alloc];
    [webView initWithFrame:[self frame]];
    [webView setDrawsBackground:NO];

    // delegation policy for interactive mode
    [webView setPolicyDelegate: self];
    [webView setUIDelegate:self];

    // load screensaver
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:screensaverUrl]];

    scriptObject = [webView windowScriptObject];
    [scriptObject setValue:self forKey:@"screensaver"];

    [self addSubview:webView];
}

- (void)stopAnimation
{   
    [[webView mainFrame] stopLoading];
    [webView removeFromSuperview];
    [webView release];
    [super stopAnimation];
}

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector 
{       
    if (selector == @selector(quitScreenSaver)) {
        return NO;
    }

    if(selector == @selector(gotoUrl:) ){
        return NO;
    }

    return YES;
}

+(NSString *)webScriptNameForSelector:(SEL)selector
{   
    if(selector == @selector(quitScreenSaver))
    {
        return @"quitNoOpen";
    }

    if(selector == @selector(gotoUrl:))
    {
        return @"openAndQuit";
    }

    return nil;
}

- (void) quitScreenSaver
{
    quitFlag = true;
    [super stopAnimation];
}

- (void) gotoUrl:(NSString *) destinationURL 
{   
    if(destinationURL == NULL)
    {
        return;
    }

    NSString * path    = destinationURL;
    NSURL    * fileURL = [NSURL URLWithString:path];
    [[ NSWorkspace sharedWorkspace ] openURL:fileURL];
    [self quitScreenSaver];
}

@end

いくつかの問題/解決策を確認するのに十分なコードであることを願っています. どんな答えでも本当に感謝しています。

4

4 に答える 4

4

どういうわけか、NSCFArray (NSMutableArray) に NSAutoreleasePool 向けの「ドレイン」メッセージが送信されています。

NSMutableArray のドレイン メソッドを実装することで、配列が何であるかについてもう少し情報を取得できる可能性があるため、現在認識されているセレクターをトラップして、配列オブジェクトの内容を出力できます。これをコードのどこかに追加してみてください:

@interface NSMutableArray (drain)

- (void) drain;

@end

@implementation NSMutableArray (drain)

- (void) drain
{
   NSLog(@"drain message received by object: %@", self);
}

@end

コンソールにメッセージが表示されない場合は、上記のコードの「NSMutableArray」を「NSObject」に変更してみてください。

于 2010-02-12T09:36:52.903 に答える
1

試すべきこと:

  • ターミナルウィンドウを開き、次の行を入力して、NSZombieEnabledでシステム環境設定を実行します。

env NSZombieEnabled=YES "/Applications/System Preferences.app/Contents/MacOS/System Preferences"

  • クラッシュにつながる手順を実行します。

  • コンソールアプリを実行し、右上のフィルターを[システム環境設定]に設定して、NSZombieメッセージを探します。

お役に立てれば!

于 2010-02-12T06:18:23.197 に答える
1

注意すべきことの 1 つは、システム設定の [テスト] ボタンを使用してスクリーンセーバーを起動すると、実際にはスクリーンセーバー ビューの 2 つのインスタンスが異なるスレッドの同じプロセスのアドレス空間で実行されていることです。1 つ (isPreview==YES の場合) は SysPrefs ウィンドウの小さなプレビュー (フルスクリーン バージョンが開始されても実行を継続します) で、もう 1 つはフルスクリーン バージョンです。どちらも SysPrefs.app プロセスで実行されています。そのため、すべての通知などを注意深く確認する必要があります。それらが期待するビューインスタンスから来ているかどうかを確認します。

投稿されたコードを一目見ただけでは明らかな問題は見られませんが、別の場所にある可能性があります。どこかで通知を使用していますか?

同様の webview-in-a-screensaver プロジェクトを github の http://github.com/kelan/WikiWalkerに配置しましたが、最初は同様の問題がいくつかありました (JavaScript のものは使用していませんでしたが)。完璧なコードではありませんが、役立つかもしれません。また、通知をメイン スレッド (描画用) に転送するためのいくつかのトリックも実行しました。WWScreenSaverView.{h,m} の「スレッド化された通知のサポート」の部分を参照してください。

于 2010-02-10T00:37:08.223 に答える
0

トラブルシューティングのために、WebView を解放しないようにしましたか?

また、最初にリリースする前に、WebView のデリゲートを nil に設定することもできますか?

于 2010-02-09T22:53:03.490 に答える