
暂无个人介绍
同步发布:http://www.yuanrengu.com/index.php/20180221.html Java的集合类主要由两个接口派生而出:Collection和Map。Collection是一个接口,它主要的两个分支为List和Set,Map的介绍会在后面的系列中进行详细的分析。如下图所示为Collection接口、子接口及其实现类的继承树。 源码分析如下: package java.util; /** * Collection继承了迭代器的接口,即整个集合类都采用了迭代器模式 */ public interface Collection<E> extends Iterable<E> { // Query Operations /** * 返回集合的大小。 * 如果集合的大小超过Integer.MAX_VALUE,则返回Integer.MAX_VALUE */ int size(); /** * 判断集合是否为空 */ boolean isEmpty(); /** * 判断集合中是否有元素o。 * 这里要特别注意下元素o是否与集合里的元素类型兼容,以及o是否为null */ boolean contains(Object o); /** * 返回集合中元素的迭代器,但不能保证返回顺序(除非集合指定了顺序) */ Iterator<E> iterator(); /** * 返回一个数组(包含集合中所有的元素)。 * 如何集合中的元素是有序的,则返回的数组中的元素也是有序的。 * 这个方法可用于集合与数组之间的转换 */ Object[] toArray(); /** * 以数组形式返回指定数组类型的集合元素 */ <T> T[] toArray(T[] a); // Modification Operations /** * 用于向集合里添加元素 * 如果集合对象被添加操作改变了则返回true */ boolean add(E e); /** * 删除元素 */ boolean remove(Object o); // Bulk Operations /** * 用来判断是否含有指定集合c中的所有元素 */ boolean containsAll(Collection<?> c); /** * 将指定集合c中的所有元素添加至调用者的集合中 */ boolean addAll(Collection<? extends E> c); /** * 删除集合中所包含的c里面的元素 */ boolean removeAll(Collection<?> c); /** * 保留与集合c中相同的元素(即移除与指定集合不同的元素) * 相当于把调用该方法的集合变成该集合和集合c的交集 */ boolean retainAll(Collection<?> c); /** * 清除集合里的所有元素,集合长度变为0 */ void clear(); // Comparison and hashing /** * 判断与指定元素是否相等 */ boolean equals(Object o); /** * 返回集合的哈希码值 */ int hashCode(); } Collection继承了Iterable,如图所示: Iterable源码分析如下: /** * 迭代器接口 */ public interface Iterable<T> { /** * 返回元素类型为T的迭代器 */ Iterator<T> iterator(); } 其中Iterator的源码如下: /** * 迭代器接口类 */ public interface Iterator<E> { /** * 如果被迭代的集合元素还没有被遍历,则返回true */ boolean hasNext(); /** * 返回集合里的下一个元素 */ E next(); /** * 删除集合里上一次next方法返回的元素 */ void remove(); } Iterator仅用于遍历集合,Iterator本身并不提供装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。Iterator必须依附于Collection对象,如有一个Iterator对象,则必然有一个与之关联的Collection对象。特别要注意的是,当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
同步首发:http://www.yuanrengu.com/index.php/20171226.html 在实际的项目开发中有时会有对数据库某字段截取部分的需求,这种场景有时直接通过数据库操作来实现比通过代码实现要更方便快捷些,mysql有很多字符串函数可以用来处理这些需求,如Mysql字符串截取总结:left()、right()、substring()、substring_index()。 一.从左开始截取字符串 用法:left(str, length),即:left(被截取字符串, 截取长度) SELECT LEFT('www.yuanrengu.com',8) 结果为:www.yuan 二.从右开始截取字符串 用法:right(str, length),即:right(被截取字符串, 截取长度) SELECT RIGHT('www.yuanrengu.com',6) 结果为:gu.com 三.截取特定长度的字符串 用法: substring(str, pos),即:substring(被截取字符串, 从第几位开始截取) substring(str, pos, length),即:substring(被截取字符串,从第几位开始截取,截取长度) 1.从字符串的第9个字符开始读取直至结束 SELECT SUBSTRING('www.yuanrengu.com', 9) 结果为:rengu.com 2.从字符串的第9个字符开始,只取3个字符 SELECT SUBSTRING('www.yuanrengu.com', 9, 3) 结果为:ren 3.从字符串的倒数第6个字符开始读取直至结束 SELECT SUBSTRING('www.yuanrengu.com', -6) 结果为:gu.com 4.从字符串的倒数第6个字符开始读取,只取2个字符 SELECT SUBSTRING('www.yuanrengu.com', -6, 2) 结果为:gu 四.按关键字进行读取 用法:substring_index(str, delim, count),即:substring_index(被截取字符串,关键字,关键字出现的次数) 1.截取第二个“.”之前的所有字符 SELECT SUBSTRING_INDEX('www.yuanrengu.com', '.', 2); 结果为:www.yuanrengu 2.截取倒数第二个“.”之后的所有字符 SELECT SUBSTRING_INDEX('www.yuanrengu.com', '.', -2); 结果为:yuanrengu.com 3.如果关键字不存在,则返回整个字符串 SELECT SUBSTRING_INDEX('www.yuanrengu.com', 'sprite', 1); 结果为:www.yuanrengu.com 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
同步首发:http://www.yuanrengu.com/index.php/20171130.html 项目开发接近尾声,开始着手在生产环境部署项目,开发阶段部署项目都没用nginx。项目是采用SOA架构,多系统开发,主要包括服务系统、中台系统、后台系统、金融系统、接口系统、调度系统、报表系统等。这类分布式的系统,一般也都会用到nginx来做负载均衡。 从公司刚成立就进来,赶鸭子上架来做架构师,负责公司的所有研发事情,搭建公司的整个技术架构,起初的所有核心业务代码基本都由自己亲自把关来进行编码。系统也从最初的只有一个pc端,发展到如今pc中台、后台、android端3个app、iOS端3个app,产品越做越多,亲自负责招聘面试、培训。之前很多时候都有过无助和苦恼,因为负责公司整个架构,又要负责核心业务的编码,技术难点的攻克,新员工的招聘及培训,现在团队已经都发展到16个人,而且这全是研发人员。 回想这一路,觉得之前看似爬不过去的山也不过如此,也许这就是成长吧,成长总是会伴随些许汗水与泪水吧。由于是负责团队的所有事情,所以数据库的维护、迁移数据、建索引等性能优化,项目部署等所有事情必须得一肩挑,不要问我为什么公司没有DBA?为什么没有运维?我真的只能给你一个眼神,让你慢慢去体会。 话不多说,直接开始技术干货分享。 nginx做负载均衡的优势网上有很多介绍资料,这里我不再多做介绍。因为有很多系统要部署,涉及到域名、二级域名、多个域名等的部署。在实际的部署由于对nginx的不够熟悉,遇到过很多坑,其中这种多域名的配置,xxxx.com转发到www.xxxx.com、访问域名转发到tomcat里的项目等,现在先总结一部坑的解决办法。 如将xxxx.com这个域名指向8082端口里的tomcat项目,在做这个介绍前先讲个插曲,如访问xxxx.com需转向到www.xxxx.com,这一点很多人都会忽略。 现在如果要部署中台、后台、金融系统,找到nginx/conf/nginx.conf,修改配置: upstream web{ server localhost:8082; } upstream admin{ server localhost:8083; } upstream finance{ server localhost:8084; } server { listen 80; server_name finance.xxxx.com; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://finance; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 80; server_name www.xxx.com; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://web; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } server { server_name xxxx.com; rewrite ^(.*) http://www.xxxx.com$1 permanent; } server { listen 80; server_name admin.xxxx.com; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://admin; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } 上面的配置还包括了访问xxxx.com转向www.xxxx.com的配置,如下: server { server_name xxxx.com; rewrite ^(.*) http://www.xxxx.com$1 permanent; } nginx的基本配置大致就是这样,如果绑定多个域名(不管是一级域名还是二级域名),需配置多个server,你会发现这几个server配置都差不多,主要是更改server_name及proxy_pass指向即可。upstream节点其实就是代理服务的访问路径。 如果此时访问域名,你会发现nginx的配置生效了,只是目前显示的是tomcat的默认界面。nginx的配置基本就这样了,接下来对tomcat做些配置的修改。找到tomcat里的conf/server.xml,注释掉默认的Host配置,添加如下Host配置: <Host name="localhost" appBase="E:\tomcat\apache-tomcat-8.0.35-8082\webapps\web" deployOnStartup ="false" autoDeploy="false" unpackWARs="true"> <Context path="/" docBase="E:\tomcat\apache-tomcat-8.0.35-8082\webapps\web" /> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> 以上是windows服务器下的配置,如为linux,只需更改appBase和docBase,指向项目的路径。tomcat的配置也已经完成,重启tomcat,访问域名就指向了tomcat里的项目。 希望能对大家有帮助,如果在使用的过程中遇到什么问题,可以在底下留言。 我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
同步首发:http://www.yuanrengu.com/index.php/20171112.html 一.GitLab简介 GitLab是利用Ruby On Rails开发的一个开源版本管理系统,实现了一个自托管的Git项目仓库,是集代码托管,测试,部署于一体的开源git仓库管理软件,可通过web界面来进行访问公开的或私人项目。与Github类似,GitLab能够浏览代码,管理缺陷和注释。可以管理团队对仓库的访问,它非常易于浏览提交过的版本,并提供一个文件历史库。它还提供一个代码片段收集功能可以轻松实现代码复用,便于日后需要的时候查找。 Git的家族成员: Git:是一种版本控制系统,是一个命令,是一种工具。 Gitlib:是用于实现Git功能的开发库。 Github:是一个基于Git实现的在线代码托管仓库,公开项目是免费的,也可以付费创建私人项目。 GitLab:是一个基于Git实现的在线代码仓库托管软件,可以用GitLab搭建一套类似Github的系统。 GitLab对硬件还是有一定要求的,1核心的CPU基本上可以满足需求,大概支撑100个左右的用户,不过在运行GitLab网站的同时还需要运行多个后台job,就会显得有点捉襟见肘了。需要至少4GB的可寻址内存(RAM交换)来安装和使用GitLab,操作系统和任何其他正在运行的应用程序也将使用内存,因此请记住,在运行GitLab之前,您至少需要4GB的可用空间。如果使用更少的内存,GitLab将在重新配置运行期间给出奇怪的错误,我用虚拟机来分别新建1G,2G内存的CentOS系统来装GitLab,确实非常捉襟见肘啊,伤不起。 二.GitLab的安装 1.在CentOS系统上,下面的命令将会打开系统防火墙HTTP和SSH访问。 sudo yum install curl policycoreutils openssh-server openssh-clients sudo systemctl enable sshd sudo systemctl start sshd sudo yum install postfix sudo systemctl enable postfix sudo systemctl start postfix sudo firewall-cmd --permanent --add-service=http sudo systemctl reload firewalld 2.添加GitLab镜像源并安装 curl -sS http://packages.gitlab.com.cn/install/gitlab-ce/script.rpm.sh | sudo bash 这是官方的yum源,安装速度会比较慢,可以使用国内源,修改如下文件即可: vim /etc/yum.repos.d/gitlab_gitlab-ce.repo 修改内容如下: [gitlab-ce] name=gitlab-ce baseurl=http://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7 repo_gpgcheck=0 gpgcheck=0 enabled=1 gpgkey=https://packages.gitlab.com/gpg.key 然后执行: sudo yum install gitlab-ce #配置并启动 GitLab sudo gitlab-ctl reconfigure 安装成功会有如下提示: 3.第一次访问GitLab,系统会重定向页面到重定向到重置密码页面,你需要输入初始化管理员账号的密码,管理员的用户名为root,初始密码为5iveL!fe。重置密码后,新密码即为刚输入的密码。 三.GitLab的汉化 成功安装GitLab后,很多朋友会想到汉化,当然如果团队里英文水平都不错的话,是没必要汉化的。 GitLab中文社区的项目,v7-v8.8是由Larry Li发起的“GitLab中文社区版项目”(https://gitlab.com/larryli/gitlab),从v8.9之后由@xhang开始继续汉化项目(https://gitlab.com/xhang/gitlab)。 mkdir /home/local/gitlab cd /home/local/gitlab 如没安装git,需提前安装: yum install -y git 下载最新的汉化包: git clone https://gitlab.com/xhang/gitlab.git 如果是要下载老版本的汉化包,需要加上老版本的分支,如果想下载10.0.2,可以运行如下语句: git clone https://gitlab.com/xhang/gitlab.git -b v10.0.2-zh 停止GitLab并执行如下语句: gitlab-ctl stop cp /home/local/gitlab/* /opt/gitlab/embedded/service/gitlab-rails/ -rf 复制时可能不断提示是否要覆盖,这时可能是系统每次执行cp命令时,其实是执行了cp -i命令的别名。出现这种情况可以修改~/.bashrc,在“alias cp=’cp-i’”前加#注释即可。 复制可能出现如下提示,可以不用理会。 注释后记得执行: source ~/.bashrc 或者重启即可。 接下来可以重新配置和启动: sudo gitlab-ctl reconfigure sudo gitlab-ctl restart 成功汉化后的界面如下: 四.GitLab的命令 语法: gitlab-ctl command (subcommand) Service Management Commands start 启动所有服务 stop 关闭所有服务 restart 重启所有服务 status 查看所有服务状态 tail 查看日志信息 service-list 列举所有启动服务 graceful-kill 平稳停止一个服务 例子: #启动所有服务 [root@gitlab ~]# gitlab-ctl start #启动单独一个服务 [root@gitlab ~]# gitlab-ctl start nginx #查看日志,查看所有日志 [root@gitlab ~]# gitlab-ctl tail #查看具体一个日志,类似tail -f [root@gitlab ~]# gitlab-ctl tail nginx General Commands help 帮助 reconfigure 修改配置文件之后,需要重新加载下 show-config 查看所有服务配置文件信息 uninstall 卸载这个软件 cleanse 删除gitlab数据,重新白手起家 例子: #显示所有服务配置文件 [root@gitlab ~]#gitlab-ctl show-config #卸载gitlab [root@gitlab ~]#gitlab-ctl uninstall 五.QQ邮箱配置 默认情况下,GitLab用qq邮箱注册是发不出确认邮件的。查看了网上很多邮箱配置的教程,大部分都是误导的。 像这类软件,归根到底总结为一句话:一切以官网文档为准。 qq邮箱最好用企业邮箱,本人用个人邮箱进行测试是有些小问题的。 正确配置如下: # vim /etc/gitlab/gitlab.rb gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "smtp.exmail.qq.com" gitlab_rails['smtp_port'] = 465 gitlab_rails['smtp_user_name'] = "xxxx@xx.com" gitlab_rails['smtp_password'] = "password" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_tls'] = true gitlab_rails['gitlab_email_from'] = 'xxxx@xx.com' 大家如果在安装和使用的过程有遇到什么问题,可以在我的个人博客里留言,希望对大家有些许帮助。 参考:https://www.gitlab.cc/installation/#centos-7 https://yq.aliyun.com/articles/74395 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
同步发布:http://www.yuanrengu.com/index.php/20170511.html 先简单介绍下反射的概念:java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以在运行时装配。在实际的业务中,可能会动态根据属性去获取值。 工具类如下: package com.yaoguang.common.utils.field; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 实体属性操作工具类 * * @author heyonggang * @date 2017年5月10日下午5:56:59 */ public class ObjectFieldUtil { private static Logger log = LoggerFactory.getLogger(ObjectFieldUtil.class); /** * 根据属性名获取属性值 * * @param fieldName 字段名 * @param o 实体 * @return */ public static Object getFieldValueByName(String fieldName, Object o) { try { String firstLetter = fieldName.substring(0, 1).toUpperCase(); String getter = "get" + firstLetter + fieldName.substring(1); Method method = o.getClass().getMethod(getter, new Class[] {}); Object value = method.invoke(o, new Object[] {}); return value; } catch (Exception e) { log.error(e.getMessage(), e); return null; } } /** * 获取属性名数组 * * @param o 实体 * @return */ public static String[] getFiledName(Object o) { Field[] fields = o.getClass().getDeclaredFields(); String[] fieldNames = new String[fields.length]; for (int i = 0; i < fields.length; i++) { System.out.println(fields[i].getType()); fieldNames[i] = fields[i].getName(); } return fieldNames; } /** * 获取属性类型(type),属性名(name),属性值(value)的map组成的list * * @param o 实体 * @return */ public static List<Map<String, Object>> getFiledsInfo(Object o) { Field[] fields = o.getClass().getDeclaredFields(); // String[] fieldNames = new String[fields.length]; List<Map<String, Object>> list = new ArrayList<>(); Map<String, Object> infoMap = null; for (int i = 0; i < fields.length; i++) { infoMap = new HashMap<String, Object>(); infoMap.put("type", fields[i].getType().toString()); infoMap.put("name", fields[i].getName()); infoMap.put("value", getFieldValueByName(fields[i].getName(), o)); list.add(infoMap); } return list; } /** * 获取对象的所有属性值,返回一个对象数组 * * @param o 实体 * @return */ public static Object[] getFiledValues(Object o) { String[] fieldNames = getFiledName(o); Object[] value = new Object[fieldNames.length]; for (int i = 0; i < fieldNames.length; i++) { value[i] = getFieldValueByName(fieldNames[i], o); } return value; } /** * 根据对象属性名设置属性值 * * @param fieldName 字段名 * @param value 字段值 * @param o 实体 * @return */ public static void setFieldValueByName(String fieldName, Object o,Object value) { try { BeanInfo obj =Introspector.getBeanInfo(o.getClass(), Object.class); PropertyDescriptor[] pds = obj.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { if(pd.getName().equals(fieldName)){ pd.getWriteMethod().invoke(o, value); break; } } } catch (Exception e) { log.error(e.getMessage(), e); } } } 测试用例如下: /** * 根据实体和属性名获取值 */ @Test public void testGetField(){ TruckBills truckBills = iTruckBillsService.geTruckBills("02cb5069b44f45dca578e5ada08bf513", "88"); String orderSn = (String) ObjectFieldUtil.getFieldValueByName("orderSn", truckBills); String shipper = (String) ObjectFieldUtil.getFieldValueByName("shipper", truckBills); String[] fieldNames = ObjectFieldUtil.getFiledName(truckBills); List<Map<String, Object>> listMap = ObjectFieldUtil.getFiledsInfo(truckBills); System.out.println("---------------------------"); System.out.println(orderSn); System.out.println(shipper); System.out.println(Arrays.toString(fieldNames)); for (Map<String, Object> map : listMap) { System.out.println("---------------------------"); Set<Entry<String, Object>> entrySet = map.entrySet(); for (Entry<String, Object> entry : entrySet) { System.out.println(entry.getKey() + "-----" + entry.getValue()); } System.out.println("---------------------------"); } } 还有一种将字符串转换成java代码并执行的方法:Java Expression Language (JEXL) 是一个表达式语言引擎,可以用来在应用或者框架中使用。 JEXL受Velocity和JSP 标签库 1.1 (JSTL) 的影响而产生的,需要注意的是,JEXL 并不时 JSTL 中的表达式语言的实现。 需要先添加jar包,maven配置如下: <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-jexl</artifactId> <version>2.0</version> </dependency> 核心代码如下: public class DyMethodUtil { /** * 将字符串转换成java代码并执行 * * @param jexlExp 需要转换的字符串 * @param map 参数集合 * @return 方法执行结果 * 如: * String jexlExp="testService.save(person)"; * map.put("testService",testService); * map.put("person",person); */ public static Object invokeMethod(String jexlExp, Map<String, Object> map) { JexlEngine jexl = new JexlEngine(); Expression e = jexl.createExpression(jexlExp); JexlContext jc = new MapContext(); for (String key : map.keySet()) { jc.set(key, map.get(key)); } if (null == e.evaluate(jc)) { return ""; } return e.evaluate(jc); } } 测试示例如下: /** * 动态构建 */ @Test @Rollback(false) public void testTemple(){ //1.拿到结果集 //2.构建语言表达式 //3.动态构建 TruckBills truckBills = iTruckBillsService.geTruckBills("02cb5069b44f45dca578e5ada08bf513", "88"); List<TruckGoodsAddr> truckGoodsAddrs = truckBills.getTruckGoodsAddrs(); TruckOther truckOther = truckBills.getTruckOther(); Map<String, Object> map = new HashMap<>(); map.put("truckBills", truckBills); System.out.println("------------------------"); System.out.println(JsonBinder.buildNormalBinder().toJson(map)); System.out.println("------------------------"); String expression = "truckBills.getTruckGoodsAddrs().get(0).getBillsId()"; Object aa = DyMethodUtil.invokeMethod(expression, map); System.out.println("------------------------"); System.out.println(JsonBinder.buildNormalBinder().toJson(aa)); System.out.println("------------------------"); } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
同步发布:http://www.yuanrengu.com/index.php/springmvc-user-initbinder.html 在使用SpingMVC框架的项目中,经常会遇到页面某些数据类型是Date、Integer、Double等的数据要绑定到控制器的实体,或者控制器需要接受这些数据,如果这类数据类型不做处理的话将无法绑定。 这里我们可以使用注解@InitBinder来解决这些问题,这样SpingMVC在绑定表单之前,都会先注册这些编辑器。一般会将这些方法些在BaseController中,需要进行这类转换的控制器只需继承BaseController即可。其实Spring提供了很多的实现类,如CustomDateEditor、CustomBooleanEditor、CustomNumberEditor等,基本上是够用的。 demo如下: public class BaseController { @InitBinder protected void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Date.class, new MyDateEditor()); binder.registerCustomEditor(Double.class, new DoubleEditor()); binder.registerCustomEditor(Integer.class, new IntegerEditor()); } private class MyDateEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = null; try { date = format.parse(text); } catch (ParseException e) { format = new SimpleDateFormat("yyyy-MM-dd"); try { date = format.parse(text); } catch (ParseException e1) { } } setValue(date); } } public class DoubleEditor extends PropertiesEditor { @Override public void setAsText(String text) throws IllegalArgumentException { if (text == null || text.equals("")) { text = "0"; } setValue(Double.parseDouble(text)); } @Override public String getAsText() { return getValue().toString(); } } public class IntegerEditor extends PropertiesEditor { @Override public void setAsText(String text) throws IllegalArgumentException { if (text == null || text.equals("")) { text = "0"; } setValue(Integer.parseInt(text)); } @Override public String getAsText() { return getValue().toString(); } } } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://melin.iteye.com/blog/838258 三种Singleton的实现方式,一种是用大家熟悉的DCL,另外两种使用cas特性来实现。 public class LazySingleton { private static volatile LazySingleton instance; public static LazySingleton getInstantce() { if (instance == null) { synchronized (LazySingleton.class) { if (instance == null) { instance = new LazySingleton(); } } } return instance; } } /** * 利用putIfAbsent线程安全操作,实现单例模式 * * @author Administrator * */ public class ConcurrentSingleton { private static final ConcurrentMap<String, ConcurrentSingleton> map = new ConcurrentHashMap<String, ConcurrentSingleton>(); private static volatile ConcurrentSingleton instance; public static ConcurrentSingleton getInstance() { if (instance == null) { instance = map.putIfAbsent("INSTANCE", new ConcurrentSingleton()); } return instance; } } public class AtomicBooleanSingleton { private static AtomicBoolean initialized = new AtomicBoolean(false); private static volatile AtomicBooleanSingleton instance; public static AtomicBooleanSingleton getInstantce() { checkInitialized(); return instance; } private static void checkInitialized() { if(instance == null && initialized.compareAndSet(false, true)) { instance = new AtomicBooleanSingleton(); } } } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
同步发布:http://www.yuanrengu.com/index.php/mysqlsolvetimestamp.html 在使用mysql时,如果数据库中的字段类型是timestamp,默认为0000-00-00,会发生异常:Value ‘0000-00-00 00:00:00’ can not be represented as java.sql.Timestamp. 解决办法如下: 给数据库的jdbc.url加上zeroDateTimeBehavior参数,如下: jdbc.url=jdbc:mysql://localhost:3306/table?characterEncoding=UTF-8&zeroDateTimeBehavior=round zeroDateTimeBehavior参数有两种配置: zeroDateTimeBehavior=round ,”0000-00-00“会默认转换为”0001-01-01 00:00:00” zeroDateTimeBehavior=convertToNull,“0000-00-00“会转换为null 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
以后慢慢启用个人博客:http://www.yuanrengu.com/index.php/mybatis1021.html 一直在使用Mybatis这个ORM框架,都是使用mybatis里的一些常用功能。今天在项目开发中有个业务是需要限制各个用户对某些表里的字段查询以及某些字段是否显示,如某张表的某些字段不让用户查询到。这种情况下,就需要构建sql来动态传入表名、字段名了。现在对解决方法进行下总结,希望对遇到同样问题的伙伴有些帮助。 动态SQL是mybatis的强大特性之一,mybatis在对sql语句进行预编译之前,会对sql进行动态解析,解析为一个BoundSql对象,也是在此处对动态sql进行处理。下面让我们先来熟悉下mybatis里#{}与${}的用法: 在动态sql解析过程,#{}与${}的效果是不一样的: #{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。 如以下sql语句 select * from user where name = #{name}; 会被解析为: select * from user where name = ?; 可以看到#{}被解析为一个参数占位符?。 ${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换 如以下sql语句: select * from user where name = ${name}; 当我们传递参数“sprite”时,sql会解析为: select * from user where name = "sprite"; 可以看到预编译之前的sql语句已经不包含变量name了。 综上所得, ${ } 的变量的替换阶段是在动态 SQL 解析阶段,而 #{ }的变量的替换是在 DBMS 中。 #{}与${}的区别可以简单总结如下: #{}将传入的参数当成一个字符串,会给传入的参数加一个双引号 ${}将传入的参数直接显示生成在sql中,不会添加引号 #{}能够很大程度上防止sql注入,${}无法防止sql注入 ${}在预编译之前已经被变量替换了,这会存在sql注入的风险。如下sql select * from ${tableName} where name = ${name} 如果传入的参数tableName为user; delete user; --,那么sql动态解析之后,预编译之前的sql将变为: select * from user; delete user; -- where name = ?; --之后的语句将作为注释不起作用,顿时我和我的小伙伴惊呆了!!!看到没,本来的查询语句,竟然偷偷的包含了一个删除表数据的sql,是删除,删除,删除!!!重要的事情说三遍,可想而知,这个风险是有多大。 ${}一般用于传输数据库的表名、字段名等 能用#{}的地方尽量别用${} 进入正题,通过上面的分析,相信大家可能已经对如何动态调用表名和字段名有些思路了。示例如下: <select id="getUser" resultType="java.util.Map" parameterType="java.lang.String" statementType="STATEMENT"> select ${columns} from ${tableName} where COMPANY_REMARK = ${company} </select> 要实现动态调用表名和字段名,就不能使用预编译了,需添加statementType="STATEMENT"" 。 statementType:STATEMENT(非预编译),PREPARED(预编译)或CALLABLE中的任意一个,这就告诉 MyBatis 分别使用Statement,PreparedStatement或者CallableStatement。默认:PREPARED。这里显然不能使用预编译,要改成非预编译。 其次,sql里的变量取值是${xxx},不是#{xxx}。 因为${}是将传入的参数直接显示生成sql,如${xxx}传入的参数为字符串数据,需在参数传入前加上引号,如: String name = "sprite"; name = "'" + name + "'"; mybatis动态调用表名和字段名,还可以应用于日志的收集上,如数据库的日志表,每隔一个月动态建一个日志表,表名前缀相同(如log_201610,log_201611等),这样实现日志的分月分表存储,方便日志的分析。 希望对大家有帮助!如有疑问可以在底下留言。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
现在的项目是以Mybatis作为O/R映射框架,确实好用,也非常方便项目的开发。MyBatis支持普通sql的查询、视图的查询、存储过程调用,是一种非常优秀的持久层框架。它可利用简单的XML或注解用语配置和原始映射,将接口和java中的POJO映射成数据库中的纪录。 一.调用视图 如下就是调用视图来查询收益明细,sql部分如下: <!-- 获取明细 --> <select id ="getContactEarnsDetail" resultType= "java.util.Map" parameterType ="java.lang.Integer"> select title,trade_time,trade_amount from v_contacts_earn where user_id = #{userId} </select > 该视图返回的数据类型为map。 mapper部分如下: List<Map<String, Object>> getContactEarnsDetail(Integer userId); 接口部分如下: List<Map<String, Object>> getContactEarnsDetail(Integer userId); 实现如下: @Override public List<Map<String, Object>> getContactEarnsDetail(Integer userId) { Assert. notNull(userId); return contactEarnsMapper.getContactEarnsDetail(userId); } 如上例所示,调用视图如同调用正常的sql查询语句一般。 二.调用存储过程 调用存储过程可能还会有返回结果集,在这里我主要针对返回结果集的情况进行阐述。 (1)含有返回结果集 如存储过程结构如下: p_my_wallet(IN var_user_id INT); 参数是用户id revenue_today 今日收益 revenue_contacts 人脉收益 balance 可用余额 sql部分如下: <!-- 获取钱包信息 --> <select id="getMyWallet" parameterType="java.lang.Integer" resultType="java.util.Map" statementType="CALLABLE"> { call p_my_wallet( #{userId,jdbcType=INTEGER,mode=IN} ) } </select> 则mapper部分为: Map<String, Object> getMyWallet(@Param("userId")Integer userId); 接口部分为: Map<String, Object> getMyWallet(Integer userId); (2)没有返回结果集 sql部分如下: < select id= "cardBuild" statementType ="CALLABLE"> <![CDATA[ {call p_insert_card_build_info (#{is_customized_,mode=IN,jdbcType=INTEGER},#{face_value_,mode=IN,jdbcType=INTEGER},#{number_,mode=IN,jdbcType=INTEGER})} ]]> </ select> 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
日期格式与时间戳之间的转化 一:日期格式转化为时间戳 function timeTodate(date) { var new_str = date.replace(/:/g,'-'); new_str = new_str.replace(/ /g,'-'); var arr = new_str.split("-"); var datum = new Date(Date.UTC(arr[0],arr[1]-1,arr[2],arr[3]-8,arr[4],arr[5])); return strtotime = datum.getTime()/1000; } 使用方法: var str_time = '2013-04-19 23:40:48'; var rst_strto_time = timeTodate(str_time); document.write("时间戳: "+rst_strto_time); 二:时间戳转化为日期 function dateTotime(date_time) { var timestr = new Date(parseInt(date_time) * 1000); var datetime = timestr.toLocaleString().replace(/年|月/g, "-").replace(/日/g, " "); return datetime; } 使用方法: var strtotime = 1408502536; var rst_date_time = dateTotime(strtotime ); document.write("日期: "+rst_date_time); 测试示例: 时间戳: 1366386048日期: 2013/4/19 下午11:40:48 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
一、JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范,JVM将内存划分为: New(年轻代) Tenured(年老代) 永久代(Perm) 其中New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小。 年轻代(New):年轻代用来存放JVM刚分配的Java对象 年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代 永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。 New又分为几个部分: Eden:Eden用来存放JVM刚分配的对象 Survivor1 Survivro2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回 Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃 圾回收的可能性。 2.垃圾回收算法 垃圾回收算法可以分为三类,都基于标记-清除(复制)算法: Serial算法(单线程) 并行算法 并发算法 JVM会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于1个核,会对年轻代选择并行算法,关于选择细节请参考JVM调优文档。 稍微解释下的是,并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不停止应用执行。所以,并发算法适用于 交互性高的一些程序。经过观察,并发算法会减少年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低。 还有一个问题是,垃圾回收动作何时执行? 当年轻代内存满时,会引发一次普通GC,该GC仅回收年轻代。需要强调的时,年轻代满是指Eden代满,Survivor满不会引发GC 当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代 当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载 另一个问题是,何时会抛出OutOfMemoryException,并不是内存被耗空的时候才抛出 JVM98%的时间都花费在内存回收 每次回收的内存小于2% 满足这两个条件将触发OutOfMemoryException,这将会留给系统一个微小的间隙以做一些Down之前的操作,比如手动打印Heap Dump。 二、内存泄漏及解决方法 1.系统崩溃前的一些现象: 每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC 年老代的内存越来越大并且每次FullGC后年老代没有内存被释放 之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值。 2.生成堆的dump文件 通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。 3.分析dump文件 下面要考虑的是如何打开这个3G的堆信息文件,显然一般的Window系统没有这么大的内存,必须借助高配置的Linux。当然我们可以借助X-Window把Linux上的图形导入到Window。我们考虑用下面几种工具打开该文件: Visual VM IBM HeapAnalyzer JDK 自带的Hprof工具 使用这些工具时为了确保加载速度,建议设置最大内存为6G。使用后发现,这些工具都无法直观地观察到内存泄漏,Visual VM虽能观察到对象大小,但看不到调用堆栈;HeapAnalyzer虽然能看到调用堆栈,却无法正确打开一个3G的文件。因此,我们又选用了 Eclipse专门的静态内存分析工具:Mat。 4.分析内存泄漏 通过Mat我们能清楚地看到,哪些对象被怀疑为内存泄漏,哪些对象占的空间最大及对象的调用关系。针对本案,在ThreadLocal中有很多的JbpmContext实例,经过调查是JBPM的Context没有关闭所致。 另,通过Mat或JMX我们还可以分析线程状态,可以观察到线程被阻塞在哪个对象上,从而判断系统的瓶颈。 5.回归问题 Q:为什么崩溃前垃圾回收的时间越来越长? A:根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判断内存泄漏的依据 Q:为什么Full GC的次数越来越多? A:因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收 Q:为什么年老代占用的内存越来越大? A:因为年轻代的内存无法被回收,越来越多地被Copy到年老代 三、性能调优 除了上述内存泄漏外,我们还发现CPU长期不足3%,系统吞吐量不够,针对8core×16G、64bit的Linux服务器来说,是严重的资源浪费。 在CPU负载不足的同时,偶尔会有用户反映请求的时间过长,我们意识到必须对程序及JVM进行调优。从以下几个方面进行: 线程池:解决用户响应时间长的问题 连接池 JVM启动参数:调整各代的内存比例和垃圾回收算法,提高吞吐量 程序算法:改进程序逻辑算法提高性能 1.Java线程池(java.util.concurrent.ThreadPoolExecutor) 大多数JVM6上的应用采用的线程池都是JDK自带的线程池,之所以把成熟的Java线程池进行罗嗦说明,是因为该线程池的行为与我们想象的有点出入。Java线程池有几个重要的配置参数: corePoolSize:核心线程数(最新线程数) maximumPoolSize:最大线程数,超过这个数量的任务会被拒绝,用户可以通过RejectedExecutionHandler接口自定义处理方式 keepAliveTime:线程保持活动的时间 workQueue:工作队列,存放执行的任务 Java线程池需要传入一个Queue参数(workQueue)用来存放执行的任务,而对Queue的不同选择,线程池有完全不同的行为: SynchronousQueue: 一个无容量的等待队列,一个线程的insert操作必须等待另一线程的remove操作,采用这个Queue线程池将会为每个任务分配一个新线程 LinkedBlockingQueue : 无界队列,采用该Queue,线程池将忽略 maximumPoolSize参数,仅用corePoolSize的线程处理所有的任务,未处理的任务便在LinkedBlockingQueue中排队 ArrayBlockingQueue: 有界队列,在有界队列和 maximumPoolSize的作用下,程序将很难被调优:更大的Queue和小的maximumPoolSize将导致CPU的低负载;小的Queue和大的池,Queue就没起动应有的作用。 其实我们的要求很简单,希望线程池能跟连接池一样,能设置最小线程数、最大线程数,当最小数<任务<最大数时,应该分配新的线程处理;当任务>最大数时,应该等待有空闲线程再处理该任务。 但线程池的设计思路是,任务应该放到Queue中,当Queue放不下时再考虑用新线程处理,如果Queue满且无法派生新线程,就拒绝该任务。设计导致 “先放等执行”、“放不下再执行”、“拒绝不等待”。所以,根据不同的Queue参数,要提高吞吐量不能一味地增大maximumPoolSize。 当然,要达到我们的目标,必须对线程池进行一定的封装,幸运的是ThreadPoolExecutor中留了足够的自定义接口以帮助我们达到目标。我们封装的方式是: 以SynchronousQueue作为参数,使maximumPoolSize发挥作用,以防止线程被无限制的分配,同时可以通过提高maximumPoolSize来提高系统吞吐量 自定义一个RejectedExecutionHandler,当线程数超过maximumPoolSize时进行处理,处理方式为隔一段时间检 查线程池是否可以执行新Task,如果可以把拒绝的Task重新放入到线程池,检查的时间依赖keepAliveTime的大小。 2.连接池(org.apache.commons.dbcp.BasicDataSource) 在使用org.apache.commons.dbcp.BasicDataSource的时候,因为之前采用了默认配置,所以当访问量大时,通过JMX 观察到很多Tomcat线程都阻塞在BasicDataSource使用的Apache ObjectPool的锁上,直接原因当时是因为BasicDataSource连接池的最大连接数设置的太小,默认的BasicDataSource配 置,仅使用8个最大连接。 我还观察到一个问题,当较长的时间不访问系统,比如2天,DB上的Mysql会断掉所以的连接,导致连接池中缓存的连接不能用。为了解决这些问题,我们充分研究了BasicDataSource,发现了一些优化的点: Mysql默认支持100个链接,所以每个连接池的配置要根据集群中的机器数进行,如有2台服务器,可每个设置为60 initialSize:参数是一直打开的连接数 minEvictableIdleTimeMillis:该参数设置每个连接的空闲时间,超过这个时间连接将被关闭 timeBetweenEvictionRunsMillis:后台线程的运行周期,用来检测过期连接 maxActive:最大能分配的连接数 maxIdle:最大空闲数,当连接使用完毕后发现连接数大于maxIdle,连接将被直接关闭。只有initialSize < x < maxIdle的连接将被定期检测是否超期。这个参数主要用来在峰值访问时提高吞吐量。 initialSize是如何保持的?经过研究代码发现,BasicDataSource会关闭所有超期的连接,然后再打开 initialSize数量的连接,这个特性与minEvictableIdleTimeMillis、 timeBetweenEvictionRunsMillis一起保证了所有超期的initialSize连接都会被重新连接,从而避免了Mysql长时 间无动作会断掉连接的问题。 3.JVM参数 在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到一些目标: GC的时间足够的小 GC的次数足够的少 发生Full GC的周期足够的长 前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。 (1)针对JVM堆的设置一般,可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值 (2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如 年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小 (3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该 适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间 (4)在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集 (5)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。 (4)可以通过下面的参数打Heap Dump信息 -XX:HeapDumpPath -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt 通过下面参数可以控制OutOfMemoryError时打印堆的信息 -XX:+HeapDumpOnOutOfMemoryError 请看一下一个时间的Java参数配置:(服务器:Linux 64Bit,8Core×16G) JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G" 经过观察该配置非常稳定,每次普通GC的时间在10ms左右,Full GC基本不发生,或隔很长很长的时间才发生一次 通过分析dump文件可以发现,每个1小时都会发生一次Full GC,经过多方求证,只要在JVM中开启了JMX服务,JMX将会1小时执行一次Full GC以清除引用,关于这点请参考附件文档。 4.程序算法调优:本次不作为重点 参考资料: http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。 currentTimeMillis方法 public static long currentTimeMillis() 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。 可以直接把这个方法强制转换成date类型。 代码如下: long currentTime = System.currentTimeMillis(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy年-MM月dd日-HH时mm分ss秒"); Date date = new Date(currentTime); System.out.println(formatter.format(date)); 运行结果如下: 当前时间:2011年-08月10日-14时11分46秒 另: 可获得当前的系统和用户属性: String osName = System.getProperty(“os.name”); String user = System.getProperty(“user.name”); System.out.println(“当前操作系统是:” + osName); System.out.println(“当前用户是:” + user); System.getProperty 这个方法可以得到很多系统的属性。 转自:http://blog.csdn.net/mywebstudy/article/details/7814695 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
新浪微博开放平台为第三方应用提供了简便的合作模式,满足了手机用户和平板电脑用户随时随地分享信息的需求。通过调用平台的api即可实现很多微博上的功能。 本篇主要目的是记录新浪微博移动SDK iOS版本的在iOS5下的嵌入和使用。 1、申请一个新浪微博的移动应用 。 申请地址:http://open.weibo.com/development,申请后得到App key 和 App Secret 2、下载iOS_sdk 下载地址:http://open.weibo.com/wiki/SDK#iOS_SDK ,下载第一个就ok了。 3、新建一个项目Sina_weibo,选择single View app。而且使用5.0后的ARC特性 。 导入解压后的sdk 导入SDK 4、适配SDK在arc环境下运行 这时候运行程序,你会发现很多关于ARC的错误,因为sdk里是没有使用arc的。这时候如果想sdk的文件不参与arc方式的编译,那就需要做下设置,在Build Phases里添加“-fno-objc-arc”标示 双击需要标识的文件,输入-fno-objc-arc。 这样weibo SDK的文件就不会以arc的方式编译了。 5、 在自己的工程里面增加Security.framework。SDK需要使用Security.framework将OAuth认证以后的token放到keyChain里面从而增加整个工程的安全性。 这时候运行,程序就编译运行正常了 6、其他的和SDK里的Demo一样了 登录调用 [weiBoEnginelogIn]; 注销调用 [weiBoEnginelogOut]; 发微博: 可以调用SDK默认的界面发送: WBSendView *sendView = [[WBSendViewalloc] initWithAppKey:appKeyappSecret:appSecrettext:@"test"image:[UIImageimageNamed:@"bg.png"]]; [sendView setDelegate:self]; [sendView show:YES]; 对应的发送微博的api是:statuses/upload 发送微博并上传图片。如果在微博上显示地图,那就发送经纬度参数,多加上 lat false float 纬度,有效范围:-90.0到+90.0,+表示北纬,默认为0.0。 long false float 经度,有效范围:-180.0到+180.0,+表示东经,默认为0.0。 7、调用自定义api 6步骤里调用的是sdk里封装好的,那微博这么api和功能,怎么调用呢? 我们试着获取个人信息 [cpp] view plaincopy NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:2]; [params setObject:[engine accessToken]forKey:@"access_token"]; [params setObject:[engine userID]forKey:@"uid"]; NSLog(@"params:%@", params); [engine loadRequestWithMethodName:@"users/show.json" httpMethod:@"GET" params:params postDataType:kWBRequestPostDataTypeNone httpHeaderFields:nil]; params的参数是必须的。 返回的数据参考接口http://open.weibo.com/wiki/2/users/show 这样可以获取微博自己的昵称等信息。 微博所有api文档都在这个页面http://open.weibo.com/wiki/API%E6%96%87%E6%A1%A3_V2,使用的方法和例子都有。 需要什么用什么接口,把loadRequestWithMethodName 改变成自己需要的接口,params参数改成需要的参数,就可以了。 有的接口是不需要params的,比如 statuses/friends_timeline.json获取关注人的微博,这里params可以是nil. PS:本篇记录用的是Oauth认证,xauth认证需要审核资格才能使用的。 8、项目源码下载地址:http://download.csdn.net/detail/totogo2010/4633077 继上篇 iOS学习之iOS5.0以上 使用新浪微博开放平台OAuth 过后,新浪微博授权弹出的网页又有调整,中间还有过瘫痪情况。如果按上篇做出来的授权页面就成这样了: 第一:网页页面变大了, 第二:没有了取消按钮。 根据这个情况在sina weibo SDK里做了写调整 调整:增加一个关闭按钮,弹出窗口大小。 在WBAuthorizeWebView.m文件的方法:bounceOutAnimationStopped里添加按钮: [cpp] view plaincopy UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; [closeButton setFrame:CGRectMake(280, 430, 60, 60)]; [closeButton setImageEdgeInsets:UIEdgeInsetsMake(3, 0, 0, 0)]; [closeButton setImage:[UIImage imageNamed:@"close"] forState:UIControlStateNormal]; [closeButton addTarget:self action:@selector(hideAndCleanUp) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:closeButton]; close.png图片sdk里自带就有。hideAndCleanUp方法就是把窗口移除。hideAndCleanUp方法原来就有。运行效果: 看右下角有个关闭按钮,为什么放在右下角呢,因为右上角有个注册按钮,容易被点到。一会把网页窗口最大化了就能看到了。 扩大窗口 在WBAuthorizeWebView.m文件的方法- (void)sizeToFitOrientation:(UIInterfaceOrientation)orientation 修改如下: 上面的尺寸是横屏的时候的,我修改了竖屏时的窗口的大小。 [cpp] view plaincopy - (void)sizeToFitOrientation:(UIInterfaceOrientation)orientation { [self setTransform:CGAffineTransformIdentity]; if (UIInterfaceOrientationIsLandscape(orientation)) { [self setFrame:CGRectMake(0, 0, 480, 320)]; [panelView setFrame:CGRectMake(10, 30, 460, 280)]; [containerView setFrame:CGRectMake(10, 10, 440, 260)]; [webView setFrame:CGRectMake(0, 0, 440, 260)]; [indicatorView setCenter:CGPointMake(240, 160)]; } else { [self setFrame:CGRectMake(0, 5, 320, 470)]; [panelView setFrame:CGRectMake(0, 5, 320, 470)]; [containerView setFrame:CGRectMake(0, 5, 320, 460)]; [webView setFrame:CGRectMake(0, 0, 320, 460)]; [indicatorView setCenter:CGPointMake(160, 240)]; } [self setCenter:CGPointMake(160, 240)]; [self setTransform:[self transformForOrientation:orientation]]; previousOrientation = orientation; } 运行效果: 这个状态差不多就可以了。 还有在调用WeiBoEngine 的Logout 登出无效的情况。修改如下: 在WBAuthorize.m文件,把startAuthorize函数修改如下: [cpp] view plaincopy NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:appKey, @"client_id", @"code", @"response_type", redirectURI, @"redirect_uri", @"mobile", @"display", @"true",@"forcelogin", nil]; 就是在 params里添加@”true”,@”forcelogin”。 以上是使用新浪微博sdk开发遇到的问题和解决的一些方法。 修改过的项目代码:http://download.csdn.net/detail/totogo2010/4928029 来源:http://blog.csdn.net/totogo2010/article/details/8435174 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://blog.csdn.net/dqjyong/article/details/7672865 Objective-C里面的Key-Value Observing (KVO)机制,非常不错,可以很好的减少浇水代码。关于KVO的学习,可以参考文章:《Key-Value Observing快速入门》:http://www.cocoadev.cn/Objective-C/Key-Value-Observing-Quick-Start-cn.asp KVO概念: KVO是cocoa中的一个核心概念,简单理解就是:关注Model某个数据(Key)的对象可以注册为监听器,一旦Model某个Key的Value发生变化,就会广播给所有的监听器 KVO机制: KVO(Key Value Observe)是cocoa中用来设值或取值的协议(NSKeyValueCoding),跟java的ejb有点类似,通过对变量和函数名进行规范达 到方便设置类成员值的目的。它有点类似于Notification,但是,它提供了观察某一属性变化的方法,而Notification需要一个发送 notification的对象,这样KVO就比Notification极大的简化了代码。 这种观察-被观察模型适用于这样的情况,比方说根据A(数据类)的某个属性值变化,B(view类)中的某个属性做出相应变化。对于推崇MVC的cocoa而言,kvo应用价值很高。 Key-Value Coding(KVC)实现分析 KVC运用了一个isa-swizzling技术。isa-swizzling就是类型混合指针机制。KVC主要通过isa- swizzling,来实现其内部查找定位的。isa指针,如其名称所指,(就是is a kind of的意思),指向维护分发表的对象的类。该分发表实际上包含了指向实现类中的方法的指针,和其它数据。 比如说如下的一行KVC的代码: [site setValue:@"sitename" forKey:@"name"]; 就会被编译器处理成: SEL sel = sel_get_uid ("setValue:forKey:"); IMP method = objc_msg_lookup (site->isa,sel); method(site, sel, @"sitename", @"name"); 首先介绍两个基本概念: (1)SEL数据类型:它是编译器运行Objective-C里的方法的环境参数。 (2)IMP数据类型:他其实就是一个编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。 关于如何找到实现函数的指针,可参考文章:《Objective-C如何避免动态绑定,而获得方法地址》:http://www.cocoadev.cn/Objective-C/Get-method-address.asp 这下KVC内部的实现就很清楚的清楚了:一个对象在调用setValue的时候,(1)首先根据方法名找到运行方法的时候所需要的环境参数。(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。(3)再直接查找得来的具体的方法实现。 Key-Value Observing(KVO)实现 在上面所介绍的KVC机制上加上KVO的自动观察消息通知机制就水到渠成了。 当观察者为一个对象的属性进行了注册,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。所以isa指 针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名。 熟悉KVO的朋友都知道,只有当我们调用KVC去访问key值的时候KVO才会起作用。所以肯定确定的是,KVO是基于KVC实现的。其实看了上面我们的分析以后,关系KVO的架构的构思也就水到渠成了。 因为KVC的实现机制,可以很容易看到某个KVC操作的Key,而后也很容易的跟观察者注册表中的Key进行匹对。假如访问的Key是被观察的Key,那么我们在内部就可以很容易的到观察者注册表中去找到观察者对象,而后给他发送消息。 ======================================================= 对kvo/kvc做了简单的介绍,可作为入门读物。 有些术语描述不够精确请指正。 欢迎讨论。 KVO是Cocoa的一个重要机制,他提供了观察某一属性变化的方法,极大的简化了代码。这种观察-被观察模型适用于这样的情况,比方说根据A(数 据类)的某个属性值变化,B(view类)中的某个属性做出相应变化。对于推崇MVC的cocoa而言,kvo应用的地方非常广泛。 适用kvo时,通常遵循如下流程: 1 注册: -(void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context keyPath就是要观察的属性值,options给你观察键值变化的选择,而context方便传输你需要的数据(注意这是一个void型) 2 实现变化方法: -(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context change里存储了一些变化的数据,比如变化前的数据,变化后的数据;如果注册时context不为空,这里context就能接收到。 是不是很简单?kvo的逻辑非常清晰,实现步骤简单。 说了这么多,大家都要跃跃欲试了吧。可是,在此之前,我们还需要了解KVC机制。其实,知道了kvo的逻辑只是帮助你理解而已,要真正掌握的,不在 于kvo的实现步骤是什么,而在于KVC,因为只有符合KVC标准的对象才能使用kvo(强烈推荐要使用kvo的人先理解KVC)。 KVC是一种间接访问对象属性(用字符串表征)的机制,而不是直接调用对象的accessor方法或是直接访问成员对象。 key就是确定对象某个值的字符串,它通常和accessor方法或是变量同名,并且必须以小写字母开头。Keypath就是以“.”分隔的key,因为属性值也能包含属性。比如我们可以person这样的key,也可以有key.gender这样的key path。 获取属性值时可以通过valueForKey:的方法,设置属性值用setValue:forKey:。与此同时,KVC还对未定义的属性值定义了 valueForUndefinedKey:,你可以重载以获取你要的实现(补充下,KVC定义载NSKeyValueCoding的非正式协议里)。 在O-C 2.0引入了property,我们也可以通过.运算符来访问属性。下面直接看个例子: @property NSInteger number; instance.number = 3; [instance setValue:[NSNumber numberWithInteger:3] forKey:@"number"]; 注意KVC中的value都必须是对象。 以上介绍了通过KVC来获取/设置属性,接下来要说明下实现KVC的访问器方法(accessor method)。Apple给出的惯例通常是: -key:,以及setKey:(使用的name convention和setter/getter命名一致)。对于未定义的属性可以用setNilValueForKey:。 至此,KVC的基本概念你应该已经掌握了。之所以是基本,因为只涉及到了单值情况,kvc还可以运用到对多关系,这里就不说了,留给各位自我学习的空间 接下来,我们要以集合为例,来对掌握的KVC进行一下实践。 之所以选择array,因为在ios中,array往往做为tableview的数据源,有这样的一种情况: 假设我们已经有N条数据,在进行了某个操作后,有在原先的数据后多了2条记录;或者对N中的某些数据进行更新替换。不使用KVC我们可以使用 reloadData方法或reloadRowsAtIndexPaths。前一种的弊端在于如果N很大消耗就很大。试想你只添加了几条数据却要重载之前 N数据。后一种方法的不足在于代码会很冗余,你要一次计算各个indexPath再去reload,而且还要提前想好究竟在哪些情况下会引起数据更新, 倘若使用了KVC/kvo,这样的麻烦就迎刃而解了,你将不用关心追加或是更新多少条数据。 下面将以添加数据为例,说明需要实现的方法: 实现insertObject:inKeyAtIndex:或者insertKey:atIndexes。同时在kvo中我们可以通过change这个dictionary得知发生了哪种变化,从而进行相应的处理。 http://www.cocoadev.cn/Objective-C/Key-Value-Observing-Quick-Start-cn.asp KVO与Notification之间的区别: notification是需要一个发送notification的对象,一般是notificationCenter,来通知观察者。 KVO是直接通知到观察对象,并且逻辑非常清晰,实现步骤简单。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
如果在一个类中想要执行另一个类中的方法可以使用通知 1.创建一个通知对象:使用notificationWithName:object: 或者 notificationWithName:object:userInfo: NSNotification* notification = [NSNotification notificationWithName:kImageNotificationLoadFailed(connection.imageURL) object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:error,@"error",connection.imageURL,@"imageURL",nil]]; 这 里需要注意的是,创建自己的通知并不是必须的。而是在创建自己的通知之前,采用NSNotificationCenter类的方 法 postNotificationName:object: 和 postNotificationName:object:userInfo:更加便利的发出通知。这种情况,一般使用NSNotificationCenter的类方法defaultCenter就获得默认的通知对象,这样你就可以给该程序的默认通知中心发送通知了。注意:每一个程序都有一个自己的通知中心,即NSNotificationCenter对象。该对象采用单例设计模式,采用defaultCenter方法就可以获得唯一的NSNotificationCenter对象。 注意:NSNotification对象是不可变的,因为一旦创建,对象是不能更改的。 2.注册通知:addObserver:selector:name:object: 可以看到除了添加观察者之外,还有其接收到通知之后的执行方法入口,即selector的实参。因此为了进行防御式编程,最好先检查观察者是否定义了该方法。例如:添加观察者代码有 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aWindowBecameMain:) name:NSWindowDidBecomeMainNotification object:nil]; 这里保证了self定义了aWindowBecameMain:方法。而对于一个任意的观察者observer,不能保证其对应的selector有aWindowBecameMain:,可采用[observer respondsToSelector:@selector(aWindowBecameMain:)]] 进行检查。所以完整的添加观察者过程为: if([observer respondsToSelector:@selector(aWindowBecameMain:)]) { [[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(aWindowBecameMain:) name:NSWindowDidBecomeMainNotification object:nil]; } 注 意到addObserver:selector:name:object:不仅指定一个观察者,指定通知中心发送给观察者的消息,还有接收通知的名字,以 及指定的对象。一般来说不需要指定name和object,但如果仅仅指定了一个object,观察者将收到该对象的所有通知。例如将上面的代码中 name改为nil,那么观察者将接收到object对象的所有消息,但是确定不了接收这些消息的顺序。如果指指定一个通知名称,观察者将收到它每次发出 的通知。例如,上面的代码中object为nil,那么客户对象(self)将收到任何对象发出NSWindowDidBecomeMainNotification通知。如果既没有指定指定object,也没有指定name,那么该观察者将收到所有对象的所有消息。 3.发送通知:postNotificationName:object:或者performSelectorOnMainThread:withObject:waitUntilDone: 例如程序可以实现将一个文本可以进行一系列的转换,例如对于一个实例、RTF格式转换成ASCII格式。而转换在一个类(如Converter类)的对象中得到处理,在诚寻执行过程中可以加入或者删除这种转换。而且当添加或者删除Converter操作时,你的程序可能需要通知其他的对象,但是这些Converter对象并不需要知道被通知对象是什么,能干什么。你只需要声明两个通知,"ConverterAdded" 和 "ConverterRemoved",并且在某一事件发生时就发出这两个通知。 当一个用户安装或者删除一个Converter,它将发送下面的消息给通知中心: [[NSNotificationCenter defaultCenter] postNotificationName:@"ConverterAdded" object:self]; 或者是 [[NSNotificationCenter defaultCenter] postNotificationName:@"ConverterRemoved" object:self]; 通知中心将会区分它们对象对这些通知感兴趣并且通知他们。如果除了关心观察者的通知名称和观察的对象,还关心其他之外的对象,那么就把之外的对象放在通知的可选字典中,或者用方法postNotificationName:object:userInfo:。 而采用performSelectorOnMainThread:withObject:waitUntilDone:则是直接调用NSNotification的方法postNotification,而postNotificationName和object参数可以放到withObject的实参中。例如: [[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];//注意这里的notification为自定义的一个通知对象,可定义为NSNotification* notification = [NSNotification notificationWithName:@"ConverterAdded"object:self];//那么它的作用与上面的一致 4.移除通知:removeObserver:和removeObserver:name:object: 其中,removeObserver:是删除通知中心保存的调度表一个观察者的所有入口,而removeObserver:name:object:是删除匹配了通知中心保存的调度表中观察者的一个入口。 这个比较简单,直接调用该方法就行。例如: [[NSNotificationCenter defaultCenter] removeObserver:observer name:nil object:self]; 注意参数notificationObserver为要删除的观察者,一定不能置为nil。 PS:这里简单说一下通知中心保存的调度表。通知中心的调度表是给一些观察者指定的一些通知集。一个通知集是通知中心发出的通知的子集。每个表的入口包含: 通知观察者(必须要的)、通知名称、通知的发送者。 下图表示通知集中指定的通知的调用表入口的四种类型: 下图表示四种观察者的调度表 最后,提醒一下观察者收到通知的顺序是没有定义的。同时通知发出和观察的对象有可能是一样的。通知中心同步转发通知给观察者,就是说 postNotification: 方法直到接收并处理完通知才返回值。要想异步的发送通知,可以使用NSNotificationQueue。在多线程编程中,通知一般是在一个发出通知的那个线程中转发,但也可能是不在同一个线程中转发通知。 在对象间传递信息的标准方法是消息传递-即一个对象调用另一个对象的方法。然而,消息传递要求发送消息的对象知道消息的接收者,以及它可以响应什么 消息。这个要求对于委托消息和其它类型的消息是可以的。有些时候,我们不希望两个对象之间具有这种紧密的耦合-特别值得注意的原因是它会把本来独立的子系 统联结在一起。而且这种要求也是不切实际的,因为它需要把应用程序中很多全然不同的对象之间建立硬编码的连接。 对于不能使用标准的消息传递的场合,Cocoa提供了通告广播模型。通过通告机制,一个对象可以通知其它对象自己正在干什么。在这个意义上,通告机 制类似于委托,但是它们之间的区别是很重要的。委托和通告的关键区别在于前者是一对一的通讯路径(在向外委托任务的对象和被委托的对象之间)。而通告是潜 在的一对多的通讯方式-也就是一种广播。一个对象只能有一个委托,但可以有很多观察者,因为通告的接收者是未知的。对象不必知道那些观察者是什么对象。任何对象都可以间接地通过通告来观察一个事件,并通过调整自己的外观、行为、和状态来响应事件。通告是一种在应用程序中进行协调和聚合的强大机制。 通告机制是如何工作的?这在概念上相当直接。在进程中有一个称为通告中心的对象,充当通告的信息交换和广播中心。在应用程序的其它地方,需要知道某 个事件的对象在通告中心进行注册,让它知道当该事件发生时,自己希望得到通知。这种场景的一个例子是当一个弹出式菜单被选择时,控制器对象需要知道这个事 件,以便在用户界面上对反映这个变化。当事件发生时,处理该事件的对象向通告中心发出一个通告,然后通告中心会将它派发给所有的相关的观察者。图5-8描述了这种机制。 请注意:通告中心同步地将通告派发给它的观察者。发出通告的对象直到所有的通告被发出后,才重新获得程序的控制权。如果需要以异步的方式发送通告,必须使用通告队列(参见"通告队列")。通告队列在对特定的通告进行延迟处理,并根据某些具体的条件将类似的通告进行组合之后,才将通告发给通告中心。 图5-8 公布和广播通告 任何对象都可以发出通告,也可以在通告中心注册为通告的观察者。发出通告的对象、通告中包含的对象、还有通告的观察者可以是不同的对象,也可以是同 一个对象(用同一个对象作为通告的发送者和观察者有它的用处,比如用于空闲处理方面)。发送通告的对象不需要知道通告观察者的任何信息。另一方面,观察者 至少需要知道通告的名称和通告对象中封装的字典的键("通告对象" 部分描述了通告对象的是有什么组成的)。 进一步阅读: 如果需要有关通告机制的完全讨论。 本部分包含如下主要内容: 何时以及如何使用通告通告对象通告中心通告队列 何时以及如何使用通告 和委托一样,通告机制是实现应用程序中的对象间通讯的好工具。它使应用程序中的对象可以了解其它地方发生的改变。一般地说,一个对象注册为通告的观 察者,是因为它希望在相应的事件发生后或即将发生时进行调整。举例来说,如果一个定制视图希望在窗口调整尺寸的时候改变自己的外观,则可以观察窗口对象发 出的NSWindowDidResizeNotification通告。通告也允许在对象间传递信息,因为通告中可以包含一个与事件相关的字典。 但是,通告和委托之间是不同的,这些差别也导致这两种机制应该用于不同的地方。在早些时候提到,通告模型和委托模型的主要区别在于前者是广播机制,而委托是一对一的关系。每种模型都有自己的优点,通告机制的优点如下: 发出通告的对象不需要知道观察者的标识。 应用程序并不受限于Cocoa框架声明的通告;任何类都可以声明通告,其实例可以发布通告。 通告并不限于应用程序内部的通讯;通过分布式通告,一个进程可以将发生的事件通知另一个进程。 但是,一对一的委托模型也有自己的优点。委托有机会通过将值返回给进行委托的对象来影响事件。另一方面,通告的观察者必须发挥更为被动的作用,在响应事件时,它只能对自身及其环境产生影响。通告方法必须具有如下形式的签名: - (void)notificationHandlerName:(NSNotification *); 这个要求使观察对象无法以任何直接的方式影响原来的事件。但是,委托通常可以影响进行委托的对象对事件的处理方式。而且,Application Kit对象的委托自动注册为其通告的观察者。您只需要实现框架类定义的通告方法,就可以接收通告。 在Cocoa中,通告机制并不是观察对象状态变化的唯一选择,在很多情况下甚至都不是最好的选择。Cocoa绑定技术,特别是为其提供支持的键-值 观察(KVO)和键-值绑定(KVB)协议,也可以使应用程序中的对象观察其它对象性质的变化。绑定机制比通告机制更为有效。在绑定机制中,被观察对象和 观察对象直接进行通讯,不需要像通告中心这样的中间对象。而且,绑定机制不会像常见的通告那样,因为处理无观察者的变化而对性能产生不利影响。 但是在某些场合中,选择通告比选择绑定更为合理。您可能希望观察事件,而不是对象性质的改变;或者,有些时候遵循KVO和KVB是不适合实际情况的,特别是当需要发送和观察的通告很少的时候。 即使在适合使用通告的场合下,您也应该知道它对性能的潜在影响。通告发出之后,最终会通过本地的通告中心同步地派发给观察对象。不管通告的发送是同 步的还是异步的,这个过程都是要发生的。如果有很多观察者,或者每个观察者在处理通告时都做很多工作,您的程序就会有明显的延迟。因此,您应该小心,不要 过度或低效地使用通告。最后,下面这些关于通告用法的原则应该有帮助: 对应用程序应该观察的通告有所选择。 注册通告时,要具体到通告的名称和发送的对象。 尽可能高效地实现处理通告的方法。 避免添加或移除很多观察者;通过一些“中间的”观察者将通告的结果传递给它们可以访问的对象要好得多。 进一步阅读:如果您需要有效使用通告的详细信息,请参见Cocoa性能指南中的"通告"部分。 通告对象 通告是一个对象,是NSNotification的一个实例。该对象封装了与事件有关的信息,比如某个窗口获得了焦点,或者某个网络连接关闭了。当事件发生时,负责处理该事件的对象会向通告中心发出一个通告,通告中心则立刻将通告广播给所有注册的对象。 NSNotification对象中包含一个名称、一个对象、还有一个可选的字典。名称是标识通告的标签;对象是指通告的发送者希望发给通告观察者的对象(它通常是通告发送者本身),类似于委托消息的发送者对象,通告的接收者可以向该对象查询更多的信息;字典则用于存储与事件有关的信息。 通告中心 通告中心负责发送和接受通告。它将通告通知给所有符合条件的观察者。通告信息封装在NSNotification对象中。客户对象在通告中心中将自己注册为特定通告的观察者。当事件发生时,对象向通告中心发出相应的通告。而通告中心会向所有注册过的观察者派发消息,并将通告作为唯一的参数进行传递。通告的发送对象和观察对象有可能是同一个对象。 Cocoa框架中包含两种类型的通告中心: 通告中心(NSNotificationCenter的实例),管理单任务的通告。 分布式通告中心(NSDistributedNotificationCenter的实例),管理单台计算机上多任务之间的通告。 请注意,NSNotificationCenter和很多其它的Foundation类不同,不能和其在Core Foundation中对应的结构(CFNotificationCenterRef)进行自由桥接。 NSNotificationCenter 每个任务都有一个缺省的通告中心,您可以通过NSNotificationCenter的defaultCenter类方法来进行访问。通告中心在单任务中处理通告。如果需要在同一个机器的不同任务之间进行通讯,可以使用分布式通告中心。 通告中心同步地将通告发送给观察者。换句话说,在发出一个通告时,在所有的观察者接收和处理完成通告之前,程序的控制权不会返回给发送者。如果需要异步发送通告,可以使用通告队列,这在"通告队列"部分中进行描述。 在多线程的应用程序中,通告总是在发送的线程中传送,这个线程可能不同于观察者注册所在的线程。 NSDistributedNotificationCenter 每个任务都有一个缺省的分布式通告中心,您可以通过NSDistributedNotificationCenter的defaultCenter类方法来访问。这个分布式通告中心负责处理同一个机器的不通任务之间的通告。如果需要实现不同机器上的任务间通讯,请使用分布式对象。 发送分布式通告是一个开销昂贵的操作。通告会被发送给一个系统级别的服务器,然后再分发到注册了该分布式通告的对象所在的任务中。发送通告和通告到达另一个任务之间的延迟是很大的。事实上,如果发出的通告太多,以致于充满了服务器的队列,就可能被丢弃。 分布式通告通过任务的运行循环来分发。任务必须运行在某种“常见”模式的运行循环下,比如NSDefaultRunLoopMode模式,才能接收分布式通告。如果接收通告的任务是多线程的,则不要以通告会到达主线程作为前提。通告通常被分发到主线程的运行循环上,但是其它线程也可以接收通告。 尽管常规的通告中心允许任何对象作为通告对象(也就是通告封装的对象),分布式通告中心只支持将NSString对象作为它的通告对象。由于发出通告的对象和通告的观察者可能位于不同的任务中,通告不能包含指向任意对象的指针。因此,分布式通告中心要求通告使用字符串作为通告对象。通告的匹配就是基于这个字符串进行的,而不是基于对象指针。 通告队列 NSNotificationQueue对象(或者简单称为通告队列)的作用是充当通告中心(NSNotificationCenter的实例)的缓冲区。通告队列通常以先进先出(FIFO)的顺序维护通告。当一个通告上升到队列的前面时,队列就将它发送给通告中心,通告中心随后将它派发给所有注册为观察者的对象。 每个线程都有一个缺省的通告队列,与任务的缺省通告中心相关联。图5-9展示了这种关联。您可以创建自己的通告队列,使得每个线程和通告中心有多个队列。 图5-9 通告队列和通告中心 聚结的通告 NSNotificationQueue类为Foundation Kit的通告机制增加了两个重要的特性:即通告的聚结和异步发送。聚结是把和刚进入队列的通告相类似的其它通告从队列中移除的过程。如果一个新的通告和已 经在队列中的通告相类似,则新的通告不进入队列,而所有类似的通告(除了队列中的第一个通告以外)都被移除。然而,您不应该依赖于这个特殊的聚结行为。 您可以为enqueueNotification:postingStyle:coalesceMask:forModes:方法的第三个参数指定如下的一或多个常量,指示简化的条件: NSNotificationNoCoalescing NSNotificationCoalescingOnName NSNotificationCoalescingOnSender 您可以对NSNotificationCoalescingOnName和NSNotificationCoalescingOnSender常量进行位或操作,指示Cocoa同时使用通告名称和通告对象进行聚结。那样的话,和刚刚进入队列的通告具有相同名称和发送者对象的所有通告都会被聚结。 异步发送通告 通过NSNotificationCenter类的postNotification:方法及其变体,您可以将通告立即发送给通告中心。但是,这个方法的调用是同步的:即在通告发送对象可以继续执行其所在线程的工作之前,必须等待通告中心将通告派发给所有的观察者并将控制权返回。但是,您也可以通过NSNotificationQueue的enqueueNotification:postingStyle:和enqueueNotification:postingStyle:coalesceMask:forModes:方法将通告放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象。 Cocoa根据排队方法中指定的发送风格和运行循环模式来清空通告队列和发送通告。模式参数指定在什么运行循环模式下清空队列。举例来说,如果您指定NSModalPanelRunLoopMode模式,则通告只有当运行循环处于该模式下才会被发送。如果当前运行循环不在该模式下,通告就需要等待,直到下次运行循环进入该模式。 向通告队列发送通告可以有三种风格:NSPostASAP、NSPostWhenIdle、和NSPostNow,这些风格将在接下来的部分中进行说明。 尽快发送 以NSPostASAP风格进入队列的通告会在运行循环的当前迭代完成时被发送给通告中心,如果当前运行循环模式和请求 的模式相匹配的话(如果请求的模式和当前模式不同,则通告在进入请求的模式时被发出)。由于运行循环在每个迭代过程中可能进行多个调用分支 (callout),所以在当前调用分支退出及控制权返回运行循环时,通告可能被分发,也可能不被分发。其它的调用分支可能先发生,比如定时器或由其它源 触发了事件,或者其它异步的通告被分发了。 您通常可以将NSPostASAP风格用于开销昂贵的资源,比如显示服务器。如果在运行循环的一个调用分支过程中有很多客户代码在窗口缓冲区中进行描画,在每次描画之后将缓冲区的内容刷新到显示服务器的开销是很昂贵的。在这种情况下,每个draw...方法都会将诸如“FlushTheServer” 这样的通告排入队列,并指定按名称和对象进行聚结,以及使用NSPostASAP风格。结果,在运行循环的最后,那些通告中只有一个被派发,而窗口缓冲区也只被刷新一次。 空闲时发送 以NSPostWhenIdle风格进入队列的通告只在运行循环处于等待状态时才被发出。在这种状态下,运行循环的输入通道中没有任何事件,包括定时器和异步事件。以NSPostWhenIdle风 格进入队列的一个典型的例子是当用户键入文本、而程序的其它地方需要显示文本字节长度的时候。在用户输入每一个字符后都对文本输入框的尺寸进行更新的开销 是很大的(而且不是特别有用),特别是当用户快速输入的时候。在这种情况下,Cocoa会在每个字符键入之后,将诸如 “ChangeTheDisplayedSize”这样的通告进行排队,同时把聚结开关打开,并使用NSPostWhenIdle风 格。当用户停止输入的时候,队列中只有一个“ChangeTheDisplayedSize”通告(由于聚结的原因)会在运行循环进入等待状态时被发出, 显示部分也因此被刷新。请注意,运行循环即将退出(当所有的输入通道都过时的时候,会发生这种情况)时并不处于等待状态,因此也不会发出通告。 立即发送 以NSPostNow风格进入队列的通告会在聚结之后,立即发送到通告中心。您可以在不需要异步调用行为的时候 使用NSPostNow风格(或者通过NSNotificationCenter的postNotification:方法来发送)。在很多编程环境下,我们不仅允许同步的行为,而且希望使用这种行为:即您希望通告中心在通告派发之后返回,以便确定观察者对象收到通告并进行了处理。当然,当您希望通过聚结移除队列中类似的通告时,应该用enqueueNotification...方法,且使用NSPostNow风格,而不是使用postNotification:方法。 来源:http://www.cnblogs.com/pengyingh/articles/2346301.html 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
以后慢慢启用个人博客:http://www.yuanrengu.com 当应用程序需要访问网络时,它首先应该检查设备的网络状态,确认设备的网络环境及连接情况,并针对这些情况提醒用户做出相应的处理。最好能监听设备的网络状态的改变,当设备网络状态连接、断开时,程序也应该有相应的处理。 工欲善其事必先利器,在检查设备的网络状态前,我们要先实现两个步骤: 下载,添加Reachability类。 下载Reachability.zip压缩包,最新的版本为V3.5,解压该压缩包会得到一个Xcode项目,其实关键是得到改项目的Reachability.h和 Reachability.m文件,并把它们添加到项目中。 2. 为项目添加SystemConfiguration.framework框架。 添加方法: 1) 选中项目名称 2)选中TARGETS 3)选中Build Phases 4)在Link Binary With Libraries中添加。 将Reachability.h和 Reachability.m文件添加到项目中。 注意:如果Reachability不是3.0以上的版本,而是Reachability 2.x版本,它是不支持ARC的。本项目已经启用了ARC,早期版本的Reachability类并不支持ARC,因此需要手动设置该类禁用ARC。 打开Main.storyboard界面设计文件,向该文件中添加1个UILabel,1个UITextFieldhe 3个UIButton,如下图所示(^_^不好意思,最下面2个UILabel是打广告的)。为了在程序中访问界面上的文本框,将文本框绑定到siteField IBOutlet属性。为了让程序能相应界面上3个按钮的点击事件,将“测试”按钮的“Touch UP Inside”事件绑定testNetStatus:事件处理方法,为“测试WIFI”按钮的“Touch UP Inside”事件绑定testWifi:事件处理方法,为“测试3G/4G”按钮的“Touch UP Inside”事件绑定testInternet:事件处理方法。 接下来编辑该示例的视图控制器类,该视图控制器类的实现部分主要依靠Reachability类来检测网络状态。 核心实现代码: 1 // ViewController.m 2 // NetWorkDemo 3 // 4 // Copyright (c) 2014年 MiracleHe. All rights reserved. 5 // 6 7 #import "ViewController.h" 8 #import "Reachability.h" 9 10 @interface ViewController () 11 12 @end 13 14 @implementation ViewController 15 @synthesize siteField; 16 17 - (void)viewDidLoad 18 { 19 [super viewDidLoad]; 20 // Do any additional setup after loading the view, typically from a nib. 21 } 22 - (IBAction)testNetStatus:(id)sender { 23 NSString *site = self.siteField.text; 24 Reachability *reach = [Reachability reachabilityWithHostName: site]; 25 switch ([reach currentReachabilityStatus]) { 26 case NotReachable: 27 [self showAlert:[NSString stringWithFormat:@"不能访问%@", site]]; 28 break; 29 30 case ReachableViaWWAN: 31 [self showAlert:[NSString stringWithFormat:@"使用3G/4G网络访问%@", site]]; 32 break; 33 34 case ReachableViaWiFi: 35 [self showAlert:[NSString stringWithFormat:@"使用Wifi网络访问%@", site]]; 36 break; 37 } 38 39 } 40 41 42 - (IBAction)testWifi:(id)sender { 43 if ([[Reachability reachabilityForLocalWiFi] currentReachabilityStatus] != NotReachable) { 44 [self showAlert:@"wifi网络已经连接"]; 45 }else{ 46 [self showAlert:@"wifi网络不可用。"]; 47 } 48 } 49 50 51 - (IBAction)testInternet:(id)sender { 52 if ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] != NotReachable) { 53 [self showAlert:@"3G/4G网络已经连接"]; 54 }else{ 55 [self showAlert:@"3G/4G网络不可用"]; 56 } 57 } 58 59 -(void) showAlert:(NSString*) msg 60 { 61 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"网络状态" message:msg delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil]; 62 [alert show]; 63 64 } 65 66 -(BOOL)textFieldShouldReturn:(UITextField *)textField 67 { 68 [siteField resignFirstResponder]; 69 return YES; 70 71 } 72 73 - (void)didReceiveMemoryWarning 74 { 75 [super didReceiveMemoryWarning]; 76 // Dispose of any resources that can be recreated. 77 } 78 79 @end 上面程序首先调用了Reachability类的reachabilityWithHostName:类方法来获取Reachability对象,然后调用该对象的currentReachabilityStatus方法来获取访问指定站点的方式,该方法返回NetworkStatus枚举值,该枚举值有如下3个: typedef enum{ NotReachable = 0, //无连接 ReachableViaWiFi, //使用3G/4G网络 ReachableViaWWAN //使用WiFi网络 }NetworkStatus; 上面程序对Reachability的currentReachabilityStatus方法返回值进行判断,这样即可获取该应用访问网络的状态和方式。 编译、运行该程序,如对www.cnblogs.com进行“测试”,效果如下图。 如果访问的站点本身不存在,即时设备的网络处于连接状态,Reachability对象的currentReachabilityStatus方法也将返回NotReachable。 如果程序仅需要测试设备的WiFi或3G/4G网络是否连接,则可先调用Reachability类的reachabilityForLocalWiFi或reachabilityForInternetConnection类方法获取Reachability对象,然后调用该Reachability对象的currentReachabilityStatus方法获取网络连接状态,如果网络连接状态返回NotReachable,则表明这种类型的网络暂未连接。 除了直接检测网络连接状态之外,有时候程序还需要监听网络状态的改变。当网络断开连接时,提醒用户,网络连接已经断开,应用可能需要暂停;当网络重新连接时,再次提醒用户,应用可以继续运行。程序获取Reachability对象之后,调用Reachability对象的startNotifier方法即可开启该对象的被监听状态——当Reachability的连接状态发生改变时,该对象将会发送一个kReachabilityChangedNotification通知给默认的通知中心,因此程序只要使用默认的通知中心监听该通知即可。 为了监听网络状态的改变,在应用程序委托类(AppDelegate.m)的application: didFinishLaunchingWithOptions:方法中增加如下代码: //使用通知中心监听kReachabilityChangedNotification通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil]; //获取访问指定站点的Reachability对象 Reachability *reach = [Reachability reachabilityWithHostName:@"www.cnblogs.com"]; //让Reachability对象开启被监听状态 [reach startNotifier]; 上面的代码使用默认的通知中心检测kReachabilityChangedNotification通知,这意味着当Reachability的连接状态发生改变时,默认的通知中心就会收到该通知,从而触发应用程序委托类的reachabilityChanged:方法,还需要在应用程序委托类中定义如下方法: - (void) reachabilityChanged:(NSNotification*) note { //通过通知对象获取被监听的Reachability对象 Reachability *curReach = [note object]; //获取Reachability对象的网络状态 NetworkStatus status = [curReach currentReachabilityStatus]; if (status == NotReachable) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提醒" message:@"不能访问www.cnblogs.com" delegate:nil cancelButtonTitle:@"YES" otherButtonTitles: nil]; [alert show]; } } reachabilityChanged:会判断该Reachability对象的网络连接状态,当该对象的网络连接状态处于NotReachable时,程序会使用UIAlertView进行提醒。 希望上面的总结能对正在学习iOS开发的小伙伴有一点点帮助,假如觉得还不错,烦请小伙伴不要忘记右下角的点“推荐”哦! 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
NSUserDefaults类提供了一个与默认系统进行交互的编程接口。NSUserDefaults对象是用来保存,恢复应用程序相关的偏好设置,配置数据等等。默认系统允许应用程序自定义它的行为去迎合用户的喜好。你可以在程序运行的时候从用户默认的数据库中读取程序的设置。同时NSUserDefaults的缓存避免了在每次读取数据时候都打开用户默认数据库的操作。可以通过调用synchronize方法来使内存中的缓存与用户默认系统进行同步。 NSUserDefaults类提供了非常方便的方法来获取常用的类型,例如 floats,doubles,intergers,Booleans,URLs。所以一个NSUserDefaults的对象必须是属性表,这也就是说 我们可以存储NSData,NSString,NSNUmber,NSDate,NSArray,NSDictionary这些实例。如果你想存储其他类 型的对象,你要将其归档并创建一个NSData来实现存储。 从NSUserDefaults返回的值是不可改变的,即便是你在存储的时候使用的是可变的值。例如你使用mutable string做为“MyStringDefault”的值,当你做使用stringForKey:方法获取的值,这个值仍然是不可变的。 NSUserDefaults是单例,同时也是线程安全的。 在使用NSUserDefaults的时候,先看下下面的代码: NSDictionary* defaults = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; NSLog(@"Defaults: %@", defaults); 是用来获取设备上的所有的NSUserDefaults的设置。 上面代码输出了 Defaults: { AppleITunesStoreItemKinds = ( eBook, document, "software-update", booklet, "itunes-u", newsstand, artist, podcast, "podcast-episode", software ); AppleKeyboards = ( "zh_Hans-Pinyin@sw=Pinyin;hw=US", "en_US@hw=US;sw=QWERTY" ); AppleKeyboardsExpanded = 1; AppleLanguages = ( "zh-Hans", en, fr, de, ja, nl, it, es, pt, "pt-PT", da, fi, nb, sv, ko, "zh-Hant", ru, pl, tr, uk, ar, hr, cs, el, he, ro, sk, th, id, "en-GB", ca, hu, vi ); 如果想单独看某个key的设置,例如: NSArray *array = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleKeyboards"]; NSLog(@"Keyboards: %@", array); 会输出 AppleKeyboards = ( "zh_Hans-Pinyin@sw=Pinyin;hw=US", "en_US@hw=US;sw=QWERTY" ); 在看下面的代码 if([[NSUserDefaults standardUserDefaults] objectForKey:@"message"]==nil){ [[NSUserDefaults standardUserDefaults] setObject:@"This_is_my_default_message" forKey:@"message"]; } 代码意思是判断NSUserDefaults的“message”key 在dictionaryRepresentation中是否存在,如果不存在就 设置“message”key为This_is_my_default_message。 在加上句[[NSUserDefaults standardUserDefaults] synchronize];,这样这个设置就存到默认参数中了。 来源:http://cocos2d.diandian.com/post/2012-05-13/17552816 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
1. TCP连接 当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接 时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的 经典的三次握手示意图: 经典的四次握手关闭图: 2. TCP短连接 我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server 发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起 close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在 client/server间传递一次读写操作 短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段 3.TCP长连接 接下来我们再模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。 首先说一下TCP/IP详解上讲到的TCP保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资 源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务 器端检测到这种半开放的连接。 如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一: 客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。 客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。 客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应。 从上面可以看出,TCP保活功能主要为探测长连接的存活状况,不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。 在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问 题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可 以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的 客户端连累后端服务。 长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。 参考: 1. TCP/IP详解 卷一 来源:http://www.cnblogs.com/beifei/archive/2011/06/26/2090611.html 什么是“长连接”和“短连接”? 解释1 所谓长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差; 所谓短连接指建立SOCKET连接后发送后接收完数据后马上断开连接,一般银行都使用短连接 解释2 长连接就是指在基于tcp的通讯中,一直保持连接,不管当前是否发送或者接收数据。 而短连接就是只有在有数据传输的时候才进行连接,客户-服务器通信/传输数据完毕就关闭连接。 解释3 长连接和短连接这个概念好像只有移动的CMPP协议中提到了,其他的地方没有看到过。 通信方式 各网元之间共有两种连接方式:长连接和短连接。所谓长连接,指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接。短连接是指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接,即每次TCP连接只完成一对 CMPP消息的发送。 现阶段,要求ISMG之间必须采用长连接的通信方式,建议SP与ISMG之间采用长连接的通信方式。 解释4 短连接:比如http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。 长连接:有些服务需要长时间连接到服务器,比如CMPP,一般需要自己做在线维持。 HTTP协议之长、短连接 一、长连接与短连接: 长连接:client方与server方先建立连接,连接建立后不断开,然后再进行报文发送和接收。 这种方式下由于通讯连接一直存在。此种方式常用于P2P通信。 短连接:Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。 此方式常用于一点对多点通讯。C/S通信。 二、长连接与短连接的操作过程: 短连接的操作步骤是: 建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接 长连接的操作步骤是: 建立连接——数据传输...(保持连接)...数据传输——关闭连接 三、长连接与短连接的使用时机: 长连接:长连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。 每个TCP连接的建立都需要三次握手,每个TCP连接的断开要四次握手。 如果每次操作都要建立连接然后再操作的话处理速度会降低,所以每次操作后,下次操作时直接发送数据就可以了,不用再建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,频繁的socket创建也是对资源的浪费。 短连接:web网站的http服务一般都用短连接。因为长连接对于服务器来说要耗费一定的资源。像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接更省一些资源。试想如果都用长连接,而且同时用成千上万的用户,每个用户都占有一个连接的话,可想而知服务器的压力有多大。所以并发量大,但是每个用户又不需频繁操作的情况下需要短连接。总之:长连接和短连接的选择要根据需求而定。 四、发送接收方式: 1、异步:报文发送和接收是分开的,相互独立,互不影响的。这种方式又分两种情况: 异步双工:接收和发送在同一个程序中,有两个不同的子进程分别负责发送和接送。 异步单工:接送和发送使用两个不同的程序来完成。 2、同步:报文发送和接收是同步进行,即报文发送后等待接送返回报文。同步方式一般需要考虑超时问题,试想我们发送报文以后也不能无限等待啊,所以我们要设定一个等待 时候。超过等待时间发送方不再等待读返回报文。直接通知超时返回。 五、报文格式: 通信报文格式多样性更多,相应地就必须设计对应的读写报文的接收和发送报文函数。 阻塞与非阻塞方式 1、非阻塞方式:读函数不停的进行读动作,如果没有报文接收到,等待一段时间后超时返回,这种情况一般需要指定超时时间。 2、阻塞方式:如果没有接收到报文,则读函数一直处于等待状态,知道报文到达。 循环读写方式 1、一次直接读写报文:在一次接收或发送报文动作中一次性不加分别地全部读取或全部发送报文字节。 2、不指定长度循环读写:这一版发生在短连接进程中,受网络路由等限制,一次较长的报文可能在网络传输过程中被分解成很多个包,一次读取可能不能全部读完一次报文,这就需要循环读取报文,直到读完为止。 3、带长度报文头循环读写:这种情况一般在长连接中,由于在长连接中没有条件能够判断循环读写什么时候结束。必须要加长度报文头。读函数先是读取报文头的长度,再根据这个长度去读报文,实际情况中,报头码制格式还经常不一样,如果是非ASCII的报文头,还必须转换成ASCII常见的报文头编制有: 1、n个字节的ASCII码。 2、n个字节的BCD码。 3、n个字节的网络整型码。 以上是几种比较典型的读写报文方式,可以与通信方式模板一起预先提供一些典型的API读写函数。当然在实际问题中,可能还必须编写与对方报文格式配套的读写API. 在实际情况中,往往需要把我们自己的系统与别人的系统进行连接, 有了以上模板与API,可以说连接任何方式的通信程序都不存在问题。 来源:http://www.360doc.com/content/14/0412/16/16726605_368309628.shtml 什么时候用长连接,短连接? 长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。 而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的 连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频 繁操作情况下需用短连好。 总之,长连接和短连接的选择要视情况而定。 公司的服务器端使用的是resin做中间件,通过客户端每隔几秒发送请求来进行互动。 这种就应该是短连接了吧? 短连接需要频繁的建立与断开连接,是不是对服务器的资源浪费很大? 如果换成长连接呢?长连接的缺点在哪里? 回答: 从网络技术层面来说:TCP本身是长连接的。 当然从业务层面来说:每次连接只处理一笔请求的可以称为短连接;处理业务后不断开连接而是等待处理下一笔可以称为长连接。 至于实际场合究竟需要使用短连接还是长连接,主要看实时性要求、数据流向 和 并发量 这三个问题。 由于你没有说明请求关于这三个问题上的特点,所以没法给你具体建议。 长连接优点:节约TCP握手时间,可以保证高实时性,数据流向可以采用服务器端的主动推模式。 长连接缺点:并发量不宜太高,持续占用服务端口(相对消耗资源)。 我有一个基于长连接推模型的聊天室的简单样例,你可以看看:http://blog.csdn.net/ldh911/article/details/7268879 1.现在游戏中的玩家与玩家之间的聊天无法实现实时性,而且系统有邮件或信息时也不能及时的通知玩家 —— 如果涉及到聊天的话,一般来说还是用长连接会更合适,否则大量时间浪费到握手上了; —— 但是手机的网络长连接网络质量可能会比较撮,你需要严重考虑容错和重链机制。 2.客户端每隔几秒就会发送一个请求,这样服务器的压力岂不是很大? —— 压力会比较大,关键是聊天往往对时间的要求很高,如果是团战的话,1秒内没看到信息,可能就会觉得完全受不了了;当然也看你聊天的场景如何,是群聊还是单聊,以后会不会发展为语音啥的; NIO没有任何问题,大规模长连接处理的主流都是用NIO;而且也不是Java发明的,本身就是借助了操作系统的网络管理能力。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
实现快速排序算法的关键在于先在数组中选择一个数字,接下来把数组中的数字分为两部分,比选择的数字小的数字移到数组的左边,比选择的数字大的数字移到数组的右边。 这个函数可以如下实现: int Partition(int data[], int length, int start, int end) { if(data == NULL || length <= 0 || start < 0 || end >= length) throw new std::exception("Invalid Parameters"); int index = RandomInRange(start, end); swap(&data[index], &data[end]); int small = start - 1; for(index = start; index < end; ++index) { if(data[index] < data[end]) { ++small; if(small != index) swap(&data[index], &data[small]); } } ++small; swap(&data[small], &data[end]); return small; } 函数RandomInRange用来生成一个在start和end之间的随机数,函数swap的作用是用来交换两个数字。 如:输入n个整数,找出其中最小的k个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4. void GetLeastNumbers(int *input, int n, int *output, int k) { if(input == NULL || output == NULL || k > n || n <= 0 || k <= 0) return; int start = 0; int end = n - 1; int index = Partition(input, n, start, end); while(index != k - 1) { if(index > k - 1) { end = index - 1; index = Partition(input, n, start, end); } else { start = index + 1; index = Partition(input, n, start, end); } } for(int i = 0; i < k; ++i) output[i] = input[i]; } 采用这种思路是有限制的。因为会修改输入的数组,函数Partition会调整数组中数字的顺序。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
转自:http://blog.csdn.net/caryaliu/article/details/8280647 从以下几个方面来比较排序算法: 1. 算法的时间和空间复杂度 2. 排序的稳定性 3. 算法结构的复杂度 4. 参加排序的数据规模 排序的稳定性: 稳定排序方法: 插入排序、冒泡排序、二路归并排序、基数排序是稳定排序算法; 不稳定排序方法: 选择排序、谢尔排序、快速排序、堆积排序是不稳定排序算法。 算法复杂度比较: 各种内排序算法的时间、空间复杂度 排序方法 平均时间 最坏情况 辅助空间 插入排序 O(n*n) O(n*n) O(1) 谢尔排序 O(n*log2n) O(n*log2n) O(1) 泡排序 O(n*n) O(n*n) O(1) 快速排序 O(n*log2n) O(n*n) O(log2n) 选择排序 O(n*n) O(n*n) O(1) 堆积排序 O(n*log2n) O(n*log2n) O(1) 归并排序 O(n*log2n) O(n*log2n) O(n) 基数排序 O(d*(n+r)) O(d*(n+r)) O(n+r) 有结论如下: 1. 平均情况下,谢尔排序、快速排序、堆积排序、归并排序都能达到较快的排序速度,快速排序最快,堆积排序空间消费最省。 2. 平均情况下,插入排序和泡排序的排序速度较慢,但当参加排序的序列开始就局部有序时,这两种排序算法就能够达到较快的排序速度。最后情况下速度为 O(n), 比情况 1 中叙述的4中方法都好,辅助空间消费也少。所以当 n 较小或者序列开始就局部有序时,可选择这两种方法。 3. 基数排序方法消费的辅助空间较多,但其时间复杂度可简化为O(dn); 当元素的位数较少时,可继续简化为O(n), 这种情况下也能达到较快的排序速度,另外,归并排序需要的辅助空间也较多。 4. 从算法结构的简单性来看,插入排序、泡排序、选择排序比较简单和直接;而谢尔排序、快速排序、堆积排序以及归并排序都可以看做对某一种排序算法的进一步改进,相对而言,改进后的算法都比较复杂。 5. 从参加排序的数据序列的规模大小来看,n越小,采用简单排序方法就越合适;n越大,采用改进的排序方法越合适。这是因为n越小,n*n 与 log2n 的差距越小。 以下是在考虑内排序算法的优缺点后,在某些情况下比较适合的算法选择: 1. 当参加排序的数据规模较大,关键字元素分布比较随机,并且不要求排序稳定性时,宜采用快速排序法。 2. 当参加排序的数据规模较大,内存空间又允许,并且有排序稳定性要求,宜采用归并排序法。 3. 当参加排序的数据规模较大,元素分布可能出现升序或者逆序的情况,并且对排序的稳定性无要求时,宜采用插入排序方法。 4. 当参加排序的数据规模较小(如小于100),元素基本有序(升序),或者分布也比较随机,并且有排序稳定性要求时,宜采用插入排序方法。 5. 当参加排序的数据规模n较小,对排序稳定性又不要求时,宜采用选择排序。 插入排序、选择排序、归并排序都比较容易在链表上实现,当利用链表作为存储结构时,可以避免将大量时间花费在元素的位置移动上。 算法描述: 1. 插入排序: [cpp] view plaincopyprint? /* 简单插入排序 * |data|中存储数组元素 * |len|是数组大小 * 1. * 需要 |len - 1| 趟排序,每一趟排序将未排序的序列中第一个元素插入到已排序的序列中 * 2. * 每一趟排序寻找插入位置的比较次数不定,但是至少需要一次 * 3. * 最好的情况,原序列已是升序排列,则需要|n-1|趟排序,每一趟排序进行一次元素间的比较,时间复杂度O(n) * 4. * 最坏的情况,原序列是降序排列,同样需要|n-1|趟排序,第 i(i>=1 && i<len)趟排序需进行 i 次元素间比较,时间复杂度O(n*n) * 5. * 平均时间复杂度O(n*n) * 6. * 插入排序是稳定性排序 * */ void simple_insert(int *data, int len) { if(data == NULL || len < 1) return; int temp, i, j; for(i = 1; i < len; ++i) { temp = data[i]; j = i - 1; while(j >= 0 && temp < data[j]) { data[j+1] = data[j--]; } data[j+1] = temp; } } /* 折半插入排序 * 与简单插入排序相比 * 1. 折半插入排序减少了寻找插入位置时的元素之间的比较次数 * 2. 并未减少元素的移动次数和排序所需的趟数 * 3. 时间复杂度降为O(n*log2n) */ void binary_insert(int *data, int len) { if(data == NULL || len < 1) return; int temp, i, j, low, mid, high; for(i = 1; i < len; ++i) { temp = data[i]; low = 0; high = i - 1; while(low <= high) { mid = (low + high)/2; if(temp < data[mid]) high = mid - 1; else low = mid + 1; } for(j = i; j > low; --j) data[j] = data[j-1]; data[low] = temp; } } 2. 谢尔排序 [cpp] view plaincopyprint? /* 谢尔排序,又称缩小增量排序法,是对直接插入排序法的一种改进 * 1. * 时间复杂度在O(n*log2n) 和 O(n*n)之间 * 2. * 谢尔排序是一种不稳定排序,不适合在链表结构上实现的排序算法 * */ void shellSort(int *data, int len) { if(data == NULL || len < 1) return; unsigned int gap = len; int i,j,temp; while(gap > 0) { gap >>= 1; for(i = gap; i < len; ++i) { j = i - gap; temp = data[i]; while(j >= 0 && temp < data[j]) { data[j+gap] = data[j]; j -= gap; } data[j+gap] = temp; } } } 3. 泡排序 [cpp] view plaincopyprint? /* 冒泡排序 * 1. * 至少需要进行一趟排序,此时是原始序列已经升序排列的情况,需进行|len - 1|次元素间的比较,时间复杂度O(n) * 2. * 最多进行|len - 1|趟排序(最小值处于原始序列最后),第 i(i>=1 && i<=len-1) 趟排序需要进行|len - i|次元素间的比较, 时间复杂度O(n*n) * 3. * 平均时间复杂度是O(n*n) * 4. * 冒泡排序是稳定性排序,适用于参加排序的数据量较小并且序列的初始状态为基本有序的情况 * */ void bubbleSort(int *data, int len) { if(data == NULL || len < 1) return; int flag = 1; int i, j, temp; i = len - 1; while(i > 0 && flag == 1) { flag = 0; for(j = 0; j < i; ++j) { if(data[j] > data[j+1]) { temp = data[j]; data[j] = data[j+1]; data[j+1] = temp; flag = 1; } } --i; } } 4. 快速排序 [cpp] view plaincopyprint? /* randomInRange函数: * 产生[start, end] 之间的随机数,包括start, end两个数 * */ int randomInRange(int start, int end) { srand(time(NULL)); return start + rand()%(end-start+1); } /* * 交换elem1, elem2的值 */ void swapElem(int *elem1, int *elem2) { int temp = *elem1; *elem1 = *elem2; *elem2 = temp; } /* partition函数: * 在data序列的[start, end]之间随机选取一个元素值作为分界元素vp, * 该分界元素vp左边的值都小于等于vp, 右边的值都大于等于vp, * 函数返回该分界元素在序列中的索引位置 * */ int partition(int *data, int len, int start, int end) { if(data == NULL || len < 1 || start < 0 || end >= len) { printf("invalid parameters."); exit(1); } int index = randomInRange(start, end); swapElem(&data[index], &data[end]); int small = start - 1; for(index = start; index < end; ++index) { if(data[index] < data[end]) { ++small; if(small != index) swapElem(&data[index], &data[small]); } } swapElem(&data[++small], &data[end]); return small; } /* 快速排序递归算法: * 1. * 时间复杂度是O(n*log2n) * 2. * 快速排序是不稳定排序,不适合在链表结构上实现的排序算法 * 3. * 每次排序时如果各个部分的分界元素恰好就是最大值元素时,快速排序就会倒退为"慢速排序", 此时并未把序列分界为两个子序列 * */ void quickSort(int *data, int len, int start, int end) { if(start == end) return; int index = partition(data, len, start, end); if(index > start) quickSort(data, len, start, index - 1); if(index < end) quickSort(data, len, index + 1, end); } 5. 选择排序 [cpp] view plaincopyprint? /* 选择排序 * 1. * 总共需要|len - 1|趟排序,且第 i(i>=1 && i<=len-1) 趟排序需要进行 |len - i|次元素间的比较 * 2. * 时间复杂度与初始序列的状态无关,都是O(n*n) * 3. * 选择排序是不稳定排序,例如(49, 38, 65, 97, 49, 13, 27) * */ void selectSort(int *data, int len) { if(data == NULL || len < 1) return; int i, j, minIndex; for(i = 0; i < len - 1; ++i) { minIndex = i; j = i + 1; while(j < len) { if(data[j] < data[minIndex]) minIndex = j; ++j; } if(minIndex != i) { int temp = data[i]; data[i] = data[minIndex]; data[minIndex] = temp; } } } 6. 堆积排序 [cpp] view plaincopyprint? /* adjust函数: * |len| 数组序列的大小 * |root| 数组中某一元素的索引 * 以结点root为根结点的子树不是堆积,但结点root的左右子树都是堆积, * adjust函数将把第root个结点作为根结点的子树调整为一个新的堆积 * */ void adjust(int *data, int len, int root) { int temp = data[root]; int i = root * 2; while(i <= len) { if(i < len && data[i] < data[i + 1]) ++i; if(temp >= data[i]) break; data[i/2] = data[i]; i *= 2; } data[i/2] = temp; } /* 堆排序: * |len| data序列中最后一个元素的索引号, 也是参加排序的元素个数, * 注意不是data序列的元素个数 * 1. * data序列中第1个元素(data[0])不参加排序 * 2. * 总共执行|len - 1|趟排序,每一趟排序确定未排序序列中的最大值的位置,并执行一次adjust函数 * 3. * 无论是最坏情况还是平均情况,堆积排序的时间复杂度都是O(n*log2n) * 4. * 堆积排序的空间复杂度是O(1), 适合于len较大的序列排序 * 5. * 堆积排序不适合在链表上实现 * 6. * 堆积排序是不稳定排序,例如(5, 4, 3, 2, 2, 2, 1) */ void heapSort(int *data, int len) { if(data == NULL || len < 1) return; int i; for(i = len/2; i > 0; --i) { adjust(data, len, i); } int temp; for(i = len; i > 1; --i) { temp = data[1]; data[1] = data[i]; data[i] = temp; adjust(data, i-1, 1); } } 7. 归并排序 [cpp] view plaincopyprint? /* merge函数: * 将source[start..mid]和source[mid+1..end]中的元素归并有序序列des[start..end] * * */ void merge(int *source, int *des, int start, int mid, int end) { int i = start; int j = mid + 1; int k = start; while(i <= mid && j <= end) { // if(source[i] <= source[j]) // des[k++] = source[i++]; // else // des[k++] = source[j++]; des[k++] = source[i] <= source[j] ? source[i++] : source[j++]; } while(i <= mid) des[k++] = source[i++]; while(j <= end) des[k++] = source[j++]; } /* mpass函数: * 将参加排序的序列分成若干个长度为t的,且各自按值有序的子序列, * 然后多次调用归并子算法merge将所有两两相邻成对的子序列合并成 * 若干个长度为2t的,且各自按值有序的子序列。 * * 若最后一趟排序时,剩下的元素不足2t个元素: * 1. * 若剩余元素个数大于t,则再调用一次merge算法将剩余的两个不等长子序列合并为一个子序列 * 2. * 若剩余元素个数小于等于t,则直接将所有剩余元素依次复制到前一个子序列的后面 * */ void mpass(int *source, int *des, int len, int t) { int i = 0; if(len - i >= 2*t) { merge(source, des, i, i + t - 1, i + 2 * t - 1); i += 2 * t; } if(len - i > t) merge(source, des, i, i + t - 1, len - 1); else { for(; i < len; ++i) des[i] = source[i]; } } /* mergeSort排序: * |flag|,若flag = -1,则表示输入参数错误; * 若flag = 1, 则表示排好序的元素存放于data序列中 * 若flag = 0,则表示排好序的元素存放于des序列中 * 1. * 总共会进行log2n趟排序,每趟排序调用mpass函数,mpass的时间复杂度为O(n),二路归并排序的时间复杂度为O(nlog2n) * 2. * 排序过程中需要同原始序列同等大小的辅助空间,空间复杂度为O(n) * 3. * 二路归并排序是稳定性排序 * 4. * 二路归并排序的时间复杂度与序列的初始状态无关 */ int mergeSort(int *data, int *des, int len) { int flag = -1; if(data == NULL || len < 1) return flag; int t = 1; flag = 1; while(t < len) { mpass(data, des, len, t); flag = 0; t *= 2; if(t < len) { mpass(des, data, len, t); t *= 2; flag = 1; } } return flag; } 8. 基数排序 [cpp] view plaincopyprint? /* * 定义的存储数据元素的结点 */ typedef struct Node { int data; struct Node *next; }QLink; /* maxDig函数: * 找出data序列所有元素中最长的位数maxd并返回 * */ int maxDig(int *data, int len) { int maxd = 0; int maxCurD = 1; int base = 10; for(int i = 0; i < len; ++i) { maxCurD = 1; base = 10; while(data[i] >= base) { ++maxCurD; if(maxd < maxCurD) maxd = maxCurD; base *= 10; } } return maxd; } /* * 将data序列中所有元素均存储到链表中,并返回链表头结点head */ QLink* transferArrToQLink(int *data, int len) { if(data == NULL || len < 1) return NULL; QLink *head = NULL; QLink *rear = NULL; for(int i = 0; i < len; ++i) { QLink *cur = (QLink *)malloc(sizeof(QLink)); cur->data = data[i]; cur->next = NULL; if(head == NULL) head = cur; else rear->next = cur; rear = cur; } return head; } /* * 释放链表中所有元素结点 */ void freeQLink(QLink *phead) { QLink *toFree = NULL; while(phead != NULL) { toFree = phead; phead = phead->next; free(toFree); } } /* * 获取num 中从右边数第index位的数字,index从0 开始计数 * */ int getCurPlaceV(int num, int index) { int placeValue; int i = 0; while(i <= index) { placeValue = num%10; num /= 10; ++i; } return placeValue; } /* * |data| 数据元素序列,|len|数据元素的个数,|radix|是基数 */ void radixSort(int *data, int len, int radix) { int maxd = maxDig(data, len); printf("maxd = %d\n", maxd); QLink *phead = transferArrToQLink(data, len); QLink *Front[radix], *End[radix]; QLink *q, *rear; int i, j, curplaceV; for(i = 0; i < maxd; ++i) { printf("the %d times sort\n", i); for(j = 0; j < radix; ++j) Front[j] = End[j] = NULL; q = phead; while(q != NULL) { curplaceV = getCurPlaceV(q->data, i); if(Front[curplaceV] == NULL) Front[curplaceV] = q; else End[curplaceV]->next = q; End[curplaceV] = q; q = q->next; } j = 0; while(Front[j] == NULL) ++j; phead = Front[j]; rear = End[j]; ++j; for(; j < radix; ++j) { if(Front[j] != NULL) { rear->next = Front[j]; rear = End[j]; } } rear->next = NULL; } q = phead; i = 0; while(q != NULL) { data[i++] = q->data; q = q->next; } freeQLink(phead); phead = NULL; } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
题目:在8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法。请求出总共有多少种摆法。 这就是有名的八皇后问题。解决这个问题通常需要用递归,而递归对编程能力的要求比较高。因此有不少面试官青睐这个题目,用来考察应聘者的分析复杂问题的能力以及编程的能力。 由于八个皇后的任意两个不能处在同一行,那么这肯定是每一个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行的皇后的列号。先把ColumnIndex的八个数字分别用0-7初始化,接下来我们要做的事情就是对数组ColumnIndex做全排列。由于我们是用不同的数字初始化数组中的数字,因此任意两个皇后肯定不同列。我们只需要判断得到的每一个排列对应的八个皇后是不是在同一对角斜线上,也就是数组的两个下标i和j,是不是i-j==ColumnIndex[i]-ColumnIndex[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]。 关于排列的详细讨论,详见《字符串的排列》,这里不再赘述。 接下来就是写代码了。思路想清楚之后,编码并不是很难的事情。下面是一段参考代码: int g_number = 0; void EightQueen() { const int queens = 8; int ColumnIndex[queens]; for(int i = 0; i < queens; ++ i) ColumnIndex[i] = i; Permutation(ColumnIndex, queens, 0); } void Permutation(int ColumnIndex[], int length, int index) { if(index == length) { if(Check(ColumnIndex, length)) { ++ g_number; PrintQueen(ColumnIndex, length); } } else { for(int i = index; i < length; ++ i) { int temp = ColumnIndex[i]; ColumnIndex[i] = ColumnIndex[index]; ColumnIndex[index] = temp; Permutation(ColumnIndex, length, index + 1); temp = ColumnIndex[index]; ColumnIndex[index] = ColumnIndex[i]; ColumnIndex[i] = temp; } } } bool Check(int ColumnIndex[], int length) { for(int i = 0; i < length; ++ i) { for(int j = i + 1; j < length; ++ j) { if((i - j == ColumnIndex[i] - ColumnIndex[j]) || (j - i == ColumnIndex[i] - ColumnIndex[j])) return false; } } return true; } void PrintQueen(int ColumnIndex[], int length) { printf("Solution %d\n", g_number); for(int i = 0; i < length; ++i) printf("%d\t", ColumnIndex[i]); printf("\n"); } 转自:http://zhedahht.blog.163.com/blog/static/2541117420114331616329/ 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
题目:0,1,...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。 这就是有名的约瑟夫(Josephuse)环问题。可以用环形链表模拟圆圈的经典解法。 分析:用模板库中的std::list来模拟一个环形链表。由于std::list本身不是一个环形结构,因此每当迭代器扫描到链表末尾的时候,要记得把迭代器移到链表的头部,这样就相当于按照顺序在一个圆圈里遍历了。 这种思路的代码如下: int LastRemaining(unsigned int n, unsigned int m) { if(n < 1 || m < 1) return -1; unsigned int i = 0; list<int> numbers; for(i = 0; i < n; ++i) numbers.push_back(i); list<int>::iterator current = numbers.begin(); while(numbers.size() > 1) { for(int i = 1; i < m; ++i) { current++; if(current == numbers.end()) current = numbers.begin(); } list<int>::iterator next = ++current; if(next == numbers.end()) next = numbers.begin(); --current; numbers.erase(current); current = next; } return *(current); } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
题目:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、×、÷四则运算符号。 分析: 第一步:不考虑进位对每一位相加。0加0、1加1的结果都是0,0加1,1加0的结果都是1 。注意到,这和异或的结果是一样的。 第二步:进位,对0加0,0加1,1加0而言,都不会产生进位,只有1加1时,会向前产生1个进位。此时我们刻意想象成是两个数先做位与运算,然后再向左移动一位。 第三步:相加的过程依然是重复前面两步,知道不产生进位为止。 如下是一段基于循环实现的参考代码: int Add(int num1, int num2) { int sum, carry; do{ sum = num1 ^ num2; carry = (num1 & num2) << 1; num1 = sum; num2 = carry; }while(num2 != 0); return num1; } 相关题目: 不使用新的变量,变换两个变量的值。比如有两个变量a,b,希望交换它们的值。 a = a ^ b; b = a ^ b; a = a ^ b; 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
在Linux下查看磁盘空间使用情况,最常使用的就是du和df了。然而两者还是有很大区别的,有时候其输出结果甚至非常悬殊。 1. 如何记忆这两个命令 du-Disk Usage df-Disk Free 2. df 和du 的工作原理 2.1 du的工作原理 du命令会对待统计文件逐个调用fstat这个系统调用,获取文件大小。它的数据是基于文件获取的,所以有很大的灵活性,不一定非要针对一个分区,可以跨越多个分区操作。如果针对的目录中文件很多,du速度就会很慢了。 2.2 df的工作原理 df命令使用的事statfs这个系统调用,直接读取分区的超级块信息获取分区使用情况。它的数据是基于分区元数据的,所以只能针对整个分区。由于df直接读取超级块,所以运行速度不受文件多少影响。 3 du和df不一致情况模拟 常见的df和du不一致情况就是文件删除的问题。当一个文件被删除后,在文件系统 目录中已经不可见了,所以du就不会再统计它了。然而如果此时还有运行的进程持有这个已经被删除了的文件的句柄,那么这个文件就不会真正在磁盘中被删除, 分区超级块中的信息也就不会更改。这样df仍旧会统计这个被删除了的文件。 (1)当前分区sda1的使用情况 [plain] view plaincopy [root@centos192 testdu]# df -h /dev/sda1 文件系统 容量 已用 可用 已用%% 挂载点 /dev/sda1 49G 776M 45G 2% /var (2)新建一个1GB的大文件 [plain] view plaincopy [root@centos192 var]# dd if=/dev/zero of=myfile.iso bs=1024k count=1000 记录了1000+0 的读入 记录了1000+0 的写出 1048576000字节(1.0 GB)已复制,24.0954 秒,43.5 MB/秒 (3)此时的分区sda1使用情况 df结果: [plain] view plaincopy [root@centos192 var]# df -h /dev/sda1 文件系统<span style="white-space:pre"> </span> 容量 已用 可用 已用%% 挂载点 /dev/sda1 49G 1.8G 44G 4% /var du结果: [plain] view plaincopy [root@centos192 var]# du -sh /var/ 1.6G /var/ 此时两者结果基本相同。 (4)模拟一个进程打开这个大文件,然后删除这个大文件 [plain] view plaincopy [root@centos192 var]# tail -f myfile.iso & [1] 23277 [root@centos192 var]# rm -f myfile.iso (5)此时,再对比du和df的结果 首先确认有进程持有myfile.iso句柄。 [plain] view plaincopy [root@centos192 var]# lsof | grep myfile.iso tail 23955 root 3r REG 8,1 1048576000 7999 /var/myfile.iso (deleted) [plain] view plaincopy [root@centos192 var]# du -sh /var/ 596M /var/ [root@centos192 var]# df -h /dev/sda1 文件系统 容量 已用 可用 已用%% 挂载点 /dev/sda1 49G 1.8G 44G 4% /var 可以看出,df结果没有变化,而du则不再统计被删除了的文件myfile.iso。 (6)停止模拟进程,再对比du和df结果 首先确认没有进程持有myfile.iso句柄。 [plain] view plaincopy [root@centos192 var]# lsof | grep myfile.iso [root@centos192 var]# [plain] view plaincopy [root@centos192 var]# du -sh /var/; df -h /dev/sda1 596M /var/ 文件系统 容量 已用 可用 已用%% 挂载点 /dev/sda1 49G 776M 45G 2% /var 此时,myfile.iso已经没有进程占有它了,也就从磁盘上删除了,分区的超级块信息已经更改,df也就显示正常了。 4 工作中需要注意的地方 (1)当出现du和df差距很大的情况时,考虑是否是有删除文件未完成造成的,方法是lsof命令,然后停止相关进程即可。 (2)可以使用清空文件的方式来代替删除文件,方式是:echo > myfile.iso。 (3)对于经常发生删除问题的日志文件,以改名、清空、删除的顺序操作。 (4)除了rm外,有些命令会间接的删除文件,如gzip命令完成后会删除原来的文件,为了避免删除问题,压缩前先确认没有进程打开该文件。 du和df命令都被用于获得文件系统大小的信息:df用于报告文件系统的总块数及剩余块数,du -s /<filesystem>用于报告文件系统使用的块数。但是,我们可以发现从df命令算出的文件系统使用块数的值与通过du命令得出的值是不一致的。如下例: # du -s /tmp 返回如下值: 12920 /tmp 而 df /tmp返回如下值: Filesystem 512-blocks Free %Used Iused %Iused Mounted on /dev/hd3 57344 42208 26% 391 4% /tmp 从上面的值我们可以算出<total from df> - <Free from df> = <used block count>: 57344 - 42208 = 15136. 而15136大于12920。该值差异的存在是由于du与df命令实施上的不同: du -s命令通过将指定文件系统中所有的目录、符号链接和文件使用的块数累加得到该文件系统使用的总块数;而df命令通过查看文件系统磁盘块分配图得出总块数与剩余块数。文件系统分配其中的一些磁盘块用来记录它自身的一些数据,如i节点,磁盘分布图,间接块,超级块等。这些数据对大多数用 户级的程序来说是不可见的,通常称为Meta Data。 du命令是用户级的程序,它不考虑Meta Data,而df命令则查看文件系统的磁盘分配图并考虑Meta Data。df命令获得真正的文件系统数据,而du命令只查看文件系统的部分情况。例如,一个frag=4096 并且 nbpi=4096的空的大小为4MB的日志文件系统 中Meta Data 的分配情况如下: 1 4k block for the LVM 2 4k super blocks 2 4k blocks for disk maps 2 4k blocks for inode maps 2 4k blocks for .indirect 32 4k blocks for inodes ------------------------- 41 4k blocks for meta data on an empty 4MB file system 对于AIX 4.X 版本: 执行 du /foo返回的结果如下: 8 /foo/lost+found 16 /foo 要使du命令输出的结果与df 命令输出的结果匹配,我们必须要加上Meta Data。首先,将41个4k 的块转换为以512字节为单 位的值: 41 * 8 = 328 328(meta data) + 16(from du) = 344 所以有344个以512字节为单位的块分配给了这个空的文件系统。 而使用 df /foo命令我们可以得到下面的结果: Filesystem 512-blocks Free %Used Iused %Iused Mounted on /dev/lv01 8192 7848 5% 16 2% /foo 从中我们可以得到该文件系统使用的块数:8192(total blocks) - 7848(free blocks) = 344。该值与上面得出的值一致。 上面的换算方法对于空的文件系统很容易实现,但是对于非空的文件系统,由于Meta Data中文件间接块的大小不定,因此较难实现。所以我们不需要查看du 与 df返回的值的匹配关系,而只需要了解du -s命令返回的值反映了分配给文件及目录的磁盘块数,而df命令则反映了文件系统的实际分配情况。df命令反映的实际情况包含了用户数据(文件及目录)和Meta Data。 另一个表现出du与df命令不同之处的例子如下: 如果用户删除了一个正在运行的应用所打开的某个目录下的文件,则du命令返回的值显示出减去了该文件后的目录的大小。但df命令并不显示减去该文件后的大小。直到该运行的应用关闭了这个打开的文件,df返回的值才显示出减去了该文件后的文件系统的使用情况。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
已知ip地址为10.130.89.95,其子网掩码为255.255.255.224,求其网络号、子网号和主机号。 要看子网掩码变长在第几节,255.255.255.224是在第四节借了位 把224转换为2进制,windows的计算器科学型能帮你计算。是11100000,借了三位 借了三位,子网个数为2的三次方等于8 即八个子网 其实书上说得挺复杂,我感觉,计算网络号最简单的方法就是 256(这是个固定的数字)除以8(子网个数),等于32 那么,八个子网号就分别是 10.130.89.0 10.130.89.32 10.130.89.64 10.130.89.96 10.130.89.128 10.130.89.160 10.130.89.192 10.130.89.224 即从0开始每一跳加32,就得到了这个答案。 广播地址的算法就是除了10.130.89.0以外,其他的子网号减1,还有一个10.130.89.255 就是: 10.130.89.31 10.130.89.63 10.130.89.95 10.130.89.127 10.130.89.159 10.130.89.191 10.130.89.223 主机号就是除了网络号和广播地址之外的所有地址。 10.130.89.1~10.130.89.30 10.130.89.33 ~ 63 10.130.89.65 ~ 95 10.130.89.97 ~ 127 10.130.89.129 ~ 159 10.130.89.161 ~ 191 10.130.89.193 ~ 223 10.130.89.225 ~ 254 然后得到的答案就是10.130.89.95是一个广播地址 网络号是10.130.89.64 地址范围是10.130.89.65~94 根据掩码255.255.255.224,可知块大小为32那子网号应该是0,32,64,96.....显然该IP属于网络10.130.89.64并且是该网络的广播地址该网络主机范围是10.130.89.65---94 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
题目:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,输出任意一对即可。 例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。 思路整理一下:最初我们找到数组的第一个数字和最后一个数字。首先定义两个指针,第一个指针指向数组的第一个(也就是最小的)数字,第二个指针指向数组的最后一个(也就是最大的)数字。当两个数字的和大于输入的数字时,把较大的数字往前移动;当两个数字的和小于数字时,把较小的数字往后移动;当相等时,打完收工。这样扫描的顺序是从数组的两端向数组的中间扫描。 bool FindNumbersWithSum(int data[], int length, int sum, int *num1, int *num2) { bool found = false; if(length < 1 || num1 == NULL || num2 == NULL) return false; int ahead = length - 1; int behind = 0; while(ahead > behind) { long long curSum = data[ahead] + data[behind]; if(curSum == sum) { *num1 = data[behind]; *num2 = data[ahead]; found = true; break; } else if(curSum > sum) ahead--; else behind++; } return found; } 测试代码: #include<iostream> using namespace std; bool FindNumbersWithSum(int data[], int length, int sum, int *num1, int *num2) { bool found = false; if(length < 1 || num1 == NULL || num2 == NULL) return false; int ahead = length - 1; int behind = 0; while(ahead > behind) { long long curSum = data[ahead] + data[behind]; if(curSum == sum) { *num1 = data[behind]; *num2 = data[ahead]; found = true; break; } else if(curSum > sum) ahead--; else behind++; } return found; } int main() { int data[] = {1, 2, 4, 7, 11, 15}; int length = sizeof(data) / sizeof(int); int num1, num2; bool result = FindNumbersWithSum(data, length, 15, &num1, &num2); if(result) { cout<<"num1:"<<num1<<endl; cout<<"num2:"<<num2<<endl; } else cout<<"failed!"<<endl; return 0; } 题目:输入一个正数S,打印出所有和为S的连续正数序列(至少有两个数)。例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以结果打印出3个连续序列1~5,4~6和7~8. 有了解决前面问题的经验,这里也考虑两个数small和big分别表示序列的最小值和最大值。 首先把small初始化为1,big初始化为2.如果从small到big的序列的和大于S,可以从序列中去掉较小的值,也就是增大small的值。 如果从small到big的序列的和小于S,可以增大big,让这个序列包含更多的数字。因为这个序列至少要有两个数字,我们一直增加small到(1+S)/2为止。 void FindContinuousSequence(int sum) { if(sum < 3) return; int small = 1; int big = 2; int middle = (1 + sum) / 2; int curSum = small + big; while(small < middle) { if(curSum == sum) PrintContinuousSequence(small, big); while(curSum > sum && small < middle) { curSum -= small; small++; if(curSum == sum) PrintContinuousSequence(small, big); } big++; curSum += big; } } void PrintContinuousSequence(int small, int big) { for(int i = small; i <= big; ++i) printf("%d ", i); printf("\n"); } 扩展: 2010年中兴面试题编程求解:输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数,使其和等于 m ,要求将其中所有的可能组合列出来。 #include<list> #include<iostream> using namespace std; list<int>list1; void find_factor(int sum, int n) { // 递归出口 if(n <= 0 || sum <= 0) return; // 输出找到的结果 if(sum == n) { // 反转list list1.reverse(); for(list<int>::iterator iter = list1.begin(); iter != list1.end(); iter++) cout << *iter << " + "; cout << n << endl; list1.reverse(); } list1.push_front(n); //典型的01背包问题 find_factor(sum-n, n-1); //放n,n-1个数填满sum-n list1.pop_front(); find_factor(sum, n-1); //不放n,n-1个数填满sum } int main() { int sum, n; cout << "请输入你要等于多少的数值sum:" << endl; cin >> sum; cout << "请输入你要从1.....n数列中取值的n:" << endl; cin >> n; cout << "所有可能的序列,如下:" << endl; find_factor(sum,n); return 0; } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
题目:统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4. 找到排序数组中的第一个K: int GetFirstK(int *data, int length, int k, int start, int end) { if(start > end) return -1; int middleIndex = start + ((end - start) >> 1); int middleData = data[middleIndex]; if(middleData == k) { if((middleIndex > 0 && data[middleIndex - 1] != k) || middleIndex == 0) return middleIndex; else end = middleIndex - 1; } else if(middleData > k) end = middleIndex - 1; else start = middleIndex + 1; return GetFirstK(data, length, k, start, end); } 找到排序数组中最后一个k: int GetLastK(int *data, int length, int k, int start, int end) { if(start > end) return -1; int middleIndex = start + ((end - start) >> 1); int middleData = data[middleIndex]; if(middleData == k) { if((middleIndex < length - 1 && data[middleIndex + 1] != k) || middleIndex == length -1) return middleIndex; else start = middleIndex + 1; } else if(middleData < k) start = middleIndex + 1; else end = middleIndex - 1; return GetLastK(data, length, k, start, end); } 在分别找到第一个k和最后一个k的下标之后,就能计算出k在数组中出现的次数了。相应的代码如下: int GetNumberOfK(int *data, int length, int k) { int number = 0; if(data != NULL && length > 0) { int first = GetFirstK(data, length, k, 0, length - 1); int last = GetLastK(data, length, k, 0, length - 1); if(first > -1 && last > -1) number = last - first + 1; } return number; } 完整代码如下: int GetFirstK(int *data, int length, int k, int start, int end) { if(start > end) return -1; int middleIndex = start + ((end - start) >> 1); int middleData = data[middleIndex]; if(middleData == k) { if((middleIndex > 0 && data[middleIndex - 1] != k) || middleIndex == 0) return middleIndex; else end = middleIndex - 1; } else if(middleData > k) end = middleIndex - 1; else start = middleIndex + 1; return GetFirstK(data, length, k, start, end); } int GetLastK(int *data, int length, int k, int start, int end) { if(start > end) return -1; int middleIndex = start + ((end - start) >> 1); int middleData = data[middleIndex]; if(middleData == k) { if((middleIndex < length - 1 && data[middleIndex + 1] != k) || middleIndex == length -1) return middleIndex; else start = middleIndex + 1; } else if(middleData < k) start = middleIndex + 1; else end = middleIndex - 1; return GetLastK(data, length, k, start, end); } int GetNumberOfK(int *data, int length, int k) { int number = 0; if(data != NULL && length > 0) { int first = GetFirstK(data, length, k, 0, length - 1); int last = GetLastK(data, length, k, 0, length - 1); if(first > -1 && last > -1) number = last - first + 1; } return number; } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。 看到这道题,我们马上就会想到,要是这个数组是排序的数组就好了。如果是排序的数组,那么我们只要遍历一次就可以统计出每个数字出现的次数,这样也就能找出符合要求的数字了。题目给出的数组没有说是排好序的,因此我们需要给它排序。排序的时间复杂度是O(nlogn),再加上遍历的时间复杂度O(n),因此总的复杂度是O(nlogn)。 接下来我们试着看看能不能想出更快的算法。前面思路的时间主要是花在排序上。我们可以创建一个哈希表来消除排序的时间。哈希表的键值(Key)为数组中的数字,值(Value)为该数字对应的次数。有了这个辅助的哈希表之后,我们只需要遍历数组中的每个数字,找到它在哈希表中对应的位置并增加它出现的次数。这种哈希表的方法在数组的所有数字都在一个比较窄的范围内的时候很有效。本博客系列的第13题就是一个应用哈希表的例子。不过本题并没有限制数组里数字的范围,我们要么需要创建一个很大的哈希表,要么需要设计一个很复杂的方法来计算哈希值。因此总体说来这个方法还不是很好。 前 面两种思路都没有考虑到题目中数组的特性:数组中有个数字出现的次数超过了数组长度的一半。也就是说,有个数字出现的次数比其他所有数字出现次数的和还要 多。 因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的 数字相同,则次数加1。如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。 基于这个思路,我们不难写出如下代码: #include<iostream> using namespace std; bool CheckMoreThanHalf(int *numbers, int length, int number); int MoreThanHalfNumber(int *numbers, int length) { if(numbers == NULL || length <= 0) return -1; int result = numbers[0]; int times = 1; for(int i = 1; i < length; ++i) { if(times == 0) { result = numbers[i]; times = 1; } else if(numbers[i] == result) times++; else times--; } if(!CheckMoreThanHalf(numbers, length, result)) result = 0; return result; } bool CheckMoreThanHalf(int *numbers, int length, int number) { int times = 0; for(int i = 0; i < length; ++i) { if(numbers[i] == number) times++; } bool isMoreThanHalf = true; if(times * 2 <= length) { isMoreThanHalf = false; } return isMoreThanHalf; } int main() { int numbers[] = {1, 2, 3, 2, 2, 2, 5, 4, 2}; int length = sizeof(numbers) / sizeof(int); cout<<MoreThanHalfNumber(numbers, length)<<endl; return 0; } 在上述代码中,有两点值得讨论: (1) 我们需要考虑当输入的数组或者长度无效时,如何告诉函数的调用者输入无效。关于处理无效输入的几种常用方法,在本博客系列的第17题中有详细的讨论; (2) 本算法的前提是输入的数组中的确包含一个出现次数超过数组长度一半的数字。如果数组中并不包含这么一个数字,那么输入也是无效的。因此在函数结束前我还加了一段代码来验证输入是不是有效的。 来源:http://zhedahht.blog.163.com/blog/static/25411174201085114733349/ 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
一.关于推送通知 来源:http://blog.csdn.net/enuola/article/details/8627283 推送通知,也被叫做远程通知,是在iOS 3.0以后被引入的功能。是当程序没有启动或不在前台运行时,告诉用户有新消息的一种途径,是从外部服务器发送到应用程序上的。一般说来,当要显示消息或 下载数据的时候,通知是由远程服务器(程序的提供者)发送,然后通过苹果的推送通知服务(Apple Push Notification Service,简称apns)推送到设备的程序上。 推送的新消息可能是一条信息、一项即将到期的日程或是一份远程服务器上的新数据。在系统上展现的时候,可以显示警告信息或在程序icon上显示数字,同 时,也可以播放警告音。一旦用户注意到程序有新的信息、时间或是数据,他们可以运行程序并访问新的内容。也可以选择忽略通知,这时程序将不会被激活。 iPhone, iPad和iPod touch上同一时刻只有一个app在前台运行。大多数程序在后台运行的时候,可以对某些用户感兴趣的内容做出回应(定时、或数据等)。推送通知能让程序在这些事件发生的时候通知用户。 作为提供者为程序开发和部署推送通知,必须通过iOS Developer Program Portal获得SSL证书。每个证书限用于一个程序,使用程序的bundle ID作为标识。证书有两种用途的:一种是针对sandbox(用于开发和测试),另外一种针对发布产品。这两种运行环境拥有为各自指定的IP地址并且需要 不同的证书。还必须为两种不同的环境获取各自的provisioning profiles。 APNS提供了两项基本的服务:消息推送和反馈服务。 消息推送:使用流式TCP套接字将推送通知作为二进制数据发送给APNs。消息推送有分别针对开发和测试用的sandbox、发布产品的两个接口,每个都 有各自的地址和端口。不管用哪个接口,都需要通过TLS或SSL,使用SSL证书来建立一个安全的信道。提供者编制通知信息,然后通过这个信道将其发送给 APNs。 注:sandbox: gateway.sandbox.push.apple.com:219产品接口:gateway.push.apple.com:2195 反馈服务:可以得到针对某个程序的发送失败记录。提供者应该使用反馈服务周期性检查哪些设备一直收不到通知,不需要重复发送通知到这些设备,降低推送服务器的负担。注:sandbox:feedback.push.apple.com:2196产品接口:feedback.sandbox.push.apple.com:2196 二.Apple Push Notification的工作机制 下面是一个完整推送流程图 从上图,我们可以看到。 首先是应用程序注册消息推送。 IOS跟APNS Server要deviceToken。应用程序接受deviceToken。 应用程序将deviceToken发送给PUSH服务端程序(Provider)。 服务端程序向APNS服务发送消息。 APNS服务将消息发送给iPhone应用程序。 无论是iPhone客户端跟APNS,还是Provider和APNS都需要通过证书进行连接的: 图中, 1. Provider是指某个iPhone软件的Push服务器,是我们将要开发的服务器。 2. APNS 是Apple Push Notification Service(Apple Push服务器)的缩写,是苹果的服务器。 上图可以分为三个阶段: 第一阶段:推送服务器(provider)把要发送的消息、目的iPhone的标识打包,发给APNS; 第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone; 第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。 三.开发证书和推送证书的配置 1. 使用开发者帐号登录IOS Provisioning ,选择或新建一个App Id,这里以“info.luoyl.iostest”为例 2. 创建完后,进入App Id列表,可以看到新建的App Id默认是没有激活推送功能的,点击Configure链接,进入推送功能激活页面: 3. 在“Enable for Apple Push Notification service”选项上打勾,然后在行点“configure”按钮: 4. 此时会弹出一窗口,点“continue” 5. 弹出证书上传页面,证书选择事先做好的“CertificateSigningRequest.certSigningRequest”,然后点“Generate”按钮; 6. 接下来会有“Your APNs SSL Certificate has been generated.”提示,点“continue”: 7. 下载刚生成的证书“aps_development.cer”到电脑: 8. 至此,appid的Development Push SSL Certificate已经变成“Enabled”状态了: 9. 制作一开发者测试证书,appid指定为“info.luoyl.iostest”, 下载后双击安装到电脑上: 10. 双击在步骤7下载的“aps_development.cer”安装到keychain Access上: 11. 选中push Services证书,右键导出证书为个人信息交换(.p12)格式文件,这里我命名为“aps_development.p12”,点存储时会弹出一个密码设置窗口,可留空不填: 12. 在终端执行下面的命令,把刚才导出的个人信息交换(.p12)格式文件加密转换成推送服务器的推送证书: [cpp] view plaincopy openssl pkcs12 -clcerts -nokeys -out cert.pem -in aps_development.p12 openssl pkcs12 -nocerts -out key.pem -in aps_development.p12 openssl rsa -in key.pem -out key.unencrypted.pem cat cert.pem key.unencrypted.pem > iostest_push_dev.pem 上面的命令在执行时有4处是需要输入密码的,其中1和2直接回车,3必须设定一个key如“push”,在4处输入3设定的key “push”; 命令执行完后生成的“iostest_push_dev.pem”就是我们推送服务器要使用的推送证书; 经过以上步骤的配置,已经完成了开发推送功能所需要的条件了,接下来将会新建一个ios应用来体验完成推送功能,在ios应用需要实现的接口。 四.开发带有推送功能的IOS应用 为使应用能支持推送功能,我们的项目配置时要注意: Bundle Identifier、Code Signing指定的开发证书绑定的AppId要和推送证书绑定的AppId一致(见下图); 如 果项目中的开发证书在AppId激活推送功能前已经创建了,这时必须重新生成一个。支持推送功能的开发证书会比旧证书多出一项名为 “aps- environment”的授权串,如果继续使用旧证书,在程序启动尝试注册推送功能时会出现“ 未找到应用程序的“aps-environment”的权利字符串 ”的错误; 测试需要用真机,模拟器不支持。 在代码方面,推送的注册、监听和处理都集中在AppDelegate类里: 1.(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 在该方法体里主要实现两个功能: 一是完成推送功能的注册请求,即在程序启动时弹出是否使用推送功能; 二是实现的程序启动是通过推送消息窗口触发的,在这里可以处理推送内容; [cpp] view plaincopy - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; // Override point for customization after application launch. self.viewController = [[[ViewController alloc] init] autorelease]; self.window.rootViewController = self.viewController; [self.window setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"background.png"]]]; [self.window makeKeyAndVisible]; /** 注册推送通知功能, */ [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)]; //判断程序是不是由推送服务完成的 if (launchOptions) { NSDictionary* pushNotificationKey = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; if (pushNotificationKey) { UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"推送通知" message:@"这是通过推送窗口启动的程序,你可以在这里处理推送内容" delegate:nil cancelButtonTitle:@"知道了" otherButtonTitles:nil, nil]; [alert show]; [alert release]; } } return YES; } 2. 接收从苹果服务器返回的唯一的设备token,该token是推送服务器发送推送消息的依据,所以需要发送回推送服务器保存 [html] view plaincopy - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSString* token = [NSString stringWithFormat:@"%@",deviceToken]; NSLog(@"apns -> 生成的devToken:%@", token); //把deviceToken发送到我们的推送服务器 DeviceSender* sender = [[[DeviceSender alloc]initWithDelegate:self ]autorelease]; [sender sendDeviceToPushServer:token ]; } 3.接收注册推送通知功能时出现的错误,并做相关处理: [html] view plaincopy - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err { NSLog(@"apns -> 注册推送功能时发生错误, 错误信息:\n %@", err); } 4. 接收到推送消息,解析处理 [cpp] view plaincopy - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { NSLog(@"\napns -> didReceiveRemoteNotification,Receive Data:\n%@", userInfo); //把icon上的标记数字设置为0, application.applicationIconBadgeNumber = 0; if ([[userInfo objectForKey:@"aps"] objectForKey:@"alert"]!=NULL) { UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"**推送消息**" message:[[userInfo objectForKey:@"aps"] objectForKey:@"alert"] delegate:self cancelButtonTitle:@"关闭" otherButtonTitles:@"处理推送内容",nil]; alert.tag = alert_tag_push; [alert show]; } } 通过上面的代码,基本推送功能的开发已经完成了。最后附件上面代码中所需用到的DeviceSender的类文件,需要将其头文件导入到AppDelegate中。下面是DeviceSender类的.h和.m文件的下载地址: http://download.csdn.net/detail/enuola/5100134 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://www.cocoachina.com/applenews/devnews/2013/0918/7022.html 古人云“工欲善其事必先利其器”,打造一个强大的开发环境,是立即提升自身战斗力的绝佳途径!以下是搜集的一些有力的XCode插件。 1.全能搜索家CodePilot 2.0 你要找的是文件?是文件夹?是代码?Never Mind,CMD+SHIFT+X调出CodePilot,输入任何你想到搜的东西吧!想搜 appFinishLaunchingWithOptions?忘记咋拼了?没关系强大的代码搜索能力,appflaun一样也可以找到!超级强大的正则 匹配,匹配任何你所想! 项目地址:http://codepilot.cc 2.Vim控必备的XVim XVim是一个针对Xcode的Vim插件,能让开发者在不放弃任何xcode功能的前提下体验vim的功能。 项目地址:https://github.com/JugglerShu/XVim 3.YouCompleteMe(vim的插件) 如果你比较喜欢用vim来写代码的话,这里有一个非常棒的vim插件——YouCompleteMe——当你在编写OC代码时,可以提升体验。 YouCompleteMe可以在Vim中添加代码自动补全功能,并且不需要你来按某个键来查看代码补全建议——针对OC、OC++、C++以及C该插件 可以自动补全建议。 项目地址:https://github.com/Valloric/YouCompleteMe 4.XCode颜色显示插件ColorSense 代码里的那些冷冰冰的颜色数值,到底时什么颜色?如果你经常遇到这个问题,每每不得不运行下模拟器去看看,那么这个插件绝对不容错过。更彪悍的是你甚至可以点击显示的颜色面板,直接通过系统的ColorPicker来自动生成对应颜色代码,再也不用做各种颜色代码转换了! 项目地址: https://github.com/omz/ColorSense-for-Xcode 5.大段文本利器HOStringSense 经常输入大段文本的时候,如果文本里面有各种换行和特殊字符,经常会让人很头疼,有了HOStringSense,再也不不用为这个问题犯愁了,顺便附送字数统计功能。 项目地址:https://github.com/holtwick/HOStringSense-for-Xcode 6.规范注释生成器VVDocumenter 很多时候,为了快速开发,很多的技术文档都是能省则省,这个时候注释就变得异常重要,再配合Doxygen这种注释自动生成文档的,就完美了。 但是每次都要手动输入规范化的注释,着实也麻烦,但有了VVDocumenter,规范化的注释,主需要输入三个斜线“///”,就OK啦! (VVDocumenter在Mac OSX 10.8.5和Xcode 4.6.3上进行开发,应该能支持所有Xcode 4版本,如果想支持Xcode 5,可以对plist文件稍作修改。 项目地址:https://github.com/onevcat/VVDocumenter-Xcode 7.CocoaPods for Xcode 非常方便的Xcode pods插件。可以很方便的在Xcode通过pods安装各种objective-c第三方库,省去以前还要手动去跑pods命令行的麻烦;此外,还支持 通过cocoaDocs来安装库文档。唯一的遗憾是,它目前只支持Xcode5,4版本还用不了。 项目地址:https://github.com/kattrali/cocoapods-xcode-plugin 8.Xcode语法高亮插件 以前用eclipse开发,自带的有语法高亮的效果。做ios开发也许久了,但是没发现一款语法高亮的插件,因为xcode自己的效果是仅在变 量或类名下面加了个虚线,平时看起代码来十分不舒服,最近果断为xcode写了一款语法高亮的插件,不过功能非常有限,没有eclipse的那么好用,也 没对对象的作用域区分,勉强能使用吧。和有需要的分享一下吧。 下载附件,解压后放在:你的用户/Library/Application Support/Developer/Shared/Xcode/Plug-ins目录下,有的童鞋还没有Plug-ins这个目录吧,那就手动建一个, 然后把解压后的highlight-Plugin.xcplugin放进去,重启xcode即可。然后就能看到高亮的菜单了。 项目地址: http://www.cocoachina.com/bbs/read.php?tid=150107 9. KSImageNamed-Xcode 为项目中使用的UIImage的imageNamed提供文件名自动补全功能。使用[UIImage imageNamed:@"xxx"]时,该插件会扫描整个workspace中的图片文件。 项目地址: https://github.com/ksuther/KSImageNamed-Xcode 10.xcode-extend-plug-in 帮助你快速格式化代码、生成注释、复制一行等。 项目地址: https://code.google.com/p/xcode-extend-plug-in/ 11.XcodeColors 改变调试控制台颜色 项目地址: https://github.com/robbiehanson/XcodeColors 12.SCXcodeMiniMap 一个Xcode插件,可以在当前的窗口内创建一个代码迷你地图,并在屏幕上高亮提示。 项目地址: https://github.com/stefanceriu/SCXcodeMiniMap 13.Lin本地化字符串 之前我们提到过一个开源的Mac基础工具SCStringsUtility,可以让你在一个清爽的界面编辑不同的语言,简单地输入/输出 NSLocalizedString数据。Lin是一款功能相近的Xcode插件,提供了一个非常不错的操作界面,并且为不同的语言提供了不同的区域。 项目地址:https://github.com/questbeat/Lin 14.插件管理Alcatraz Alcatraz是一个开源的Xcode 4包管理器,可以让你更便捷地发现、安装以及管理插件、模板和配色方案。只需要简单地点击或者勾选,不需要手工复制和粘贴。 项目地址: https://github.com/mneorr/Alcatraz 15.FuzzyAutocompletePlugin--Xcode 5代码自动补全插件 FuzzyAutocompletePlugin是一个Xcode 5兼容的插件,通过添加模糊匹配来提高Xcode代码自动补全功能,开发者无需遵循从头匹配的原则,只要记得方法里某个关键字即可进行匹配,很好地提高了工作效率。 注意:该插件只在Xcode 5上进行过测试,没有测试和其他插件之间的兼容性(KSImageNamed除外)。 项目地址:https://github.com/chendo/FuzzyAutocompletePlugin 附件: /cms/uploads/soft/131031/4196-131031114408.zip 16.一个用来对齐常规代码的Xcode插件--XAlign 一个用来对齐常规代码的Xcode插件,十分强大的自定义对齐模式。这里是一个对齐模式示例,模式文件在main/main/patterns.plist. 详细信息可参看:http://github.so/XAlign/ 附件: /cms/uploads/soft/131211/4196-131211113G3.zip 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/ 前言 这里有关于block的5道测试题,建议你阅读本文之前先做一下测试。 先介绍一下什么是闭包。在wikipedia上,闭包的定义)是: In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function. 翻译过来,闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。 block实际上就是Objective-C语言对于闭包的实现。 block配合上dispatch_queue,可以方便地实现简单的多线程编程和异步编程,关于这个,我之前写过一篇文章介绍:《使用GCD》。 本文主要介绍Objective-C语言的block在编译器中的实现方式。主要包括: block的内部实现数据结构介绍 block的三种类型及其相关的内存管理方式 block如何通过capture变量来达到访问函数外的变量 实现方式 数据结构定义 block的数据结构定义如下(图片来自这里): 对应的结构体定义如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ }; 通过该图,我们可以知道,一个block实例实际上由6部分构成: isa指针,所有对象都有该指针,用于实现对象相关的功能。 flags,用于按bit位表示一些block的附加信息,本文后面介绍block copy的实现代码可以看到对该变量的使用。 reserved,保留变量。 invoke,函数指针,指向具体的block实现的函数调用地址。 descriptor, 表示该block的附加描述信息,主要是size大小,以及copy和dispose函数的指针。 variables,capture过来的变量,block能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。 该数据结构和后面的clang分析出来的结构实际是一样的,不过仅是结构体的嵌套方式不一样。但这一点我一开始没有想明白,所以也给大家解释一下,如下2个结构体SampleA和SampleB在内存上是完全一样的,原因是结构体本身并不带有任何额外的附加信息。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct SampleA { int a; int b; int c; }; struct SampleB { int a; struct Part1 { int b; }; struct Part2 { int c; }; }; 在Objective-C语言中,一共有3种类型的block: _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量。 _NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。 _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁。 我们在下面会分别来查看它们各自的实现方式上的差别。 研究工具:clang 为了研究编译器是如何实现block的,我们需要使用clang。clang提供一个命令,可以将Objetive-C的源码改写成c语言的,借此可以研究block具体的源码实现方式。该命令是 1 clang -rewrite-objc block.c NSConcreteGlobalBlock 类型的block的实现 我们先新建一个名为block1.c的源文件: 1 2 3 4 5 6 7 #include <stdio.h> int main() { ^{ printf("Hello, World!\n"); } (); return 0; } 然后在命令行中输入clang -rewrite-objc block1.c即可在目录中看到clang输出了一个名为block1.cpp的文件。该文件就是block在c语言实现,我将block1.cpp中一些无关的代码去掉,将关键代码引用如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Hello, World!\n"); } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; int main() { (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) (); return 0; } 下面我们就具体看一下是如何实现的。__main_block_impl_0就是该block的实现,从中我们可以看出: 一个block实际是一个对象,它主要由一个 isa 和 一个 impl 和 一个descriptor组成。 在本例中,isa指向 _NSConcreteGlobalBlock, 主要是为了实现对象的所有特性,在此我们就不展开讨论了。 由于clang改写的具体实现方式和LLVM不太一样,并且这里没有开启ARC。所以这里我们看到isa指向的还是_NSConcreteStackBlock。但在LLVM的实现中,开启ARC时,block应该是_NSConcreteGlobalBlock类型,具体可以看《objective-c-blocks-quiz》第二题的解释。 impl是实际的函数指针,本例中,它指向__main_block_func_0。这里的impl相当于之前提到的invoke变量,只是clang编译器对变量的命名不一样而已。 descriptor是用于描述当前这个block的附加信息的,包括结构体的大小,需要capture和dispose的变量列表等。结构体大 小需要保存是因为,每个block因为会capture一些变量,这些变量会加到__main_block_impl_0这个结构体中,使其体积变大。在 该例子中我们还看不到相关capture的代码,后面将会看到。 NSConcreteStackBlock 类型的block的实现 我们另外新建一个名为block2.c的文件,输入以下内容: 1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main() { int a = 100; void (^block2)(void) = ^{ printf("%d\n", a); }; block2(); return 0; } 用之前提到的clang工具,转换后的关键代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy printf("%d\n", a); } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main() { int a = 100; void (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a); ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2); return 0; } 在本例中,我们可以看到: 本例中,isa指向_NSConcreteStackBlock,说明这是一个分配在栈上的实例。 main_block_impl_0 中增加了一个变量a,在block中引用的变量a实际是在申明block时,被复制到main_block_impl_0结构体中的那个变量a。因为这样,我们就能理解,在block内部修改变量a的内容,不会影响外部的实际变量a。 main_block_impl_0 中由于增加了一个变量a,所以结构体的大小变大了,该结构体大小被写在了main_block_desc_0中。 我们修改上面的源码,在变量前面增加__block关键字: 1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main() { __block int i = 1024; void (^block1)(void) = ^{ printf("%d\n", i); i = 1023; }; block1(); return 0; } 生成的关键代码如下,可以看到,差异相当大: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_i_0 *i; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_i_0 *i = __cself->i; // bound by ref printf("%d\n", (i->__forwarding->i)); (i->__forwarding->i) = 1023; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main() { __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024}; void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344); ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1); return 0; } 从代码中我们可以看到: 源码中增加一个名为__Block_byref_i_0 的结构体,用来保存我们要capture并且修改的变量i。 main_block_impl_0 中引用的是Block_byref_i_0的结构体指针,这样就可以达到修改外部变量的作用。 __Block_byref_i_0结构体中带有isa,说明它也是一个对象。 我们需要负责Block_byref_i_0结构体相关的内存管理,所以main_block_desc_0中增加了copy和dispose函数指针,对于在调用前后修改相应变量的引用计数。 NSConcreteMallocBlock 类型的block的实现 NSConcreteMallocBlock类型的block通常不会在源码中直接出现,因为默认它是当一个block被copy的时候,才会将这个block复制到堆中。以下是一个block被copy时的示例代码(来自这里),可以看到,在第8步,目标的block类型被修改为_NSConcreteMallocBlock。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; // 1 if (!arg) return NULL; // 2 aBlock = (struct Block_layout *)arg; // 3 if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } // 4 else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } // 5 struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return (void *)0; // 6 memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // 7 result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; // 8 result->isa = _NSConcreteMallocBlock; // 9 if (result->flags & BLOCK_HAS_COPY_DISPOSE) { (*aBlock->descriptor->copy)(result, aBlock); // do fixup } return result; } 变量的复制 对于block外的变量引用,block默认是将其复制到其数据结构中来实现访问的,如下图所示(图片来自这里): 对于用__block修饰的外部变量引用,block是复制其引用地址来实现访问的,如下图所示(图片来自这里): LLVM源码 在LLVM开源的关于block的实现源码,其内容也和我们用clang改写得到的内容相似,印证了我们对于block内部数据结构的推测。 ARC对block类型的影响 在ARC开启的情况下,将只会有 NSConcreteGlobalBlock和 NSConcreteMallocBlock类型的block。 原本的NSConcreteStackBlock的block会被NSConcreteMallocBlock类型的block替代。证明方式是以下代码在XCode中,会输出 <__NSMallocBlock__: 0x100109960>。在苹果的官方文档中也提到,当把栈中的block返回时,不需要调用copy方法了。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { int i = 1024; void (^block1)(void) = ^{ printf("%d\n", i); }; block1(); NSLog(@"%@", block1); } return 0; } 我个人认为这么做的原因是,由于ARC已经能很好地处理对象的生命周期的管理,这样所有对象都放到堆上管理,对于编译器实现来说,会比较方便。 参考链接 希望本文能加深你对于block的理解。我在学习中,查阅了以下文章,一并分享给大家。祝大家玩得开心~ A look inside blocks: Episode 1 A look inside blocks: Episode 2 A look inside blocks: Episode 3 对Objective-C中Block的追探 LLVM中block实现源码 objective-c-blocks-quiz Blocks 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://blog.csdn.net/chhuach2005/article/details/21168179 1.题目 编写两个任意位数的大数相乘的程序,给出计算结果。 2.题目分析 该题相继被ACM、华为、腾讯等选作笔试、面试题,笔者2014年替师兄去腾讯笔试就遇到此题,当然若无准备要写出这种程序,还是要花一定的时间的。故,觉得有必要深入研究一下。搜索了网上的大多数该类程序和算法,发现,大数乘法主要有模拟手工计算的普通大数乘法,分治算法和FFT算法。其中普通大数乘法占据了90%以上,其优点是空间复杂度低,实现简单,时间复杂度为O(N²),分治算法虽然时间复杂度降低为, 但其实现需要配 合字符串模拟加减法操作,实现较为复杂, 参考博客1http://cnn237111.blog.51cto.com/2359144/1201901 FFT算法则更为复杂,较少适用,有兴趣 参考博客2 http://blog.csdn.net/hondely/article/details/6938497 和博客3http://blog.csdn.net/jackyguo1992/article/details/12613287。 3.题目解答 3.1 逐位相乘处理进位法 参考博客4的思路 乘积是逐位相乘,也就是aibj,结果加入到积C的第i+j位,最后处理进位即可,例如:A =17 = 1*10 + 7 = (7,1)最后是十进制的幂表示法,幂次是从低位到高位,以下同。B=25 = 2*10 + 5 = (5, 2);C = A * B = (7 * 5, 1 * 5 + 2 * 7, 1 * 2) = (35, 19, 2) = (5, 22, 2) = (5, 2. 4)=425。 原博客的思路为: (1)转换并反转,字符串转换为数字并将字序反转; (2)逐位相乘,结果存放在result_num[i+j]中; (3)处理进位,消除多余的0; (4)转换并反转,将计算结果转换为字符串并反转。 原博客中采用指针参数传递,字符串长度有限制,改为通过string传参数,按原思路编程如下: 头文件和数据结构: [cpp] view plaincopy #include <iostream> #include <string> #include <vector> #include <stdlib.h> using namespace std; struct bigcheng { vector<int> a; vector<int> b; string result_str; }; void chartonum(string a,string b,bigcheng &tempcheng);//字符串转换为数字并反转 void multiply(bigcheng &tempchengh,vector<int> &result_num);//逐位相乘,处理进位消除多余的0 void numtochar(bigcheng &tempcheng,vector<int> &result_num);//将计算结果转换为字符串并反转 (1)转换并反转,字符串转换为数字并将字序反转; [cpp] view plaincopy void chartonum(string a,string b,bigcheng &tempcheng) { int size_a=a.size(); int size_b=b.size(); for (int i=size_a-1;i>=0;i--) { tempcheng.a.push_back(a[i]-'0'); } for (int i=size_b-1;i>=0;i--) { tempcheng.b.push_back(b[i]-'0'); } } (2)逐位相乘,结果存放在result_num[i+j]中; (3)处理进位,消除多余的0;代码为: [cpp] view plaincopy void multiply(bigcheng &tempcheng,vector<int> &result_num) { for (unsigned int i=0;i<tempcheng.a.size();i++) { for (unsigned int j=0;j<tempcheng.b.size();j++) { result_num[i+j]+=(tempcheng.a[i])*(tempcheng.b[j]); } } for (int i=result_num.size()-1;i>=0;i--) { if (result_num[i]!=0) { break; } else result_num.pop_back(); } int c=0; for (unsigned int i=0;i<result_num.size();i++)//处理进位 { result_num[i]+=c; c=result_num[i]/10; result_num[i]=result_num[i]%10; } if (c!=0) { result_num.push_back(c); } } (4)转换并反转,将计算结果转换为字符串并反转。 [cpp] view plaincopy void numtochar(bigcheng &tempcheng,vector<int> &result_num) { int size=result_num.size(); for (unsigned int i=0;i<result_num.size();i++) { tempcheng.result_str.push_back(char(result_num[size-1-i]+'0')); } } 主函数为: [cpp] view plaincopy int main() { bigcheng tempcheng; string a,b; cin>>a>>b; chartonum(a,b,tempcheng); vector<int> resultnum(a.size()+b.size(),0); multiply(tempcheng,resultnum); numtochar(tempcheng,resultnum); cout<<tempcheng.result_str<<endl; system("pause"); return 0; } 上面的思路还是很清晰的,但代码有些过长,考虑优化如下: (1)上述思路是先转换反转,其实无需先将全部字符串转换为数字的,可即用即转,节约空间; (2)无需等到逐位相乘都结束,才处理进位,可即乘即进; (3)无需等到所有结果出来后,将结果转换为字符,可即乘即转。 优化后时间复杂度不变,但节省了空间,代码更简洁。如下: 头文件和数据结构: [cpp] view plaincopy #include <iostream> #include <string> #include <vector> #include <stdlib.h> #include <assert.h> using namespace std; struct bigcheng2 { string a; string b; string result_str; }; void reverse_data( string &data);//字符串反转 void multiply2(bigcheng2 &tempcheng2);//字符串模拟相乘 字符串反转: [cpp] view plaincopy void reverse_data( string &data) { char temp = '0'; int start=0; int end=data.size()-1; assert( data.size()&& start <= end ); while ( start < end ) { temp = data[start]; data[start++] = data[end]; data[end--] = temp; } } 两数相乘: [cpp] view plaincopy void multiply2(bigcheng2 &tempcheng2) { reverse_data(tempcheng2.a);//字符串反转 reverse_data(tempcheng2.b); int c=0; string temp(tempcheng2.a.size()+tempcheng2.b.size(),'0');//将temp全部初始化为0字符 for (unsigned int i=0;i<tempcheng2.a.size();i++) { unsigned int j; for (j=0;j<tempcheng2.b.size();j++) { c+=temp[i+j]-'0'+(tempcheng2.a[i]-'0')*(tempcheng2.b[j]-'0');//注意temp[i+j]可能保存有上一次计算的结果 temp[i+j]=(c%10)+'0';//将结果转换为字符 c=c/10; } while(c) { temp[i+j++]+=c%10;//temp里已存字符 c=c/10; } } for (int i=temp.size()-1;i>=0;i--) { if (temp[i]!='0') break; else temp.pop_back(); } reverse_data(temp);//结果?字Á?符¤?串ä?反¤¡ä转Áa tempcheng2.result_str=temp; } 主函数: [cpp] view plaincopy int main() { bigcheng2 tempcheng2; string a,b; cin>>a>>b; tempcheng2.a=a; tempcheng2.b=b; multiply2(tempcheng2); cout<<tempcheng2.result_str<<endl; system("pause"); return 0; } 3.2 移位进位法 移位进位法也是普通的大数相乘算法,其时间复杂度也为O(N²)其基本思路参考博客5,简述如下: 按照乘法的计算过程来模拟计算: 1 2 × 3 6 ---------- ---- 其中,上标数字为进位数值。 71 2 --- 在这个计算过程中,2×6=12。本位保留2,进位为1.这里是一个简单的计算过程,如果在高位也需要进位的情况下,如何处理? 3 6 ----------- 413 2 其代码优化如下: [cpp] view plaincopy #include <iostream> #include <string> #include <vector> #include <stdlib.h> #include <assert.h> using namespace std; void reverse_data( string &data);//字符串反转 void compute_value( string lhs,string rhs,string &result ); void reverse_data( string &data) { char temp = '0'; int start=0; int end=data.size()-1; assert( data.size()&& start <= end ); while ( start < end ) { temp = data[start]; data[start++] = data[end]; data[end--] = temp; } } void compute_value( string lhs,string rhs,string &result ) { reverse_data(lhs); reverse_data(rhs); int i = 0, j = 0, res_i = 0; int tmp_i = 0; int carry = 0; for ( i = 0; i!=lhs.size(); ++i, ++tmp_i ) { res_i = tmp_i; //在每次计算时,结果存储的位需要增加 for ( j = 0; j!= rhs.size(); ++j ) { carry += ( result[res_i] - '0' )+(lhs[i] - '0') * (rhs[j] - '0');//此处注意,每次计算并不能保证以前计算结果的进位都消除, 并且以前的计算结果也需考虑。 result[res_i++] = ( carry % 10 + '0' ); carry /= 10; } while (carry)//乘数的一次计算完成,可能存在有的进位没有处理 { result[res_i++] = (carry % 10 + '0'); carry /= 10; } } for (int i=result.size()-1;i>=0;i--) { if (result[i]!='0') break; else result.pop_back(); } reverse_data(result); } int main() { string a,b; cin>>a>>b; string result(a.size()+b.size(),'0'); compute_value(a,b,result); cout<<result<<endl; system("pause"); return 0; } 3.3运行结果 运行结果如图1、图2所示 图1 图2 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
题目:输入两个链表,找出它们的第一个公共结点。 链表结点定义如下: struct ListNode { int m_nKey; ListNode *m_pNext; }; 解决办法:首先遍历两个链表得到它们的长度,就能知道哪个链表比较长,以及长的链表比短的链表多几个结点。在第二次遍历的时候,在较长的链表上先走若干步,接着再同时在两个链表上遍历,找到的第一个相同的结点就是它们的第一个公共结点。 ListNode *FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2) { //得到两个链表的长度 unsigned int nLength1 = GetListLength(pHead1); unsigned int nLength2 = GetListLength(pHead2); int nLengthDif = nLength1 - nLength2; ListNode *pListHeadLong = pHead1; ListNode *pListHeadShort = pHead2; if(nLength2 > nLength1) { pListHeadLong = pHead2; pListHeadShort = pHead1; nLengthDif = nLength2 - nLength1; } //先在长链表上走几步,再同时在两个链表上遍历 for(int i = 0; i < nLengthDif; ++i) { pListHeadLong = pListHeadLong->m_pNext; } while((pListHeadLong != NULL) && (pListHeadShort != NULL) && (pListHeadLong != pListHeadShort)) { pListHeadLong = pListHeadLong->m_pNext; pListHeadShort = pListHeadShort->m_pNext; } //得到第一个公共结点 ListNode *pFirstCommonNode = pListHeadLong; return pFirstCommonNode; } unsigned int GetListLength(ListNode *pHead) { unsigned int nLength = 0; ListNode *pNode = pHead; while(pNode != NULL) { ++nLength; pNode = pNode->m_pNext; } return nLength; } 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
学习表视图(Table View)的应用时,自己写了个demo,最后表格出来了,可是滑动时报错了,报错如下: 这是我ViewController.m部分的代码: 1 #import "ViewController.h" 2 3 @interface ViewController () 4 5 @end 6 7 @implementation ViewController 8 { 9 NSArray *tableData; 10 } 11 12 - (void)viewDidLoad 13 { 14 [super viewDidLoad]; 15 // Do any additional setup after loading the view, typically from a nib. 16 tableData = [NSArray arrayWithObjects:@"Egg Benedict" , @"Mushroom Risotto" , @"Full Breakfast" , @"Hamburger" ,@"Ham and Egg Sandwich" , @"Creme brelee" , @"white chocolate donut" , @"starbucks coffee" , @"vegetable curry" , @"instant noodle with egg" , @"noodle with bbq pork" , @"japanese noodle" , @"green tea" , @"thai shrimp cake" , @"angry birds cake" , @"ham and cheese panini" , nil]; 17 //[tableData retain]; 18 19 } 20 21 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 22 { 23 return [tableData count]; 24 25 } 26 27 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 28 { 29 static NSString *simpleTableIdentifier = @"SimpleTableItem"; 30 31 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier]; 32 33 if (cell == nil) { 34 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier]; 35 } 36 37 //[[cell textLabel] setText:[tableData objectAtIndex:[indexPath row]]]; 38 cell.textLabel.text = [tableData objectAtIndex:indexPath.row]; 39 cell.imageView.image = [UIImage imageNamed:@"icon.png"]; 40 41 42 return cell; 43 44 } 45 46 - (void)didReceiveMemoryWarning 47 { 48 [super didReceiveMemoryWarning]; 49 // Dispose of any resources that can be recreated. 50 } 51 52 @end 经过反复的测试后,解决办法如下: 在第17行加上: [tableData retain]; 这样就可以解决报错问题了。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
iOS设备检查更新版本: #pragma mark - 检查更新 - (void)checkUpdateWithAPPID:(NSString *)APPID { //获取当前应用版本号 NSDictionary *appInfo = [[NSBundle mainBundle] infoDictionary]; NSString *currentVersion = [appInfo objectForKey:@"CFBundleVersion"]; NSString *updateUrlString = [NSString stringWithFormat:@"http://itunes.apple.com/lookup?id=%@",APPID]; NSURL *updateUrl = [NSURL URLWithString:updateUrlString]; versionRequest = [ASIFormDataRequest requestWithURL:updateUrl]; [versionRequest setRequestMethod:@"GET"]; [versionRequest setTimeOutSeconds:60]; [versionRequest addRequestHeader:@"Content-Type" value:@"application/json"]; //loading view CustomAlertView *checkingAlertView = [[CustomAlertView alloc] initWithFrame:NAVIGATION_FRAME style:CustomAlertViewStyleDefault noticeText:@"正在检查更新..."]; checkingAlertView.userInteractionEnabled = YES; [self.navigationController.view addSubview:checkingAlertView]; [checkingAlertView release]; [versionRequest setCompletionBlock:^{ [checkingAlertView removeFromSuperview]; NSError *error = nil; NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[versionRequest responseData] options:NSJSONReadingMutableContainers error:&error]; if (!error) { if (dict != nil) { // DLog(@"dict %@",dict); int resultCount = [[dict objectForKey:@"resultCount"] integerValue]; if (resultCount == 1) { NSArray *resultArray = [dict objectForKey:@"results"]; // DLog(@"version %@",[resultArray objectAtIndex:0]); NSDictionary *resultDict = [resultArray objectAtIndex:0]; // DLog(@"version is %@",[resultDict objectForKey:@"version"]); NSString *newVersion = [resultDict objectForKey:@"version"]; if ([newVersion doubleValue] > [currentVersion doubleValue]) { NSString *msg = [NSString stringWithFormat:@"最新版本为%@,是否更新?",newVersion]; newVersionURlString = [[resultDict objectForKey:@"trackViewUrl"] copy]; DLog(@"newVersionUrl is %@",newVersionURlString); // if ([newVersionURlString hasPrefix:@"https"]) { // [newVersionURlString replaceCharactersInRange:NSMakeRange(0, 5) withString:@"itms-apps"]; // } UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:msg delegate:self cancelButtonTitle:@"暂不" otherButtonTitles:@"立即更新", nil]; alertView.tag = 1000; [alertView show]; [alertView release]; }else { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"您使用的是最新版本!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"确定", nil]; alertView.tag = 1001; [alertView show]; [alertView release]; } } } }else { DLog("error is %@",[error debugDescription]); } }]; [versionRequest setFailedBlock:^{ [checkingAlertView removeFromSuperview]; CustomAlertView *alertView = [[CustomAlertView alloc] initWithFrame:NAVIGATION_FRAME style:CustomAlertViewStyleWarning noticeText:@"操作失败,请稍候再试!"]; [self.navigationController.view addSubview:alertView]; [alertView release]; [alertView selfRemoveFromSuperviewAfterSeconds:1.0]; }]; [versionRequest startSynchronous]; } - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { DLog(@"newVersionUrl is %@",newVersionURlString); if (buttonIndex) { if (alertView.tag == 1000) { if(newVersionURlString) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:newVersionURlString]]; } } } } 来源:http://blog.csdn.net/heartofthesea/article/details/14127587 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
最近项目中,介于测试人员提出的问题,有些情况只在ios6的设备上才能显现,而本机的xcode已升级到最新的5.0,这可如何是好呢,在网上搜索了一番,找到如下方法解决此问题:1.打开xcode5.0的目录:Finder中点击“应用程序”,找到xcode,右击选择“显示包内容”,进入“Contents—Developer—Platforms—iPhoneOS.platform—Developer—SDKs”2.加载xcode4.6的安装包,同样“显示包内容”,定位到与上方(1)相同目录,将其中的“iPhoneOS6.1.sdk”,复制到xcode5.0的上方目录中3.打开xcode5.0的Contents—Developer—Platforms—iPhoneSimulator.platform—Developer—SDKs”4.将xcode4.6同样定位到与上方(3)相同目录,将其中的“iPhoneSimulator6.1.sdk”,复制到xcode5.0的上方(3)目录中此时,文件都已准备就绪。接下来就是使用了! 先重启一下xcode,打开之后,选择模拟器,若出现如下界面,则配置Ok 使用过程中,就可以在Build Settings中通过设置Bse SDK,随便切换了,如下图 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
多个人共同操作同一个项目或拷贝项目时,经常会出现类似这样的问题: Undefined symbols for architecture i386: "_OBJC_CLASS_$_xx文件名", referenced from: 下面是可能导致这类问题出现的原因及修改: 1.相关工程文件未导入 你可以直接在这里+进来,也可以在左边工程目录中把文件全部重新导人一遍(多人操作工程时,一般这种解决办法) 2..framework文件未导入 把xx文件库+进来,本问题"_OBJC_CLASS_$_ASIdentifierManager", 就是因为AdSupport.Framework类库未加 3.文件路径缺失 检查是否某些文件路径未加入进来或者写错了 工程编译报出:Undefined symbols for architecture i386:和"_OBJC_CLASS_$_xx", referenced from:错误,问题大致是由于上面这几种情况,把各个方面检查下基本就ok了。 备注:出现这个问题大都是由于多人共同操作缺少了文件。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
之前一段时间在学习ios的开发,近一段时间想着也接触下Android开发,以来加深对移动端开发的理解。这里根据自己配置Android开发环境的过程,比较详细的来总结下自己的安装过程,希望对一些正准备配置Android开发环境的小伙伴们有一定帮助。 1.Java JDK 需要先说明下,OS X系统是自带有Java JDK1.6的。不过这里我安装的是JDK7,下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html。见下图: 下载后,双击安装,如下图: 2.ADT(Android Develop Bundle) 下载地址:http://developer.android.com/sdk/index.html 如下图: 下载成功后,安装: 解压开发工具包后可看到底下有两个文件夹:eclipse和sdk。其中eclipse这个目录里有我们编程用的集成开发环境,而sdk这个目录里放的是和 android 开发相关的资源,具体到里面每个目录做什么,这里我就先不详细展开。 注意:尽量将解压的开发工具包放在一级目录下。有时你放在用中文命名的目录下,运行Eclipse会报错。 3.运行eclipse。 进入到eclipse目录下,运行Eclipse。之后会提示你工程文件夹放哪里,这个你可以自己设置路径(不要放在中文目录下),也可以使用默认路径。 有可能运行Eclipse时,会弹出如下图信息。这时也不用慌张,直接点击“安装” ,安装Java SE 6 runtime成功后,就可以运行Eclipse了。 进行到这里,mac下Android的开发环境就基本大功告成了,是不是so easy? 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://blog.csdn.net/totogo2010/article/details/8233565 虽然iOS 5.0版本之后加入了ARC机制,由于相互引用关系比较复杂时,内存泄露还是可能存在。所以了解原理很重要。 这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄露,以及NSZombieEnabled设置的使用。 本文假设你已经比较熟悉Obj-C的内存管理机制。 实验的开发环境:XCode 4.5.2 1、运行Demo。 先下载一个实现准备好的内存泄露的Demo吧:leak app 下载下来,打开运行,程序是一个寿司的列表,列出各种寿司卷。试着选择里面的几行,应该是选第二行的时候就崩溃了。崩溃截图: 在崩溃的地方断住了,知道crash的地方了,但是不知道具体crash的原因。 2、设置NSZombieEnabled 这是一个 “EXC_BAD_ACCESS”错误。我们打开XCode的选项:“NSZombieEnabled” 。在crash时可能会给你更多的一些提示信息。 设置步骤:1 2:勾上红色框里的 运行,按刚才的操作选中其中的cell。再次crash,这次在output窗口会看到多了一项错误信息: 2012-11-28 13:22:08.911 PropMemFun[2132:11303] *** -[CFString respondsToSelector:]: message sent to deallocated instance 0x713ebc0 大概意思是:向已释放的内存发送消息。也就是说使用了已释放的内存,在C语言相当于使用了“野指针” 看了下crash的这个语句,sushiString应该是没问题的,它是从stringWithFormat初始化出来的。那就是_lastSushiSelected的问题。 _lastSushiSelected指向了sushiString,sushiString是一个autorelease变量。 在第二次点击时,使用的是sushiString已经被释放,所以crash了。那为_lastSushiSelected保留一下,就可以用了。代码修改如下: [cpp] view plaincopy <span style="font-size:14px;"> _lastSushiSelected = [sushiString retain]; </span> 运行,这时候不崩溃。 3、分析内存泄露(shift+command+b) app不crash了,那看看有没有内存泄露。用XCode的Analyze就能分析到哪里有内存泄露 分析之后可以看到: 这里提示alertView没被释放,有内存泄露,那我们释放 [alertView release]; 再分析,这个问题解决了。 4、使用Instruments的leaks工具 分析内存泄露不能把所有的内存泄露查出来,有的内存泄露是在运行时,用户操作时才产生的。那就需要用到Instruments了。 按上面操作,build成功后跳出Instruments工具,选择Leaks选项,这时候寿司程序也运行起来了,选中list中的项,拖动等操作后,工具显示效果如下: 大家可能都能猜到,红色的柱子表示内存泄露了。怎么通过这个工具看到在哪泄露了呢? 先在工具栏按下红色的圆形按钮,把工具监视内存的活动停下来。选择Leak,然后点中间十字交叉那,选择Call Tree. 这时候左下角的Call Tree的可选项可以选了。选中Invert Call Tree 和Hide System Libraries,显示如下: 这时候内存泄露的具体代码找到了,在右边的红色框框里指定了哪个方法出现了内存泄露。 你只要在这些方法上双击,就会跳转到具体的代码,哈哈,是不是很方便。 这里应该是提示100%内存会泄露。 6、解决内存泄露问题 问题找到了,那就解决吧 关于:tableView:didSelectRowAtIndexPath ,分析下它的内存过程: sushiString变量通过autorelease创建,它的引用计数是1. 这行代码使得引用计数增加到2, _lastSushiSelected = [sushiString retain]; 这个方法结束时,sushiString的autorelease生效了,这个变量的引用计数减少为1 当再次执行tableView:didSelectRowAtIndexPath这个方法时,_lastSushiSelected被赋值了新指针,老的_lastSushiSelected的引用计数还是1,没有被释放,产生了内存泄露。 怎么解决呢? 在_lastSushiSelected = [sushiString retain];之前把原来的release就ok了: [cpp] view plaincopy [_lastSushiSelected release]; _lastSushiSelected = [sushiString retain]; 关于:tableView:cellForRowAtIndexPath 这个比较明显,sushiString被alloc和init之后就没有释放,可以用stringWithFormat来调用autorelease,代码如下: [cpp] view plaincopy NSString *sushiString = [NSString stringWithFormat:@"%d: %@", indexPath.row, sushiName]; 好了,泄露都fix了,再用工具分析看看,这时候你再点,再拖,再怎么操作,都没有内存泄露了。表明内存泄露被堵住了。 这是本文修复好的app代码:no LeakApp 本文参考:http://www.raywenderlich.com/ 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
一.SVN简介 SVN作为以一种崛起的版本管理工具,是CVS的接班人。对于概念性的介绍我这里就不多赘述,网上很多介绍。 工作流程如下图: 二.安装 SVN的重要性就不再赘述,这里以Versionsv1.2.2为例,主要讲讲安装及自己在安装中遇到的问题。 1.安装Versionsv1.2.2.dmp后,若成功安装就可看到下图: 2.点击左下角, new->New Repository Bookmark, 如下图: 3.进行第2步后,就可看到如下界面: 4.现在以我自己的账号进行登录和提取信息为例。 5.最后点击 Create . 恭喜!!!成功!工作终于开始了,要勤奋、好学、多请教, 切记:“上传代码之前,一定要先Updata。"(不然你会覆盖小伙伴的代码) 三.安装过程中的常见问题 1.进行上面的第4步登录,总是登录不上,或者登录上后看不到任何资源。 这时你就要注意,安装的程序是否有问题。切记是装Versionsv1.2.2.dmp,不是装.app的程序。 遇到这个问题可直接定位错误原因了。 2.Versionsv1.2.2.dmp装不上,这是mac的权限问题。因为mac默认只允许从appStore和被认可的开发者下载应用程序。 这里你需要对mac的“系统偏好设置”进行设置,在“通用”下的“允许从以下位置下载的应用程序”中选择“任何来源”,这样就可以成功安装了。如下图: 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
症状如下: 点击打开xcode后,就一直会看到loading,但是CPU消耗很高,基本上就是死了(动弹不得),通过活动监测器看到xcode显示为“未响应” 以为是安装程序的问题,结果选中xcode拉到废纸篓中,重新下载安装,还是一样的总是,都快崩溃了。 出错原因:可能是上次强制退出时保存xcode出错,导致之后每次打开xcode都会加载这个错误的工程,出现假死现象。 出现这个问题就真得崩溃了,有些小伙伴甚至还重装了Xcode,这里给大家推荐一个行之有效的方法。 有效地解决方法: 打开终端:cd /Users/mac/Library/Autosave\ Information/ (其中mac为当前登录用户名) 删除下面的文件:rm -rf Unsaved\ Xcode* 然后重新打开xcode就正常了。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
网上介绍了很多方法,觉得有些不太靠谱。这里只解释我试验过的最简单最粗暴的方法: 删除模拟器上旧的APP 以外,也可以做 CLEAN (cmd+shift+K) 把旧的build 删掉。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://blog.csdn.net/hello_haozi/article/details/7564223 想在自己的android应用中获得当天的天气情况,这该怎么做呢?不用担心。中国国家气象局提供了获取所在城市天气预报信息接口。通过这个接口,我们就可以获取天气信息了。 中国国家气象局天气预报接口总共提供了三个: http://www.weather.com.cn/data/sk/101010100.html http://www.weather.com.cn/data/cityinfo/101010100.html http://m.weather.com.cn/data/101010100.html 最详细的信息来自第三个接口。上面url中的101010100是城市代码,这里是北京的城市代码。只需要改变城市代码,就可以得到所在城市的天气信息。笔者在福州,所以选择的城市代码是福州101230101。 在浏览器上输入url:http://m.weather.com.cn/data/101230101.html得到信息,天气信息是json的数据格式,数据如下: {"weatherinfo":{"city":"福州","city_en":"fuzhou","date_y":"2012年5月14 日","date":"","week":"星期 一","fchh":"08","cityid":"101230101","temp1":"29℃~23℃","temp2":"26℃~20℃","temp3":"24℃~20℃","temp4":"25℃~20℃","temp5":"24℃~21℃","temp6":"25℃~22℃","tempF1":"84.2℉~73.4℉","tempF2":"78.8℉~68℉","tempF3":"75.2℉~68℉","tempF4":"77℉~68℉","tempF5":"75.2℉~69.8℉","tempF6":"77℉~71.6℉","weather1":" 阵雨转中雨","weather2":"中雨转小雨","weather3":"小雨","weather4":"小雨","weather5":"小雨 转阵雨","weather6":"阵雨转小 雨","img1":"3","img2":"8","img3":"8","img4":"7","img5":"7","img6":"99","img7":"7","img8":"99","img9":"7","img10":"3","img11":"3","img12":"7","img_single":"3","img_title1":" 阵雨","img_title2":"中雨","img_title3":"中雨","img_title4":"小雨","img_title5":" 小雨","img_title6":"小雨","img_title7":"小雨","img_title8":"小雨","img_title9":" 小雨","img_title10":"阵雨","img_title11":"阵雨","img_title12":"小 雨","img_title_single":"阵雨","wind1":"微风","wind2":"微风","wind3":"微 风","wind4":"微风","wind5":"微风","wind6":"微风","fx1":"微风","fx2":"微风","fl1":"小 于3级","fl2":"小于3级","fl3":"小于3级","fl4":"小于3级","fl5":"小于3级","fl6":"小于3 级","index":"热","index_d":"天气较热,建议着短裙、短裤、短套装、T恤等夏季服装。年老体弱者宜着长袖衬衫和单 裤。","index48":"暖","index48_d":"较凉爽,建议着长袖衬衫加单裤等春秋过渡装。年老体弱者宜着针织长袖衬衫、马甲和长 裤。","index_uv":"弱","index48_uv":"最弱","index_xc":"不宜","index_tr":"适 宜","index_co":"较不舒 适","st1":"27","st2":"21","st3":"24","st4":"18","st5":"22","st6":"18","index_cl":" 较不宜","index_ls":"不太适宜","index_ag":"不易发"}} 我们可以解析json数据去得到自己想用的天气信息。 天气信息解释: [html] view plaincopyprint? { "weatherinfo":{ <!-- 基本信息 --> "city":"福州", "city_en":"fuzhou", "date_y":"2012年5月14日", "date":"", "week":"星期一", "fchh":"08", "cityid":"101230101", <!-- 从今天开始到第六天的每天的天气情况,这里的温度是摄氏温度 --> "temp1":"29℃~23℃","temp2":"26℃~20℃","temp3":"24℃~20℃","temp4":"25℃~20℃","temp5":"24℃~21℃","temp6":"25℃~22℃", <!-- 从今天开始到第六天的每天的天气情况,这里的温度是华氏温度 --> "tempF1":"84.2℉~73.4℉","tempF2":"78.8℉~68℉","tempF3":"75.2℉~68℉","tempF4":"77℉~68℉","tempF5":"75.2℉~69.8℉","tempF6":"77℉~71.6℉", <!-- 天气描述 --> "weather1":"阵雨转中雨","weather2":"中雨转小雨","weather3":"小雨","weather4":"小雨","weather5":"小雨转阵雨","weather6":"阵雨转小雨", <!-- 天气描述图片序号 --> "img1":"3","img2":"8","img3":"8","img4":"7","img5":"7","img6":"99","img7":"7","img8":"99","img9":"7","img10":"3","img11":"3","img12":"7","img_single":"3", <!-- 图片名称 --> "img_title1":" 阵雨","img_title2":"中雨","img_title3":"中雨","img_title4":"小雨","img_title5":" 小雨","img_title6":"小雨","img_title7":"小雨","img_title8":"小雨","img_title9":" 小雨","img_title10":"阵雨","img_title11":"阵雨","img_title12":"小 雨","img_title_single":"阵雨", <!-- 风速描述 --> "wind1":"微风","wind2":"微风","wind3":"微风","wind4":"微风","wind5":"微风","wind6":"微风","fx1":"微风","fx2":"微风", <!-- 风速级别描述 --> "fl1":"小于3级","fl2":"小于3级","fl3":"小于3级","fl4":"小于3级","fl5":"小于3级","fl6":"小于3级", <!-- 今天穿衣指数 --> "index":"热", "index_d":"天气较热,建议着短裙、短裤、短套装、T恤等夏季服装。年老体弱者宜着长袖衬衫和单裤。", <!-- 48小时穿衣指数 --> "index48":"暖","index48_d":"较凉爽,建议着长袖衬衫加单裤等春秋过渡装。年老体弱者宜着针织长袖衬衫、马甲和长裤。", <!-- 紫外线及48小时紫外线 --> "index_uv":"弱","index48_uv":"最弱", <!-- 洗车 --> "index_xc":"不宜", <!-- 旅游 --> "index_tr":"适宜",、 <!-- 舒适指数 --> "index_co":"较不舒适", "st1":"27","st2":"21","st3":"24","st4":"18","st5":"22","st6":"18", <!-- 晨练 --> "index_cl":"较不宜", <!-- 晾晒 --> "index_ls":"不太适宜", <!-- 过敏 --> "index_ag":"不易发" } } 附录中国城市代码: [plain] view plaincopyprint? 直辖市 "北京","上海","天津","重庆" "101010100","101020100","101030100","101040100" 特别行政区 "香港","澳门" "101320101","101330101" 黑龙江 "哈尔滨","齐齐哈尔","牡丹江","大庆","伊春","双鸭山","鹤岗","鸡西","佳木斯","七台河","黑河","绥化","大兴安岭" "101050101","101050201","101050301","101050901","101050801","101051301","101051201","101051101","101050401","101051002","101050601","101050501","101050701" 吉林 "长春","延吉","吉林","白山","白城","四平","松原","辽源","大安","通化" "101060101","101060301","101060201","101060901","101060601","101060401","101060801","101060701","101060603","101060501" 辽宁 "沈阳","大连","葫芦岛","盘锦","本溪","抚顺","铁岭","辽阳","营口","阜新","朝阳","锦州","丹东","鞍山" "101070101","101070201","101071401","101071301","101070501","101070401","101071101","101071001","101070801","101070901","101071201","101070701","101070601","101070301" 内蒙古 "呼和浩特","呼伦贝尔","锡林浩特","包头","赤峰","海拉尔","乌海","鄂尔多斯","通辽" "101080101","101081000","101080901","101080201","101080601","101081001","101080301","101080701","101080501" 河北 "石家庄","唐山","张家口","廊坊","邢台","邯郸","沧州","衡水","承德","保定","秦皇岛" "101090101","101090501","101090301","101090601","101090901","101091001","101090701","101090801","101090402","101090201","101091101" 河南 "郑州","开封","洛阳","平顶山","焦作","鹤壁","新乡","安阳","濮阳","许昌","漯河","三门峡","南阳","商丘","信阳","周口","驻马店" "101180101","101180801","101180901","101180501","101181101","101181201","101180301","101180201","101181301","101180401","101181501","101181701","101180701","101181001","101180601","101181401","101181601" 山东 "济南","青岛","淄博","威海","曲阜","临沂","烟台","枣庄","聊城","济宁","菏泽","泰安","日照","东营","德州","滨州","莱芜","潍坊" "101120101","101120201","101120301","101121301","101120710","101120901","101120501","101121401","101121701","101120701","101121001","101120801","101121501","101121201","101120401","101121101","101121601","101120601" 山西 "太原","阳泉","晋城","晋中","临汾","运城","长治","朔州","忻州","大同","吕梁" "101100101","101100301","101100601","101100401","101100701","101100801","101100501","101100901","101101001","101100201","101101101" 江苏 "南京","苏州","昆山","南通","太仓","吴县","徐州","宜兴","镇江","淮安","常熟","盐城","泰州","无锡","连云港","扬州","常州","宿迁" "101190101","101190401","101190404","101190501","101190408","101190406","101190801","101190203","101190301","101190901","101190402","101190701","101191201","101190201","101191001","101190601","101191101","101191301" 安徽 "合肥","巢湖","蚌埠","安庆","六安","滁州","马鞍山","阜阳","宣城","铜陵","淮北","芜湖","毫州","宿州","淮南","池州" "101220101","101221601","101220201","101220601","101221501","101221101","101220501","101220801","101221401","101221301","101221201","101220301","101220901","101220701","101220401","101221701" 陕西 "西安","韩城","安康","汉中","宝鸡","咸阳","榆林","渭南","商洛","铜川","延安" "101110101","101110510","101110701","101110801","101110901","101110200","101110401","101110501","101110601","101111001","101110300" 宁夏 "银川","固原","中卫","石嘴山","吴忠" "101170101","101170401","101170501","101170201","101170301" 甘肃 "兰州","白银","庆阳","酒泉","天水","武威","张掖","甘南","临夏","平凉","定西","金昌" "101160101","101161301","101160401","101160801","101160901","101160501","101160701","101050204","101161101","101160301","101160201","101160601" 青海 "西宁","海北","海西","黄南","果洛","玉树","海东","海南" "101150101","101150801","101150701","101150301","101150501","101150601","101150201","101150401" 湖北 "武汉","宜昌","黄冈","恩施","荆州","神农架","十堰","咸宁","襄阳","孝感","随州","黄石","荆门","鄂州" "101200101","101200901","101200501","101201001","101200801","101201201","101201101","101200701","101200201","101200401","101201301","101200601","101201401","101200301" 湖南 "长沙","邵阳","常德","郴州","吉首","株洲","娄底","湘潭","益阳","永州","岳阳","衡阳","怀化","韶山","张家界" "101250101","101250901","101250601","101250501","101251501","101250301","101250801","101250201","101250701","101251401","101251001","101250401","101251201","101250202","101251101" 浙江 "杭州","湖州","金华","宁波","丽水","绍兴","衢州","嘉兴","台州","舟山","温州" "101210101","101210201","101210901","101210401","101210801","101210501","101211001","101210301","101210601","101211101","101210701" 江西 "南昌","萍乡","九江","上饶","抚州","吉安","鹰潭","宜春","新余","景德镇","赣州" "101240101","101240901","101240201","101240301","101240401","101240601","101241101","101240501","101241001","101240801","101240701" 福建 "福州","厦门","龙岩","南平","宁德","莆田","泉州","三明","漳州" "101230101","101230201","101230701","101230901","101230301","101230401","101230501","101230801","101230601" 贵州 "贵阳","安顺","赤水","遵义","铜仁","六盘水","毕节","凯里","都匀" "101260101","101260301","101260208","101260201","101260601","101260801","101260701","101260501","101260401" 四川 "成都","泸州","内江","凉山","阿坝","巴中","广元","乐山","绵阳","德阳","攀枝花","雅安","宜宾","自贡","甘孜州","达州","资阳","广安","遂宁","眉山","南充" "101270101","101271001","101271201","101271601","101271901","101270901","101272101","101271401","101270401","101272001","101270201","101271701","101271101","101270301","101271801","101270601","101271301","101270801","101270701","101271501","101270501" 广东 "广州","深圳","潮州","韶关","湛江","惠州","清远","东莞","江门","茂名","肇庆","汕尾","河源","揭阳","梅州","中山","德庆","阳江","云浮","珠海","汕头","佛山" "101280101","101280601","101281501","101280201","101281001","101280301","101281301","101281601","101281101","101282001","101280901","101282101","101281201","101281901","101280401","101281701","101280905","101281801","101281401","101280701","101280501","101280800" 广西 "南宁","桂林","阳朔","柳州","梧州","玉林","桂平","贺州","钦州","贵港","防城港","百色","北海","河池","来宾","崇左" "101300101","101300501","101300510","101300301","101300601","101300901","101300802","101300701","101301101","101300801","101301401","101301001","101301301","101301201","101300401","101300201" 云南 "昆明","保山","楚雄","德宏","红河","临沧","怒江","曲靖","思茅","文山","玉溪","昭通","丽江","大理" "101290101","101290501","101290801","101291501","101290301","101291101","101291201","101290401","101290901","101290601","101290701","101291001","101291401","101290201" 海南 "海口","三亚","儋州","琼山","通什","文昌" "101310101","101310201","101310205","101310102","101310222","101310212" 新疆 "乌鲁木齐","阿勒泰","阿克苏","昌吉","哈密","和田","喀什","克拉玛依","石河子","塔城","库尔勒","吐鲁番","伊宁" "101130101","101131401","101130801","101130401","101131201","101131301","101130901","101130201","101130301","101131101","101130601","101130501","101131001" 西藏 "拉萨","阿里","昌都","那曲","日喀则","山南","林芝" "101140101","101140701","101140501","101140601","101140201","101140301","101140401" 台湾 "台北","高雄" "101340102","101340201" 还有一个方法,请求http://61.4.185.48:81/g/,通过它,你会直接得到城市代码。更方便。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
相关链接:IOS开发~Cocoa Touch Static Library(静态库) 一、Framework 简介(Introduction to Framework Programming Guide) Mac OS X 扩展了 framework 的功能,让我们能够利用它来共享代码和资源。通过 framework 我们可以共享所有形式的资源,如动态共享库,nib 文件,图像字符资源以及文档等。系统会在需要的时候将 framework 载入内存中,多个应用程序可以同时使用同一个 framework,而内存中的拷贝只有一份。一个 framework 同时也是一个 bundle,我们可以在 finder 里浏览其内容,也可以在代码中通过 NSBundle 访问它。利用 framework 我们可以实现动态或静态库的功能。 翻译:https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPFrameworks/Frameworks.html 二、Framework制作方法 1、首先新建两个项目,分别为FrameworkHome 和 FrameworkDemo,其中FrameworkHome为framework制作项目,FrameworkDemo为framework测试项目。 (1)建立FrameworkHome(选择静态库模版) a、选择工程模版 b、清理工程无用文件( Target 、 FrameworkHome、 FrameworkHomeTests) 删除前: 删除后: c、删除旧目标对应的编译设置 点击Manage Scheme 点击左下角 “ - ” 号 选择Delete并且点击OK d、增加一个新的Target 点击 Add Target 选择模版 点击Next,并且配置不需要需改,起一个名字,然后点击Finish 结果 e、修改项目配置 点击 “步骤d” 中创建的Target,并选择 Build Settings -> Architectures -> Base SDK 改为Latest iOS(ios 7.0) 并将 Architectures 改为 Standard architectures (armv7, armv7s) 在 Deployment 下,将 “Mac OS X Deployment Target”改为”Compiler Default”,将 “Targeted Device Family”改为需要的,此处改成了”iPhone/iPad”,同时可以根据需要修改 “iOS Deployment Target”,此处改为了 “iOS 5.0”: 在 Linking 中,将 “Dead Code Stripping” 改为 “NO”,将 “Link with Standard Libraries” 改为 “NO”,将 “Mac-O Type” 改为 “Relocatable Object File”: Packaging 中,将 “Wrapper Extention” 改为“framework”: 修改 Info,将 “Bundle OS Type Code” 改为 “FMWK”(Framework ) 修改预编译头文件,注视其中代码 到此为止,基本的配置就算完成了,可以看到现在的 Products中的文件为 DemoLibrary.framework,不错,这个就是给FrameworkDemo 使用的framwwork,虽然现在FrameworkDemo还没有创建。但在这之前首先编写一些 DemoLibrary.framework 中的内容,然后把接口提供给FrameworkDemo。 f、提供对外接口 首先,创建一个类,建议不要使用IXIB,因为以后打包成 framework以后,我遇到了viewController找不到XIB文件的问题,所以不建议使用XIB。 创建两个ViewController,分别为 OpenViewController 和 PraviteViewController ,其中OpenViewController 是对外公开的接口,内部实现使用到了 PraviteViewController。 别忘记选择Target g、导出头文件 选中Target(DemoLibrary ) -> Build Phases - > Editor - > Add Build Phase - > Add Copys Headers Build Phase 展开 “Copy Headers” 点击右下角的 “ + ”选择相应的 .h 文件来添加对外的接口 还要把相应Project下的文件拖动到Public下 大功告成,但这个地方有一个细节要注意,当前选择build生成的framework要选择ios Device,不要选择你当前链接的真机,否则会出现在打包的framework在别的机器上使用时出错。 另外,当前的framework适合真机,如果要做模拟器的framework,要修改成模拟器版本 现在可以build FrameworkHome 工程了! 这个地方有个小技巧,当选择模拟器,build之后,发现Products下的文件仍然是红色字体,表示不存在,实际上文件已经有了。那把模拟器换 成Devixe,再build一下,会发现DemoLibrary.framework 字体变黑,表示文件有了,用finder找到起位置: 其中Debug-iphoneos中的framework就是真机版本的,下边的文件夹就是模拟器版本的。 2、建立FrameworkDemo工程,选择Empty Application模版就可以了,将刚刚生成的 DemoLibrary.framework 拷贝(也可以引用形式)拖拽到FrameworkDemo中并运行FrameworkDemo。这个地方还有个小细节,FrameworkDemo ->Target - > Architectures 的设置要和framework中的设置相同,不然会出现问题。 编译运行: 控制台打印: 补充:一般framework项目中会有一些图片等资源要一同提供给使用者,这时就需要将这些资源打包成bundle文件,和framework一起拷贝到相应的项目中。 1、建立bundle文件 新建文件夹 -> 将图片资源放到文件夹中 - > 改文件夹名字为 XXX.bundle ,再将这个bundle文件一同放到目标工程中。 2、读取文件 framework中的代码就要这样读取文件了,当然还有其他的初始化路径方法,有需要的可以以后补充。 [objc] view plaincopyprint? NSBundle *bundle = [NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:@"Resource" withExtension:@"bundle"]]; UIImage *img = [UIImage imageWithContentsOfFile:[bundle pathForResource:@"testImg" ofType:@"png"]]; [viewCtr.view addSubview:[[UIImageView alloc] initWithImage:img]]; 3、一些错误的解决办法 http://stackoverflow.com/questions/14367793/duplicate-symbol-error-in-xcode duplicate symbol _NXArgcin: 解决办法:Please set the option "Link with Standard Library" to NO in your build setting 也可以参考:http://blog.csdn.net/lizhongfu2013/article/details/12912807 4、建立一个真机和模拟器通用的framework 首先用finder找到framework所在的位置 然后找到framework中的文件,例如这里的 Kalagame-library,并且纪录其路径 os_frame_path 同样方法打开另一个文件夹,纪录其中库的路径,simulator_frame_path 然后打开控制台,输入 lipo -create os_frame_path simulator_frame_path -output newframe 这样就完成了模拟器和真机版本framework的合并,用finder找到这个newframe,然后把newframe改名字(例如这里的Kalagame-library),并放回到framework文件夹中,替换原来的文件。 补充(2013/12/20): 1、在制作framework或者lib的时候,如果使用了category ,则使用该FMWK的程序运行时会crash,此时需要在该工程中 other linker flags 添加两个参数 -ObjC -all_load 2、编译出Framework是,需要把 GenerateDebugSymbols =NO,project与target都要设置下,否则会出现很多 warning: 类似 warning: (armv6) /Users/myuser/Library/Developer/Xcode/DerivedData/ ....build/Objects-normal/armv6/ImageRequest.o unable to open object file ios static library 为什么代码只有700k,最终编译出来的有3.4m。 原因 1: 选择的是debug模式,改成release模式后,估计能够降低很多。 2:ios static library 是静态库,包含了所有的引用到的代码,因此多出来的大小,是引用的代码的大小。所以改成release以后,大小也不是固定的,取决你所引用代码的多少。 补充(2014/01/03): 公开了public的类,但public类中引用了private的类,于是打包好之后,对外会报错,说找不到那个private类。 可以把 import “private.h” 放到 public 的.m中,这样就不会报错了。 其他:完整的Demo下载 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
一、UIView 动画 使用iPhone作为开发平台,你可以体验到UIView带来的既另类又有趣的动画功能。UIView动画能够完美地建立起一座连接视图当前状态和未来状态地视觉桥梁。可以产生动画效果的变化包括: 1、位置变化:在屏幕上移动视图; 2、大小变化:改变视图框架和边界; 3、拉伸变化:改变视图内容的延伸区域; 4、改变透明程度:改变视图的alpha值; 5、改变状态:隐藏或显示状态; 6、改变视图顺序:哪个视图显示在前,哪个在后; 7、旋转:换句话说,就是任何应用到视图上的仿射变换。 UIView动画是成块运行的,也就是说作为完整的事务一次性运行。发出beginAnimations: context:请求开启一个动画块。commitAnimations结束一个动画块。注意: 把这两个类方法发送给UIView而不是发送给单独的视图。同时在这两个调用之间的块中,添加动画的展现方式并更新视图。你可以使用以下步骤创建UIView动画。 (1)调用beginAnimations : context方法开启一个动画块; (2)调用setAnimationCurve方法定义动画加速和减速方式; (3)调用setAnimationDuration方法设定动画时长; (4)自定义动画效果; (5)调用commitAnimations方法结束你的动画块。 你可以设置动画委托,通知它在你的动画开始或结束的时候调用相应的回调方法。例如: [cpp] view plaincopy [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(doSomething)]; 下面是一些常用的UIView过渡动画: a、下翻页过渡 [cpp] view plaincopy CGContextRef context = UIGraphicsGetCurrentContext(); [UIView beginAnimations:nil context:context]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.7]; [UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:self.view cache:YES]; // do something here [UIView commitAnimations]; b、上翻页过渡 [cpp] view plaincopy CGContextRef context = UIGraphicsGetCurrentContext(); [UIView beginAnimations:nil context:context]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.7]; [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES]; // do something here [UIView commitAnimations]; c、页面左转过渡 [cpp] view plaincopy CGContextRef context = UIGraphicsGetCurrentContext(); [UIView beginAnimations:nil context:context]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.7]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES]; // do something here [UIView commitAnimations]; d、页面右转过渡 [cpp] view plaincopy CGContextRef context = UIGraphicsGetCurrentContext(); [UIView beginAnimations:nil context:context]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.7]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES]; // do something here [UIView commitAnimations]; 二、Core Animation动画 除了UIView动画以外,Core Animation API可为iPhone应用程序提供高度灵活的动画解决方案。Core Animation Transitions仅在实现中做了几个小小的变动便丰富了UIView动画的内涵。注意:CATransition只针对图层,不针对视图。图层是Core Animation与每个UIView产生联系的工作层面。使用Core Animation时,应该将CATransition应用到视图的默认图层([myView layer])而不是视图本身。建立的CA动画步骤如下: (1)建立CA对象; (2)设置它的参数; (3)把这个带着参数的过渡添加到图层。 CA动画使用了类型和子类型两个概念。类型指定了过渡的种类,子类型设置了过渡的方向。对类型和子类型应用动画时,它们一起描述了视图应该怎么样完成过渡。 Core Animation是QuartzCore框架的一个组成部分,因此你必须将Quartz Core框架添加到项目中,并在使用这些功能时将<QuartzCore/QuartzCore.h>包含进你的代码中。 下面是一些常用的CA过渡动画: a、淡化(fade) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = kCATransitionFade; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; b、推挤(push) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = kCATransitionPush; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; c、揭开(reveal) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = kCATransitionReveal; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; d、覆盖(moveIn) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = kCATransitionMoveIn; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; e、立方体翻转(cube) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"cube"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; f、吸收(suckEffect) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"suckEffect"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; g、翻转(oglFlip) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"oglFlip"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; h、水波纹(rippleEffect) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"rippleEffect"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; i、翻页(pageCurl) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"pageCurl"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; j、反翻页(pageUnCurl) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"pageUnCurl"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; k、镜头开(cameraIrisHollowOpen) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"cameraIrisHollowOpen"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; l、镜头关(cameraIrisHollowClose) [cpp] view plaincopy CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.7; animation.timingFunction = UIViewAnimationCurveEaseInOut; animation.type = @"cameraIrisHollowClose"; animation.subtype = kCATransitionFromLeft; /* kCATransitionFromLeft: 从左至右 kCATransitionFromBottom: 下下至上 kCATransitionFromRight: 从右至左 kCATransitionFromTop: 从上至下 */ // do something here [[self.view layer] addAnimation:animation forKey:@"animation"]; 参考:http://blog.csdn.net/zyx586/article/details/8841857 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
UILabel的各种属性与方法的使用(转) [cpp] view plaincopy #import "LabelTestViewController.h" @implementation LabelTestViewController /* Accessing the Text Attributes text property font property textColor property textAlignment property lineBreakMode property enabled property Sizing the Label’s Text adjustsFontSizeToFitWidth property baselineAdjustment property minimumFontSize property 无例 numberOfLines property Managing Highlight Values highlightedTextColor property highlighted property Drawing a Shadow shadowColor property shadowOffset property Drawing and Positioning Overrides – textRectForBounds:limitedToNumberOfLines: 无例 – drawTextInRect: 无例 Setting and Getting Attributes userInteractionEnabled property */ // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { UILabel *label1 = [[UILabel alloc]initWithFrame:CGRectMake(50.0, 20.0, 200.0, 50.0)]; UILabel *label2 = [[UILabel alloc]initWithFrame:CGRectMake(50.0, 80.0, 200.0, 50.0)]; UILabel *label3 = [[UILabel alloc]initWithFrame:CGRectMake(50.0, 140.0, 200.0, 50.0)]; UILabel *label4 = [[UILabel alloc]initWithFrame:CGRectMake(50.0, 200.0, 200.0, 50.0)]; UILabel *label5 = [[UILabel alloc]initWithFrame:CGRectMake(50.0, 260.0, 200.0, 50.0)]; UILabel *label6 = [[UILabel alloc]initWithFrame:CGRectMake(50.0, 320.0, 200.0, 50.0)]; UILabel *label7 = [[UILabel alloc]initWithFrame:CGRectMake(50.0, 380.0, 200.0, 50.0)]; //设置显示文字 label1.text = @"label1"; label2.text = @"label2"; label3.text = @"label3--label3--label3--label3--label3--label3--label3--label3--label3--label3--label3--"; label4.text = @"label4--label4--label4--label4--"; label5.text = @"label5--label5--label5--label5--label5--label5--"; label6.text = @"label6"; label7.text = @"label7"; //设置字体:粗体,正常的是 SystemFontOfSize label1.font = [UIFont boldSystemFontOfSize:20]; //设置文字颜色 label1.textColor = [UIColor orangeColor]; label2.textColor = [UIColor purpleColor]; //设置文字位置 label1.textAlignment = UITextAlignmentRight; label2.textAlignment = UITextAlignmentCenter; //设置字体大小适应label宽度 label4.adjustsFontSizeToFitWidth = YES; //设置label的行数 label5.numberOfLines = 2; UIlabel.backgroudColor=[UIColor clearColor]; //可以去掉背景色 //设置高亮 label6.highlighted = YES; label6.highlightedTextColor = [UIColor orangeColor]; //设置阴影 label7.shadowColor = [UIColor redColor]; label7.shadowOffset = CGSizeMake(1.0,1.0); //设置是否能与用户进行交互 label7.userInteractionEnabled = YES; //设置label中的文字是否可变,默认值是YES label3.enabled = NO; //设置文字过长时的显示格式 label3.lineBreakMode = UILineBreakModeMiddleTruncation;//截去中间 // typedef enum { // UILineBreakModeWordWrap = 0, // UILineBreakModeCharacterWrap, // UILineBreakModeClip,//截去多余部分 // UILineBreakModeHeadTruncation,//截去头部 // UILineBreakModeTailTruncation,//截去尾部 // UILineBreakModeMiddleTruncation,//截去中间 // } UILineBreakMode; //如果adjustsFontSizeToFitWidth属性设置为YES,这个属性就来控制文本基线的行为 label4.baselineAdjustment = UIBaselineAdjustmentNone; // typedef enum { // UIBaselineAdjustmentAlignBaselines, // UIBaselineAdjustmentAlignCenters, // UIBaselineAdjustmentNone, // } UIBaselineAdjustment; [self.view addSubview:label1]; [self.view addSubview:label2]; [self.view addSubview:label3]; [self.view addSubview:label4]; [self.view addSubview:label5]; [self.view addSubview:label6]; [self.view addSubview:label7]; [label1 release]; [label2 release]; [label3 release]; [label4 release]; [label5 release]; [label6 release]; [label7 release]; [super viewDidLoad]; } /* // Override to allow orientations other than the default portrait orientation. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait); } */ - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (void)dealloc { [super dealloc]; } @end UIFont字体设置 一、创建任意样式字体 [cpp] view plaincopy label.font = [UIFont fontWithName:@"fontName" size:17]; label.font = [label.font fontWithSize:17]; 二、创建指定大小的系统默认字体(默认:Helvetica) [cpp] view plaincopy label.font = [UIFont systemFontOfSize:17]; label.font = [UIFont boldSystemFontOfSize:17]; // 指定大小粗体 label.font = [UIFont italicSystemFontOfSize:17]; // 指定大小斜体 三、获取可用的字体名数组 [cpp] view plaincopy NSArray *fontFamilies = [UIFont familyNames]; // 返回所有可用的fontFamily NSArray *fontNames = [UIFont fontNamesForFamilyName:@"fongFamilyName"]; // 返回指定fontFamily下的所有fontName 四、获取指定字体的familyName/fontName [cpp] view plaincopy NSString *familyName = [label.font familyName]; NSString *fontName = [label.font fontName]; 五、获取系统标准字体大小 [cpp] view plaincopy CGFloat labelFontSize = [UIFont labelFontSize]; // Returns the standard font size used for labels. CGFloat buttonFontSize = [UIFont buttonFontSize]; // Returns the standard font size used for buttons. CGFloat smallSystemFontSize = [UIFont smallSystemFontSize]; // Returns the size of the standard small system font. CGFloat systemFontSize = [UIFont systemFontSize]; // Returns the size of the standard system font. 附字体样式表: Thonburi -Thonburi-Bold -Thonburi Snell Roundhand -SnellRoundhand-Bold -SnellRoundhand-Black -SnellRoundhand Academy Engraved LET -AcademyEngravedLetPlain Avenir -Avenir-LightOblique -Avenir-MediumOblique -Avenir-Medium -Avenir-HeavyOblique -Avenir-BlackOblique -Avenir-Oblique -Avenir-Book -Avenir-Roman -Avenir-BookOblique -Avenir-Light -Avenir-Heavy -Avenir-Black Marker Felt -MarkerFelt-Wide -MarkerFelt-Thin Geeza Pro -GeezaPro-Bold -GeezaPro Arial Rounded MT Bold -ArialRoundedMTBold Trebuchet MS -TrebuchetMS -TrebuchetMS-Bold -TrebuchetMS-Italic -Trebuchet-BoldItalic Arial -Arial-BoldMT -ArialMT -Arial-ItalicMT -Arial-BoldItalicMT Marion -Marion-Regular -Marion-Bold -Marion-Italic Gurmukhi MN -GurmukhiMN -GurmukhiMN-Bold Malayalam Sangam MN -MalayalamSangamMN-Bold -MalayalamSangamMN Bradley Hand -BradleyHandITCTT-Bold Kannada Sangam MN -KannadaSangamMN -KannadaSangamMN-Bold Bodoni 72 Oldstyle -BodoniSvtyTwoOSITCTT-Book -BodoniSvtyTwoOSITCTT-Bold -BodoniSvtyTwoOSITCTT-BookIt Cochin -Cochin -Cochin-BoldItalic -Cochin-Italic -Cochin-Bold Sinhala Sangam MN -SinhalaSangamMN -SinhalaSangamMN-Bold Hiragino Kaku Gothic ProN -HiraKakuProN-W6 -HiraKakuProN-W3 Papyrus -Papyrus-Condensed -Papyrus Verdana -Verdana -Verdana-Bold -Verdana-BoldItalic -Verdana-Italic Zapf Dingbats -ZapfDingbatsITC Avenir Next Condensed -AvenirNextCondensed-HeavyItalic -AvenirNextCondensed-DemiBold -AvenirNextCondensed-Italic -AvenirNextCondensed-Heavy -AvenirNextCondensed-DemiBoldItalic -AvenirNextCondensed-Medium -AvenirNextCondensed-BoldItalic -AvenirNextCondensed-Bold -AvenirNextCondensed-UltraLightItalic -AvenirNextCondensed-UltraLight -AvenirNextCondensed-MediumItalic -AvenirNextCondensed-Regular Courier -Courier-Bold -Courier -Courier-BoldOblique -Courier-Oblique Hoefler Text -HoeflerText-Black -HoeflerText-Italic -HoeflerText-Regular -HoeflerText-BlackItalic Helvetica -Helvetica-LightOblique -Helvetica -Helvetica-Oblique -Helvetica-BoldOblique -Helvetica-Bold -Helvetica-Light Euphemia UCAS -EuphemiaUCAS-Bold -EuphemiaUCAS -EuphemiaUCAS-Italic Hiragino Mincho ProN -HiraMinProN-W3 -HiraMinProN-W6 Bodoni Ornaments -BodoniOrnamentsITCTT Apple Color Emoji -AppleColorEmoji Optima -Optima-ExtraBlack -Optima-Italic -Optima-Regular -Optima-BoldItalic -Optima-Bold Gujarati Sangam MN -GujaratiSangamMN -GujaratiSangamMN-Bold Devanagari Sangam MN -DevanagariSangamMN -DevanagariSangamMN-Bold Times New Roman -TimesNewRomanPS-ItalicMT -TimesNewRomanPS-BoldMT -TimesNewRomanPSMT -TimesNewRomanPS-BoldItalicMT Kailasa -Kailasa -Kailasa-Bold Telugu Sangam MN -TeluguSangamMN-Bold -TeluguSangamMN Heiti SC -STHeitiSC-Medium -STHeitiSC-Light Apple SD Gothic Neo -AppleSDGothicNeo-Bold -AppleSDGothicNeo-Medium Futura -Futura-Medium -Futura-CondensedExtraBold -Futura-CondensedMedium -Futura-MediumItalic Bodoni 72 -BodoniSvtyTwoITCTT-BookIta -BodoniSvtyTwoITCTT-Book -BodoniSvtyTwoITCTT-Bold Baskerville -Baskerville-SemiBoldItalic -Baskerville-Bold -Baskerville-Italic -Baskerville-BoldItalic -Baskerville-SemiBold -Baskerville Chalkboard SE -ChalkboardSE-Regular -ChalkboardSE-Bold -ChalkboardSE-Light Heiti TC -STHeitiTC-Medium -STHeitiTC-Light Copperplate -Copperplate -Copperplate-Light -Copperplate-Bold Party LET -PartyLetPlain American Typewriter -AmericanTypewriter-CondensedLight -AmericanTypewriter-Light -AmericanTypewriter-Bold -AmericanTypewriter -AmericanTypewriter-CondensedBold -AmericanTypewriter-Condensed Symbol -Symbol Avenir Next -AvenirNext-Heavy -AvenirNext-DemiBoldItalic -AvenirNext-UltraLightItalic -AvenirNext-HeavyItalic -AvenirNext-MediumItalic -AvenirNext-UltraLight -AvenirNext-BoldItalic -AvenirNext-DemiBold -AvenirNext-Bold -AvenirNext-Regular -AvenirNext-Medium -AvenirNext-Italic Noteworthy -Noteworthy-Light -Noteworthy-Bold Bangla Sangam MN -BanglaSangamMN-Bold -BanglaSangamMN Zapfino -Zapfino Tamil Sangam MN -TamilSangamMN -TamilSangamMN-Bold Chalkduster -Chalkduster Arial Hebrew -ArialHebrew -ArialHebrew-Bold Georgia -Georgia-Italic -Georgia-BoldItalic -Georgia-Bold -Georgia Helvetica Neue -HelveticaNeue-Bold -HelveticaNeue-CondensedBlack -HelveticaNeue-Medium -HelveticaNeue -HelveticaNeue-Light -HelveticaNeue-CondensedBold -HelveticaNeue-LightItalic -HelveticaNeue-UltraLightItalic -HelveticaNeue-UltraLight -HelveticaNeue-BoldItalic -HelveticaNeue-Italic Gill Sans -GillSans-LightItalic -GillSans-BoldItalic -GillSans-Italic -GillSans -GillSans-Bold -GillSans-Light Palatino -Palatino-Roman -Palatino-Bold -Palatino-BoldItalic -Palatino-Italic Courier New -CourierNewPS-BoldMT -CourierNewPSMT -CourierNewPS-BoldItalicMT -CourierNewPS-ItalicMT Oriya Sangam MN -OriyaSangamMN-Bold -OriyaSangamMN Didot -Didot-Italic -Didot -Didot-Bold Bodoni 72 Smallcaps -BodoniSvtyTwoSCITCTT-Book 文本大小自适应 文本大小自适应需要调用NSString类的实例方法计算出文本的size,然后根据这个size来设定UILabel的frame来实现。计算size的方法有: (1) - sizeWithFont: [cpp] view plaincopy - (CGSize)countTextSize:(NSString *)text { /* 1、换行方式默认取NSLineBreakByWordWrapping; 2、求出的size是单行显示时的高度和宽度. */ UIFont *font = [UIFont fontWithName:@"Arial" size:20.0f]; CGSize size = [text sizeWithFont:font]; return size; } (2) - sizeWithFont: forWidth: lineBreakMode: [cpp] view plaincopy - (CGSize)countTextSize:(NSString *)text { /* 1、如果指定宽度小于字符串宽度,则宽度返回0; 2、求出的size是单行显示时的高度和宽度. */ UIFont *font = [UIFont fontWithName:@"Arial" size:20.0f]; CGSize size = [text sizeWithFont:font forWidth:400.0f lineBreakMode:NSLineBreakByWordWrapping]; return size; } (3) - sizeWithFont: constrainedToSize: [cpp] view plaincopy - (CGSize)countTextSize:(NSString *)text { /* 字符串用指定字体在指定区域进行单行显示时,需要的高度和宽度; 一般的用法是,指定区域的高度固定而宽度用MAXFLOAT,则返回值包含对应的宽度; 如果指定区域的宽度不够,则宽度返回0;高度不够则没影响; 核心:单行显示,指定区域的宽度要够大,获取宽度. */ UIFont *font = [UIFont fontWithName:@"Arial" size:20.0f]; CGSize size = [text sizeWithFont:font constrainedToSize:CGSizeMake(MAXFLOAT, 100.0f)]; return size; } (4) - sizeWithFont: constrainedToSize: lineBreakMode: (最常用) [cpp] view plaincopy - (CGSize)countTextSize:(NSString *)text { /* 字符串在指定区域内按照指定的字体显示时,需要的高度和宽度(宽度在字符串只有一行时有用) 一般用法:指定区域的宽度而高度用MAXFLOAT,则返回值包含对应的高度 如果指定区域的宽度指定,而字符串要显示的区域的高度超过了指定区域的高度,则高度返回0 核心:多行显示,指定宽度,获取高度 */ UIFont *font = [UIFont fontWithName:@"Arial" size:20.0f]; CGSize size = [text sizeWithFont:font constrainedToSize:CGSizeMake(320.f, MAXFLOAT) lineBreakMode:NSLineBreakByWordWrapping]; return size; } (5) - sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: [cpp] view plaincopy - (CGSize)countTextSize:(NSString *)text { /* 虽然指定了换行方式,在实际计算时也会换行,但返回的结果只是第一行的高度很宽度; 指定了应该显示的字体,最小的字体,实际的字体,在实际计算中,如果宽度不够,则尽量缩小字符串的字体直至能够一行全部显示,如果缩到最小还不能完全显示字符串,则进行截断,返回截断后的字符串的高度和宽度; 字体实际的大小,存放在 actualFontSize里. */ UIFont *font = [UIFont fontWithName:@"Arial" size:20.0f]; CGFloat f = 0.0f; CGSize size = [text sizeWithFont:font minFontSize:10.0f actualFontSize:&f forWidth:100.0f lineBreakMode:NSLineBreakByWordWrapping]; return size; } 参考资料:http://blog.csdn.net/mamong/article/details/8542404 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
iOS的应用程序的生命周期,还有程序是运行在前台还是后台,应用程序各个状态的变换,这些对于开发者来说都是很重要的。 iOS系统的资源是有限的,应用程序在前台和在后台的状态是不一样的。在后台时,程序会受到系统的很多限制,这样可以提高电池的使用和用户体验。 //开发app,我们要遵循apple公司的一些指导原则,原则如下: 1、应用程序的状态 状态如下: Not running 未运行 程序没启动 Inactive 未激活 程序在前台运行,不过没有接收到事件。在没有事件处理情况下程序通常停留在这个状态 Active 激活 程序在前台运行而且接收到了事件。这也是前台的一个正常的模式 Backgroud 后台 程序在后台而且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到之后会进入挂起状态(Suspended)。有的程序经过特殊的请求后可以长期处于Backgroud状态 Suspended 挂起 程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。 下图是程序状态变化图: 各个程序运行状态时代理的回调: - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions 告诉代理进程启动但还没进入状态保存- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 告诉代理启动基本完成程序准备开始运行- (void)applicationWillResignActive:(UIApplication *)application 当应用程序将要入非活动状态执行,在此期间,应用程序不接收消息或事件,比如来电话了- (void)applicationDidBecomeActive:(UIApplication *)application 当应用程序入活动状态执行,这个刚好跟上面那个方法相反- (void)applicationDidEnterBackground:(UIApplication *)application 当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可- (void)applicationWillEnterForeground:(UIApplication *)application当程序从后台将要重新回到前台时候调用,这个刚好跟上面的那个方法相反。- (void)applicationWillTerminate:(UIApplication *)application当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作。这个需要要设置UIApplicationExitsOnSuspend的键值。- (void)applicationDidFinishLaunching:(UIApplication*)application当程序载入后执行 在上面8个方法对应的方法中键入NSLog打印。 现在启动程序看看执行的顺序: 启动程序lifeCycle[40428:11303] willFinishLaunchingWithOptionslifeCycle[40428:11303] didFinishLaunchingWithOptionslifeCycle[40428:11303] applicationDidBecomeActive 按下home键 lifeCycle[40428:11303] applicationWillResignActivelifeCycle[40428:11303] applicationDidEnterBackground 双击home键,再打开程序 lifeCycle[40428:11303] applicationWillEnterForegroundlifeCycle[40428:11303] applicationDidBecomeActive 2、应用程序的生命周期 2.1、加载应用程序进入前台 2.2、加载应用程序进入后台 2.3、关于main函数 main函数是程序启动的入口,在iOS app中,main函数的功能被最小化,它的主要工作都交给了UIKit framework [cpp] view plaincopy #import <UIKit/UIKit.h> int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class])); } } [cpp] view plaincopy #import <UIKit/UIKit.h> int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class])); } } UIApplicationMain函数有四个参数,你不需要改变这些参数值,不过我们也需要理解这些参数和程序是如何开始的 argc 和argv参数包含了系统带过来的启动时间。 第三个参数确定了主要应用程序类的名称,这个参数指定为nil,这样UIKit就会使用默认的程序类UIApplication。第四个参数是程序自定义的代理类名,这个类负责系统和代码之间的交互。它一般在Xcode新建项目时会自动生成。 另外 UIApplicationMain函数加载了程序主界面的文件。虽然这个函数加载了界面文件,但是没有放到应用程序的windows上,你需要在Delegate的 application:willFinishLaunchingWithOptions方法中加载它。 一个应用程序可以有一个主的storyboard文件或者有一个主的nib文件,但不能同时有两个存在。 如果程序在启动时没有自动加载主要的故事版或nib文件,你可以在application:willFinishLaunchingWithOptions方法里准备windows的展示。 3、响应中断 3.1 当一个基于警告式的中断发生时,比如有电话打进来了,这是程序会临时进入inactive状态,这用户可以选择如何处理这个中断,流程如下图: 在iOS5,通知不会把程序变成为激活状态,通知会显示在状态栏上,如果你;拉下状态栏,程序会变成inactive,把状态栏放回去,程序变回active。 按锁屏键也是另外一种程序的中断,当你按下锁屏键,系统屏蔽了所有触摸事件,把app放到了后台,这时app状态是 inactive,并进入后台。 3.2 当有这些中断时,我们的app该怎么办呢?我们应该在applicationWillResignActive:方法中: 停止timer 和其他周期性的任务 停止任何正在运行的请求 暂停视频的播放 如果是游戏那就暂停它 减少OpenGL ES的帧率 挂起任何分发的队列和不重要的操作队列(你可以继续处理网络请求或其他时间敏感的后台任务)。 当程序回到active状态 , applicationDidBecomeActive: 方法应该上面提到的任务重新开始,比如重新开始timer, 继续分发队列,提高OpenGL ES的帧率。不过游戏要回到暂停状态,不能自动开始。 4、转到后台运行 4.1 如图所示: PS:只有在IOS4以上系统或者支持多任务的设备才能后台运行。不然会直接结束状态。 4.2 当应用程序进入后台时,我们应该做写什么呢? 保存用户数据或状态信息,所有没写到磁盘的文件或信息,在进入后台时,最后都写到磁盘去,因为程序可能在后台被杀死, 释放尽可能释放的内存 applicationDidEnterBackgound: 方法有大概5秒的时间让你完成这些任务。如果超过时间还有未完成的任务,你的程序就会被终止而且从内存中清除。如果还需要长时间的运行任务,可以调用 beginBackgroundTaskWithExpirationHandler 方法去请求后台运行时间和启动线程来运行长时间运行的任务。 4.3 应用程序在后台时的内存使用 在后台时,每个应用程序都应该释放最大的内存。系统努力的保持更多的应用程序在后台同时 运行。不过当内存不足时,会终止一些挂起的程序来回收内存,那些内存最大的程序首先被终止。 事实上,应用程序应该的对象如果不再使用了,那就应该尽快的去掉强引用,这样编译器可以回收这些内存。如果你想缓存一些对象提升程序的性能,你可以在进入后台时,把这些对象去掉强引用。 下面这样的对象应该尽快的去掉强引用: 图片对象 你可以重新加载的 大的视频或数据文件 任何没用而且可以轻易创建的对象 在后台时,为了减少程序占用的内存,系统会自动在回收一些系统帮助你开辟的内存。比如: 系统回收Core Animation的后备存储。 去掉任何系统引用的缓存图片 去掉系统管理数据缓存强引用 5 、返回前台运行 流程如图所示: 当app处于挂起状态时,它是不能执行任何代码的。因此它不能处理在挂起期间发过来的通知,比如方向改变,时间改变,设置的改变还有其他影响程序展现的或状态的通知。在程序返回后台或前台是,程序都要正确的处理这些通知。 6、程序的终止 程序只要符合以下情况之一,只要进入后台或挂起状态就会终止: iOS4.0以前的系统 app是基于iOS4.0之前系统开发的。 设备不支持多任务 在Info.plist文件中,程序包含了 UIApplicationExitsOnSuspend 键。 app如果终止了 ,系统会调用app的代理的方法 applicationWillTerminate: 这样可以让你可以做一些清理工作。你可以保存一些数据或app的状态。这个方法也有5秒钟的限制。超时后方法会返回程序从内存中清除。 注意:用户可以手工关闭应用程序。 7、 The Main Run Loop 主运行循环 Main Run Loop负责处理用户相关的事件。UIApplication对象在程序启动时启动main run Loop,它处理事件和更新视图的界面。看Main Run Loop就知道,它是运行在程序的主线程上的。这样保证了接收到用户相关操作的事件是按顺序处理的。 Main Run Loop 处理事件的架构图: 用户操作设备,相关的操作事件被系统生成并通过UIKit的指定端口分发。事件在内部排成队列,一个个的分发到Main run loop 去做处理。UIApplication对象是第一个接收到时间的对象,它决定事件如何被处理。触摸事件分发到主窗口,窗口再分发到对应出发触摸事件的View。其他的事件通过其他途径分发给其他对象变量做处理。 大部分的事件可以在你的应用里分发,类似于触摸事件,远程操控事件(线控耳机等)都是由app的 responder objects 对象处理的。Responder objects 在你的app里到处都是,比如:UIApplication 对象。view对象,view controller 对象,都是resopnder objects。大部分事件的目标都指定了resopnder object,不过事件也可以传递给其他对象。比如,如果view对象不处理事件,可以传给父类view或者view controller。 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
UIActionSheet是在iOS弹出的选择按钮项,可以添加多项,并为每项添加点击事件。 为了快速完成这例子,我们打开Xcode 4.3.2, 先建立一个single view application。然后再xib文件添加一个button,用来弹出sheet view。 1、首先在.h文件中实现协议 加代码的地方在@interface那行的最后添加<UIActionSheetDelegate>,协议相当于java里的接口,实现协议里的方法。 [cpp] view plaincopy @interface sheetviewViewController : UIViewController<UIActionSheetDelegate> @end 2、添加button,命名button为showSheetView. 3、为button建立Action映射,映射到.h文件上,事件类型为Action ,命名为showSheet。 4、在.m文件上添加点击事件代码 图的效果是这样的: [cpp] view plaincopy - (IBAction)showSheet:(id)sender { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"title,nil时不显示" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"确定" otherButtonTitles:@"第一项", @"第二项",nil]; actionSheet.actionSheetStyle = UIActionSheetStyleBlackOpaque; [actionSheet showInView:self.view]; } actionSheet.actionSheetStyle = UIActionSheetStyleBlackOpaque;//设置样式 参数解释: cancelButtonTitle destructiveButtonTitle是系统自动的两项。 otherButtonTitles是自己定义的项,注意,最后一个参数要是nil。 [actionSheet showInView:self.view];这行语句的意思是在当前view显示Action sheet。当然还可以用其他方法显示Action sheet。 对应上面的图和代码,一目了然了把 5、接下来我们怎么相应Action Sheet的选项的事件呢? 实现协议里的方法。为了能看出点击Action sheet每一项的效果,我们加入UIAlertView来做信息显示。下面是封装的一个方法,传入对应的信息,在UIAlertView显示对应的信息。 [cpp] view plaincopy -(void)showAlert:(NSString *)msg { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Action Sheet选择项" message:msg delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil]; [alert show]; } 那相应被Action Sheet选项执行的代码如下: [cpp] view plaincopy (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { [self showAlert:@"确定"]; }else if (buttonIndex == 1) { [self showAlert:@"第一项"]; }else if(buttonIndex == 2) { [self showAlert:@"第二项"]; }else if(buttonIndex == 3) { [self showAlert:@"取消"]; } } - (void)actionSheetCancel:(UIActionSheet *)actionSheet{ } -(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex{ } -(void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex{ } 可以看到 buttonIndex 是对应的项的索引。 看到那个红色的按钮没?那是ActionSheet支持的一种所谓的销毁按钮,对某户的某个动作起到警示作用, 比如永久性删除一条消息或图像时。如果你指定了一个销毁按钮他就会以红色高亮显示: actionSheet.destructiveButtonIndex=1; 与导航栏类似,操作表单也支持三种风格 : UIActionSheetStyleDefault //默认风格:灰色背景上显示白色文字 UIActionSheetStyleBlackTranslucent //透明黑色背景,白色文字 UIActionSheetStyleBlackOpaque //纯黑背景,白色文字 用法: actionSheet.actionSheetStyle = UIActionSheetStyleBlackOpaque;//设置样式 我选sheet 里的第一项,显示如下: 6、注意事项 在开发过程中,发现有时候UIActionSheet的最后一项点击失效,点最后一项的上半区域时有效,这是在特定情况下才会发生,这个场景就是试用了UITabBar的时候才有。解决办法: 在showView时这样使用,[actionSheet showInView:[UIApplication sharedApplication].keyWindow];或者[sheet showInView:[AppDelegate sharedDelegate].tabBarController.view];这样就不会发生遮挡现象了。 代码获取:http://download.csdn.net/detail/totogo2010/4343267 https://github.com/schelling/YcDemo 著作权声明:本文由http://blog.csdn.net/totogo2010/原创 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
来源:http://blog.csdn.net/htttw/article/details/7842295 iPhone播放音乐 今天我们简要介绍如何在iPhone中播放音乐: 强烈建议你参考官方文档(需要登录): http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVAudioPlayerClassReference/Reference/Reference.html%23//apple_ref/doc/uid/TP40008067 1. 打开XCode,新建一个Window-based Application,项目名称是MusicPlayer: 2. 打开MainWindow.xib,按下图加入控件: 其中,最上面是两个Label,左边的Current(sec)始终不变,右边的0显示当前已播放的时间,下面是一个Slider,类似与一般播放器的进度条,再下面是音量调节的Slider,它们的min都是0.0,max都是1.0。最底下是两个Button。 3. 由于播放声音需要用到AVFoundation.framework,因此我们将它加入到我们的工程中: 右击Frameworks,选择Add/Existing Frameworks,加入AVFoundation: 4. 打开MusicPlayerAppDelegate.h,修改如下: [cpp] view plaincopy // // MusicPlayerAppDelegate.h // MusicPlayer // // Created by HuTao on 8/8/12. // Copyright __MyCompanyName__ 2012. All rights reserved. // #import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> @interface MusicPlayerAppDelegate : NSObject <UIApplicationDelegate> { UIWindow * window; IBOutlet UIButton * btnPlay; IBOutlet UILabel * labelVolume; IBOutlet UILabel * labelCurrentTime; IBOutlet UISlider * sliderCurrentTime; NSTimer * playTimer; AVAudioPlayer * player; } @property (nonatomic, retain) IBOutlet UIWindow * window; @property (nonatomic, retain) IBOutlet UIButton * btnPlay; @property (nonatomic, retain) IBOutlet UILabel * labelVolume; @property (nonatomic, retain) IBOutlet UILabel * labelCurrentTime; @property (nonatomic, retain) IBOutlet UISlider * sliderCurrentTime; -(IBAction)soundStartOrPause:(id)sender; -(IBAction)soundStop:(id)sender; -(IBAction)volumeChanged:(id)sender; -(IBAction)currentTimeChanged:(id)sender; -(void)updateSoundAt:(float)percent; -(void)updateCurrentTime; -(void)initPlayer; @end 首先,加入: [cpp] view plaincopy #import <AVFoundation/AVFoundation.h> 其次: btnLabel,labelVolume,labelCurrentTime,sliderCurrentTime都是控件对应的Outlet: btnLabel:在点击了Start按钮后文本需要变成Pause,所以我们给Button也增加了一个Outlet; labelVolume,labelCurrentTime:在滑动Slider时对应的Label也需要变化以反应当前值; sliderCurrentTime:歌曲播放时需要通过Slider来反应当前已播放的时间,因此Slider也需要一个Outlet。 之后的playerTimer会每一定时间运行一次,根据当前已播放的时间更新进度条;AVAudioPlayer是AVFoundation提供的播放音乐的一个类。 之后的四个IBAction分别是:按下Start按钮;按下Stop按钮;滑动音量的Slider;滑动已播放时间的Slider对应的Action。 5. 打开MusicPlayerAppDelegate.m,修改如下: [cpp] view plaincopy // // MusicPlayerAppDelegate.m // MusicPlayer // // Created by HuTao on 8/8/12. // Copyright __MyCompanyName__ 2012. All rights reserved. // #import "MusicPlayerAppDelegate.h" @implementation MusicPlayerAppDelegate @synthesize window; @synthesize btnPlay; @synthesize labelVolume; @synthesize labelCurrentTime; @synthesize sliderCurrentTime; #pragma mark - #pragma mark Application lifecycle - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //初始化AVAudioPlayer [self initPlayer]; [window makeKeyAndVisible]; return YES; } -(void)initPlayer { NSString * path = [[NSBundle mainBundle] pathForResource:@"北京欢迎你" ofType:@"mp3"]; //判断是否找到该音乐文件 if (path) { NSLog(@"Init sound"); //用path路径初始化AVAudioPlayer player = [[AVAudioPlayer alloc]initWithContentsOfURL:[[NSURL alloc]initFileURLWithPath:path]error:nil]; //初始化播放器 [player prepareToPlay]; //设置播放循环次数:如果numberOfLoops为负数 音频文件就会一直循环播放下去 player.numberOfLoops = -1; //设置音频音量:volume的取值范围在[0.0f, 0.1f]之间 player.volume = 0.5f; //将当前播放进度调为0 [self updateSoundAt:0.0f]; } } -(void)updateSoundAt:(float)percent { float atTime = (player ? player.duration * percent : 0.0f); NSString * time = [NSString stringWithFormat:@"%d", (int)atTime]; labelCurrentTime.text = time; sliderCurrentTime.value = percent; } -(IBAction)soundStartOrPause:(id)sender { //点击Start按钮后开始播放音乐 if(player) { UIButton * btn = (UIButton *)sender; if(![player isPlaying]) { NSLog(@"Start sound"); [player play]; [btn setTitle:@"Pause" forState:UIControlStateNormal]; if(!playTimer) { playTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateCurrentTime) userInfo:nil repeats:YES]; } } else { NSLog(@"Pause sound"); [player pause]; [btn setTitle:@"Start" forState:UIControlStateNormal]; } } } -(void)updateCurrentTime { [self updateSoundAt:1.0 * player.currentTime / player.duration]; } -(IBAction)soundStop:(id)sender { //停止播放声音 if(player) { NSLog(@"Stop sound"); player.currentTime = 0; [player stop]; [btnPlay setTitle:@"Start" forState:UIControlStateNormal]; [self updateSoundAt:0.0f]; } } -(IBAction)volumeChanged:(id)sender { UISlider * slider = (UISlider *)sender; NSString * value = [[NSString alloc]initWithFormat:@"%d%%", (int)(slider.value * 100)]; labelVolume.text = value; player.volume = slider.value; [value release]; } -(IBAction)currentTimeChanged:(id)sender { UISlider * slider = (UISlider *)sender; int time = (player ? slider.value * player.duration : 0); player.currentTime = time; [self updateSoundAt:slider.value]; } - (void)dealloc { [window release]; [btnPlay release]; [labelVolume release]; [labelCurrentTime release]; [sliderCurrentTime release]; [super dealloc]; } @end 有几点说明: 1.先将要播放的音乐加入到Resouces中; 2. playTimer定时器每0.5秒运行一次,更新当前的进度条; 6. 下面要开始将控件和IBOutlet以及IBAction相连接了: (a) 打开MainWindow.xib,按住Ctrl键,鼠标从Music Player Delegate上拖动到相应的Label上,将它和IBOutlet相连接: 要注意的是不要忘了连接btnStart!一共要连两个Slider,两个Label和一个Button。 (b) 右键Button,选择Touch Up Inside,将后面的小圆圈拖动到Music Player Delegate上,连接相应的IBAction: 要注意的是Slider需要连接ValueChanged事件: 7. 运行结果如下: 最后我把代码也上传上来了: http://download.csdn.net/detail/htttw/4484442 完成! 微信公众号: 猿人谷 如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】 如果您希望与我交流互动,欢迎关注微信公众号 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。