为什么我放入session的值六级第一次过了第二次没过取不到,第二次取的却是六级第一次过了第二次没过的值?

网站的验证码第一次session为空,手动点击刷新验证码后session有值。但是页面加载却没有值? - 知乎有问题,上知乎。知乎作为中文互联网最大的知识分享平台,以「知识连接一切」为愿景,致力于构建一个人人都可以便捷接入的知识分享网络,让人们便捷地与世界分享知识、经验和见解,发现更大的世界。2被浏览<strong class="NumberBoard-itemValue" title="分享邀请回答赞同 添加评论分享收藏感谢收起写回答在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
session中没有写权限
分享到微博?
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。对于web应用集群的技术实现而言,最大的难点就是:如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块。要实现这一点, 大体上有两种方式:
一种是把所有Session数据放到一台服务器上或者数据库中,集群中的所有节点通过访问这台Session服务器来获取数据;
另一种就是在集群中的所有节点间进行Session数据的同步拷贝,任何一个节点均保存了所有的Session数据。
Tomcat集群session同步方案有以下几种方式:
1)使用tomcat自带的cluster方式,多个tomcat间自动实时复制session信息,配置起来很简单。但这个方案的效率比较低,在大并发下表现并不好。
2)利用nginx的基于访问ip的hash路由策略,保证访问的ip始终被路由到同一个tomcat上,这个配置更简单。但如果应用是某一个局域网大量用户同时登录,这样负载均衡就没什么作用了。
3)利用nginx插件实现tomcat集群和session同步,nginx-upstream-jvm-route-0.1.tar.gz,是一个Nginx的扩展模块,用来实现基于Cookie的Session Sticky的功能。
4)利用memcached实现(MSM工具)。memcached存储session,并把多个tomcat的session集中管理,前端在利用nginx负载均衡和动静态资源分离,在兼顾系统水平扩展的同时又能保证较高的性能。
5)利用redis实现。使用redis不仅仅可以将缓存的session持久化,还因为它支持的单个对象比较大,而且数据类型丰富,不只是缓存 session,还可以做其他用途,可以一举几得。
6)利用filter方法实现。这种方法比较推荐,因为它的服务器使用范围比较多,不仅限于tomcat ,而且实现的原理比较简单容易控制。
7)利用terracotta服务器共享session。这种方式配置比较复杂。
在Tomcat集群中,当一个节点出现故障,虽然有高可用集群来负责故障转移,但用户的session信息如何保持呢?
下面介绍第4种方案,session复制同步使用MSM(Memcache-Session-Manager),即利用MSM+Memcached做Session共享。
MSM介绍:(详细介绍可以参考:)
MSM是一个高可用的Tomcat Session共享解决方案,除了可以从本机内存快速读取Session信息(仅针对黏性Session)外,还可使用Memcached存取Session,以实现高可用。
传统tomcat集群,会话复制随着结点数增多,扩展性成为瓶颈。MSM使用memcached完成统一管理tomcat会话,避免tomcat结点间过多会话复制。
MSM利用Value(Tomcat 阀)对Request进行跟踪。Request请求到来时,从memcached加载session,Request请求结束时,将tomcat session更新至memcached,以达到session共享之目的,支持sticky和non-sticky模式:
sticky : 会话粘连模式(黏性session)。客户端在一台tomcat实例上完成登录后,以后的请求均会根据IP直接绑定到该tomcat实例。
no-sticky:会话非粘连模式(非粘性session)。客户端的请求是随机分发,多台tomcat实例均会收到请求。
在进行环境部署之前,要对cookie和session的工作机制非常了解,如果不了解其中的原理且只是机械性地去按照参考文档部署,那么这是毫无意义的。
a)cookie是怎么工作的?
加入我们创建了一个名字为login的Cookie来包含访问者的信息,创建Cookie时,服务器端的Header如下面所示,这里假设访问者的注册名是“wangshibo”,同时还对所创建的Cookie的属性如path、domain、expires等进行了指定。
Set-Cookie:login=path=/;domain=msn.
expires=Monday,01-Mar-99 00:00:01 GMT
上面这个Header会自动在浏览器端计算机的Cookie文件中添加一条记录。浏览器将变量名为“login”的Cookie赋值为“wangshibo”。
注意,在实际传递过程中这个Cookie的值是经过了URLEncode方法的URL编码操作的。 这个含有Cookie值的HTTP Header被保存到浏览器的Cookie文件后,Header就通知浏览器将Cookie通过请求以忽略路径的方式返回到服务器,完成浏览器的认证操作。
此外,我们使用了Cookie的一些属性来限定该Cookie的使用。例如Domain属性能够在浏览器端对Cookie发送进行限定,具体到上面的例子,该Cookie只能传到指定的服务器上,而决不会跑到其他的Web站点上去。Expires属性则指定了该Cookie保存的时间期限,例如上面的Cookie在浏览器上只保存到日1秒。 当然,如果浏览器上Cookie太多,超过了系统所允许的范围,浏览器将自动对它进行删除。至于属性Path,用来指定Cookie将被发送到服务器的哪一个目录路径下。
说明:浏览器创建了一个Cookie后,对于每一个针对该网站的请求,都会在Header中带着这个Cookie;不过,对于其他网站的请求Cookie是绝对不会跟着发送的。而且浏览器会这样一直发送,直到Cookie过期为止。
b)session是如何工作的?
由于http是无状态的协议,你访问了页面A,然后再访问B页面,http无法确定这2个访问来自一个人,因此要用cookie或session来跟踪用户,根据授权和用户身份来 显示不同的页面。比如用户A登陆了,那么能看到自己的个人信息,而B没登陆,无法看到个人信息。还有A可能在购物,把商品放入购物车,此时B也有这个过程, 你无法确定A,B的身份和购物信息,所以需要一个session ID来维持这个过程。
cookie是服务器发给客户端并保持在客户端的一个文件,里面包含了用户的访问信息(账户密码等),可以手动删除或设置有效期,在下次访问的时候,会返给服务器。
注意:cookie可以被禁用,所以要想其他办法,这就是session。cookie数据存放在客户的浏览器上,session数据放在服务器上。cookie同时也是session id的载体,cookie保存session id。另外:cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。session是服务器端缓存,cookie是客户端缓存。所以建议:将登陆信息等重要信息存放为session;其他信息如果需要保留,可以放在cookie中,
比如:你去商场购物,商场会给你办一张会员卡,下次你来出示该卡,会有打折优惠,该卡可以自己保存(cookie),或是商场代为保管,由于会员太多,个人需要保存卡号信息(session ID)。
为什么要持久化session(共享session)?
因为:在客户端每个用户的Session对象存在Servlet容器中,如果Tomcat服务器重启或者宕机的话,那么该session就会丢失,而客户端的操作会由于session丢失而造成数据丢失;如果当前用户访问量巨大,每个用户的Session里存放大量数据的话,那么就很占用服务器大量的内存,进而致使服务器性能受到影响。
可以使用数据库持久化session,分为物理数据库和内存数据库。物理数据库备份session,由于其性能原因,不推荐;内存数据库可以使用redis和memcached,这里介绍memcached的方法。
MSM工作原理:
a)Sticky Session(黏性) 模式下的工作原理:
Tomcat本地Session为主Session,Memcached 中的Session为备Session。
安装在Tomcat上的MSM使用本机内存保存Session,当一个请求执行完毕之后,如果对应的Session在本地不存在(即某用户的第一次请求),则将该Session复制一份至Memcached;当该Session的下一个请求到达时,会使用Tomcat的本地Session,请求处理结束之后,Session的变化会同步更新到 Memcached,保证数据一致。
当集群中的一个Tomcat挂掉,下一次请求会被路由到其他Tomcat上。负责处理此此请求的Tomcat并不清楚Session信息,于是从Memcached查找该Session,更新该Session并将其保存至本机。此次请求结束,Session被修改,送回Memcached备份。
b)Non-sticky Session (非黏性)模式下的工作原理(记住:多台tomcat集群或多个tomcat实例时需要选择Non-Sticky模式,即sticky="false"):
Tomcat本地Session为中转Session,Memcached为主备Session。
收到请求,加载备Session至本地容器,若备Session加载失败则从主Session加载;
请求处理结束之后,Session的变化会同步更新到Memcached,并清除Tomcat本地Session。
memcached实现session共享问题总结
使用Memcache实现Session共享(单点登录)的原理
java集群中session共享有多重方案,比如可以使用tomcat的session复制的功能来实现集群session共享,但这样做的缺点也很明显,每台服务器上都需要存相同的session,性能随着服...
业务场景描述:
有这样的业务架构,一台nginx将客户端请求分发到2台tomcat中,现在的问题是当tomcat1挂掉之后,nginx将请求转发到tomcat2中,此时tomcat2会要求用户重新登入...
安装完centos后
一、更换国内源sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup...
session共享的应用场景
大型分布式网站,通常使用的架构是,硬件均衡设备(cisco以太网通道、windows nlb技术、linux lvs技术、f5等负载均衡器)——&多台Squid、ngin...
由于tomcat的并发数瓶颈问题,可以说使用tomcat的web应用,几乎都存在session不同步问题。
借鉴网上的资料,我也找时间实验一把。
文中涉及的软件下载和安装,一一略过,想必大家也没必...
nginx+tomcat7+memcache集群,使用memcached-session-manager实现session共享方案
服务器说明:192.168.1.8 tomcat1 memcached(虚拟机1)192.168.1.9 tomcat2(虚拟机2)192.168.1.200 nginx (本机)对于tomcat+n...
我们系统经常要保存用户登录信息,有Cookie和Session机制,Cookie客户端保存用户信息,Session在服务端保存用户信息,如果浏览器不支持Cookie或者用户把Cookie禁掉了,Coo...
没有更多推荐了,PHP经典面试题【优就业】
  经典面试题
