我的iPhone Objective-C代码如何在UIWebView中收到Javascript错误的通知?

Rob*_*ers 46 iphone webkit objective-c

我需要让我的iPhone Objective-C代码在UIWebView中捕获Javascript错误.这包括未捕获的异常,加载文件时的语法错误,未定义的变量引用等.

这适用于开发环境,因此它不需要是SDK-kosher.实际上,它只需要在模拟器上工作.

我已经发现使用了一些隐藏的WebKit技巧,例如将Obj-C对象暴露给JS并拦截警报弹出窗口,但是这个仍然在逃避我.

[注意:发布后我确实找到了一种使用调试委托的方法.有没有一种方法可以降低开销,使用错误控制台/ Web检查器?]

Rob*_*ers 28

我现在已经找到了一种在WebView中使用脚本调试器挂钩的方法(注意,不是UIWebView).我首先必须子类化UIWebView并添加如下方法:

- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject {
    // save these goodies
    windowScriptObject = newWindowScriptObject;
    privateWebView = webView;

    if (scriptDebuggingEnabled) {
        [webView setScriptDebugDelegate:[[YourScriptDebugDelegate alloc] init]];
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,您应该创建一个包含以下方法的YourScriptDebugDelegate类:

// in YourScriptDebugDelegate

- (void)webView:(WebView *)webView       didParseSource:(NSString *)source
 baseLineNumber:(unsigned)lineNumber
        fromURL:(NSURL *)url
       sourceId:(int)sid
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: called didParseSource: sid=%d, url=%@", sid, url);
}

// some source failed to parse
- (void)webView:(WebView *)webView  failedToParseSource:(NSString *)source
 baseLineNumber:(unsigned)lineNumber
        fromURL:(NSURL *)url
      withError:(NSError *)error
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: called failedToParseSource: url=%@ line=%d error=%@\nsource=%@", url, lineNumber, error, source);
}

- (void)webView:(WebView *)webView   exceptionWasRaised:(WebScriptCallFrame *)frame
       sourceId:(int)sid
           line:(int)lineno
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: exception: sid=%d line=%d function=%@, caller=%@, exception=%@", 
          sid, lineno, [frame functionName], [frame caller], [frame exception]);
}
Run Code Online (Sandbox Code Playgroud)

这可能会对运行时产生很大的影响,因为调试委托还可以提供调用进入和退出堆栈帧以及执行每行代码的方法.

有关WebScriptDebugDelegate的Objective-C++定义,请参阅http://www.koders.com/noncode/fid7DE7ECEB052C3531743728D41A233A951C79E0AE.aspx.

那些其他方法:

// just entered a stack frame (i.e. called a function, or started global scope)
- (void)webView:(WebView *)webView    didEnterCallFrame:(WebScriptCallFrame *)frame
      sourceId:(int)sid
          line:(int)lineno
   forWebFrame:(WebFrame *)webFrame;

// about to execute some code
- (void)webView:(WebView *)webView willExecuteStatement:(WebScriptCallFrame *)frame
      sourceId:(int)sid
          line:(int)lineno
   forWebFrame:(WebFrame *)webFrame;

// about to leave a stack frame (i.e. return from a function)
- (void)webView:(WebView *)webView   willLeaveCallFrame:(WebScriptCallFrame *)frame
      sourceId:(int)sid
          line:(int)lineno
   forWebFrame:(WebFrame *)webFrame;
Run Code Online (Sandbox Code Playgroud)

请注意,这一切都隐藏在私有框架中,所以不要试图将它放在您提交给App Store的代码中,并为一些hackery做好准备以使其工作.


小智 13

我创建了一个很好的小插件类别,可以添加到您的项目中......它基于Robert Sanders解决方案.荣誉.

你可以在这里下载它:

UIWebView的+调试

这应该可以让你更容易调试UIWebView :)


Prc*_*ela 11

我使用了Robert Sanders提出的出色解决方案:我的iPhone Objective-C代码如何在UIWebView中收到Javascript错误的通知?

Webkit的钩子在iPhone上运行良好.而不是标准的UIWebView我分配派生的MyUIWebView.我还需要在MyWebScriptObjectDelegate.h中定义隐藏的类:

@class WebView;
@class WebFrame;
@class WebScriptCallFrame;

在ios sdk 4.1中的功能:

- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject 
Run Code Online (Sandbox Code Playgroud)

弃用,取而代之的是我使用的功能:

- (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame
Run Code Online (Sandbox Code Playgroud)

此外,我收到一些恼人的警告,如"NSObject可能无法响应-windowScriptObject",因为类接口是隐藏的.我忽略它们并且效果很好.


bob*_*obc 9

如果你有Safari v 6+(我不确定你需要什么iOS版本),那么在开发过程中有一种方法是使用Safari开发工具并通过它挂钩到UIWebView.

  1. 在Safari中:启用"开发"菜单(菜单栏中的"首选项">"高级">"显示开发"菜单)
  2. 通过电缆将手机插入计算机.
  3. 项目清单
  4. 加载应用程序(通过xcode或只是启动它)并转到要调试的屏幕.
  5. 回到Safari,打开"开发"菜单,在该菜单中查找设备的名称(我的名为iPhone 5),应该在User Agent下.
  6. 选择它,您应该会看到应用中当前可见的网络视图下拉列表.
  7. 如果屏幕上有多个webview,您可以尝试通过在开发菜单中滚动应用程序名称来区分它们.相应的UIWebView将变为蓝色.
  8. 选择应用程序的名称,打开开发窗口,您可以检查控制台.您甚至可以通过它发出JS命令.

  • 这应该是公认的答案,它是一个比其他人更好的解决方案:) (2认同)

psy*_*psy 8

直线前进方式:将此代码放在使用UIWebView的控制器/视图之上

#ifdef DEBUG
@interface DebugWebDelegate : NSObject
@end
@implementation DebugWebDelegate
@class WebView;
@class WebScriptCallFrame;
@class WebFrame;
- (void)webView:(WebView *)webView   exceptionWasRaised:(WebScriptCallFrame *)frame
       sourceId:(int)sid
           line:(int)lineno
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: exception: sid=%d line=%d function=%@, caller=%@, exception=%@", 
          sid, lineno, [frame functionName], [frame caller], [frame exception]);
}
@end
@interface DebugWebView : UIWebView
id windowScriptObject;
id privateWebView;
@end
@implementation DebugWebView
- (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame
{
    [sender setScriptDebugDelegate:[[DebugWebDelegate alloc] init]];
}
@end
#endif
Run Code Online (Sandbox Code Playgroud)

然后像这样实例化它:

#ifdef DEBUG
        myWebview = [[DebugWebView alloc] initWithFrame:frame];
#else
        myWebview = [[UIWebView alloc] initWithFrame:frame];
#endif
Run Code Online (Sandbox Code Playgroud)

使用#ifdef DEBUG确保它不会进入发布版本,但我建议您在不使用它时将其注释掉,因为它会对性能产生影响.感谢Robert Sanders和Prcela的原始代码

此外,如果使用ARC,您可能需要添加"-fno-objc-arc"以防止某些构建错误.

  • 您还可以使用webView:didClearWindowObject:forFrame:方法为UIWebView创建一个类别,而不必实例化不同的类类型. (2认同)

kdb*_*las 0

我在固件 1.x 中做到了这一点,但在 2.x 中没有做到。这是我在 1.x 中使用的代码,它至少应该对您有所帮助。

// Dismiss Javascript alerts and telephone confirms
/*- (void)alertSheet:(UIAlertSheet*)sheet buttonClicked:(int)button
{
    if (button == 1)
    {
        [sheet setContext: nil];
    }

    [sheet dismiss];
}*/

// Javascript errors and logs
- (void) webView: (WebView*)webView addMessageToConsole: (NSDictionary*)dictionary
{
    NSLog(@"Javascript log: %@", dictionary);
}

// Javascript alerts
- (void) webView: (WebView*)webView runJavaScriptAlertPanelWithMessage: (NSString*) message initiatedByFrame: (WebFrame*) frame
{
    NSLog(@"Javascript Alert: %@", message);

    UIAlertSheet *alertSheet = [[UIAlertSheet alloc] init];
    [alertSheet setTitle: @"Javascript Alert"];
    [alertSheet addButtonWithTitle: @"OK"];
    [alertSheet setBodyText:message];
    [alertSheet setDelegate: self];
    [alertSheet setContext: self];
    [alertSheet popupAlertAnimated:YES];
}
Run Code Online (Sandbox Code Playgroud)