• 创建一个名为JS与OC交互Demo的iOS工程。然后在storyboard添加一个UIWebVeiw,并设置上下左右约束为0,背景设为白色。
  • 刚才创建的webView作为ViewController的属性。用webView加载百度界面

runloop会运行在不同的mode, 简单来说有以下两种mode

occurs when you try and display a new viewcontroller before the
current view controller is finished displaying.

JavaScriptCore是webkit的一个重要组成部分,主要是对js进行解析和提供执行环境。具体介绍请看这篇简书的文章:JavaScriptCore
使用

总之, 如果想要销毁NSTimer, 那么确定, 一定以及肯定要调用invalidate方法

所以最后我们把web活动的数据请求放到了viewDidAppear里面,并做了些处理,这样问题就解决了。

schedules it on the current run loop in the default mode

Unknown class XXViewController in Interface Builder file.

  • 运行之后会弹出一个alert窗口,证明html上的js方法test1被调用了

    图片 1OC调用JS方法

  • 上面的代码都是无参的,加入是有参数的呢?我们在html文件中加一个js方法test3。方法有两个参数。

- viewWillDisappear:animated { [super viewWillDisappear:animated]; [_timer invalidate]; _timer = nil;}

Unbalanced calls to begin/end appearance transitions for …

  • js里面直接调用方法

  • js里面通过对象调用方法我们这里只讨论第一次比较简单的情况。

  • js方法没有参数的情况

2016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 72016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 82016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 7

“Unknown class XXViewController in Interface Builder file.” 问题处理

 <!-- 创建一个按钮,点击就调用 printHelloWorld() 函数 --> <button onclick="printHelloWorld()">在Xcode控制台打印HelloWold</button>

-webViewDidFinishLoad:(UIWebView *)webView { NSLog(@"网页加载完成"); // [self demo1];// [self demo2];// [self demo3]; [self demo4];}js调用OC方法- demo4 { //创建JSContext对象 JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; //注册printHelloWorld方法 context[@"printHelloWorld"] = ^() { NSLog(@"Hello World !"); };}

所以scheduledTimerWithTimeInterval创建的NSTimer在UI滚动时,
是不会被及时触发的, 因为此时NSTimer被加到了default mode

我们到stackoverflow上查了一下,有这么一段提示:

  • 上面是OC调用html中定义的JS方法。我们还可以在iOS程序里面写一段JS代码来调用。

创建NSTimer的不常用方法是

我们的某个业务有这么一个需求,进入一个列表后需要立马又push一个web页面,做一些活动的推广。在iOS
8上,我们的实现是一切OK的;但到了iOS
7上,就发现这个web页面push不出来了,同时控制台给了一条警告消息,即如下:

其实我是不想谈runloop的(因为理解不深, 所以怕误导人民群众),
但是这里不得不解释下了

意思是说在当前视图控制器完成显示之前,又试图去显示一个新的视图控制器。

<body> <br /><br /><br /><br /> <!-- 创建一个按钮,点击就调用 printHelloWorld() 函数 --> <button onclick="printHelloWorld()">在Xcode控制台打印HelloWold</button> <br/> <!-- 创建一个按钮,点击就调用printAandB()方法 --> <button onclick="printAandB('我是A','我是B')">打印参数A和参数B</button></body>

各位请注意, 创建NSTimer和销毁NSTimer后,
ViewController(就是这里的self)引用计数的变化

关于Unbalanced calls to begin/end appearance transitions for …问题的处理

图片 2

答案是: 当然不可以! (详见上述的结论, “总之, 巴拉巴拉…”)

最近在静态库中写了一个XXViewController类,然后在主工程的xib中,将xib的类指定为XXViewController,程序运行时,报了如下错误:

<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8"> <title>JS与OC交互</title> <!-- 定义js函数 --> <script type="text/javascript"> function test1() { alert("我是被OC调用的js方法") } </script> </head><body> <br /><br /><br /><br /> <!-- 创建一个按钮,点击就调用 printHelloWorld() 方法 --> <button onclick="printHelloWorld()">在Xcode控制台打印HelloWold</button></body></html>

用invalidate方法啊, 好像还有个fire方法, 实在不行直接将NSTimer对象置nil,
这样iOS系统就帮我们销毁了

其实这个问题与Interface
Builder无关,最直接的原因还是相关的symbol没有从静态库中加载进来。这种问题的处理就是在Target的”Build
Setting”–>“Other Link Flags”中加上”-all_load
-ObjC”这两个标识位,这样就OK了。

图片 3OC调用有多个参数的JS方法

不对! 移除NSTimer后dealloc就愉快滴走了起来, 难道NSTimer的用法一直都不对?

在这种情况下,点击导航栏中的返回按钮时,直接显示一个黑屏。

图片 4加载本地html

答案, 就不贴了, 相信你肯定知道的; 另外, 关于runloop,
计划后续会有单独的文章来详细讨论之

当几乎同时将两个视图控制器push到当前的导航控制器栈中时,或者同时pop两个不同的视图控制器,就会出现不确定的结果。所以我们应该确保同一时间,对同一个导航控制器栈只有一个操作,即便当前的视图控制器正在动画过程中,也不应该再去push或pop一个新的视图控制器。

运行结果为:

这里所说的iOS系统对ViewController的强引用,
不是指为了实现View显示的强引用,
而是指iOS为了实现NSTimer而对ViewController进行的额外强引用 (我去,
能不能不要这么拗口, 欺负我语文不好)

于是我们去排查代码,果然发现,在viewDidLoad里面去做了次网络请求操作,且请求返回后就去push这个web活动推广页。此时,当前的视图控制器可能并未显示完成(即未完成push操作)。

运行结果为:

是滴, 请注意这里的复数形式modes, 说明它不是一个mode, 它是mode的集合!

之前也遇到这个问题,但已记得不太清楚,所以又开始在stackoverflow上找答案。

  • js方法有参数如果js方法有参数,只要在block的参数里面写上参数,就可以使用这些参数了。为了测试,我们在htm里面加一个button
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];