&1、什么是面向对象?主要特征是什么?
  面向对象是程序的一种设计方式,它利于提高程序的重用性,使程序结构更加清晰。主要特征:封装、继承、多态。
  2、SESSION 与 COOKIE 的区别是什么,请从协议,产生的原因与作用说明?
  http 无状态协议,不能区分用户是否是从同一个网站上来的,同一个用户请求不同的页面不能看做是同一个用户。
  SESSION 存储在服务器端,COOKIE 保存在客户端。Session 比较安全,cookie
用某些手段可以修改,不安全。Session 依赖于 cookie 进行传递。
  禁用 cookie 后,session 不能正常使用。Session
的缺点:保存在服务器端,每次读取都从服务器进行读取,对服务器有资源消耗。Session
保存在服务器端的文件或数据库中,默认保存在文件中,文件路径由 php 配置文件的 session.save_path
指定。Session 文件是公有的。
  3、不使用 cookie 向客户端发送一个 cookie.
  理解:session_start()开启时,生成一个常量SID,当 COOKIE 开启时,这个常量为空,当 COOKIE
关闭时,这个常量中存储了 PHPSESSID 的值。通过在 URL 后加一个 SID 参数来传递 SESSIONID
的值,从而使客户端页面可以使用SESSION里面的值。当客户端开启COOKIE和服务器端开启 SESSION
时。浏览器第一次请求,服务器会向浏览器端发送一个 COOKIE 里面存储 SESSIONID. 当浏览器第二次请求时,会把已存在的
COOKIE 一起提交到服务器端。
  4、HTTP 状态中 302、403、 500、200、404、502 代码含义?
  一二三四五原则: 一. 消息系列 二. 成功系列三. 重定向系列 四. 请求错误系列 五. 服务器
  端错误系列 302:临时转移成功,请求的内容已转移到新位置 403:禁止访问 500:服务器内部错误 401 代表未授权。
