Web屏幕崩溃的Mac屏幕保护程序

时间:2010-02-09 15:20:19

标签: objective-c cocoa macos screensaver

Hy大家,

我有一个用obj-c和cocoa制作的屏幕保护程序。除了以下内容,在OsX 10.6.2下一切正常。 在我的屏幕保护程序中,我有一个运行某些应用程序的WebView。当我尝试通过javascript调用我的objective-c应用程序(屏幕保护程序)时,出现错误,屏幕保护程序和系统首选项面板崩溃。

  

系统偏好[86666]   ***由于未捕获的异常'NSInvalidArgumentException'

而终止应用程序      

原因:' - [NSCFArray drain]:无法识别的选择器发送到实例0x20049b1e0'

     

***第一次投掷筹码:(
  0 CoreFoundation 0x00007fff8123a444 __exceptionPreprocess + 180
  1 libobjc.A.dylib 0x00007fff81f130f3 objc_exception_throw + 45
  2 CoreFoundation 0x00007fff812931c0 + [NSObject(NSObject)doesNotRecognizeSelector:] + 0
  3 CoreFoundation 0x00007fff8120d08f 转发 + 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_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 486
  )

我知道这看起来像是一些内存泄漏,但正如您将在代码中看到的那样,我几乎没有分配任何对象。

当我从屏幕保护程序系统首选项中使用“测试”按钮启动屏幕保护程序时,才会发生这种情况。 当我通过终端启动屏幕保护程序或它自动启动时,相同的操作(从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 个答案:

答案 0 :(得分:4)

不知何故,NSCFArray(NSMutableArray)正在发送一条“排泄”消息,该消息意味着NSAutoreleasePool。

通过实现NSMutableArray的drain方法,您可以获得有关数组内容的更多信息,因此您可以捕获现在识别的选择器并打印出数组对象的内容。尝试在代码中的某处添加:

@interface NSMutableArray (drain)

- (void) drain;

@end

@implementation NSMutableArray (drain)

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

@end

如果您没有在控制台中看到任何消息,请尝试将上述代码中的“NSMutableArray”更改为“NSObject”。

答案 1 :(得分:0)

要解决问题,您是否尝试不发布WebView?

另外,可能先将WebView的委托设置为nil,然后再发布它?

答案 2 :(得分:0)

需要注意的一点是,当您通过System Prefs中的“Test”按钮启动屏幕保护程序时,您实际上有两个屏幕保护程序视图实例在不同线程上的同一进程的地址空间中运行。一个(使用isPreview == YES)是SysPrefs窗口中的小预览(即使启动全屏版本也会继续运行),另一个是全屏版本。它们都在SysPrefs.app进程中运行。因此,您必须小心检查所有通知/等。查看它们是否来自您期望的视图实例。

通过快速浏览一下您发布的代码,我没有看到任何明显的问题,但它可能在其他地方。你在任何地方使用通知吗?

我在http://github.com/kelan/WikiWalker的github上放了一个类似的webview-in-a-screensaver项目,我最初有一些类似的问题(虽然我没有使用任何javascript的东西)。这不是完美的代码,但可能有所帮助。我还做了一些技巧,将通知转发到主线程(用于绘图)中。请参阅WWScreenSaverView的“线程通知支持”部分。{h,m}。

答案 3 :(得分:0)

要尝试的东西:

  • 打开终端窗口并输入以下行以使用NSZombieEnabled运行系统偏好设置:

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

  • 执行导致崩溃的步骤。

  • 运行控制台应用,将右上角的过滤器设置为“系统偏好设置”,然后查找NSZombie消息。

希望这有帮助!