Basically you are trying to push two view controllers onto the stack
at almost the same time.

- webViewDidFinishLoad:(UIWebView *)webView { NSLog(@"网页加载完成"); [self demo1];}- demo1 { //创建JSContext对象,(此处通过当前webView的键获取到jscontext) JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; //OC调用JS方法 [context evaluateScript:@"test1()"];}

图片 5how-to-user-nstimer-04.png

然后在ViewController.m里面调用

图片 6how-to-user-nstimer-03.png

然后写方法

  • fire

js调用OC方法分两种情况

综上所述, 销毁NSTimer的正确姿势应该是 (这句话我怎么看着这么眼熟, 是的,
这次真的结论了)

  • 先导入JavaScriptCore.framework。工程->Build Phases->Link Binary With Libraries->点击“+”->输入“JavaScriptCore”->Add

    图片 7导入JavaScriptCore.framework

  • 在ViewController.m导入头文件#import <JavaScriptCore/JavaScriptCore.h>

  • 写代码

This method is the only way to remove a timer from an NSRunLoop object

-webViewDidFinishLoad:(UIWebView *)webView { NSLog(@"网页加载完成"); // [self demo1]; [self demo2];}//OC调用有多个参数的JS方法- demo2 { //创建JSContext对象 JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; [context evaluateScript:@"test1(\"我是参数a\",\"我是参数b\")"];}

Stops the receiver from ever firing again and requests its removal from
its run loop

  • OC调用网页上的js方法
  • 网页js调用APP中的OC方法

是的, 曾经的我也是如此混沌滴这么认为着,
那么这几种方法是不是真的都可以销毁NSTimer呢?

  • 首先, 是创建NSTimer, 加入到runloop后,
    除了ViewController之外iOS系统也会强引用NSTimer对象

我们的html文件定义了一个js方法test1(),现在我们就来调用这个方法

NSRunLoopCommonModes又是什么鬼? 不是说好的只有两种mode么?

图片 8OC调用OC代码写出来的js方法

  • 当将NSTimer对象置nil时, 虽然解除了ViewController对NSTimer的强引用,
    但是iOS系统仍然对NSTimer和ViewController存在着强引用关系

知道了如何创建NSTimer后, 我们来说说如何销毁NSTimer,
销毁NSTimer不是很简单的么?

-webViewDidFinishLoad:(UIWebView *)webView { NSLog(@"网页加载完成"); // [self demo1];// [self demo2]; [self demo3];}//OC调用OC代码写出来的js方法- demo3 { //创建JSContext对象 JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; //JS代码 NSString *jsCode = [NSString stringWithFormat:@"alert(\"我是OC里面的js方法\")"]; //OC调用JS方法 [context evaluateScript:jsCode];}

他们的区别很简单:

发表评论

电子邮件地址不会被公开。 必填项已用*标注