200 是请求成功,404 是文件未找到,502 是服务器内部错误。
  5、请写出数据类型(int char varchar datetime text)的意思;请问 varchar 和
char 有什么区别?
  char 是固定长度的字符类型,分配多少空间,就占用多长空间。 Varchar
是可变长度的字符类型,内容有多大就占用多大的空间,能有效节省空间。Varchar是变长,节省存储空间,char 是固定长度。查找效率要
char 型快,因为 varchar 是非定长,必须先查找长度,然后进行数据的提取,比 char
定长类型多了一个步骤,所以效率低一些
  6、表单中 get 与 post 提交方法的区别?
  1. get 是把参数数据队列加到提交表单的 ACTION 属性所指的 URL 中,值和表单内各个字段一一对应,在 URL
中可以看到。post 是通过 HTTP post 机制,将表单内各个字段与其内容放置在 HTML HEADER 内一起传送到
ACTION 属性所指的 URL 地址。用户看不到这个过程。
  2.对 于 get 方 式 , 服 务 器 端 用Request.QueryString 获取变量的值,对于 post
方式,服务器端用 Request.Form 获取提交的数据。
  3. get 传送的数据量较小,不能大于2KB。post
传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。 get 安全性非常低,post
安全性较高。
  7、include 与 require 的区别?
  1.include()在执行文件时每次都要进行读取和评估,require()文件只处理一次(实际上文件内容替换了
require()语句)
  2.require()通常放在 PHP
脚本程序的最前面,include()的使用和require()一样,一般放在流程控制的处理区段中,PHP 脚本文件读到
include()语句时,才将它包含的文件读进来,这种方式,可以把程序执行时的流程简单化
  3,require()和 include()语句是语言结构,不是真正的函数,可以像 PHP 的其他语言结构一样
  4,include_once()和
require_once()语句也是在脚本执行期间包括并运行指定文件,与include()require()唯一的区别是如果文件中的代码已经被包括了,则不会再次包括.
  5,require()包含文件失败,停止执行,给出错误(致命的),include()常用于动态包含,通常是自动加载的文件,即使加载出错,整个程序还是继续执行,一个页面声明,另一个页面调用,包函文件失败,继续向下执行,返回一条警告
  8、请说明 PHP 中传值与传引用的区别。什么时候传值什么时候传引用?
  按值传递:函数范围内对值的任何改变在函数外部都会被忽略
  按引用传递:函数范围内对值的任何改变在函数外部也能反映出这些修改
  优缺点:按值传递时,php
必须复制值。特别是对于大型的字符串和对象来说,这将会是一个代价很大的操作。按引用传递则不需要复制值,对于性能提高很有好处。
  9、什么是数据库索引,主键索引,唯一索引的区别,索引的缺点是什么?
  索引用来快速地寻找那些具有特定值的记录。
  主键索引和唯一索引的区别:主键是一种唯一性索引,但它必须指定为“PRIMARY
KEY”,每个表只能有一个主键。唯一索引索引列的所有值都只能出现一次,即必须唯一。
  索引的缺点:1、创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
  2、索引需要占用物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,需要的空间就会更大。
  3、当对表中 的数据进行增加、删除、修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
  10、web 应用中,数据库的读取频率远高于写入频率, 如何优化 MySQL 而应对此种情景?
  使用 memcache
缓存技术,将动态数据缓存到文件,访问动态页面时直接调用缓存文件,而不必重新访问数据库,这样就减少了查询数据库的次数。
  库读写服务器分开,使用多态服务器去处理数据库查询,使用较少的服务器去处理数据库的写入和修改。
  11、浏览器 IE 和非 IE 浏览器的划分,区别是什么?
  IE 浏览器指的是使用 IE 内核的浏览器,对一些 W3C 标准的网页代码的支持不是很好。
  非 IE 浏览器指的是没有使用 IE 内核的浏览器,对 W3C 标准的网页代码有很好的支持。
  12、PHP 字符串中单引号与双引号的区别?
  单引号不能解释变量,而双引号可以解释变量。
  单引号不能转义字符,在双引号中可以转义字符。
  13、MyISAM 和 InnoDB 的基本区别?索引结构如何实现?
  MyISAM 类型不支持事务处理等高级处理,而InnoDB 类型支持。MyISAM 类型的表强调的是性能,其执行速度比
InnoDB 类型更快,但是不提供事务支持,而 InnoDB 提供事务支持以及外部键等高级数据库功能。
  创建索引:alert table tablename add index (` 字段名`)
  14、数据库设计时,常遇到的性能瓶颈有哪些,常有的解决方案?
  瓶颈主要有:1、磁盘搜索 优化方法是:将数据分布在多个磁盘上2、磁盘读/写 优化方法是:从多个磁盘并行读写。3、CPU 周期
优化方法:扩充内存4、内存带宽
  15、了解 XSS 攻击吗? 如何防止 ?
  XSS 是跨站脚本攻击,首先是利用跨站脚本漏洞以一个特权模式去执行攻击者构造的脚本,然后利用不安全的 Activex
控件执行恶意的行为。
  使用 htmlspecialchars()函数对提交的内容进行过滤,使字符串里面的特殊符号实体化。
推荐阅读:
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。在 Swift 中使用 Watch Connectivity — Application Context
译者:Khala-wan;校对:Yousanflics,wongzigii;定稿:CMB
在 watchOS 1 时代,WatchKit Extension 位于已配对的 iOS 设备上,这使得宿主 APP 和 watch 之间的数据共享变得简单。类似偏好设置这种最简单的数据,只需要通过 App Groups 功能来存取 NSUserDefaults。目前在手机上留存的其他扩展程序和主 app 之间共享数据仍然应该使用这种方式,例如 Today View Extension,但它已不再适用于 watchOS 的 app。
幸运的是,苹果为我们提供了新的 API 来做这件事。相比 App Groups,Watch Connectivity 拥有更强大的功能。它不仅提供了你的 Apple Watch 和与其配对 iPhone 之间连接状态的更多信息,还允许它们之间进行交互消息和 3 种方式的后台传输,这些方式分别是:
Application Context
User Info Transfer
File Transfer
我们今天先讨论第一种方式:Application Context。
什么是 Application Context
假设你有个 watch app,它有一些可以在 iOS app 端设置的设置项,比如温度的显示单位是摄氏度还是华氏度。对于这样的设置项,除非你希望在用户在设置完成之后立即使用 watch 上的 app,否则将设置项的信息通过后台传输发送到 watch 才会是比较合理的。
因为它可能不是立即需要的,所以系统可能会在节省电量最多的情况下将其发送出去。你也不需要任何历史记录,因为用户可能并不关心一小时之前的设置是摄氏度。
这就是 Application Context 的用武之地。它仅用于发送最新的数据。如果你将温度设置项从摄氏度改为华氏度,然后在 Application Context 发送到 watch 之前再将它(或者其他设置项)设置为不同的值,那么最新的值会覆盖之前等待发送的信息。
如果你确实希望它能保存先前信息的历史记录,而且是以最省电的方式传输。那么可以使用 User Info 方式进行传输。它的使用方式和 Application Context 很相似,但它会将更新操作加入到一个队列中并逐一发送(而不是仅仅覆盖某些内容只发送最新的信息)。具体 User Info 的使用将作为以后另一篇文章的主题来讲。
设置 iOS 应用程序
我们将从一个类似于上一篇文章 watchOS Hello World App in Swift 中的 app 说起。不过在本文中,我们将在这个 iPhone app 上加入一个 UISwitch 控件,并通过更新 watchOS app 上的 WKInterfaceLabel 来说明 UISwitch 的状态。
首先,在 iOS app 的 viewController 中,我们需要设置一些东西:
importWatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
funcsession(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?){ }
funcsessionDidBecomeInactive(_ session: WCSession){ }
funcsessionDidDeactivate(_ session: WCSession){ }
varsession: WCSession?
override funcviewDidLoad(){
super.viewDidLoad()
ifWCSession.isSupported() {
session = WCSession. default
session?.delegate = self
session?.activate()
下面,我们最先需要导入 WatchConnectivity 框架。没有它,我们所做的都是无用功。接下来,为了响应来自 WCSession 的回调,我们需要将当前这个 ViewController 设置为 WCSession 的代理,为此我们需要让它遵守这个协议,所以在 ViewController 的父类声明后面添加 WCSessionDelegate 协议。
下一步,我们需要实现 WCSessionDelegate 中的一些方法。对于当前这个 app,它们不是特别必要,但是如果想要快速在 watch app 中切换,你就需要进一步实现它们。
之后,我们需要创建一个变量用于存储 WCSession。因为 WCSession 实际上是一个单例,技术上我们可以跳过这一步,但每次输入 session? 肯定要比 WCSession.default 更简短。
你应该在代码运行初期对 session 进行设置。在大多数情况下,这将在程序初始化的时候来做。但由于我们是在 ViewController 中执行此操作,所以最早能执行的地方大概就只有 viewDidLoad 方法中了。一般情况下来说,你不应该在 viewController 中执行这个操作,因为你的 app 希望在屏幕上未加载特定 viewController 时就可以更新它的数据模型。为了简单起见,我在 viewController 中做了这个操作,这仅仅是为了展示如何使用这些 API。如果这个 ViewController 是唯一关心使用 WCSession 的东西,那就没关系。但通常情况并非如此。
要设置 session,我们需要先根据 WCSession 的 isSupport 方法的返回值来检查是否支持。如果程序在 iPad 上运行的话,这一点尤为重要。目前,你无法将 iPad 与 Apple Watch 配对,因此它会返回 false 表示不支持在 iPad 上使用 WCSession。在 iPhone 上它会返回 true。
一旦我们完成检查,就可以将 WCSession 的 defaultSession 存储在那里,接着将这个 ViewController 设置为它的代理并激活 session。如果我们可以在初始化程序中执行 isSupported 来测试是否支持,就可以把 session 用作一个常量。而这里的 session 是一个可选值是因为我们不知道程序是否会运行在 iPad 上,所以当支持 WCSession 时,session 的值为 WCSession.defualt,反之则为 nil。这样,当我们在 iPad 上尝试访问 session 中的属性或方法时,它们甚至不会执行,因为 session 为 nil。
将一个 UISwitch 放在 Storyboard 上,并将其 ValueChanged 方法关联到 ViewController 中。
在方法中加入如下代码:
@ IBAction func switchValueChanged(_ sender: UISwitch) {
ifletvalidSession = session {
letiPhoneAppContext = [ "switchStatus": sender.isOn]
tryvalidSession.updateApplicationContext(iPhoneAppContext)
print( "Something went wrong")
首先检查我们是否有一个有效的 session,如果是运行在 iPad 上,那么将跳过整个代码块。 Application Context 是一个 Swift 字典,它以 String 作为 key,AnyObject 作为 value (Dictionary&String, AnyObject&)。 value 必须遵循属性列表的规则,并且只包含某些类型。它和 NSUserDefaults 具有相同的限制,所以在上一篇文章 NSUserDefaults — A Swift Introduction 中已经介绍过了具体可以使用哪些类型。尽管如此,当我们发送一个 Swift Bool 类型时,其将会被转换为 NSNumber boolean value,所以没关系。
调用 updateApplicationContext 可能会抛出异常,所以我们需要将它包装在 do-block 中并通过 try 来调用。如果出现异常,我们只是在控制台上打印了一些信息,你还可以设置任何你需要的东西,比如你可能需要让用户知道发生了错误,那就可以显示一个 UIAlerController,同样,如果有必要可以加入异常的清理或恢复代码。这就是为了发送 Application Context,我们所需要的全部准备。
设置 watchOS 应用程序
因为我们使用的是之前 watchOS Hello World App in Swift 文中的 Hello World App,所以部分相同的设置已经替我们完成了。跟 iPhone 类似,我们还需要做一些设置才能使用 WatchConnectivity。
import WatchConnectivity
classInterfaceController: WKInterfaceController, WCSessionDelegate{
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { }
letsession = WCSession. default
overridefunc awake(withContext context: Any?) {
super.awake(withContext: context)
session. delegate= self
session.activate()
这里省略掉了之前 App 中的一些无关代码,只展示与 WatchConnectivity 设置相关的部分。同样,我们需要导入 WatchConnectivity 框架,并让我们的 InterfaceController 遵守 WCSessionDelegate 协议,紧接着,我们将 session 常量初始化为 WCSession 的单例 defaultSession。
与 iOS 端不同的是,这里我们将 session 声明为一个非可选值的常量。很显然,运行在不低于 watchOS 2 系统上的 Apple Watch 是支持 Watch Connectivity 的,所以我们不需要在 watchOS 端进行相同的测试。 并且我们在声明时就初始化了它,又没有其他平台(如iPad)需要担心,所以我们不需要它是可选的。
接下来,在代码的初期,我们需要设置 session。在 InterfaceController 中 awakeWithContext 方法是个很好的地方,所以我们在这里做相关设置。和 iOS App 一样,我们设置当前类作为 session 的代理,然后激活 session。
让我们写一个辅助方法来处理 Application Context 的回调,因为我们可能会多次调用它,而不是仅仅当我们收到一个新 context 时(你很快会看到)。
func processApplicationContext() {
ifletiPhoneContext = session.receivedApplicationContext as? [String : Bool] {
ifiPhoneContext[ "switchStatus"] == true{
displayLabel.setText( "Switch On")
displayLabel.setText( "Switch Off")
WCSession 有 2 个与 Application Context 相关的属性,applicationContext 和 receivedApplicationContext。它们的不同之处是:
applicationContext - 此设备最近一次发送的 Application Context。
receivedApplicationContext - 此设备最近一次接收的 Application Context。
现在,把它俩放到一起来看,至少接收到的看起来很明显。但在我第一次涉及这个时(不记得 WWDC 中 Watch Connectivity的介绍视频的全部内容?),我认为 applicationContext 是从最近的发送或接收来更新的,因为我认为它们是一致的 context。然而我大错特错,我花了一段时间才意识到它们是分开的。我当然能看出来原因,因为我们可能每次都会发送不一样的数据,就像从 Watch 的角度来看,applicationContext 就是 iPhone 端需要的 Watch 相关 context,而 receivedApplicationContext 则是 Watch 端需要的 iPhone 相关 context。无论哪种方式,请记住它们是不同的两个东西,并根据实际情况选择你所需要的那个。
所以在这个方法中,我们首先尝试将 receivedApplicationContext 由 [String: AnyObject] 类型的字典转换为我们需要的 [String: Bool] 类型。如果转换成功,则再根据字典中布尔值的状态将 displayLabel 的 text 值设置为 “Switch On” 或 “Switch Off”。
当我们实际接收到一个新的 Application context 时,该 InterfaceController 将会收到我们 WCSession 对象的代理回调来通知我们这个信息,我们将在那里调用这个辅助方法。
funcsession(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]){
DispatchQueue.main.async() {
self.processApplicationContext()
现在,你大概看到了 didReceiveApplicationContext 方法的入参带有它接收到的 Application Context 副本。它存储在上面提到的 receivedApplicationContext 属性中。所以我们并不需要它来调用辅助方法, 因此这个方法不需要传入任何行参。
其实对于辅助方法 processApplicationContext 来说,增加行参 context 反而更 函数式,也更 Swift。 通过增加一个 context 的入参,可以让方法内部实现和外部依赖解耦,更加方便我们对它进行单元测试。
那么,调用 dispatch_async 是为了做什么呢?好吧,这些代理回调不在主线程上。你永远不应该在除主线程以外的任何线程更新 iOS 或 watchOS 中的 UI。而我们的辅助方法除了从 receivedApplicationContext 中读取信息之外,主要目的是用来更新 UI 元素。因此,我们要通过 dispatch_async 方法返回主线程来调用该方法。调用 dispatch_async 需要 2 个参数,首先是派发队列(对于主线程,我们通过 dispatch_get_main_queue 方法获取),其次是一个闭包来告诉它需要做什么操作,这里我们只是告诉它去调用辅助方法。
所以,为什么我们要在辅助方法里这样做,而不是直接在回调方法里面直接处理呢?好吧,当你实际接收到一个新的 Application Context 时,会回调 didReceiveApplicationContext 代理方法。当 WCSession 在关闭时接收到新的 ApplicationContext 会调用 activateSession 方法,在那不久之后也会回调到 didReceiveApplicationContext 方法。在这种情况下,我使用此 ApplicationContext 作为该信息的后备存储。我不确定这是不是一个好的主意,但是对于一个简单的 app 来说,这是合理的, 因为 label 的重点是显示 iPhone 上的 UISwitch 是开启还是关闭。
那么,当我们的 app 完成加载之后想使用最后一次接收到的值,但是 app 在关闭期间又没有收到新的 context,这种情况该怎么办?我们在视图生命周期的早期设置 label,所以现在 awakeWithContext 看起来应该是这样:
overridefunc awake(withContext context: Any?) {
super.awake(withContext: context)
processApplicationContext()
session. delegate= self
session.activate()
由于 awakeWithContext 肯定在主线程上,我们不需要 dispatch_async。 因此这就是它仅用于在 didReceiveApplicationContext 回调中来调用辅助方法而不是在辅助方法内部使用的原因。
此时 iOS App 并没有保留该 UISwitch 的状态,所以在启动时保持它们的同步并不那么重要,对于一个有价值的 app 来说,我们应该将 UISwitch 的状态存储在某个地方。比如可以在 iPhone 端使用 WCSession 的 ApplicationContext 属性。(请记住,applicationContext 是从设备发送过来的最后一个 context),但如果是在iPad上运行呢?你可以将它存储在 NSUserDefaults,或者其他许多地方,但这些不在如何使用 WatchConnectivity 的讨论范畴内。具体你可以在早期的 NSUserDefaults — A Swift Introduction 文章中了解到。
以下是该项目的完整代码:
ViewController.swiftimportUIKit
importWatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
funcsession(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?){ }
funcsessionDidBecomeInactive(_ session: WCSession){ }
funcsessionDidDeactivate(_ session: WCSession){ }
@IBOutlet vartheSwitch: UISwitch!
varsession: WCSession?
override funcviewDidLoad(){
super.viewDidLoad()
ifWCSession.isSupported() {
session = WCSession. default
session?.delegate = self
session?.activate()
funcprocessApplicationContext(){
iflet iPhoneContext = session?.applicationContext as? [String : Bool] {
ifiPhoneContext[ "switchStatus"] == true{
theSwitch.isOn = true
theSwitch.isOn = false
@IBAction funcswitchValueChanged(_ sender: UISwitch){
iflet validSession = session {
let iPhoneAppContext = [ "switchStatus": sender.isOn]
try validSession.updateApplicationContext(iPhoneAppContext)
print( "Something went wrong")
InterfaceController.swiftimportWatchKit
importWatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
funcsession(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?){ }
@IBOutlet vardisplayLabel: WKInterfaceLabel!
let session = WCSession. default
override funcawake(withContext context: Any?){
super.awake(withContext: context)
processApplicationContext()
session.delegate = self
session.activate()
@IBAction funcbuttonTapped(){
//displayLabel.setText("Hello World!")
funcsession(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]){
DispatchQueue.main.async() {
self.processApplicationContext()
funcprocessApplicationContext(){
iflet iPhoneContext = session.receivedApplicationContext as? [String : Bool] {
ifiPhoneContext[ "switchStatus"] == true{
displayLabel.setText( "Switch On")
displayLabel.setText( "Switch Off")
请记住,这些代码来自 Hello World App,但是我们没有用到 watchOS App 上的 button。所以我只是注释了原始功能的代码。
以上就是如何使用 Watch Connectivity 的 Application Context 方式进行后台传输的教程。向手机端回传数据也是完全相同的,因为它们具有同样的代理回调和属性。虽然在那种情况下,你可能还需要根据实际情况检查是否存在与该设备配对的 Apple Watch 或者 Watch 上是否安装了对应的 app。
正如我之前提到的,在 ViewController / InterfaceController 中执行所有代码可能不是最好的主意,但这只是为了简单地展示如何使用 API。我个人非常喜欢在自己的 Watch Connectivity manager 实例中执行这些操作。所以我强烈建议你阅读 Natasha The Robot 的文章 WatchConnectivity: Say Hello to WCSession,并关联他的 GitHub Gist。这将对你使用 WatchConnectivity 很有帮助。
我希望本文能对你有所帮助。如果有帮到你,请不要犹豫,在 Twitter 或者你选择的社交媒体上分享这篇文章,每个分享对我都是帮助。当然,如果你有任何疑问,请随时通过联系页面或 Twitter @CodingExplorer 与我联系,我会看看我能做些什么。谢谢!
The Swift Programming Language – Apple Inc.
Facets of Swift, Part 5: Custom Operators — Swift Programming — Medium
watchOS 2 Tutorial: Using application context to transfer data (Watch Connectivity #2) by Kristina Thai
WatchConnectivity: Sharing The Latest Data via Application Context by Natasha The Robot
责任编辑:
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
今日搜狐热点

我要回帖

更多关于 六级第一次过了第二次没过 的文章

 

随机推荐